Janet implementation of Lox from the book Crafting Interpreters

initial commit with basic expression interpreter

pvsr.dev 874a9906

+1
.gitignore
···
··· 1 + _build/
+19
LICENSE
···
··· 1 + Copyright (c) 2025 Robert Nystrom, Peter Rice 2 + 3 + Permission is hereby granted, free of charge, to any person obtaining a copy of 4 + this software and associated documentation files (the "Software"), to deal in 5 + the Software without restriction, including without limitation the rights to 6 + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 + of the Software, and to permit persons to whom the Software is furnished to do 8 + so, subject to the following conditions: 9 + 10 + The above copyright notice and this permission notice shall be included in all 11 + copies or substantial portions of the Software. 12 + 13 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 + SOFTWARE.
+7
bundle/info.jdn
···
··· 1 + @{:author "Peter Rice" 2 + :dependencies @["judge" "spork"] 3 + :description "Lox programming language, from the book Crafting Interpreters" 4 + :jpm-dependencies @["spork" "judge"] 5 + :license "MIT" 6 + :name "lox" 7 + :version "0.0.1"}
+4
bundle/init.janet
···
··· 1 + (if (dyn :install-time-syspath) 2 + (use @install-time-syspath/spork/declare-cc) 3 + (use spork/declare-cc)) 4 + (dofile "project.janet" :env (jpm-shim-env))
+27
flake.lock
···
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1754214453, 6 + "narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=", 7 + "owner": "nixos", 8 + "repo": "nixpkgs", 9 + "rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "nixos", 14 + "ref": "nixos-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+28
flake.nix
···
··· 1 + { 2 + description = "Janet implementation of Lox, from the book Crafting Interpreters"; 3 + 4 + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 5 + 6 + outputs = 7 + { self, nixpkgs }: 8 + let 9 + forAllSystems = 10 + mkOutputs: 11 + nixpkgs.lib.genAttrs [ 12 + "aarch64-linux" 13 + "aarch64-darwin" 14 + "x86_64-darwin" 15 + "x86_64-linux" 16 + ] (system: mkOutputs nixpkgs.legacyPackages.${system}); 17 + in 18 + { 19 + devShells = forAllSystems (pkgs: { 20 + default = pkgs.mkShell { 21 + packages = with pkgs; [ 22 + janet 23 + jpm 24 + ]; 25 + }; 26 + }); 27 + }; 28 + }
+67
lox/interpreter.janet
···
··· 1 + (defn- unary-op [op right] 2 + (match ((op :token) 0) 3 + :minus (if (number? right) (- right) (error "Operand must be number")) 4 + :bang (not right) 5 + token (errorf "Unknown operator %q" token))) 6 + 7 + (defn- strings [op] 8 + |(if (and (string? $0) (string? $1)) 9 + (op $0 $1) 10 + (error "Operands must be strings"))) 11 + 12 + (defn- numbers [op] 13 + |(if (and (number? $0) (number? $1)) 14 + (op $0 $1) 15 + (error "Operands must be numbers"))) 16 + 17 + (defn- bin-op [left op right] 18 + ((match ((op :token) 0) 19 + :plus (cond 20 + (and (number? left) (number? right)) + 21 + (and (string? left) (string? right)) string 22 + (error "Operands must be two numbers or two strings")) 23 + :minus (numbers -) 24 + :star (numbers *) 25 + :slash (numbers /) 26 + :eq-eq = 27 + :bang-eq not= 28 + :greater (numbers >) 29 + :greater-eq (numbers >=) 30 + :less (numbers <) 31 + :less-eq (numbers <=) 32 + token (errorf "Unknown operator %q" token)) 33 + left right)) 34 + 35 + (defn evaluate [expr] 36 + (match expr 37 + [:literal value] value 38 + [:grouping expr] (evaluate expr) 39 + [:unary op right] (unary-op op (evaluate right)) 40 + [:binary left op right] (bin-op (evaluate left) op (evaluate right)) 41 + [:logical left op right] (case ((op :token) 0) 42 + :or (if-let [left (evaluate left)] left (evaluate right)) 43 + :and (if-let [left (evaluate left)] (evaluate right) left)) 44 + (errorf "Unknown expression type %q" (expr 0)))) 45 + 46 + (var execute nil) 47 + 48 + (defn- execute-block [stmts] 49 + (each stmt stmts (execute stmt))) 50 + 51 + (varfn execute [stmt] 52 + (match stmt 53 + [:print expr] (printf "%q" (evaluate expr)) 54 + [:expr expr] (xprintf (dyn :expr-out stderr) "%Q" (evaluate expr)) 55 + # [:return word value] (throw return) 56 + [:if cond then else] (do 57 + (cond 58 + (evaluate cond) (execute then) 59 + (not (nil? else)) (execute else))) 60 + [:while cond body] (while (evaluate cond) (execute body)) 61 + [:block stmts] (execute-block stmts) 62 + (errorf "Unknown statement type %q" (stmt 0)))) 63 + 64 + (defn interpret [stmts] 65 + # TODO error handling 66 + (each stmt stmts (try (execute stmt) 67 + ([e] (eprint e)))))
+17
lox/main.janet
···
··· 1 + (import ./scanner) 2 + (import ./parser) 3 + (import ./interpreter) 4 + (import ./repl) 5 + 6 + (defn process [contents] 7 + (-> contents 8 + scanner/scan 9 + parser/make-parser 10 + :parse 11 + interpreter/interpret)) 12 + 13 + (defn main [_ &opt path & args] 14 + (unless (empty? args) (error "expected 0 or 1 args")) 15 + (if (nil? path) 16 + (repl/run process) 17 + (with-dyns [:expr-out @""] (process (slurp path)))))
+199
lox/parser.janet
···
··· 1 + (import ./scanner) 2 + 3 + # fns used out of declaration order 4 + (var- expression nil) 5 + (var- statement nil) 6 + (var- declaration nil) 7 + 8 + (defn- token-type [token] ((token :token) 0)) 9 + 10 + (def- parser-proto 11 + @{:peek |(($ :tokens) ($ :current)) 12 + :prev |(($ :tokens) (- ($ :current) 1)) 13 + :eof? |(= (length ($ :tokens)) ($ :current)) 14 + :advance |(++ ($ :current)) 15 + :check (fn [self ty] 16 + (= ty (token-type (:peek self)))) 17 + :match (fn [self & types] 18 + (unless (:eof? self) 19 + (def token (:peek self)) 20 + (when (has-value? types (token-type token)) 21 + (:advance self) 22 + token))) 23 + :consume (fn [self ty msg] 24 + (if-let [token (:match self ty)] 25 + token (error msg))) 26 + :parse |(do (def stmts @[]) 27 + (while (not (:eof? $)) 28 + (array/push stmts (declaration $))) 29 + stmts)}) 30 + 31 + (defn make-parser [tokens] 32 + (table/setproto @{:current 0 :tokens tokens} parser-proto)) 33 + 34 + (defn- primary [parser] 35 + (def token (:match parser :false :true :nil :num :str :ident :left-paren)) 36 + (unless token (errorf "Expect expression: %q" (:peek parser))) 37 + (match (token :token) 38 + [:false] [:literal false] 39 + [:true] [:literal true] 40 + [:nil] [:literal nil] 41 + [:num val] [:literal val] 42 + [:str val] [:literal val] 43 + [:ident name] [:variable name] 44 + [:left-paren] (do (def expr (expression parser)) 45 + (:consume parser :right-paren "Expect ')' after expression.") 46 + [:grouping expr]))) 47 + 48 + (defn- doCall [parser callee] 49 + (def args @[]) 50 + (unless (:check parser :right-paren) 51 + (array/push args (expression parser)) 52 + (while (:match parser :comma) 53 + (when (>= (length args) 255) 54 + (error "Can't have more than 255 arguments.")) 55 + (array/push args (expression parser)))) 56 + (def paren (:consume parser :right-paren "Expect ')' after arguments.")) 57 + [:call callee paren args]) 58 + 59 + (defn- call [parser] 60 + (var expr (primary parser)) 61 + (while (:match parser :left-paren) 62 + (set expr (doCall parser expr))) 63 + expr) 64 + 65 + (defn- unary [parser] 66 + (if-let [token (:match parser :bang :minus)] 67 + (let [op token 68 + right (unary parser)] 69 + [:unary op right]) 70 + (call parser))) 71 + 72 + (defmacro bin-parse [ty name f & token-types] 73 + ~(defn- ,name [parser] 74 + (var expr (,f parser)) 75 + (while (def op (:match parser ,;token-types)) 76 + (def right (,f parser)) 77 + (set expr [,ty expr op right])) 78 + expr)) 79 + (defmacro binary [& args] ~(bin-parse :binary ,;args)) 80 + (defmacro logical [& args] ~(bin-parse :logical ,;args)) 81 + (binary factor unary :slash :star) 82 + (binary term factor :plus :minus) 83 + (binary comparison term :greater :greater-eq :less :less-eq) 84 + (binary equality comparison :bang-eq :eq-eq) 85 + (logical op-and equality :and) 86 + (logical op-or op-and :or) 87 + 88 + (defn- assignment [parser] 89 + (var expr (op-or parser)) 90 + (when (def equals (:match parser :eq)) 91 + (def value (assignment parser)) 92 + (match expr 93 + [:variable name] (set expr [:assignment name value]) 94 + # TODO put equals token in error? 95 + _ (errorf "Invalid assignment target: %M" expr))) 96 + expr) 97 + 98 + (varfn expression [parser] (assignment parser)) 99 + 100 + (defn- block [parser] 101 + (def stmts @[]) 102 + (while (not (or (:check parser :right-brace) (:eof? parser))) 103 + (do (array/push stmts (declaration parser)))) 104 + (:consume parser :right-brace "Expect '}' after block.") 105 + stmts) 106 + 107 + (defn- function [parser kind] 108 + (def name (:consume parser :ident (string/format "Expect %s name" kind))) 109 + (:consume parser :left-paren (string/format "Expect '(' after %s name" kind)) 110 + (def params @[]) 111 + (unless (:check parser :right-paren) 112 + (array/push params (:consume parser :ident "Expect parameter name.")) 113 + (while (:match parser :comma) 114 + (when (>= (length params) 255) 115 + # TODO token in error 116 + (error "Can't have more than 255 parameters.")) 117 + (array/push params (:consume parser :ident "Expect parameter name.")))) 118 + (:consume parser :right-paren "Expect ')' after parameters.") 119 + (:consume parser :left-brace (string/format "Expect '{' before %s body" kind)) 120 + [:fun name params (block parser)]) 121 + 122 + (defn- var-decl [parser] 123 + (def name (:consume parser :ident "Expect variable name")) 124 + (def init (if (:match parser :eq) (expression parser) nil)) 125 + (:consume parser :semicolon "Expect ';' after variable declaration.") 126 + [:var name init]) 127 + 128 + (defn- expression-statement [parser] 129 + (def expr (expression parser)) 130 + (:consume parser :semicolon "Expect ';' after value.") 131 + [:expr expr]) 132 + 133 + (defn- for-statement [parser] 134 + (:consume parser :left-paren "Expect '(' after 'for'.") 135 + (def ty (if-let [token (:match parser :semicolon :var)] (token-type token))) 136 + (def init (case ty 137 + :semicolon nil 138 + :var (var-decl parser) 139 + (expression-statement parser))) 140 + (def cond (if (:check parser :semicolon) [:literal true] (expression parser))) 141 + (:consume parser :semicolon "Expect ';' after loop condition.") 142 + (def incr (unless (:check parser :right-paren) (expression parser))) 143 + (:consume parser :right-paren "Expect ')' after for clauses.") 144 + (var body (statement parser)) 145 + (when incr (set body [:block [body [:expr incr]]])) 146 + (set body [:while cond body]) 147 + (if init [:block [init body]] body)) 148 + 149 + (defn- if-statement [parser] 150 + (:consume parser :left-paren "Expect '(' after 'if'.") 151 + (def cond (expression parser)) 152 + (:consume parser :right-paren "Expect ')' after if condition.") 153 + (def then (statement parser)) 154 + (def else (if (:match parser :else) (statement parser) nil)) 155 + [:if cond then else]) 156 + 157 + (defn- print-statement [parser] 158 + (def value (expression parser)) 159 + (:consume parser :semicolon "Expect ';' after value.") 160 + [:print value]) 161 + 162 + (defn- return-statement [parser] 163 + (def kw (:prev parser)) 164 + (def value (if (:check parser :semicolon) 165 + nil 166 + (expression parser))) 167 + (:consume parser :semicolon "Expect ';' after return value.") 168 + [:return kw value]) 169 + 170 + (defn- while-statement [parser] 171 + (:consume parser :left-paren "Expect '(' after 'while'.") 172 + (def cond (expression parser)) 173 + (:consume parser :right-paren "Expect ')' after while condition.") 174 + (def body (statement parser)) 175 + [:while cond body]) 176 + 177 + (varfn statement [parser] 178 + (def token (:match parser :for :if :print :return :while :left-brace)) 179 + (def ty (if token (token-type token))) 180 + (case ty 181 + :for (for-statement parser) 182 + :if (if-statement parser) 183 + :print (print-statement parser) 184 + :return (return-statement parser) 185 + :while (while-statement parser) 186 + :left-brace [:block (block parser)] 187 + (expression-statement parser))) 188 + 189 + (varfn declaration [parser] 190 + # TODO catch parse errors and synchronize 191 + (def token (:match parser :fun :var)) 192 + (def ty (if token (token-type token))) 193 + (match ty 194 + :fun (function parser :function) 195 + :var (var-decl parser) 196 + (statement parser))) 197 + 198 + (defn parse [parser] 199 + (:parse parser))
+16
lox/project.janet
···
··· 1 + (declare-project 2 + :name "lox" 3 + :description ```lox``` 4 + :license "MIT" 5 + :author ```Peter Rice``` 6 + :dependencies @["spork" "judge"] 7 + :version "0.0.1") 8 + 9 + (declare-binscript 10 + :main "bin/lox" 11 + :hardcode-syspath true 12 + :is-janet true) 13 + 14 + (declare-executable 15 + :name "lox" 16 + :entry "./lox.janet")
+20
lox/repl.janet
···
··· 1 + (import spork/stream) 2 + 3 + (defn- inpput-prompt [] 4 + (prin "> ") 5 + (:flush stdout)) 6 + 7 + (defn run [process] 8 + (var buf @"") 9 + (os/sigaction :int |(do (buffer/clear buf) (print) (inpput-prompt)) true) 10 + (with [input (os/open "/dev/stdin" :r)] 11 + (forever 12 + (inpput-prompt) 13 + (prompt :a) 14 + (forever (match (string (:read input 1)) 15 + "" (when (empty? buf) (set buf nil) (break)) 16 + "\n" (break) 17 + c (buffer/push-string buf c))) 18 + (when (nil? buf) (break)) 19 + (try (process buf) ([err] (printf "error: %s" err))) 20 + (buffer/clear buf))))
+44
lox/scanner.janet
···
··· 1 + (def- keywords (struct ;(mapcat 2 + |[$ (keyword $)] 3 + ["and" "class" "else" "false" 4 + "for" "fun" "if" "nil" 5 + "or" "print" "return" "super" 6 + "this" "true" "var" "while"]))) 7 + 8 + (def- double-operators {"!=" :bang-eq "==" :eq-eq 9 + "<=" :less-eq ">=" :greater-eq}) 10 + 11 + (def- single-operators {"(" :left-paren ")" :right-paren 12 + "{" :left-brace "}" :right-brace 13 + "-" :minus "+" :plus "*" :star "/" :slash 14 + "!" :bang "=" :eq "<" :less ">" :greater 15 + "," :comma "." :dot ";" :semicolon}) 16 + 17 + (defn- get-keyword [word] 18 + (if-let [kw (get keywords word)] [kw] [:ident word])) 19 + 20 + (defn- token [inner make-token] 21 + ~(/ (* (line) ,inner) ,|{:token (make-token $&) :line $0})) 22 + 23 + (defn- token0 [inner tok] 24 + (token inner (fn [&] [tok]))) 25 + 26 + (defn- token1 [inner make-token] 27 + (token inner (fn [[val]] (make-token val)))) 28 + 29 + (def- grammar 30 + ~{:keyword-or-ident (<- (* :a :w*)) 31 + :number (<- (* :d+ (? (* "." :d+)))) 32 + :string (* `"` (+ (<- (to `"`)) (error (constant "unterminated string"))) `"`) 33 + :main (any (+ :s 34 + (* "//" (to "\n")) 35 + ,;(seq [[str tok] :pairs double-operators] (token0 str tok)) 36 + ,;(seq [[str tok] :pairs single-operators] (token0 str tok)) 37 + ,(token1 :keyword-or-ident get-keyword) 38 + ,(token1 :number |[:num (scan-number $)]) 39 + ,(token1 :string |[:str $])))}) 40 + 41 + (def- peg (peg/compile grammar)) 42 + 43 + (defn scan [source] 44 + (peg/match peg source))
+12
project.janet
···
··· 1 + (declare-project 2 + :name "lox" 3 + :description ```Implementation of the Lox programming language from Crafting Interpreters``` 4 + :license "MIT" 5 + :author ```Peter Rice``` 6 + :dependencies @["spork" "judge"] 7 + :version "0.0.1") 8 + 9 + (declare-executable 10 + :name "lox" 11 + :entry "lox/main.janet" 12 + :install true)
+71
test/test_interpreter.janet
···
··· 1 + (use judge) 2 + (use ../lox/interpreter) 3 + 4 + (test (evaluate [:literal false]) false) 5 + (test (evaluate [:unary {:token [:minus]} [:literal 1]]) -1) 6 + (test (evaluate [:unary {:token [:bang]} [:literal false]]) true) 7 + (test (evaluate [:unary {:token [:bang]} [:literal 1]]) false) 8 + 9 + (def add [:binary [:literal 1] {:token [:plus]} [:literal 2]]) 10 + (def concat [:binary [:literal "1"] {:token [:plus]} [:literal "2"]]) 11 + (test (evaluate add) 3) 12 + (test (evaluate [:binary add {:token [:plus]} [:literal 2]]) 5) 13 + (test (evaluate concat) "12") 14 + (test (evaluate [:binary [:literal 1] {:token [:minus]} [:literal 2]]) -1) 15 + 16 + (test-error (evaluate [:unary {:token [:minus]} [:literal ""]]) 17 + "Operand must be number") 18 + (test-error (evaluate [:binary [:literal 1] {:token [:plus]} [:literal "2"]]) 19 + "Operands must be two numbers or two strings") 20 + (test-error (evaluate [:binary [:literal "1"] {:token [:plus]} [:literal 2]]) 21 + "Operands must be two numbers or two strings") 22 + (test-error (evaluate [:binary [:literal true] {:token [:plus]} [:literal true]]) 23 + "Operands must be two numbers or two strings") 24 + (test-error (evaluate [:binary [:literal ""] {:token [:minus]} [:literal 2]]) 25 + "Operands must be numbers") 26 + (test-error (evaluate [:binary [:literal 1] {:token [:minus]} [:literal ""]]) 27 + "Operands must be numbers") 28 + (test-error (evaluate [:binary [:literal 1] {:token [:pow]} [:literal 2]]) 29 + "Unknown operator :pow") 30 + 31 + (test (evaluate [:binary [:literal 1] {:token [:greater]} [:literal 2]]) false) 32 + (test (evaluate [:binary [:literal 2] {:token [:greater-eq]} [:literal 2]]) true) 33 + (test (evaluate [:binary [:literal "12"] {:token [:eq-eq]} concat]) true) 34 + (test (evaluate [:binary [:literal 1] {:token [:eq-eq]} [:literal 1]]) true) 35 + (test (evaluate [:binary [:literal 1] {:token [:eq-eq]} [:literal 2]]) false) 36 + (test (evaluate [:binary [:literal 1] {:token [:eq-eq]} [:literal "1"]]) false) 37 + 38 + (def nan [:binary [:literal 0] {:token [:slash]} [:literal 0]]) 39 + (test (evaluate [:binary nan {:token [:eq-eq]} nan]) false) 40 + (test (evaluate [:binary [:literal 1] {:token [:bang-eq]} [:literal 1]]) false) 41 + (test (evaluate [:binary [:literal 1] {:token [:bang-eq]} [:literal 2]]) true) 42 + (test (evaluate [:logical [:literal false] {:token [:and]} [:literal true]]) false) 43 + (test (evaluate [:logical [:literal false] {:token [:or]} [:literal true]]) true) 44 + (test (evaluate [:logical [:literal false] {:token [:or]} [:literal false]]) false) 45 + 46 + (test-stdout (execute [:print [:literal nil]]) ` 47 + nil 48 + `) 49 + (test-stdout (execute [:print [:literal true]]) ` 50 + true 51 + `) 52 + (test-stdout (execute [:print [:literal 1]]) ` 53 + 1 54 + `) 55 + (test-stdout (execute [:print [:literal 1.1]]) ` 56 + 1.1 57 + `) 58 + (test-stdout (execute [:print [:literal "one"]]) ` 59 + "one" 60 + `) 61 + (test-stdout (execute [:if [:literal true] [:print [:literal 1]] nil]) ` 62 + 1 63 + `) 64 + (test-stdout (execute [:if [:literal true] [:print [:literal 1]] [:print [:literal 2]]]) ` 65 + 1 66 + `) 67 + # TODO judge fails on empty stdout 68 + # (test-stdout (execute [:if [:literal false] [:print [:literal 1]] nil]) "") 69 + (test-stdout (execute [:if [:literal false] [:print [:literal 1]] [:print [:literal 2]]]) ` 70 + 2 71 + `)
+12
test/test_main.janet
···
··· 1 + (use judge) 2 + (use ../lox/main) 3 + 4 + (test-stdout (process "print 2 * (1 + 2);") ` 5 + 6 6 + `) 7 + (test-stdout (process "if (true) print 1; else print 2;") ` 8 + 1 9 + `) 10 + (test-stdout (process "if (false) print 1; else print 2;") ` 11 + 2 12 + `)
+52
test/test_parser.janet
···
··· 1 + (use judge) 2 + (use ../lox/parser) 3 + 4 + (defn parse [tokens] (:parse (make-parser tokens))) 5 + 6 + (test (parse @[{:token [:num 1]} 7 + {:token [:plus]} 8 + {:token [:num 2]} 9 + {:token [:semicolon]}]) 10 + @[[:expr 11 + [:binary 12 + [:literal 1] 13 + {:token [:plus]} 14 + [:literal 2]]]]) 15 + (test (parse @[{:token [:print]} 16 + {:token [:num 1]} 17 + {:token [:plus]} 18 + {:token [:num 2]} 19 + {:token [:semicolon]}]) 20 + @[[:print 21 + [:binary 22 + [:literal 1] 23 + {:token [:plus]} 24 + [:literal 2]]]]) 25 + 26 + (test (parse @[{:token [:if]} 27 + {:token [:left-paren]} 28 + {:token [:true]} 29 + {:token [:right-paren]} 30 + {:token [:print]} 31 + {:token [:nil]} 32 + {:token [:semicolon]}]) 33 + @[[:if 34 + [:literal true] 35 + [:print [:literal nil]] 36 + nil]]) 37 + 38 + (test (parse @[{:token [:if]} 39 + {:token [:left-paren]} 40 + {:token [:true]} 41 + {:token [:right-paren]} 42 + {:token [:print]} 43 + {:token [:true]} 44 + {:token [:semicolon]} 45 + {:token [:else]} 46 + {:token [:print]} 47 + {:token [:false]} 48 + {:token [:semicolon]}]) 49 + @[[:if 50 + [:literal true] 51 + [:print [:literal true]] 52 + [:print [:literal false]]]])
+38
test/test_scanner.janet
···
··· 1 + (use judge) 2 + (use ../lox/scanner) 3 + 4 + (test (scan "1 + 2") 5 + @[{:line 1 :token [:num 1]} 6 + {:line 1 :token [:plus]} 7 + {:line 1 :token [:num 2]}]) 8 + (test (scan "true or false") 9 + @[{:line 1 :token [:true]} 10 + {:line 1 :token [:or]} 11 + {:line 1 :token [:false]}]) 12 + (test (scan "print 2 * (1 + 2)") 13 + @[{:line 1 :token [:print]} 14 + {:line 1 :token [:num 2]} 15 + {:line 1 :token [:star]} 16 + {:line 1 :token [:left-paren]} 17 + {:line 1 :token [:num 1]} 18 + {:line 1 :token [:plus]} 19 + {:line 1 :token [:num 2]} 20 + {:line 1 :token [:right-paren]}]) 21 + (test (scan "if (true) print nil;") 22 + @[{:line 1 :token [:if]} 23 + {:line 1 :token [:left-paren]} 24 + {:line 1 :token [:true]} 25 + {:line 1 :token [:right-paren]} 26 + {:line 1 :token [:print]} 27 + {:line 1 :token [:nil]} 28 + {:line 1 :token [:semicolon]}]) 29 + (test (scan "if (true) { print nil; }") 30 + @[{:line 1 :token [:if]} 31 + {:line 1 :token [:left-paren]} 32 + {:line 1 :token [:true]} 33 + {:line 1 :token [:right-paren]} 34 + {:line 1 :token [:left-brace]} 35 + {:line 1 :token [:print]} 36 + {:line 1 :token [:nil]} 37 + {:line 1 :token [:semicolon]} 38 + {:line 1 :token [:right-brace]}])