как перезаписать (defun eval (expr)) функцию в LISP

Я новичок в программировании на LISP, и это конец семестра, и наш учитель попросил нас сделать этот проект, и я пытался его сделать, но я застрял, поэтому любая помощь будет оценена по достоинству. Проект состоит в том, чтобы написать eval (expr) функцию на Лиспе, чтобы перезаписать уже существующую функцию. вот подробности:

Описание проекта: элементы в арифметическом выражении, разделенные пробелами;

; Input:
;    1. The form of arithmetic expression given in prefix notation like LISP 
; Assumptions:
;    1. binary operations for +, -, *, and / 
;    2. integer division, no reals
;    3. an arithmetic expression occupies only one line
;    4. nested arithmetic expressions permitted
;    5. all given inputs are syntax correct
;    6. no need for error handling  

Я написал код, который может вычислять простые арифметические выражения, и он работает!! но я НЕ МОГУ заставить его работать с вложенными арифметическими операциями. Я думаю, что у меня проблема с частью рекурсии, я делаю что-то не так, но что именно idk :(

вот мой код:

; Assign a character string to a global variable input-prompt
; treat input-prompt as a constant global variable
(setf input-prompt "Please input an arithmetic expression: ")

(setf output-prompt "The value is: ")

(defun prompt-for-input (msg)
  (format t msg)
  (format t "~%"))   ; ~% new line

(defun prompt-for-output (msg)
  (format t msg))

(defun output-msg (result)
  (format t "~S" result) ; ~S takes the result into the print message
  (format t "~%"))

(defun eval (expr)
  (print "My EVAL Function is Working *_*")
  (if (numberp expr) expr)     
  (cond
    ((eq (car expr) '+)
      (if (and (numberp (cadr  expr)) 
               (numberp (caddr expr))) ;to check if we have simple expression
        (+ (cadr expr) (caddr expr)) ;in case both are numbers we add normally
        (if (not (numberp (cadr expr))) 
           ;in case the second argument is not number 
           ;we need to call eval again to check the expression 
          ((eval (cadr exp)))
          (if (not (numberp (caddr expr))) 
             ;in case the third argument is not a number 
             ;we need to call eval again to check the expression
            ((eval (caddr exp)))
            (0)))))
    ((eq (car expr) '-)
      (if (and (numberp (cadr  expr)) 
               (numberp (caddr expr))) ;to check if we have simple expression
        (- (cadr expr) (caddr expr)) ;in case both are numbers we add normally
        (if (not (numberp (cadr expr))) 
           ;in case the second argument is not number 
           ;we need to call eval again to check the expression 
          ((eval (cadr exp)))
          (if (not (numberp (caddr expr))) 
             ;in case the third argument is not a number 
             ;we need to call eval again to check the expression
            ((eval (caddr exp)))
            (0)))))
    ((eq (car expr) '*)
      (if (and (numberp (cadr  expr)) 
               (numberp (caddr expr))) ;to check if we have simple expression
        (* (cadr expr) (caddr expr)) ;in case both are numbers we add normally
        (if (not (numberp (cadr expr))) 
           ;in case the second argument is not number 
           ;we need to call eval again to check the expression 
          ((eval (cadr exp)))
          (if (not (numberp (caddr expr))) 
             ;in case the third argument is not a number 
             ;we need to call eval again to check the expression
            ((eval (caddr exp)))
            (0)))))
    ((eq (car expr) '/)
      (if (and (numberp (cadr  expr)) 
               (numberp (caddr expr))) ;to check if we have simple expression
        (/ (cadr expr) (caddr expr)) ;in case both are numbers we add normally
        (if (not (numberp (cadr expr))) 
           ;in case the second argument is not number 
           ;we need to call eval again to check the expression 
          ((eval (cadr exp)))
          (if (not (numberp (caddr expr))) 
             ;in case the third argument is not a number 
             ;we need to call eval again to check the expression
            ((eval (caddr exp)))
            (0)))))))

    ; it should have eval(expr) function which returns the value of the
    ; arithmetic expression
    ; for instance, 
    ; (+ 2 3) outputs 5
    ; (+ (* 3 2) (/ 4 2))) outputs 8
    ; (* (- 2 3) 5) outputs -5 

    ; driver accepts the input arithmetic expression
    ; evaluate the arithmetic expression
    ; output the reulst of the evaluation of the arithmetic expression
    ; execution is in the loop; to exit the loop; type cntrl-c

(defun driver ()
  (prompt-for-input input-prompt) 
     ;to print "Please input an arithmetic expression
  (let ((expression (read)))      
    (output-msg expression)
    (let ((result (eval expression)))
      (prompt-for-output output-prompt)  
      (output-msg result)))
  (driver))

person Arianna Newman    schedule 24.04.2013    source источник
comment
пожалуйста, не используйте вкладки, когда вы публикуете исходный код на SO. :) Всегда старайтесь правильно делать отступы в коде и, если можете, старайтесь подогнать свои строки под ширину панели кода, чтобы не было горизонтальной полосы прокрутки (это не обязательно, но так намного легче читать код) .   -  person Will Ness    schedule 24.04.2013
comment
Слишком много тегов. Это Элисп? XLisp? Или Коммон Лисп?   -  person Kaz    schedule 28.04.2013


Ответы (2)


Прежде всего, основной призыв, который делает свое дело, это ... барабанная дробь ... -

(apply (symbol-function (car expr)) (cdr expr))

это предполагает - на мгновение - что все аргументы в выражении уже являются числами.

Эта одна строка заменяет все четыре случая в вашем коде, которые являются точными копиями друг друга, вплоть до выполняемой операции.

Теперь, чтобы убедиться, что у нас есть числа, нам просто нужно вызвать одно и то же eval для каждого из них. Если это были числа, то они останутся как есть, а если нет — будут оценены.

Вместо этого давайте просто назовем нашу новую функцию calc для «вычисления»:

(defun calc (expr)     ; our "eval"
  (cond
    ((numberp expr) expr)
    (T (call (car expr)
             (mapcar #'calc (cdr expr))))))

(defun call (op args)  ; our "apply"
  (apply (symbol-function op)
         args))

Это все. Если вы считаете это читерством, вы можете вызвать операцию вручную, но вам не нужно для этого копировать один и тот же блок кода четыре раза. :)

Если вы действительно сами пишете call для вызова операции вручную, обратите внимание, что значение по умолчанию для (*) равно 1, а не 0; и что в Common Lisp нет значения по умолчанию для (-) и (/) (как проверено в CLisp). Кроме того, (/ 2) должно возвращать 1/2.

person Will Ness    schedule 24.04.2013
comment
@ Уилл Несс Большое вам спасибо! Это работает и с меньшим количеством кода. Ты рок. я ценю вашу помощь - person Arianna Newman; 24.04.2013
comment
@AriannaNewman, пожалуйста, добро пожаловать в чудеса Лиспа! (и Scheme, и Haskell,... и если вы можете уместить это в свой график - Prolog). :) - person Will Ness; 24.04.2013
comment
@AriannaNewman также перепишите свою функцию driver. Common Lisp не имеет гарантии хвостовой рекурсии IIRC, то, что вы написали, больше похоже на код Scheme. Просто используйте do, чтобы придумать способ выхода из петля тоже чистая. - person Will Ness; 24.04.2013
comment
Я действительно очень ценю все ваши комментарии и вашу помощь. Я только сегодня зарегистрировался на этом сайте и не был знаком с ним :) - person Arianna Newman; 25.04.2013

несколько советов:

  • Такие выражения, как (0), не имеют смысла. 0 не является функцией. Подобные ((foo)) тоже не имеют смысла.

  • вы должны правильно форматировать и делать отступы в коде Lisp. Редактор помогает.

  • Избегайте таких функций, как CAR, CDR, CADR,... - используйте FIRST, REST, SECOND,...

  • не вызывайте функцию EVAL. Это встроенная функция в Common Lisp.

person Rainer Joswig    schedule 24.04.2013