;;;
;;;; Simple calculator in Scheme
;;;
;;
;; @created   "Tue Jan  6 12:47:23 2004"
;; @modified  "Mon Oct 25 11:07:24 2004"
;; @author    "Dominique Boucher"
;; @copyright "Dominique Boucher"
;;
;; Simple arithmetic calculator. 
;; 
;;   This program illustrates the use of the lalr-scm parser generator
;; for Scheme. It is NOT robust, since calling a function with 
;; the wrong number of arguments may generate an error that will
;; cause the calculator to crash.


;;;
;;;;   The LALR(1) parser
;;;


(import (rnrs) (rnrs mutable-pairs) (lalr))

(define calc-parser
  (lalr-parser
   
   ;; --- Options 
   ;; output a parser, called calc-parser, in a separate file - calc.yy.scm, 
   ;(output:    calc-parser "calc.yy.scm")
   ;; output the LALR table to calc.out
   ;(out-table: "calc.out")
   ;; there should be no conflict
   (expect:    0)
   
   ;; --- token definitions
   (ID NUM = LPAREN RPAREN NEWLINE COMMA
    (left: + -)
    (left: * /)
    (nonassoc: uminus))
   
   (lines    (lines line) : (display-result $2)
	     (line)       : (display-result $1))
	   

   ;; --- rules
   (line     (assign NEWLINE)        : $1
	     (expr   NEWLINE)        : $1
	     (error  NEWLINE)        : #f)
   
   (assign   (ID = expr)             : (add-binding $1 $3))
   
   (expr     (expr + expr)           : (+ $1 $3)
	     (expr - expr)           : (- $1 $3)
	     (expr * expr)           : (* $1 $3)
	     (expr / expr)           : (/ $1 $3)
	     (- expr (prec: uminus)) : (- $2)
	     (ID)                    : (get-binding $1)
	     (ID LPAREN args RPAREN) : (invoke-proc $1 $3)
	     (NUM)                   : $1
	     (LPAREN expr RPAREN)    : $2)
    
   (args     ()                      : '()
	     (expr arg-rest)         : (cons $1 $2))
   
   (arg-rest (COMMA expr arg-rest)   : (cons $2 $3)
	     ()                      : '())))


(define (display-result v)
  (if v
      (begin
	(display "==> ")
	(display v)
	(newline))))


;;;
;;;;   The lexer
;;;


(define (make-lexer errorp)
  (lambda ()
    (letrec ((skip-spaces
	      (lambda ()
		(let loop ((c (peek-char)))
		  (if (and (not (eof-object? c))
			   (or (char=? c #\space) (char=? c #\tab)))
		      (begin
			(read-char)
			(loop (peek-char)))))))   
	     (read-number 
	      (lambda (l)
		(let ((c (peek-char)))
		  (if (char-numeric? c)
		      (read-number (cons (read-char) l))
		      (string->number (apply string (reverse l)))))))
	     (read-id
	      (lambda (l)
		(let ((c (peek-char)))
		  (if (char-alphabetic? c)
		      (read-id (cons (read-char) l))
		      (string->symbol (apply string (reverse l))))))))

      ;; -- skip spaces
      (skip-spaces)
      ;; -- read the next token
      (let loop ((c (read-char)))
	(cond
	 ((eof-object? c)      '*eoi*)
	 ((char=? c #\newline) 'NEWLINE)
	 ((char=? c #\+)       '+)
	 ((char=? c #\-)       '-)
	 ((char=? c #\*)       '*)
	 ((char=? c #\/)       '/)
	 ((char=? c #\=)       '=)
	 ((char=? c #\,)       'COMMA)
	 ((char=? c #\()       'LPAREN)
	 ((char=? c #\))       'RPAREN)
	 ((char-numeric? c)    (cons 'NUM (read-number (list c))))
	 ((char-alphabetic? c) (cons 'ID  (read-id (list c))))
	 (else                 
	  (errorp "PARSE ERROR : illegal character: " c)
	  (skip-spaces)
	  (loop (read-char))))))))


(define (read-line)
  (let loop ((c (read-char)))
    (if (and (not (eof-object? c))
	     (not (char=? c #\newline)))
	(loop (read-char)))))


;;;
;;;;   Environment management
;;;


(define *env* (list (cons '$$ 0)))


(define (init-bindings)
  (set-cdr! *env* '())
  (add-binding 'cos cos)
  (add-binding 'sin sin)
  (add-binding 'tan tan)
  (add-binding 'expt expt)
  (add-binding 'sqrt sqrt))


(define (add-binding var val)
  (set! *env* (cons (cons var val) *env*))
  val)


(define (get-binding var)
  (let ((p (assq var *env*)))
    (if p 
	(cdr p)
	0)))


(define (invoke-proc proc-name args)
  (let ((proc (get-binding proc-name)))
    (if (procedure? proc)
	(apply proc args)
	(begin
	  (display "ERROR: invalid procedure:")
	  (display proc-name)
	  (newline)
	  0))))


;;;
;;;;   The main program
;;;


(define calc
  (lambda ()
    (call-with-current-continuation
     (lambda (k)
       (display "********************************") (newline)
       (display "*  Mini calculator in Scheme   *") (newline)
       (display "*                              *") (newline)
       (display "* Enter expressions followed   *") (newline)
       (display "* by [RETURN] or 'quit()' to   *") (newline)
       (display "* exit.                        *") (newline)
       (display "********************************") (newline)
       (init-bindings)
       (add-binding 'quit k)
       (letrec ((errorp
		 (lambda args
		   (for-each display args) (newline)))
		(start
		 (lambda ()
		   (calc-parser (make-lexer errorp) errorp))))
	 (start))))))

(calc)