;;; File: ;;; expression.ss ;;; Author: ;;; Samuel A. Rebelsky ;;; Version: ;;; 1.0 of 30 September 2002 ;;; Summary: ;;; A semi-hack to implement a simple expression parser and evaluator. ;;; Contents: ;;; Values ;;; lparen - the left parenthesis character ;;; rparen - the right parenthesis character ;;; User Interface ;;; (evaluate str) - compute the value of an expression ;;; Testing ;;; (eexp str) - evaluate an expression, getting value and remaining ;;; characters ;;; (eterm str) - evaluate a term, getting value and remaining chars ;;; (efactor str) - guess ;;; Parsing ;;; (exp charlist) - evaluate an expression, getting value and remaining ;;; characters ;;; (exphelper (prev-value . charlist)) - evaluate the tail of an ;;; expression ;;; (term charlist) - evaluate a term, getting value and remaining chars ;;; (termhelper (prev-value . charlist)) - evaluate the tail of a term ;;; (factor charlist) - evaluate a factor, getting value and remaining ;;; characters ;;; Helpers ;;; (add-op? ch) - determine if ch represents an addition operator ;;; (mul-op? ch) - determine if ch represents a multiplicative operator ;;; (operator ch) - convert a character that represents an operator ;;; to the corresponding Scheme procedure ;;; Notes: ;;; Here's the basic grammar, which is taken from the standard expression ;;; grammar and then had left recursion removed. ;;; Exp ::= Term ExpHelper ;;; ExpHelper ::= epsilon ;;; | AddOp Term ExpHelper ;;; Term ::= Factor TermHelper ;;; TermHelper ::= epsilon ;;; | MulOp Factor TermHelper ;;; Factor ::= OPEN Exp CLOSE ;;; | NUMBER ;;; ;;; exp, term, and factor are implemented as Scheme procedures that ;;; take one parameter, a list of characters, and returns a pair of ;;; parameters: the list after the thing is consumed and the value ;;; of the thing consumed. ;;; ;;; exphelper and termhelper are implemented as procedures that take ;;; one parameter: a pair of list and value, and return the same ;;; kind of pair as the other procedures. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Values (define lparen #\() (define rparen #\)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Interface ;;; Procedure: ;;; evaluate ;;; Parameters: ;;; str, a string ;;; Purpose: ;;; To evaluate the arithmetic expression given by str. ;;; Produces: ;;; value, integer. ;;; Preconditions: ;;; str is a valid arithmetic expression using the operators ;;; +,-,*,/, parentheses, and single-digits numbers. ;;; Postconditions: ;;; value is the value of the arithmetic expression using ;;; precedence. (define evaluate (lambda (str) (cdr (eexp str)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Testing ;;; Procedures: ;;; eexp ;;; efact ;;; eterm ;;; Parameter: ;;; str, a string ;;; Purpose: ;;; Test exp, factor, and term, respectively. ;;; Produces: ;;; (chars . value), a pair of a list and a number. ;;; Preconditions: ;;; str must represent a valid arithmetic expression (or at ;;; least the start of one). ;;; Postconditions: ;;; chars is the remaining part of string after the first ;;; expression, factor, or term has been consumed. ;;; value is the value of the consumed expression, factor, or term. (define eexp (lambda (str) (exp (string->list str)))) (define efact (lambda (str) (factor (string->list str)))) (define eterm (lambda (str) (term (string->list str)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Parsing and Evaluating ;;; Procedure: ;;; exp ;;; Parameters: ;;; chars, a list of characters ;;; Purpose: ;;; Parses and evaluates an expression. ;;; Produces: ;;; (remaining-chars . value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Preconditions: ;;; chars begins with a valid expression. ;;; Postconditions: ;;; remaining-chars contains the elements of chars after the ;;; first expression is removed. ;;; value is the value of the removed expression. (define exp (lambda (chars) (if (null? chars) (error "The empty string cannot be an expression") (exphelper (term chars))))) ;;; Procedure: ;;; exphelper ;;; Parameters: ;;; (chars . prev-value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Purpose: ;;; Parses and evaluates the tail of an expression. ;;; Produces: ;;; (remaining-chars . value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Preconditions: ;;; chars begins with a valid expression tail (either nothing ;;; or an addop, a term, and an expression tail). ;;; Postconditions: ;;; remaining-chars contains the elements of chars after the ;;; first term tail is removed. ;;; value is the value of the removed expression tail composed ;;; with prev-value. (define exphelper (lambda (pair) (let ((chars (car pair)) (val1 (cdr pair))) (cond ((and (not (null? chars)) (add-op? (car chars))) (let ((pair2 (term (cdr chars)))) (exphelper (cons (car pair2) ((operator (car chars)) val1 (cdr pair2)))))) ((null? chars) pair) ((char=? (car chars) rparen) pair) (else (error "Could not find expression tail in" (list->string chars))))))) ;;; Procedure: ;;; term ;;; Parameters: ;;; chars, a list of characters ;;; Purpose: ;;; Parses and evaluates a term (an expression with no ;;; unparenthesized addition). ;;; Produces: ;;; (remaining-chars . value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Preconditions: ;;; chars begins with a valid term. ;;; Postconditions: ;;; remaining-chars contains the elements of chars after the ;;; first term is removed. ;;; value is the value of the removed term. (define term (lambda (chars) (if (null? chars) (error "The empty string cannot be a term") (termhelper (factor chars))))) ;;; Procedure: ;;; termhelper ;;; Parameters: ;;; (chars . prev-value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Purpose: ;;; Parses and evaluates the tail of a term. ;;; Produces: ;;; (remaining-chars . value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Preconditions: ;;; chars begins with a valid term tail (either nothing ;;; or a mulop, a factor, and a term tail). ;;; Postconditions: ;;; remaining-chars contains the elements of chars after the ;;; first term tail is removed. ;;; value is the value of the removed expression as composed ;;; with the previus value. (define termhelper (lambda (pair) (let ((chars (car pair)) (val1 (cdr pair))) (cond ((and (not (null? chars)) (mul-op? (car chars))) (let ((pair2 (factor (cdr chars)))) (termhelper (cons (car pair2) ((operator (car chars)) val1 (cdr pair2)))))) ((null? chars) pair) ((or (char=? (car chars) rparen) (add-op? (car chars))) pair) (else (error "Couldn't find a term tail in" (list->string chars))))))) ;;; Procedure: ;;; factor ;;; Parameters: ;;; chars, a list of characters ;;; Purpose: ;;; Parses and evaluates a factor (an expression with no ;;; unparenthesized addition or multiplication) ;;; Produces: ;;; (remaining-chars . value), a pair whose first element is ;;; a list of characters and whose second element is a number. ;;; Preconditions: ;;; chars begins with a valid expression. ;;; Postconditions: ;;; remaining-chars contains the elements of chars after the ;;; first factor is removed. ;;; value is the value of the removed factor. (define factor (lambda (chars) (cond ((null? chars) (error "Expecting a factor, found nothing!")) ((char-numeric? (car chars)) (cons (cdr chars) (string->number (string (car chars))))) ((char=? (car chars) lparen) (let ((pair (exp (cdr chars)))) (if (char=? (caar pair) rparen) (cons (cdar pair) (cdr pair))))) (else (error "Couldn't find a term in" (list->string (car pair))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Helpers ;;; Procedure: ;;; add-op? ;;; Parameters: ;;; ch, a character ;;; Purpose: ;;; Determines whether ch represents an additive operator. ;;; Produces: ;;; is-add-op ;;; Preconditions: ;;; [Standard] ;;; Postconditions: ;;; is-add-op is true (#t) if ch represents an additive operator. ;;; is-add-op is false (#f) otherwise. (define add-op? (lambda (ch) (and (char? ch) (or (char=? ch #\+) (char=? ch #\-))))) ;;; Procedure: ;;; mul-op? ;;; Parameters: ;;; ch, a character ;;; Purpose: ;;; Determines whether ch represents a multiplicative operator. ;;; Produces: ;;; is-mul-op ;;; Preconditions: ;;; [Standard] ;;; Postconditions: ;;; is-mul-op is true (#t) if ch represents a multiplicative operator. ;;; is-add-op is false (#f) otherwise. (define mul-op? (lambda (ch) (and (char? ch) (or (char=? ch #\*) (char=? ch #\/) (char=? ch #\%))))) ;;; Procedure: ;;; operator ;;; Parameters: ;;; ch, a character ;;; Purpose: ;;; Finds the Scheme operator that corresponds to ch. ;;; Produces: ;;; op, a function from numbers to numbers ;;; Preconditions: ;;; ch names a multiplicative or additive operator [Verified] ;;; Postconditions: ;;; op is a Scheme operator. ;;; op is the operator named by ch. (define operator (lambda (ch) (cond ((char=? ch #\+) +) ((char=? ch #\*) *) ((char=? ch #\/) /) ((char=? ch #\-) -) ((char=? ch #\%) modulo) (else (error "Invalid operator:" ch)))))