more
This commit is contained in:
@@ -0,0 +1,394 @@
|
||||
#lang pl
|
||||
|
||||
;;; ==================================================================
|
||||
;;; Syntax
|
||||
|
||||
#| The BNF:
|
||||
<TOY> ::= <num>
|
||||
| <id>
|
||||
| { set! <id> <TOY> }
|
||||
| { bind {{ <id> <TOY> } ... } <TOY> <TOY> ... }
|
||||
| { bindrec {{ <id> <TOY> } ... } <TOY> <TOY> ... }
|
||||
| { fun { <id> ... } <TOY> <TOY> ... }
|
||||
| { rfun { <id> ... } <TOY> <TOY> ... }
|
||||
| { if <TOY> <TOY> <TOY> }
|
||||
| { <TOY> <TOY> ... }
|
||||
|#
|
||||
|
||||
;; A matching abstract syntax tree datatype:
|
||||
(define-type TOY
|
||||
[Num Number]
|
||||
[Id Symbol]
|
||||
[Set Symbol TOY]
|
||||
[Bind (Listof Symbol) (Listof TOY) (Listof TOY)]
|
||||
[BindRec (Listof Symbol) (Listof TOY) (Listof TOY)]
|
||||
[Fun (Listof Symbol) (Listof TOY)]
|
||||
[RFun (Listof Symbol) (Listof TOY)]
|
||||
[Call TOY (Listof TOY)]
|
||||
[If TOY TOY TOY])
|
||||
|
||||
(: unique-list? : (Listof Any) -> Boolean)
|
||||
;; Tests whether a list is unique, guards Bind and Fun values.
|
||||
(define (unique-list? xs)
|
||||
(or (null? xs)
|
||||
(and (not (member (first xs) (rest xs)))
|
||||
(unique-list? (rest xs)))))
|
||||
|
||||
(: parse-sexpr : Sexpr -> TOY)
|
||||
;; parses s-expressions into TOYs
|
||||
(define (parse-sexpr sexpr)
|
||||
(match sexpr
|
||||
[(number: n) (Num n)]
|
||||
[(symbol: name) (Id name)]
|
||||
[(cons 'set! more)
|
||||
(match sexpr
|
||||
[(list 'set! (symbol: name) new) (Set name (parse-sexpr new))]
|
||||
[else (error 'parse-sexpr "bad `set!' syntax in ~s" sexpr)])]
|
||||
[(cons (and binder (or 'bind 'bindrec)) more)
|
||||
(match sexpr
|
||||
[(list _ (list (list (symbol: names) (sexpr: nameds)) ...)
|
||||
body0 body ...)
|
||||
(if (unique-list? names)
|
||||
((if (eq? 'bind binder) Bind BindRec)
|
||||
names
|
||||
(map parse-sexpr nameds)
|
||||
(map parse-sexpr (cons body0 body)))
|
||||
(error 'parse-sexpr "duplicate `~s' names: ~s" binder names))]
|
||||
[else (error 'parse-sexpr "bad `~s' syntax in ~s"
|
||||
binder sexpr)])]
|
||||
[(cons (and funner (or 'fun 'rfun)) more)
|
||||
(match sexpr
|
||||
[(list _ (list (symbol: names) ...)
|
||||
body0 body ...)
|
||||
(if (unique-list? names)
|
||||
((if (eq? 'fun funner) Fun RFun)
|
||||
names
|
||||
(map parse-sexpr (cons body0 body)))
|
||||
(error 'parse-sexpr "duplicate `~s' names: ~s" funner names))]
|
||||
[else (error 'parse-sexpr "bad `~s' syntax in ~s"
|
||||
funner 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 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 ENV])
|
||||
|
||||
;; a frame is an association list of names and values.
|
||||
(define-type FRAME = (Listof (List Symbol (Boxof VAL))))
|
||||
|
||||
(define-type VAL
|
||||
[BogusV]
|
||||
[RktV Any]
|
||||
[FunV (Listof Symbol) (ENV -> VAL) ENV Boolean] ; `byref?' flag
|
||||
[PrimV ((Listof VAL) -> VAL)])
|
||||
|
||||
;; a single bogus value to use wherever needed
|
||||
(define the-bogus-value (BogusV))
|
||||
|
||||
(: raw-extend : (Listof Symbol) (Listof (Boxof VAL)) ENV -> ENV)
|
||||
;; extends an environment with a new frame, given names and value
|
||||
;; boxes
|
||||
(define (raw-extend names boxed-values env)
|
||||
(if (= (length names) (length boxed-values))
|
||||
(FrameEnv (map (lambda ([name : Symbol] [boxed-val : (Boxof VAL)])
|
||||
(list name boxed-val))
|
||||
names boxed-values)
|
||||
env)
|
||||
(error 'raw-extend "arity mismatch for names: ~s" names)))
|
||||
|
||||
(: extend : (Listof Symbol) (Listof VAL) ENV -> ENV)
|
||||
;; extends an environment with a new frame (given plain values).
|
||||
(define (extend names values env)
|
||||
(raw-extend names (map (inst box VAL) values) env))
|
||||
|
||||
(: extend-rec : (Listof Symbol) (Listof (ENV -> VAL)) ENV -> ENV)
|
||||
;; extends an environment with a new recursive frame (given compiled
|
||||
;; expressions).
|
||||
(define (extend-rec names compiled-exprs env)
|
||||
(define new-env
|
||||
(extend names
|
||||
(map (lambda (_) the-bogus-value) compiled-exprs)
|
||||
env))
|
||||
;; note: no need to check the lengths here, since this is only
|
||||
;; called for `bindrec', and the syntax make it impossible to have
|
||||
;; different lengths
|
||||
(for-each (lambda ([name : Symbol] [compiled : (ENV -> VAL)])
|
||||
(set-box! (lookup name new-env) (compiled new-env)))
|
||||
names compiled-exprs)
|
||||
new-env)
|
||||
|
||||
(: lookup : Symbol ENV -> (Boxof VAL))
|
||||
;; looks for 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)))]))
|
||||
|
||||
(: unwrap-rktv : VAL -> Any)
|
||||
;; helper for `racket-func->prim-val': unwrap a RktV wrapper in
|
||||
;; preparation to be sent to the primitive function
|
||||
(define (unwrap-rktv x)
|
||||
(cases x
|
||||
[(RktV v) v]
|
||||
[else (error 'racket-func "bad input: ~s" x)]))
|
||||
|
||||
(: racket-func->prim-val : Function -> (Boxof VAL))
|
||||
;; converts a racket function to a primitive evaluator function which
|
||||
;; is a PrimV holding a ((Listof VAL) -> VAL) function. (the
|
||||
;; resulting function will use the list function as is, and it is the
|
||||
;; list function's responsibility to throw an error if it's given a
|
||||
;; bad number of arguments or bad input types.)
|
||||
(define (racket-func->prim-val racket-func)
|
||||
(define list-func (make-untyped-list-function racket-func))
|
||||
(box (PrimV (lambda (args)
|
||||
(RktV (list-func (map unwrap-rktv args)))))))
|
||||
|
||||
;; The global environment has a few primitives:
|
||||
(: global-environment : ENV)
|
||||
(define global-environment
|
||||
(FrameEnv (list (list '+ (racket-func->prim-val +))
|
||||
(list '- (racket-func->prim-val -))
|
||||
(list '* (racket-func->prim-val *))
|
||||
(list '/ (racket-func->prim-val /))
|
||||
(list '< (racket-func->prim-val <))
|
||||
(list '> (racket-func->prim-val >))
|
||||
(list '= (racket-func->prim-val =))
|
||||
;; values
|
||||
(list 'true (box (RktV #t)))
|
||||
(list 'false (box (RktV #f))))
|
||||
(EmptyEnv)))
|
||||
|
||||
;;; ==================================================================
|
||||
;;; Compilation
|
||||
|
||||
(: compiler-enabled? : (Boxof Boolean))
|
||||
;; a global flag that can disable the compiler
|
||||
(define compiler-enabled? (box #f))
|
||||
|
||||
(: compile-body : (Listof TOY) -> (ENV -> VAL))
|
||||
;; compiles a list of expressions to a single Racket function.
|
||||
(define (compile-body exprs)
|
||||
(unless (unbox compiler-enabled?)
|
||||
(error 'compile-body "compiler disabled"))
|
||||
(let ([compiled-1st (compile (first exprs))]
|
||||
[rest (rest exprs)])
|
||||
(if (null? rest)
|
||||
compiled-1st
|
||||
(let ([compiled-rest (compile-body rest)])
|
||||
(lambda (env)
|
||||
(define ignored (compiled-1st env))
|
||||
(compiled-rest env))))))
|
||||
|
||||
(: compile-get-boxes : (Listof TOY) -> (ENV -> (Listof (Boxof VAL))))
|
||||
;; utility for applying rfun
|
||||
(define (compile-get-boxes exprs)
|
||||
(: compile-getter : TOY -> (ENV -> (Boxof VAL)))
|
||||
(define (compile-getter expr)
|
||||
(cases expr
|
||||
[(Id name)
|
||||
(lambda ([env : ENV]) (lookup name env))]
|
||||
[else
|
||||
(lambda ([env : ENV])
|
||||
(error 'call "rfun application with a non-identifier ~s"
|
||||
expr))]))
|
||||
(unless (unbox compiler-enabled?)
|
||||
(error 'compile-get-boxes "compiler disabled"))
|
||||
(let ([getters (map compile-getter exprs)])
|
||||
(lambda (env)
|
||||
(map (lambda ([get-box : (ENV -> (Boxof VAL))]) (get-box env))
|
||||
getters))))
|
||||
|
||||
(: compile : TOY -> (ENV -> VAL))
|
||||
;; compiles TOY expressions to Racket functions.
|
||||
(define (compile expr)
|
||||
;; convenient helper for running compiled code
|
||||
(: caller : ENV -> ((ENV -> VAL) -> VAL))
|
||||
(define (caller env)
|
||||
(lambda (compiled) (compiled env)))
|
||||
(unless (unbox compiler-enabled?)
|
||||
(error 'compile "compiler disabled"))
|
||||
(cases expr
|
||||
[(Num n) (lambda ([env : ENV]) (RktV n))]
|
||||
[(Id name) (lambda ([env : ENV]) (unbox (lookup name env)))]
|
||||
[(Set name new)
|
||||
(define compiled-new (compile new))
|
||||
(lambda ([env : ENV])
|
||||
(set-box! (lookup name env) (compiled-new env))
|
||||
the-bogus-value)]
|
||||
[(Bind names exprs bound-body)
|
||||
(define compiled-exprs (map compile exprs))
|
||||
(define compiled-body (compile-body bound-body))
|
||||
(lambda ([env : ENV])
|
||||
(compiled-body
|
||||
(extend names (map (caller env) compiled-exprs) env)))]
|
||||
[(BindRec names exprs bound-body)
|
||||
(define compiled-exprs (map compile exprs))
|
||||
(define compiled-body (compile-body bound-body))
|
||||
(lambda ([env : ENV])
|
||||
(compiled-body (extend-rec names compiled-exprs env)))]
|
||||
[(Fun names bound-body)
|
||||
(define compiled-body (compile-body bound-body))
|
||||
(lambda ([env : ENV]) (FunV names compiled-body env #f))]
|
||||
[(RFun names bound-body)
|
||||
(define compiled-body (compile-body bound-body))
|
||||
(lambda ([env : ENV]) (FunV names compiled-body env #t))]
|
||||
[(Call fun-expr arg-exprs)
|
||||
(define compiled-fun (compile fun-expr))
|
||||
(define compiled-args (map compile arg-exprs))
|
||||
(define compiled-boxes-getter (compile-get-boxes arg-exprs))
|
||||
(lambda ([env : ENV])
|
||||
(define fval (compiled-fun env))
|
||||
;; delay evaluating the arguments
|
||||
(define arg-vals (lambda () (map (caller env) compiled-args)))
|
||||
(cases fval
|
||||
[(PrimV proc) (proc (arg-vals))]
|
||||
[(FunV names compiled-body fun-env byref?)
|
||||
(compiled-body (if byref?
|
||||
(raw-extend names
|
||||
(compiled-boxes-getter env)
|
||||
fun-env)
|
||||
(extend names (arg-vals) fun-env)))]
|
||||
[else (error 'call "function call with a non-function: ~s"
|
||||
fval)]))]
|
||||
[(If cond-expr then-expr else-expr)
|
||||
(define compiled-cond (compile cond-expr))
|
||||
(define compiled-then (compile then-expr))
|
||||
(define compiled-else (compile else-expr))
|
||||
(lambda ([env : ENV])
|
||||
((if (cases (compiled-cond env)
|
||||
[(RktV v) v] ; Racket value => use as boolean
|
||||
[else #t]) ; other values are always true
|
||||
compiled-then
|
||||
compiled-else)
|
||||
env))]))
|
||||
|
||||
(: run : String -> Any)
|
||||
;; compiles and runs a TOY program contained in a string
|
||||
(define (run str)
|
||||
(set-box! compiler-enabled? #t)
|
||||
(let ([compiled (compile (parse str))])
|
||||
(set-box! compiler-enabled? #f)
|
||||
(let ([result (compiled global-environment)])
|
||||
(cases result
|
||||
[(RktV v) v]
|
||||
[else (error 'run "the program 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 "{bind {{x 5} {x 5}} x}") =error> "duplicate*bind*names")
|
||||
(test (run "{fun {x x} x}") =error> "duplicate*fun*names")
|
||||
(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")
|
||||
|
||||
;; assignment tests
|
||||
(test (run "{set! {+ x 1} x}") =error> "bad `set!' syntax")
|
||||
(test (run "{bind {{x 1}} {set! x {+ x 1}} x}") => 2)
|
||||
|
||||
;; `bindrec' tests
|
||||
(test (run "{bindrec {x 6} x}") =error> "bad `bindrec' syntax")
|
||||
(test (run "{bindrec {{fact {fun {n}
|
||||
{if {= 0 n}
|
||||
1
|
||||
{* n {fact {- n 1}}}}}}}
|
||||
{fact 5}}")
|
||||
=> 120)
|
||||
|
||||
;; tests for multiple expressions and assignment
|
||||
(test (run "{bind {{make-counter
|
||||
{fun {}
|
||||
{bind {{c 0}}
|
||||
{fun {}
|
||||
{set! c {+ 1 c}}
|
||||
c}}}}}
|
||||
{bind {{c1 {make-counter}}
|
||||
{c2 {make-counter}}}
|
||||
{* {c1} {c1} {c2} {c1}}}}")
|
||||
=> 6)
|
||||
(test (run "{bindrec {{foo {fun {}
|
||||
{set! foo {fun {} 2}}
|
||||
1}}}
|
||||
{+ {foo} {* 10 {foo}}}}")
|
||||
=> 21)
|
||||
|
||||
;; `rfun' tests
|
||||
(test (run "{{rfun {x} x} 4}") =error> "non-identifier")
|
||||
(test (run "{bind {{swap! {rfun {x y}
|
||||
{bind {{tmp x}}
|
||||
{set! x y}
|
||||
{set! y tmp}}}}
|
||||
{a 1}
|
||||
{b 2}}
|
||||
{swap! a b}
|
||||
{+ a {* 10 b}}}")
|
||||
=> 12)
|
||||
|
||||
;; test that argument are not evaluated redundantly
|
||||
(test (run "{{rfun {x} x} {/ 4 0}}") =error> "non-identifier")
|
||||
(test (run "{5 {/ 6 0}}") =error> "non-function")
|
||||
|
||||
;; test compiler-disabled flag, for complete coverage
|
||||
;; (these tests must use the functions instead of the toplevel `run',
|
||||
;; since there is no way to get this error otherwise, this indicates
|
||||
;; that this error should not occur outside of our code -- it is an
|
||||
;; internal error check)
|
||||
(test (compile (Num 1)) =error> "compiler disabled")
|
||||
(test (compile-body (list (Num 1))) =error> "compiler disabled")
|
||||
(test (compile-get-boxes (list (Num 1))) =error> "compiler disabled")
|
||||
|
||||
;;; ==================================================================
|
||||
@@ -0,0 +1,40 @@
|
||||
#lang racket
|
||||
|
||||
|
||||
|
||||
(define-syntax let*
|
||||
(syntax-rules () ;; special tokens
|
||||
[(let* () body ...)
|
||||
(let () body ...)]
|
||||
[(let* ((x v) (xs vs) ...) body ...)
|
||||
(let ((x v)) (let* ((xs vs) ...) body ...))]))
|
||||
|
||||
|
||||
|
||||
(let* [(x 5)
|
||||
(y (+ x 2))]
|
||||
y)
|
||||
;; ->
|
||||
(let ((x 5))
|
||||
(let* ((y (+ x 2)))
|
||||
y))
|
||||
;; ->
|
||||
(let ((x 5))
|
||||
(let ((y (+ x 2)))
|
||||
(let* ()
|
||||
y)))
|
||||
;; ->
|
||||
(let ((x 5))
|
||||
(let ((y (+ x 2)))
|
||||
(let ()
|
||||
y)))
|
||||
|
||||
(define-syntax annd
|
||||
(syntax-rules ()
|
||||
[(annd) #t]
|
||||
[(annd x) (if x x #f)]
|
||||
[(annd x y ...) (if x (annd y ...) #f)]))
|
||||
|
||||
(annd #f (/ 5 0))
|
||||
(and 5 2 3)
|
||||
(annd 5 2 3)
|
||||
@@ -0,0 +1,30 @@
|
||||
#lang pl
|
||||
|
||||
(define-type Token = (U Symbol Integer))
|
||||
|
||||
(: in? : Symbol (Listof Symbol) -> Boolean)
|
||||
(define (in? s l) (and (not (null? l)) (not (and (not (symbol=? s (first l))) (not (in? s (rest l)))))))
|
||||
|
||||
(test (in? 'a '(b d e a f e)))
|
||||
(test (in? 'a '(b x e a x e)))
|
||||
(test (in? 'a '(a b d e a f e)))
|
||||
(test (in? 'a '(b d e a f e a)))
|
||||
(test (not (in? 'a '(b d e f e))))
|
||||
(test (not (in? 'a '())))
|
||||
|
||||
#|
|
||||
(: abc : String -> Boolean)
|
||||
; Identifies strings that match "(ab(c+))+"
|
||||
(define abc (automaton one (four)
|
||||
[one : (a -> two)]
|
||||
[two : (b -> three)]
|
||||
[three : (c -> four)]
|
||||
[four : (c -> four) (a -> two)]))
|
||||
|
||||
(test (abc "abccc"))
|
||||
(test (abc "abcccabc"))
|
||||
(test (not (abc "ab")))
|
||||
(test (not (abc "q")))
|
||||
(test (not (abc "abcb")))
|
||||
(test (not (abc "abcab")))
|
||||
|#
|
||||
Reference in New Issue
Block a user