Evaluating Arithmetic Expression using MACRO in LISP

Discussion of Common Lisp
Post Reply
vignezds
Posts: 7
Joined: Tue Nov 18, 2014 4:26 am

Evaluating Arithmetic Expression using MACRO in LISP

Post by vignezds » Sun Nov 23, 2014 3:46 pm

Friends, I have code that does arithmetic evaluation of expression. I tried to convert as Macro but I failed each time, can anyone help me with this.
Here is my code:

Code: Select all

(defun tokenize-stream (stream)
  (labels ((whitespace-p (char)
             (find char #(#\space #\newline #\return #\tab)))
           (consume-whitespace ()
             (loop while (whitespace-p (peek-char nil stream nil #\a))
                   do (read-char stream)))
           (read-integer ()
             (loop while (digit-char-p (peek-char nil stream nil #\space))
                   collect (read-char stream) into digits
                   finally (return (parse-integer (coerce digits 'string))))))
    (consume-whitespace)
    (let ((c (peek-char nil stream nil nil)))
      (let ((token (case c
                     ((nil) nil)
                     ((#\() :lparen)
                     ((#\)) :rparen)
                     ((#\*) '*)
                     ((#\/) '/)
                     ((#\+) '+)
                     ((#\-) '-)
                     (otherwise
                       (unless (digit-char-p c)
                         (cerror "Skip it." "Unexpected character ~w." c)
                         (read-char stream)
                         (return-from tokenize-stream
                                      (tokenize-stream stream)))
                       (read-integer)))))
        (unless (or (null token) (integerp token))
          (read-char stream))
        token))))
 
(defun group-parentheses (tokens &optional (delimited nil))
  (do ((new-tokens '()))
      ((endp tokens)
       (when delimited
         (cerror "Insert it."  "Expected right parenthesis."))
       (values (nreverse new-tokens) '()))
    (let ((token (pop tokens)))
      (case token
        ((:lparen)
         (multiple-value-bind (group remaining-tokens)
             (group-parentheses tokens t)
           (setf new-tokens (cons group new-tokens)
                 tokens remaining-tokens)))
        ((:rparen)
         (if (not delimited)
           (cerror "Ignore it." "Unexpected right parenthesis.")
           (return (values (nreverse new-tokens) tokens))))
        (otherwise
         (push token new-tokens))))))
 
(defun group-operations (expression)
  (flet ((gop (exp) (group-operations exp)))
    (if (integerp expression)
      expression
      (destructuring-bind (a &optional op1 b op2 c &rest others)
                          expression
        (unless (member op1 '(+ - * / nil))
          (error "syntax error: in expr ~a expecting operator, not ~a"
                 expression op1))
        (unless (member op2 '(+ - * / nil))
          (error "syntax error: in expr ~a expecting operator, not ~a"
                 expression op2))
        (cond
         ((not op1) (gop a))
         ((not op2) `(,(gop a) ,op1 ,(gop b)))
         (t (let ((a (gop a)) (b (gop b)) (c (gop c)))
              (if (and (member op1 '(+ -)) (member op2 '(* /)))
                (gop `(,a ,op1 (,b ,op2 ,c) ,@others))
                (gop `((,a ,op1 ,b) ,op2 ,c ,@others))))))))))
 
(defun infix-to-prefix (expression)
  (if (integerp expression)
    expression
    (destructuring-bind (a op b) expression
      `(,op ,(infix-to-prefix a) ,(infix-to-prefix b)))))
 
(defun evaluate (string)
  (with-input-from-string (in string)
    (eval
      (infix-to-prefix
        (group-operations
          (group-parentheses
            (loop for token = (tokenize-stream in)
                  until (null token)
                  collect token)))))))

Goheeca
Posts: 271
Joined: Thu May 10, 2012 12:54 pm
Contact:

Re: Evaluating Arithmetic Expression using MACRO in LISP

Post by Goheeca » Sun Nov 23, 2014 4:40 pm

There are two ways:
  • the reader macro -- you can do with the input whatever you want
  • the macro -- you can only transform lisp code
The macro:

Code: Select all

(defmacro capture-to-string (&rest args)
  (apply #'concatenate 'string
         (loop for arg in args collect (format nil "~s" arg)))

(defmacro infix (&rest rest)
  `(evaluate (capture-to-strin ,@rest)))

;;; works
(infix (1 + 2) - 2 * 5) ; = -7
(macroexpand-1 '(capture-to-string (1 + 2) - 2 * 5)) ; = "(1 + 2)-2*5"

;;; doesn't work
(macroexpand-1 '(capture-to-string (1+2) - 2 * 5)) ; = "(|1+2|)-2*5"
The infix code is already treated as lisp expression, not directly evaluated although. capture-to-string should be improvable, but it's not really the right way, when you have parentheses. Actually, the issue aren't parentheses per se, but the reading of 1+2 symbol and there will be certainly next issues. It depends on how much you want the infix notation being disordered according to the lisp way of syntax.

The reader macro:

Code: Select all

(defun infix (stream char)
  (declare (ignore char))
  (evaluate (read-line stream)))

(set-macro-character #\$ #'infix)

$ (1+2)-2*5
; = -7
In this case you have the raw input stream at disposal. In my example the code swallow up everything to the new line, you cannot use it in the middle of an expression, you have to write more sophisticated reader macro.
Perhaps, look here for one.
cl-2dsyntax is my attempt to create a Python-like reader. My mirror of CLHS (and the dark themed version). Temporary mirrors of aferomentioned: CLHS and a dark version.

Post Reply