I'm trying to check every element of a char array in lisp if it's equal to some significant values. I'm using dotimes for iteration and if statements but I'm very new to lisp so I get lots of errors and I don't know why. Can someon explain?
(defun gppinterpreter (filename)
(let ((count 0)
(my-array (make-array '(1000))))
(with-open-file (stream filename)
(do ((char (read-char stream nil)
(read-char stream nil)))
((null char))
(setf (aref my-array count) char)
(incf count)))
(print my-array))
(let (c "a"))
(dotimes (n count)
(setf (aref (my-array n)) c)
(if(= c " ")
(format t "OP_PLUS ~%"))
(if(= c "-")
(format t "OP_MINUS ~%"))
(if(= c "/")
(format t "OP_DIV ~%"))
(if(= c "*")
(if(= (aref my-array n 1) "*")
(format t "OP_DBLMULT ~%"))
(format t "OP_MULT ~%"))
(if(= c "(")
(format t "OP_OP ~%"))
(if(= c ")")
(format t "OP_CP ~%"))
(if(= c " ")
(format t "OP_OC ~%"))
(if(= c " ")
(format t "OP_CC ~%"))
(if(= c ",")
(format t "OP_COMMA ~%"))
)
)
CodePudding user response:
=
is for comparing numbers, not characters. Use eql
, char=
or char-equal
.
Use characters instead of a string to compare: "a"
-> #\a
Use case
instead of multiple if
s.
(setf (aref (my-array n)) c)
has a extra pair of parentheses. -> (setf (aref my-array n) c)
Make sure that the variable c
has a character and not a string as its value.
CodePudding user response:
In addition to other answers, I think you should be able to simplify your code and separate it into different pieces:
- you can directly read from a stream, this is more general than a file and you can use other source of streams if necessary
- don't read all the characters of a file into an array, your problem can be solved by processing characters as you read them
- avoid repetition:
(format t "...~%")
, etc. can be factorized, this helps with readability and maintainability (there is a single place to modify in case the code changes if you factorize things nicely).
You can also use a case
where clauses are characters, case
compare values with eql
. NB: there must be some typo in your question, I don't know what OP_OC and OP_CC stands for but their associated strings are just spaces; here instead I use brackets, but feel free to adapt:
(defun gpp-interpreter (stream)
(loop ;; keep looping until `(return)` is called
(write-line ;; write the string that is return by case
(case (read-char stream nil nil) ;; read a character, or NIL on error
(#\ "OP_PLUS")
(#\- "OP_MINUS")
(#\/ "OP_DIV")
(#\* (case (peek-char t stream nil nil)
(#\*
;; peek did not read from the stream,
;; we must read now the character
(read-char stream)
"OP_DBLMULT")
(t "OP_MULT")))
(#\( "OP_OP")
(#\) "OP_CP")
(#\[ "OP_OC")
(#\] "OP_CC")
(#\, "OP_COMMA")
(t (return))))))
For example:
(with-input-from-string (in "*/-()[],***")
(gpp-interpreter in))
prints:
OP_MULT
OP_DIV
OP_MINUS
OP_OP
OP_CP
OP_OC
OP_CC
OP_COMMA
OP_DBLMULT
OP_MULT
Instead of using strings, typically in Lisp you would associate symbols for elements in your syntax tree.
Also, I would not just write
them to the standard output, but accept a callback function that is called on each token, so that the lexer is a bit more generic.
(defun gpp-interpreter (stream callback)
(loop
(funcall callback
(case (read-char stream nil nil)
(#\ 'plus)
(#\- 'minus)
(#\/ 'div)
(#\* (case (peek-char t stream nil nil)
(#\* (prog1 'exponent (read-char stream)))
(t 'mult)))
(#\( 'paren-open)
(#\) 'paren-close)
(#\[ 'bracket-open)
(#\] 'bracket-close)
(#\, 'comma)
(t (return))))))
For example, here the custom function format them to the output:
(with-input-from-string (in "*/-()[],***")
(gpp-interpreter in (lambda (s) (format t "~a~%" s))))
But we can also collect all the read tokens in a vector:
(with-input-from-string (stream "*/-()[],***")
(let ((result (make-array 1 :fill-pointer 0 :adjustable t)))
(prog1 result
(gpp-interpreter stream
(lambda (token)
(vector-push-extend token
result
(array-total-size result)))))))
#(MULT DIV MINUS PAREN-OPEN PAREN-CLOSE BRACKET-OPEN BRACKET-CLOSE COMMA
EXPONENT MULT)
First, we build a vector, and for each token being read, the token is pushed at the end of the vector. If there is not enough space in the vector, its size is doubled (it grows by the current array-total-size
).
CodePudding user response:
Burcu, I think you are broadly on the right lines.
In Common Lisp, things are nested with parentheses. So, the following doesn't do what you want it to do:
(let (c "a"))
The syntax is wrong. Because you may want to declare more than one value, there is one set of parentheses to contain them all, and additionally each declaration is within a pair of parentheses, like this:
(let ( (first-variable first-value) (second-variable second-value) ) )
; for your case
(let ((c "a")) )
The last parenthesis also contains what you want to happen within the context of the let declaration.
(let ((c "a")) do-something)
As it currently stands, your code is syntactically incorrect, and if the required parentheses are added the declaration will come and go without anything happening.
Next, you lookup each character. You use this code:
(setf (aref (my-array n)) c)
I think that you are trying to copy an element of my-array
into the variable c
. The setf is the wrong way around, and you have one too many pair of parentheses, having this syntax:
(setf target value)
; in your case
(setf c (aref my-array n))
Then you check against each case, using code like this:
(if(= c " ")
(format t "OP_PLUS ~%"))
There are a few problems. There must be at least one space after if
, otherwise it becomes one symbol if(=
, which is clearly not your intention. Then, you don't want to use =
, which is reserved only for numbers. If c
is a string then use string=
, and if it is a character then use char=
. Every computer language has some ugliness, and I guess that this is Common Lisp's ugliness. Clojure does this particular thing better.
But, let's take a step back, and reassess. What you're trying to do is to find the matching value for the provided value, so " "
has a matching symbol "OP-PLUS"
. This is a lookup table, and the types are called alist
and plist
. The idea is that you provide a table, and then ask Lisp to search for your value. Probably an alist
, something like this:
; Lookup table
(defparameter lookup-table
'((" " . "OP_PLUS")
("*" . "OP_MULT")
("-" . "OP_MINUS")
("/" . "OP_DIV")
))
; Search for item
(cdr (assoc " " lookup-table :test #'string=)) ; => "OP_PLUS"
(cdr (assoc "*" lookup-table :test #'string=)) ; => "OP_MULT"
(cdr (assoc "silly" lookup-table :test #'string=)) ; => NIL
Please note, the spaces provided either side of the dot in the alist
, and the single quote character, this is mandatory. We have to specify the test with :test
. The assoc
function returns a pair, like ("*" . "OP_MULT")
, and cdr
just takes the second value. If the item cannot be found then it returns NIL
. The advantage of doing it this way is that you can simply add new items to the bottom of the list. I would also turn the lookup code into a small function, like this:
(defun lookup (x lookup-table)
(cdr (assoc x lookup-table :test #'string=)))
You can test the new lookup function in your REPL and ensure that it works. Then, you know that at least that piece of your code works, and you have a useful function for next time.
I hope this is useful.