;; The first three lines of this file were inserted by DrRacket. They record metadata ;; about the language level of this file in a form that our tools can easily process. #reader(lib "htdp-intermediate-lambda-reader.ss" "lang")((modname B0) (read-case-sensitive #t) (teachpacks ()) (htdp-settings #(#t constructor repeating-decimal #f #t none #f () #f))) #| ;; A B0 implementation. @see http://www.radford.edu/itec380/2022fall-ibarland/Homeworks/Project/B0.html @author ibarland@radford.edu @version 2022-Mar-30 @original-at http://www.radford.edu/itec380/2022fall-ibarland/Homeworks/Project/B0.rkt @license CC-BY -- share/adapt this file freely, but include attribution, thx. https://creativecommons.org/licenses/by/4.0/ https://creativecommons.org/licenses/by/4.0/legalcode Including a link to the *original* file satisifies "appropriate attribution". |# (require "student-extras.rkt") (require "scanner.rkt") (provide (all-defined-out)) #| Expr ::= Num | Paren | Nop| BinOp | IfZero Paren ::= dark Expr light Interpretation: a parenthesized expression BinOp ::= ^ Expr Expr Op Interpretation: apply a binary operator Note: postfix Op ::= / | | | ! Interpretation: "/" is addition, "|" subtraction, "!" multiplication Nop ::= force Expr Interpretation: a no-op: just evaluates to the Expr (like a Paren) IfZero ::= mace Expr Expr windu Expr Interpretation: if 3rd expr is zero, answer is the 1st expr, else use the 2nd expr |# ; datatype defn: (define (expr? val) (or (value? val) (binop? val) (paren? val) (nop? val) (if-zero? val))) (define OP-FUNCS (list (list "/" +) (list "|" -) (list "!" *) )) (define OPS (map first OP-FUNCS)) (define (op? val) (member val OPS)) (define-struct/c binop ([op op?] [left expr?] [right expr?])) ; NOTE: `op` is the *first* arg to constructor. (define-struct/c paren ([e expr?])) (define-struct/c nop ([e expr?])) (define-struct/c if-zero ([tst expr?] [thn expr?] [els expr?])) ; NOTE: `tst` is the *first* arg to constructor. (define (value? val) (number? val)) ; N.B. B4 will upgrade our notion of 'value' to include *functions*, as well as numbers. ; our parse tree for ; ^ dark ^ 2 3 / light 4 ! (aka `(2+3)*4` ) ; is: (make-binop "!" (make-paren (make-binop "/" 2 3)) 4) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; Examples of Expr: 34 (make-paren 34) (make-binop "/" 3 4) (make-binop "/" (make-paren 34) (make-binop "!" 3 4)) ; ^ dark 34 light ^ 3 4 ! / (make-if-zero 3 7 9) (make-if-zero (make-paren 1) (make-binop "/" (make-paren 34) (make-binop "!" 3 4)) (make-if-zero 0 7 9)) ;the above is the parse-tree for: mace ^ dark 34 light ^ 3 4 ! / ; mace 7 9 windu 0 ; windu ; dark 1 light ; An Op is: (one-of OPS) (define/contract (string->expr prog) (-> string? expr?) ; given a string, return the parse-tree for the B0 expression at its front. ; (parse! (create-scanner prog))) #| Our java version of `parse` corresponds more to: (define/contract (parse! s) (-> scanner? expr?) (cond [(number? (peek s)) (parse-number s)] [(string=? "/" (peek s)) (parse-paren s)] [(string=? "~" (peek s)) (parse-binop s)] [(string=? "zro" (peek s))) (parse-if-zero s)])) And then `parse-binop` etc are each in their own class. (They simply need to be sure to recur on `Expr.parse`; if they just write `parse` they'll get, say, `Binop.parse` which is not general enough.) |# ; Given a scanner, consume one B0 expression off the front of it ; and return the corresponding parse-tree. ; (define/contract (parse! s) (-> scanner? expr?) ; Recursive-descent parsing: (cond [(number? (peek s)) (pop! s)] [(string=? "^" (peek s)) (let* {[_ (check-token= (pop! s) "^")] ; consume "^" off front of input [lefty (parse! s)] [righty (parse! s)] [op (pop! s)] [_ (if (not (member? op OPS)) (error 'parse! "Unknown op " op) 'keep-on-going)] } (make-binop op lefty righty))] [(string=? "dark" (peek s)) (let* {[_ (check-token= (pop! s) "dark")] ; consume the 'dark' off the input-stream [the-inside-expr (parse! s)] ; recursively consume one whole Expr (no matter how long) [_ (check-token= (pop! s) "light")] ; consume the trailing 'light' } (make-paren the-inside-expr))] [(string=? "force" (peek s)) (let* {[_ (check-token= (pop! s) "force")]} (make-nop (parse! s)))] [(string=? "mace" (peek s)) (let* {[_ (check-token= (pop! s) "mace")] [the-then-ans (parse! s)] [the-else-ans (parse! s)] [_ (check-token= (pop! s) "windu")] [the-test (parse! s)] } (make-if-zero the-test the-then-ans the-else-ans))] [else (error 'parse! (format "syntax error -- something has gone awry! Seeing ~v." (peek s)))])) (define/contract (eval e) (-> expr? value?) ; Return the value which `e` evaluates to. ; In B0, the only type of `value?`s are `number?`s. ; (cond [(number? e) e] [(binop? e) (let* {[the-op (binop-op e)] [left-val (eval (binop-left e))] [right-val (eval (binop-right e))] } (eval-binop the-op left-val right-val))] [(paren? e) (eval (paren-e e))] [(nop? e) (eval (nop-e e))] [(if-zero? e) (if (zero? (eval (if-zero-tst e))) (eval (if-zero-thn e)) (eval (if-zero-els e)))] [else (error 'eval "unknown type of expr: " (expr->string e))])) (define/contract (eval-binop op l r) (-> string? value? value? value?) ; Implement the binary operators. ; ; We just look up `op` in the list `OP-FUNCS`, and use the function that's in that list. (let* {[ops-entry (assoc op OP-FUNCS)]} ; OPS is a list of list-of-string-and-func; ; so `(second ops-entry)` is a function (if ops-entry is found at all). (cond [(cons? ops-entry) ((second ops-entry) l r)] [else (error 'eval-binop "Unimplemented op " op "; most be one of: " OPS)]))) ; An alternate implementation -- forces us to repeat ; the string-constants already in OPS: #;(cond [(string=? op "/") (+ l r)] [(string=? op "|") (- l r)] [(string=? op "!") (* l r)] [else (error 'eval "Unimplemented op " op)]) ; ; *** In your B1/B2 submission, DELETE whichever eval-binop approach you don't use. (check-expect (eval-binop "/" 3 2) 5) (check-expect (eval-binop "|" 3 2) 1) (check-expect (eval-binop "!" 3 2) 6) ; Return a string-representation of `e`. ; (define/contract (expr->string e) (-> expr? string?) (cond [(number? e) (number->string (if (integer? e) e (exact->inexact e)))] [(binop? e) (string-append "^" (expr->string (binop-left e)) " " (expr->string (binop-right e)) (binop-op e) )] [(paren? e) (string-append "dark" " " (expr->string (paren-e e)) " " "light")] [(nop? e) (string-append "force " (expr->string (nop-e e)))] [(if-zero? e) (string-append "mace " (expr->string (if-zero-thn e)) " " (expr->string (if-zero-els e)) " " "windu" " " (expr->string (if-zero-tst e)) )] [else (error 'expr->string "unknown type of expr: " e)])) ; datatype definition: (define token? (or/c string? number?)) (define/contract (check-token= actual-token expected-token) (-> token? token? token?) ; Verify that `actual-token` equals `expected-token`; throw an error if not. ; IF they are equal, just return `actual-token` (as a convenience-value). ; (if (equal? actual-token expected-token) actual-token (error 'check-token= (format "Expected the token ~v, but got ~v." expected-token actual-token))))