#lang pl #| CFG for the BARF language: ::= { prog ... } ::= { fun { } } ::= | | { + ... } | { * ... } | { - ... } | { / ... } | { = } | { is } -- Sugar. | { < } | { is less than } -- Sugar. | { <= } | { is less than or equal to } -- Sugar. | { with { } } | { with as } -- Sugar. | { if } | { if then else } -- Sugar. | { and } -- Sugar. | { or } -- Sugar. | { not } -- Sugar. | { lambda } | { of } | | { call } |# (define-type PROG [Prog (Listof FUN)]) (define-type FUN [Fun Symbol Symbol BARF]) (define-type BARF [Num Number] [Bool Boolean] [Sum (Listof BARF)] [Mul (Listof BARF)] [Sub BARF (Listof BARF)] [Div BARF (Listof BARF)] [Eq BARF BARF] [Lt BARF BARF] [Lte BARF BARF] [With Symbol BARF BARF] [If BARF BARF BARF] [Lambda Symbol BARF] [Call BARF BARF] [Id Symbol] [Call2 Symbol BARF]) ; This is a little strange. (define-type LambdaContents [Par Symbol] [Body BARF]) (define-type Primitive = (U Number Boolean (Listof LambdaContents))) (define reserved-names (list 'F 'T '+ '* '- '/ '= 'is '< 'less 'than '<= 'or 'equal 'to 'with 'if 'then 'else 'lambda 'of 'and 'not 'as 'call 'prog 'fun)) (define pf (Bool #f)) (define pt (Bool #t)) (: parse : Sexpr -> PROG) (define (parse sexpr) (parse-prog sexpr)) (: parse-prog : Sexpr -> PROG) ;; Parses Sexprs into PROGs. (define (parse-prog sexpr) (match sexpr [(list 'prog f ...) (Prog (map parse-fun f))] [else (error 'parse-prog "Bag prog syntax in ~s." sexpr)])) (: parse-fun : Sexpr -> FUN) ;; Parses Sexprs into FUNs. (define (parse-fun sexpr) (match sexpr [(list 'fun more) (match sexpr [(list 'fun (symbol: fname) (list (symbol: param)) barf) (Fun fname param (parse-barf barf))])])) (: parse-barf : Sexpr -> BARF) ;; parses s-expressions into BARFs (define (parse-barf sexpr) ;; utility for parsing a list of barf expressions (: parse-barfs : (Listof Sexpr) -> (Listof BARF)) (define (parse-barfs sexprs) (map parse-sexpr sexprs)) (match sexpr [(number: n) (Num n)] ['T (Bool #t)] ['F (Bool #f)] [(list '+ args ...) (Sum (parse-barfs args))] [(list '* args ...) (Mul (parse-barfs args))] [(list '- fst args ...) (Sub (parse-barf fst) (parse-barfs args))] [(list '/ fst args ...) (Div (parse-barf fst) (parse-barfs args))] [(list '= l r) (Eq (parse-barf l) (parse-barf r))] [(list l 'is r) (Eq (parse-barf l) (parse-barf r))] [(list '< l r) (Lt (parse-barf l) (parse-barf r))] [(list l 'is 'less 'than r) (Lt (parse-barf l) (parse-barf r))] [(list '<= l r) (Lte (parse-barf l) (parse-barf r))] [(list l 'is 'less 'than 'or 'equal 'to r) (Lte (parse-barf l) (parse-barf r))] [(list 'with (symbol: name) 'as val body) (With name (parse-barf val) (parse-barf body))] [(cons 'with more) (match sexpr [(list 'with (list (symbol: name) named) body) (With name (parse-barf named) (parse-barf body))] [else (error 'parse-barf "bad `with' syntax in ~s" sexpr)])] [(list 'if p b a) (If (parse-barf p) (parse-barf b) (parse-barf a))] [(list 'if p 'then b 'else a) (If (parse-barf p) (parse-barf b) (parse-barf a))] [(list 'and a b) (andhelper a b)] [(list a 'and b) (andhelper a b)] [(list 'or a b) (orhelper a b)] [(list a 'or b) (orhelper a b)] [(list 'not a) (nothelper a)] [(list 'lambda (symbol: arg) body) (Lambda arg (parse-barf body))] [(list f 'of arg) (Call (parse-barf f) (parse-barf arg))] [(symbol: name) (Id name)] [(list 'call (symbol: f) b) (Call2 f (parse-barf b))] [else (error 'parse-barf "Bad barf syntax in ~s." sexpr)])) (: andhelper : Sexpr Sexpr -> BARF) (define (andhelper a b) (If (parse-barf a) (If (parse-barf b) pt pf) pf)) (: orhelper : Sexpr Sexpr -> BARF) (define (orhelper a b) (If (parse-barf a) pt (If (parse-barf b) pt pf))) (: nothelper : Sexpr -> BARF) (define (nothelper a ) (If (parse-barf a) pf pt)) (: parse : String -> BARF) ;; parses a string containing an BARF expression to an BARF AST (define (parse str) (parse-barf (string->sexpr str))) (: subst : BARF Symbol BARF -> BARF) ;; substitutes the second argument with the third argument in the ;; first argument, as per the rules of substitution; the resulting ;; expression contains no free instances of the second argument (define (subst expr from to) ;; convenient helper -- no need to specify `from' and `to' (: subst* : BARF -> BARF) (define (subst* x) (subst x from to)) ;; helper to substitute lists (: substs* : (Listof BARF) -> (Listof BARF)) (define (substs* exprs) (map subst* exprs)) (cases expr [(Num n) expr] [(Bool p) expr] [(Sum args) (Sum (substs* args))] [(Mul args) (Mul (substs* args))] [(Sub fst args) (Sub (subst* fst) (substs* args))] [(Div fst args) (Div (subst* fst) (substs* args))] [(Eq l r) (Eq (subst* l) (subst* r))] [(Lt l r) (Lt (subst* l) (subst* r))] [(Lte l r) (Lte (subst* l) (subst* r))] [(If p b a) (If (subst* p) (subst* b) (subst* a))] [(With bound-id named-expr bound-body) (With bound-id (subst* named-expr) (if (eq? bound-id from) bound-body (subst* bound-body)))] [(Lambda arg body) (Lambda arg (if (eq? arg from) body (subst* body)))] [(Call f arg) (Call (subst* f) (subst* arg))] [(Id name) (if (eq? name from) to expr)])) (: eval-number : BARF -> Number) ;; helper for `eval': verifies that the result is a number (define (eval-number expr) (let ([result (eval expr)]) (if (number? result) result (error 'eval-number "need a number when evaluating ~s, but got ~s" expr result)))) (: eval-boolean : BARF -> Boolean) ;; helper for `eval': verifies that the result is a boolean (define (eval-boolean expr) (let ([result (eval expr)]) (if (boolean? result) result (error 'eval-boolean "need a boolean when evaluating ~s, but got ~s" expr result)))) ; This is pretty cursed, but I couldn't find a better way to have structured data. (: rehydrate : (Listof LambdaContents) -> BARF) ; Recreates a lambda from list representation. (define (rehydrate l) (Lambda (cases (first l) [(Par s) s] [_ (error 'rehydrate "what")]) (cases (second l) [(Body b) b] [_ (error 'rehydrate "what")]))) (: value->barf : Primitive -> BARF) ;; converts a value to an BARF value (so it can be used with `subst') (define (value->barf val) (cond [(number? val) (Num val)] [(boolean? val) (Bool val)] [(list? val) (rehydrate val)])) (: eval-sum : (Listof BARF) -> Number) ; Evaluates a Sum expression. (define (eval-sum args) (foldr + 0 (map eval-number args))) (: eval-mul : (Listof BARF) -> Number) ; Evaluates a Mul expression. (define (eval-mul args) (foldr * 1 (map eval-number args))) (: eval-sub : BARF (Listof BARF) -> Number) ; Evaluates a Sub expression. (define (eval-sub fst args) (define l (length args)) (cond [(= l 0) (- (eval-number fst))] [else (- (eval-number fst) (eval-sum args))])) (: eval-div : BARF (Listof BARF) -> Number) ; Evaluates a Div expression. (define (eval-div fst args) (define l (length args)) (cond [(= l 0) (/ (eval-number fst))] [else (/ (eval-number fst) (eval-mul args))])) ; Huh. (: eval-eq : BARF BARF -> Boolean) ; Evaluates an Eq expression. (define (eval-eq a b) (= (eval-number a) (eval-number b))) (: eval-lt : BARF BARF -> Boolean) ; Evaluates an Lt expression. (define (eval-lt a b) (< (eval-number a) (eval-number b))) (: eval-lte : BARF BARF -> Boolean) ; Evaluates an Lte expression. (define (eval-lte a b) (<= (eval-number a) (eval-number b))) (: eval-if : BARF BARF BARF -> Primitive) ; Evaluates an If expression. (define (eval-if pred body alt) (if (eval-boolean pred) (eval body) (eval alt))) (: eval-lambda : BARF Symbol BARF -> Primitive ) ; Evaluates a Lambda expression (in a Call expression). (define (eval-lambda arg par body) (if (member par reserved-names) (error 'eval-lambda "Can't create parameter binding to reserved name '~s'." par) (eval (subst body par (value->barf (eval arg)))))) (: eval-call : BARF BARF -> Primitive) ; Evaluates a Call expression. (define (eval-call f arg) (match f [(list (symbol: par) body) (eval-lambda arg par body)] [_ (cases f [(Lambda par body) (eval-lambda arg par body)] [_ (error 'eval-call "Expected lambda expression to call, instead got: ~s." f)])])) (: findfun : Symbol PROGRAM -> FUN) ; Finds the Fun. (define (findfun fname prog) (define foundfun (cases prog [(Prog flist) (ormap (lambda (f) (cases f [(Fun name param body) (if (string=? name fname) (f) (#f))])))])) (if (not foundfun) (error 'findfun "Function not found.") foundfun)) (: eval : BARF -> Primitive) ;; evaluates BARF expressions by reducing them to numbers (define (eval expr) (cases expr [(Num n) n] [(Bool p) p] [(Sum args) (eval-sum args)] [(Mul args) (eval-mul args)] [(Sub fst args) (eval-sub fst args)] [(Div fst args) (eval-div fst args)] [(Eq l r) (eval-eq l r)] [(Lt l r) (eval-lt l r)] [(Lte l r) (eval-lte l r)] [(If p b a) (eval-if p b a)] [(With bound-id named-expr bound-body) (if (member bound-id reserved-names) (error 'eval "Can't create binding to reserved name '~s'." bound-id) (eval (subst bound-body bound-id ;; see the above `value-barf' helper (value->barf (eval named-expr)))))] [(Lambda par body) (if (member par reserved-names) (error 'eval "Can't create parameter binding to reserved name '~s'." par) (list (Par par) (Body body)))] [(Call f arg) (eval-call (value->barf (eval f)) arg)] [(Id name) (error 'eval "free identifier: ~s" name)])) (: run : String -> Primitive) ;; evaluate an BARF program contained in a string (define (run str) (eval (parse str))) ; As we'ren't writing a lazy language, we need to use the Z combinator to delay evaluation of f (otherwise it'd never terminate). This factorial function also isn't tail recursive cause it's hard enough for me to understand already ;-;. ; Z = λf.(λx.(f λv.((x x) v)))(λx.(f λv.((x x) v))) ; factorial = (Y (λself.λn.if (<= n 1) 1 (* n (self (- n 1))))) (test (run " {with Z as {lambda f { {lambda x {f of {lambda v {{x of x} of v}}}} of {lambda x {f of {lambda v {{x of x} of v}}}} }} {with factorial as {Z of {lambda self {lambda n {if {n is less than or equal to 1} then 1 else {* n {self of {- n 1}}}} } }} {{{factorial of 4} is 24} and {{factorial of 5} is 120}}}}") => #t) (test (run "{{lambda f {f of 1}} of {lambda x {+ x 1}}}") => 2) (test (run "{{lambda x {+ x 1}} of 2}") => 3) (test (run "{{lambda + {+ + +}} of 2}") =error> "Can't create parameter binding to reserved name '+'.") (test (run "{1 of 1}") =error> "eval-call: Expected lambda expression to call, instead got: (Num 1).") (test (run "{with {x 2} {x of 1}}") =error> "eval-call: Expected lambda expression to call, instead got: (Num 2).") (test (run "{and T T}") => #t) (test (run "{and F aaaaaaaaaa}") => #f) (test (run "{or T aaaaaaaaaa}") => #T) (test (run "{or F T}") => #T) (test (run "{or T T}") => #T) (test (run "{not T}") => #F) (test (run "{not F}") => #T) (test (run "{with {p1 {< 1 2}} {with {p2 {< 3 4}} {and p1 p2}}}") => #T) (test (run "{if T 1 2}") => 1) (test (run "{if T then 1 else 2}") => 1) (test (run "{with {T 2} {+ T 1}}") =error> "Can't create binding to reserved name 'T'.") (test (run "{with {+ 2} {+ + 1}}") =error> "Can't create binding to reserved name '+'.") (test (run "{< 1 2}") => #t) (test (run "{< 2 2}") => #f) (test (run "{< 2 1}") => #f) (test (run "{1 is 2}") => #f) (test (run "{= 1 2}") => #f) (test (run "{= 2 2}") => #t) (test (run "{2 is 2}") => #t) (test (run "{<= 1 2}") => #t) (test (run "{<= 2 2}") => #t) (test (run "{<= 2 1}") => #f) (test (run "{+}") => 0) (test (run "{+ 12}") => 12) (test (run "{+ 1 2 3}") => 6) (test (run "{*}") => 1) (test (run "{* 12}") => 12) (test (run "{* 2 3 5}") => 30) (test (run "{-}") =error> "parse-sexpr: bad syntax in (-)") (test (run "{- 1}") => -1) (test (run "{- 3 2 1}") => 0) (test (run "{- 1 2 3}") => -4) (test (run "{/}") =error> "parse-sexpr: bad syntax in (/)") (test (run "{/ 2}") => 1/2) (test (run "{/ 3 2 1}") => 3/2) (test (run "{/ 1 2 3}") => 1/6) (test (run "{+ x 1}") =error> "eval: free identifier: x") ;; tests (for simple expressions) (test (run "5") => 5) (test (run "{+ 5 5}") => 10) (test (run "{with {x {+ 5 5}} {+ x x}}") => 20) (test (run "{with {x 5} {+ x x}}") => 10) (test (run "{with {x {+ 5 5}} {with {y {- x 3}} {+ y y}}}") => 14) (test (run "{with {x 5} {with {y {- x 3}} {+ y y}}}") => 4) (test (run "{with {x 5} {+ x {with {x 3} 10}}}") => 15) (test (run "{with {x 5} {+ x {with {x 3} x}}}") => 8) (test (run "{with {x 5} {+ x {with {y 3} x}}}") => 10) (test (run "{with {x 5} {with {y x} y}}") => 5) (test (run "{with {x 5} {with {x x} x}}") => 5)