diff --git a/lox/interpreter.janet b/lox/interpreter.janet index d9a467c..d6df785 100644 --- a/lox/interpreter.janet +++ b/lox/interpreter.janet @@ -32,15 +32,18 @@ token (errorf "Unknown operator %q" token)) left right)) -(defn- declare-var [name] +(defn- declare-var [{:token [_ name]}] (setdyn name @[])) -(defn- set-var [name val] +(defn- define-var [{:token [_ name]} val] + (setdyn name @[val])) + +(defn- set-var [{:token [_ name]} val] (if-let [ref (dyn name)] (do (array/clear ref) (array/push ref val)) (errorf "Undefined variable '%s'." name))) -(defn- get-var [name] +(defn- get-var [{:token [_ name]}] (if-let [@[val] (dyn name)] val (errorf "Undefined variable '%s'." name))) @@ -54,8 +57,16 @@ [:logical left op right] (case ((op :token) 0) :or (if-let [left (evaluate left)] left (evaluate right)) :and (if-let [left (evaluate left)] (evaluate right) left)) - [:assign {:token [:ident name]} value] (set-var name (evaluate value)) - [:variable {:token [:ident name]}] (get-var name) + [:assign name value] (set-var name (evaluate value)) + [:variable name] (get-var name) + [:call callee paren args] (do + (def callee (evaluate callee)) + (unless (has-key? callee :fun) + (error "Can only call functions and classes.")) + (def {:fun callee :arity arity} callee) + (unless (= arity (length args)) + (errorf "Expected %d arguments but got %d." arity (length args))) + (callee ;(map evaluate args))) [ty] (errorf "Unknown expression type %s" ty) (errorf "Invalid expression %q" expr))) @@ -72,20 +83,37 @@ nil "nil" val (string val))) [:expr expr] (xprintf (dyn :expr-out @"") "%Q" (evaluate expr)) - # [:return word value] (throw return) + [:return word val] (yield (evaluate val)) [:if cond then else] (do (cond (evaluate cond) (execute then) (not (nil? else)) (execute else))) [:while cond body] (while (evaluate cond) (execute body)) [:block stmts] (execute-block stmts) - [:var {:token [:ident name]} init] (let [val (and init (evaluate init))] - (declare-var name) - (when init (set-var name val))) + [:var name init] (let [val (and init (evaluate init))] + (declare-var name) + (when init (set-var name val))) + [:fun name params body] + (let [fun (fn [& args] + (def f (fiber/new + (fn [] + (loop [i :range [(length params)] + :let [name (params i) arg (args i)]] + (define-var name arg)) + (execute-block body)) + # inherit env, catch `yield` + :iy)) + (resume f) + (fiber/last-value f))] + (define-var name {:fun fun :arity (length params)})) [ty] (errorf "Unknown statement type %s" ty) (errorf "Invalid statement %q" stmt))) +(defn- define-fun [name arity fun] + (setdyn name @[{:arity arity :fun fun}])) + (defn interpret [stmts] - (setdyn :locals @{}) + (define-fun "clock" 0 |(os/clock :realtime :int)) + (define-fun "toString" 1 string) # TODO error handling (each stmt stmts (execute stmt))) diff --git a/readme.md b/readme.md index 1eccaa5..52c0ded 100644 --- a/readme.md +++ b/readme.md @@ -17,7 +17,7 @@ For instance the visitor pattern is replaced by pattern matching - [x] Evaluating Expressions - [x] Statements and State - [x] Control Flow - - [ ] Functions + - [x] Functions - [ ] Resolving and Binding - [ ] Classes - [ ] Inheritance diff --git a/test/test_main.janet b/test/test_main.janet index b45107c..153ce2d 100644 --- a/test/test_main.janet +++ b/test/test_main.janet @@ -36,5 +36,29 @@ -10 300 `) +(test-stdout (process "fun one() { return 1; } print one();") ` + 1 +`) +(test-stdout (process ` + var y = 5; + fun add(x) { return x + y; } + print add(1); + print add(2); + y = 50; + print add(2); + fun add(y) { return x + 100; } + print add(2); +`) ` + 6 + 7 + 52 + 102 +`) + (test-error (process "none = nil;") "Undefined variable 'none'.") (test-error (process "print none;") "Undefined variable 'none'.") +(test-error (process "add(1);") "Undefined variable 'add'.") +(test-error (process "fun one() { return 1; } print one(1);") "Expected 0 arguments but got 1.") +(test-error (process "fun add(x) { return x + 1; } print add();") "Expected 1 arguments but got 0.") +(test-error (process "fun add(x) { return x + 1; } print add(1, 2);") "Expected 1 arguments but got 2.") +(test-error (process "var x = 1; x();") "Can only call functions and classes.") diff --git a/test/test_parser.janet b/test/test_parser.janet index 7ddf985..8646f4f 100644 --- a/test/test_parser.janet +++ b/test/test_parser.janet @@ -55,9 +55,9 @@ (defn scan-parse [input] (parse (scan input))) (test (scan-parse "var three;") - @[[:var - {:line 1 :token [:ident "three"]} - nil]]) + @[[:var + {:line 1 :token [:ident "three"]} + nil]]) (test (scan-parse "var three = 1 + 2; print three;") @[[:var {:line 1 :token [:ident "three"]} [:binary @@ -65,3 +65,17 @@ {:line 1 :token [:plus]} [:literal 2]]] [:print [:variable {:line 1 :token [:ident "three"]}]]]) +(test (scan-parse "one();") + @[[:expr + [:call + [:variable + {:line 1 :token [:ident "one"]}] + {:line 1 :token [:right-paren]} + @[]]]]) +(test (scan-parse "add(1, 2);") + @[[:expr + [:call + [:variable + {:line 1 :token [:ident "add"]}] + {:line 1 :token [:right-paren]} + @[[:literal 1] [:literal 2]]]]])