;; An interactive calculator inspired by the calculator example in the bison manual. ;; Import the parser and lexer generators. (require (lib "yacc.ss" "parser-tools") (lib "lex.ss" "parser-tools") (lib "readerr.ss" "syntax")) (define-tokens value-tokens (NUM VAR FNCT)) (define-empty-tokens op-tokens (newline = OP CP + - * / ^ EOF NEG)) ;; A hash table to store variable values in for the calculator (define vars (make-hash-table)) (define-lex-abbrevs (lower-letter (- a z)) ;; In the following line if we used (- A Z) dr/mzscheme would read it as (- a z) if ;; case-sensitivity is not enabled. (- #\A #\Z) will not be altered because dr/mzscheme ;; reads them as character constants and not as symbols. (- "A" "Z") would work as well ;; since dr/mzscheme would read them as strings. The lexer generator treates single character ;; strings and symbols the same as an actual #\ character. #cs(- A Z) works too because the #cs ;; tells dr/mzscheme to read the next expression with case-sensitivity turned on. (upper-letter (- #\A #\Z)) ;; (- 0 9) would not work because the lexer does not understand numbers. (- #\0 #\9) is ok too. (digit (- "0" "9"))) (define calcl (lexer [(eof) 'EOF] ;; recursively call the lexer on the remaining input after a tab or space. Returning the ;; result of that operation. This effectively skips all whitespace. [(: #\tab #\space) (calcl input-port)] ;; The parser will treat the return of 'newline the same as (token-newline) [#\newline 'newline] [(: = + - * / ^) (string->symbol lexeme)] ["(" 'OP] [")" 'CP] [sin (token-FNCT sin)] ;; It the parens are left off of an "invocation" of an abbreviation, it means the ;; character sequence instead. [(+ (: (lower-letter) (upper-letter))) (token-VAR (string->symbol lexeme))] [(+ (digit)) (token-NUM (string->number lexeme))] ;; Strings which dr/mzscheme does not think of as symbols (such as . or ,) must be ;; entered as a string or character. "." would also be ok. [(@ (+ (digit)) #\. (* (digit))) (token-NUM (string->number lexeme))])) (define calcp (parser (start start) (end newline EOF) (tokens value-tokens op-tokens) (error (lambda (a b c) (void))) (precs (right =) (left - +) (left * /) (left NEG) (right ^)) (grammar (start [() #f] ;; If there is an error, ignore everything before the error ;; and try to start over right after the error [(error start) $2] [(exp) $1]) (exp [(NUM) $1] [(VAR) (hash-table-get vars $1 (lambda () 0))] [(VAR = exp) (begin (hash-table-put! vars $1 $3) $3)] [(FNCT OP exp CP) ($1 $3)] [(exp + exp) (+ $1 $3)] [(exp - exp) (+ $1 $3)] [(exp * exp) (* $1 $3)] [(exp / exp) (/ $1 $3)] [(- exp) (prec NEG) (- $2)] [(exp ^ exp) (expt $1 $3)] [(OP exp CP) $2])))) ;; run the calculator on the given input-port (define (calc ip) (port-count-lines! ip) (letrec ((one-line (lambda () (let ((result (calcp (lambda () (calcl ip))))) (if result (begin (printf "~a~n" result) (one-line))))))) (one-line)))