Janet implementation of Lox from the book Crafting Interpreters

support lexically scoped vars via dynamic binding

+28 -6
lox/interpreter.janet
··· 32 32 token (errorf "Unknown operator %q" token)) 33 33 left right)) 34 34 35 + (defn- declare-var [name] 36 + (setdyn name @[])) 37 + 38 + (defn- set-var [name val] 39 + (if-let [ref (dyn name)] 40 + (do (array/clear ref) (array/push ref val)) 41 + (errorf "Undefined variable '%s'." name))) 42 + 43 + (defn- get-var [name] 44 + (if-let [@[val] (dyn name)] 45 + val 46 + (errorf "Undefined variable '%s'." name))) 47 + 35 48 (defn evaluate [expr] 36 49 (match expr 37 50 [:literal value] value ··· 41 54 [:logical left op right] (case ((op :token) 0) 42 55 :or (if-let [left (evaluate left)] left (evaluate right)) 43 56 :and (if-let [left (evaluate left)] (evaluate right) left)) 44 - (errorf "Unknown expression type %q" (expr 0)))) 57 + [:assign {:token [:ident name]} value] (set-var name (evaluate value)) 58 + [:variable {:token [:ident name]}] (get-var name) 59 + [ty] (errorf "Unknown expression type %s" ty) 60 + (errorf "Invalid expression %q" expr))) 45 61 46 62 (var execute nil) 47 63 48 64 (defn- execute-block [stmts] 49 - (each stmt stmts (execute stmt))) 65 + # open new scope 66 + (with-dyns [] 67 + (each stmt stmts (execute stmt)))) 50 68 51 69 (varfn execute [stmt] 52 70 (match stmt 53 71 [:print expr] (printf "%q" (evaluate expr)) 54 - [:expr expr] (xprintf (dyn :expr-out stderr) "%Q" (evaluate expr)) 72 + [:expr expr] (xprintf (dyn :expr-out @"") "%Q" (evaluate expr)) 55 73 # [:return word value] (throw return) 56 74 [:if cond then else] (do 57 75 (cond ··· 59 77 (not (nil? else)) (execute else))) 60 78 [:while cond body] (while (evaluate cond) (execute body)) 61 79 [:block stmts] (execute-block stmts) 62 - (errorf "Unknown statement type %q" (stmt 0)))) 80 + [:var {:token [:ident name]} init] (let [val (and init (evaluate init))] 81 + (declare-var name) 82 + (when init (set-var name val))) 83 + [ty] (errorf "Unknown statement type %s" ty) 84 + (errorf "Invalid statement %q" stmt))) 63 85 64 86 (defn interpret [stmts] 87 + (setdyn :locals @{}) 65 88 # TODO error handling 66 - (each stmt stmts (try (execute stmt) 67 - ([e] (eprint e))))) 89 + (each stmt stmts (execute stmt)))
+3 -2
lox/main.janet
··· 13 13 (defn main [_ &opt path & args] 14 14 (unless (empty? args) (error "expected 0 or 1 args")) 15 15 (if (nil? path) 16 - (repl/run process) 17 - (with-dyns [:expr-out @""] (process (slurp path))))) 16 + 17 + (with-dyns [:expr-out stderr] (repl/run process)) 18 + (process (slurp path))))
+2 -2
lox/parser.janet
··· 40 40 [:nil] [:literal nil] 41 41 [:num val] [:literal val] 42 42 [:str val] [:literal val] 43 - [:ident name] [:variable name] 43 + [:ident name] [:variable token] 44 44 [:left-paren] (do (def expr (expression parser)) 45 45 (:consume parser :right-paren "Expect ')' after expression.") 46 46 [:grouping expr]))) ··· 90 90 (when (def equals (:match parser :eq)) 91 91 (def value (assignment parser)) 92 92 (match expr 93 - [:variable name] (set expr [:assignment name value]) 93 + [:variable name] (set expr [:assign name value]) 94 94 # TODO put equals token in error? 95 95 _ (errorf "Invalid assignment target: %M" expr))) 96 96 expr)
+28
test/test_main.janet
··· 10 10 (test-stdout (process "if (false) print 1; else print 2;") ` 11 11 2 12 12 `) 13 + 14 + (test-stdout (process "var three = 1 + 2; print three;") ` 15 + 3 16 + `) 17 + (test-stdout (process "var three; three = 1 + 2; print three;") ` 18 + 3 19 + `) 20 + (test-stdout (process "var none = nil; print none;") ` 21 + nil 22 + `) 23 + (test-stdout (process ` 24 + var n = 1 + 2; 25 + { 26 + print n; 27 + n = n * 10; 28 + var n = -1; 29 + n = n * 10; 30 + print n; 31 + } 32 + n = n * 10; 33 + print n; 34 + `) ` 35 + 3 36 + -10 37 + 300 38 + `) 39 + (test-error (process "none = nil;") "Undefined variable 'none'.") 40 + (test-error (process "print none;") "Undefined variable 'none'.")
+15
test/test_parser.janet
··· 1 1 (use judge) 2 + (use ../lox/scanner) 2 3 (use ../lox/parser) 3 4 4 5 (defn parse [tokens] (:parse (make-parser tokens))) ··· 50 51 [:literal true] 51 52 [:print [:literal true]] 52 53 [:print [:literal false]]]]) 54 + 55 + (defn scan-parse [input] (parse (scan input))) 56 + 57 + (test (scan-parse "var three;") 58 + @[[:var 59 + {:line 1 :token [:ident "three"]} 60 + nil]]) 61 + (test (scan-parse "var three = 1 + 2; print three;") 62 + @[[:var {:line 1 :token [:ident "three"]} 63 + [:binary 64 + [:literal 1] 65 + {:line 1 :token [:plus]} 66 + [:literal 2]]] 67 + [:print [:variable {:line 1 :token [:ident "three"]}]]])