+1
.gitignore
+1
.gitignore
···
···
1
+
_build/
+19
LICENSE
+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
+7
bundle/info.jdn
+4
bundle/init.janet
+4
bundle/init.janet
+27
flake.lock
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+12
test/test_main.janet
+52
test/test_parser.janet
+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
+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]}])