;;> The Toy language implementation #lang typed-scheme ;;; ================================================================== ;;; Syntax #| The BNF: ::= | | { bind {{ } ... } } | { fun { ... } } | { if } | { ... } |# ;; A matching abstract syntax tree datatype: (define-type TOY [Num (n Number)] [Id (name Symbol)] [Bind (names (Listof Symbol)) (exprs (Listof TOY)) (body TOY)] [Fun (names (Listof Symbol)) (body TOY)] [Call (fun-expr TOY) (arg-exprs (Listof TOY))] [If (cond-expr TOY) (then-expr TOY) (else-expr TOY)]) (: unique-list? : ((Listof Any) -> Boolean)) ;; Tests whether a list is unique, used to guard Bind anf Fun values. (define (unique-list? xs) (or (null? xs) (and (not (member (first xs) (rest xs))) (unique-list? (rest xs))))) (: parse-sexpr : (Sexpr -> TOY)) ;; to convert s-expressions into TOYs (define (parse-sexpr sexpr) (match sexpr [(number: n) (Num n)] [(symbol: name) (Id name)] [(cons 'bind more) (match sexpr [(list 'bind (list (list (symbol: names) (sexpr: nameds)) ...) body) (if (unique-list? names) (Bind names (map parse-sexpr nameds) (parse-sexpr body)) (error 'parse-sexpr "`bind' got duplicate names: ~s" names))] [else (error 'parse-sexpr "bad `bind' syntax in ~s" sexpr)])] [(cons 'fun more) (match sexpr [(list 'fun (list (symbol: names) ...) body) (if (unique-list? names) (Fun names (parse-sexpr body)) (error 'parse-sexpr "`fun' got duplicate names: ~s" names))] [else (error 'parse-sexpr "bad `fun' syntax in ~s" sexpr)])] [(cons 'if more) (match sexpr [(list 'if cond then else) (If (parse-sexpr cond) (parse-sexpr then) (parse-sexpr else))] [else (error 'parse-sexpr "bad `if' syntax in ~s" sexpr)])] [(list fun (sexpr: args) ...) ; other lists are applications (Call (parse-sexpr fun) (map parse-sexpr args))] [else (error 'parse-sexpr "bad syntax in ~s" sexpr)])) (: parse : (String -> TOY)) ;; Parses a string containing an TOY expression to a TOY AST. (define (parse str) (parse-sexpr (string->sexpr str))) ;;; ================================================================== ;;; Values and environments (define-type ENV [EmptyEnv] [FrameEnv (frame FRAME) (rest ENV)]) (define-type VAL [ScmV (x Any)] [FunV (names (Listof Symbol)) (body TOY) (env ENV)] [PrimV (prim (Any ... -> Any))]) ;; a frame is an association list of names and values. (define-type FRAME = (Listof (List Symbol VAL))) (: extend : ((Listof Symbol) (Listof VAL) ENV -> ENV)) ;; extends an environment with a new frame. (define (extend names values env) (if (= (length names) (length values)) (FrameEnv (map list names values) env) (error 'extend "arity mismatch for names: ~s" names))) (: lookup : (Symbol ENV -> VAL)) ;; looks a name in an environment, searching through each frame. (define (lookup name env) (cases env [(EmptyEnv) (error 'lookup "no binding for ~s" name)] [(FrameEnv frame rest) (let ([cell (assq name frame)]) (if cell (second cell) (lookup name rest)))])) (: scheme-func->prim-val : ((Any ... -> Any) -> VAL)) ;; converts a scheme function to a primitive evaluator function which ;; is a PrimV holding a ((Listof Any) -> Any) procedure. (the result ;; procedure doesn't check for types and arity.) (define (scheme-func->prim-val scheme-func) (PrimV (lambda (args) (let ([args (map (lambda (a) (cases a [(ScmV v) v] [else (error 'scheme-func "bad input: ~s" a)])) args)]) (ScmV (apply scheme-func args)))))) ;; The global environment has a few primitives: (: global-environment : ENV) (define global-environment (FrameEnv (list (list '+ (scheme-func->prim-val +)) (list '- (scheme-func->prim-val -)) (list '* (scheme-func->prim-val *)) (list '/ (scheme-func->prim-val /)) (list '< (scheme-func->prim-val <)) (list '> (scheme-func->prim-val >)) (list '= (scheme-func->prim-val =)) ;; values (list 'true (ScmV #t)) (list 'false (ScmV #f))) (EmptyEnv))) ;;; ================================================================== ;;; Evaluation (: eval : (TOY ENV -> VAL)) ;; evaluates TOY expressions. (define (eval expr env) (cases expr [(Num n) (ScmV n)] [(Id name) (lookup name env)] [(Bind names exprs bound-body) (eval bound-body (extend names (map (lambda (e) (eval e env)) exprs) env))] [(Fun names bound-body) (FunV names bound-body env)] [(Call fun-expr arg-exprs) (let ([fval (eval fun-expr env)] [arg-vals (map (lambda (e) (eval e env)) arg-exprs)]) (cases fval [(PrimV proc) (proc arg-vals)] [(FunV names body fun-env) (eval body (extend names arg-vals fun-env))] [else (error 'eval "function call with a non-function: ~s" fval)]))] [(If cond-expr then-expr else-expr) (eval (if (cases (eval cond-expr env) [(ScmV v) v] ; Scheme value => use as boolean [else #t]) ; other values are always true then-expr else-expr) env)])) (: run : (String -> Any)) ;; evaluate a TOY program contained in a string (define (run str) (let ([result (eval (parse str) global-environment)]) (cases result [(ScmV v) v] [else (error 'run "evaluation returned a bad value: ~s" result)]))) ;;; ================================================================== ;;; Tests (test (run "{{fun {x} {+ x 1}} 4}") => 5) (test (run "{bind {{add3 {fun {x} {+ x 3}}}} {add3 1}}") => 4) (test (run "{bind {{add3 {fun {x} {+ x 3}}} {add1 {fun {x} {+ x 1}}}} {bind {{x 3}} {add1 {add3 x}}}}") => 7) (test (run "{bind {{identity {fun {x} x}} {foo {fun {x} {+ x 1}}}} {{identity foo} 123}}") => 124) (test (run "{bind {{x 3}} {bind {{f {fun {y} {+ x y}}}} {bind {{x 5}} {f 4}}}}") => 7) (test (run "{{{fun {x} {x 1}} {fun {x} {fun {y} {+ x y}}}} 123}") => 124) ;; More tests for complete coverage (test (run "{bind x 5 x}") =error> "bad `bind' syntax") (test (run "{fun x x}") =error> "bad `fun' syntax") (test (run "{if x}") =error> "bad `if' syntax") (test (run "{}") =error> "bad syntax") (test (run "{+ x 1}") =error> "no binding for") (test (run "{+ 1 {fun {x} x}}") =error> "bad input") (test (run "{+ 1 {fun {x} x}}") =error> "bad input") (test (run "{1 2}") =error> "with a non-function") (test (run "{{fun {x} x}}") =error> "arity mismatch") (test (run "{if {< 4 5} 6 7}") => 6) (test (run "{if {< 5 4} 6 7}") => 7) (test (run "{if + 6 7}") => 6) (test (run "{fun {x} x}") =error> "returned a bad value") ;;; ==================================================================