├── .classpath
├── .hgignore
├── .project
├── COPYRIGHT
├── README
├── base
└── init.lark
├── lark
├── lark.jar
├── quasiquote.txt
├── sample
├── malefemale.lark
└── objects.lark
├── src
└── com
│ └── stuffwithstuff
│ └── lark
│ ├── Arithmetic.java
│ ├── BoolExpr.java
│ ├── CallExpr.java
│ ├── CallableExpr.java
│ ├── Expr.java
│ ├── ExprType.java
│ ├── FunctionExpr.java
│ ├── IntepreterHost.java
│ ├── Interpreter.java
│ ├── Lark.java
│ ├── LarkParser.java
│ ├── LarkScript.java
│ ├── Lexer.java
│ ├── ListExpr.java
│ ├── NameExpr.java
│ ├── NumExpr.java
│ ├── ParseException.java
│ ├── Parser.java
│ ├── Scope.java
│ ├── SpecialForms.java
│ ├── StringExpr.java
│ ├── TestRunner.java
│ ├── Token.java
│ └── TokenType.java
└── test
├── arithmetic.lark
├── def.lark
├── do.lark
├── eval.lark
├── global.lark
├── if.lark
├── newlines.lark
├── predicates.lark
├── print.lark
├── quote.lark
├── sicp01-ex.lark
└── sicp01.lark
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.hgignore:
--------------------------------------------------------------------------------
1 | syntax: glob
2 |
3 | # ignore mac desktop files
4 | **.DS_Store
5 |
6 | # ignore build output
7 | bin/**.*
8 |
9 | # ignore revert backups
10 | **.orig
11 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | lark
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/COPYRIGHT:
--------------------------------------------------------------------------------
1 | Lark uses the MIT License:
2 |
3 | Copyright (c) 2009 Robert Nystrom
4 |
5 | Permission is hereby granted, free of charge, to
6 | any person obtaining a copy of this software and
7 | associated documentation files (the "Software"),
8 | to deal in the Software without restriction,
9 | including without limitation the rights to use,
10 | copy, modify, merge, publish, distribute,
11 | sublicense, and/or sell copies of the Software,
12 | and to permit persons to whom the Software is
13 | furnished to do so, subject to the following
14 | conditions:
15 |
16 | The above copyright notice and this permission
17 | notice shall be included in all copies or
18 | substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT
21 | WARRANTY OF ANY KIND,
22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
23 | THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
24 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
25 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27 | WHETHER IN AN ACTION OF CONTRACT, TORT OR
28 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30 | THE SOFTWARE.
31 |
32 | In other words, you can do pretty much what you
33 | want, but don't come cryin' to me if you put your
34 | eye out on it.
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 |
2 | Lark
3 | ----
4 |
5 | Lark is an experiment in designing a homoiconic language with a syntax
6 | inspired by SmallTalk. It's very early, so there isn't much to see
7 | yet, but you can play with it.
8 |
9 |
10 | Lark Syntax
11 | ===========
12 |
13 | Core syntax
14 | -----------
15 | Internally, Lark has a very simple syntax. It's more complex than
16 | Scheme, but just barely. It has three core expression types:
17 |
18 | - Atoms are things like names, numbers and other literals.
19 | - Lists are an ordered set of other expressions.
20 | - Calls are a pair of expressions, the function and the argument.
21 |
22 | That's it. Scheme has the first two, and Lark just adds one more.
23 | Every expression in Lark can be desugared to these core elements. The
24 | rest of this section explains the syntactic sugar added on top of
25 | that, but that's handled only by the parser. To the interpreter, the
26 | above is the only syntax.
27 |
28 | This *should* mean that it'll be as easy to make powerful macros in
29 | Lark as it is in Scheme.
30 |
31 |
32 | Names
33 | -----
34 | The simplest syntactical element in Lark are names, identifiers. There
35 | are three kinds of identifiers. Regular ones start with a letter and
36 | don't contain any colons. Like:
37 |
38 | a, abs, do-something, what?!, abc123
39 |
40 | They are used for regular prefix function calls and don't trigger any
41 | special parsing, so you can just stick them anywhere.
42 |
43 | Operators start with a punctuation character, like:
44 |
45 | +, -, !can-have-letters-too!, !@#%#$$&$
46 |
47 | An operator name will cause the parser to parse an infix expression.
48 | If you don't want that, and just want to use the name of an operator
49 | for something (for example, to pass it to a function), you can do so
50 | by surrounding it in (). This:
51 |
52 | (+) (1, 2)
53 |
54 | is the same as:
55 |
56 | 1 + 2
57 |
58 | Keywords start with a letter and contain at least one colon. A colon
59 | by itself is also a keyword. Ex:
60 |
61 | if:, :, multiple:colons:in:one:
62 |
63 | Like operators, keywords will trigger special parsing, so must be put
64 | in parentheses if you just want to treat it like a name.
65 |
66 | Prefix functions
67 | ----------------
68 | The basic Lark syntax looks much like C. A regular name followed by an
69 | expression will call that named function and pass it the given
70 | argument. Ex:
71 |
72 | square 123
73 |
74 | Dotted functions
75 | ----------------
76 | Lark also allows functions to be called using the dot operator. It's
77 | functionally equivalent to a regular function call, but has two minor
78 | differences: the function and argument are swapped, and the precedence
79 | is higher. For example:
80 |
81 | a.b c.d
82 |
83 | is desugared to:
84 |
85 | (b(a))(d(c))
86 |
87 | Swapping the argument and function may sound a bit arbitrary, but it
88 | allows you to write in an "object.method" style familiar to many OOP
89 | programmers. For example, instead of:
90 |
91 | length list
92 |
93 | you can use:
94 |
95 | list.length
96 |
97 | Operators
98 | ---------
99 | Operators are parsed like SmallTalk. All operators have the same
100 | precedence (no Dear Aunt Sally), and are parsed from left to right.
101 | The expression:
102 |
103 | a + b * c @#%! d
104 |
105 | is equivalent to:
106 |
107 | @#%!(*(+(a, b), c), d)
108 |
109 | Keywords
110 | --------
111 | Lark has keywords that work similar to SmallTalk. The parser will
112 | translate a series of keywords separated by arguments into a single
113 | keyword name followed by the list of arguments. This:
114 |
115 | if: a then: b else: c
116 |
117 | is parsed as:
118 |
119 | if:then:else:(a, b, c)
120 |
121 | Lists
122 | -----
123 | A list can be created by separating expressions with commas, like so:
124 |
125 | 1, 2, 3
126 |
127 | Parentheses are not necessary, but are often useful since commas have
128 | low precedence. For example:
129 |
130 | this: means:
131 |
132 | difference 1, 2 ===> (difference(1), 2)
133 | difference (1, 2) ===> difference(1, 2)
134 |
135 | Braces
136 | ------
137 | A series of expressions separated by semicolons and surrounded by
138 | curly braces is syntactical sugar for a "do" function call. This gives
139 | us a simple syntax for expressing a sequence of things that executed
140 | in order (which is what the "do" special form is for). So this:
141 |
142 | { print 1, 2; print 3, 4 }
143 |
144 | is parsed as:
145 |
146 | do (print (1, 2), print (3, 4))
147 |
148 | Note that the semicolons are *separators*, not *terminators*. The last
149 | expression does not have one after it.
150 |
151 | Precedence
152 | ----------
153 | The precedence rules follow SmallTalk. From highest to lowest, it is
154 | prefix functions, operators, keywords, then ",". So, the following:
155 |
156 | if: a < b then: c, d
157 |
158 | is parsed as:
159 |
160 | (if:then:(<(a, b), c), d)
161 |
162 | That's the basic syntax. The Lark parser reads that and immediately
163 | translates it to the much simpler core syntax.
164 |
165 |
166 | Running Lark
167 | ============
168 |
169 | Lark is a Java application stored in a single jar, you can run it by
170 | doing either:
171 |
172 | $ java -jar lark.jar
173 |
174 | Or, if you have bash installed, just:
175 |
176 | $ ./lark
177 |
178 | This starts up the Lark REPL, like this:
179 |
180 | lark v0.0.0
181 | -----------
182 | Type 'q' and press Enter to quit.
183 | >
184 |
185 | From there you can enter expressions, which the interpreter will
186 | evaluate and print the result.
187 |
188 | You can also tell lark to load and interpret a script stored in a
189 | separate file by passing in the path to the file to lark:
190 |
191 | $./lark path/to/my/file.lark
192 |
193 | Built-in functions
194 | ==================
195 |
196 | So what can you actually do with it? Not much, yet. Only a few
197 | built-in functions are implemented:
198 |
199 | '
200 |
201 | The quote function simply returns its argument in unevaluated
202 | form. Ex:
203 |
204 | > ` print 123
205 | : print 123
206 | (note: does not call print function)
207 |
208 | do
209 |
210 | If the expression is anything but a list, it simply evaluates it
211 | and returns. If it's a list, it evaluates each item in the list,
212 | in order, and then returns the evaluated value of the last item.
213 |
214 | In other words, it's Lark's version of a { } block in C. Ex:
215 |
216 | > do (print 1, print 2, print 3)
217 | 1
218 | 2
219 | 3
220 | : ()
221 |
222 | print
223 |
224 | Simply evaluates the argument and prints it to the console.
225 |
226 | =>
227 |
228 | Creates an anonymous function (a lambda). should either
229 | be a single name or a list of parameter names. is an
230 | expression that forms the body of the function. Ex:
231 |
232 | > (a => do (print a, print a)) 123
233 | 123
234 | 123
235 | : ()
236 |
237 | def: is:
238 |
239 | Binds a value to a variable in the current scope. should
240 | be a single name. will be evaluated and the result bound
241 | to the name. Ex:
242 |
243 | > def: a is: 123
244 | : ()
245 | > a
246 | : 123
247 |
248 | if: then:
249 | if: then: else:
250 |
251 | Evaluates the condition. If it evaluates to true, then it
252 | evaluates and returns . If it evaluates to false, it will
253 | either return () or, if an else: clause is given, evaluate and
254 | return that. Ex:
255 |
256 | > if: true then: print 1 else: print 2
257 | 2
258 | : ()
259 |
260 | bool?
261 | int?
262 | list?
263 | name?
264 | unit?
265 |
266 | Evaluates then returns true if it is the given type. Note
267 | that ().list? returns *true* because unit is the empty list. Ex:
268 |
269 | > 123.int?
270 | : true
271 | > ('foo).name?
272 | : true
273 | > 123.list?
274 | : false
275 |
276 |
277 |
278 | Evaluates , expecting it to be a list. Returns the item
279 | at the position in the list (zero-based). Basically, this
280 | means an int is a function you can pass a list to to get an item
281 | from it. Ex:
282 |
283 | > 0 (1, 2, 3)
284 | : 1
285 | > (4, 5, 6, 7).3
286 | : 7
287 |
288 | count
289 |
290 | Evaluates , expecting it to be a list. Returns the number of
291 | items in the list. Ex:
292 |
293 | > (1, 2, 3).count
294 | : 3
295 |
296 | That's it so far. Like I said, very early...
--------------------------------------------------------------------------------
/base/init.lark:
--------------------------------------------------------------------------------
1 | # define if:then: in terms of if:then:else:
2 | global: (if:then:) is: [condition then] =>>
3 | (if: condition then: eval then else: ())
4 |
5 | global: (!=) is: [left right] => (if: left = right then: false else: true)
6 |
7 | global: neg is: n => (0 - n)
8 |
9 | global: (|) is: [left right] =>>
10 | (if: eval left then: eval false else: right)
11 |
12 | global: (&) is: [left right] =>>
13 | (if: eval left then: eval right else: false)
--------------------------------------------------------------------------------
/lark:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | java -jar lark.jar $@
--------------------------------------------------------------------------------
/lark.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/munificent/lark/bcede7b0f82686e553b54a3855c8b7d4ecde5eb1/lark.jar
--------------------------------------------------------------------------------
/quasiquote.txt:
--------------------------------------------------------------------------------
1 | ' is quote.
2 |
3 | 'q is quasiquote.
4 |
5 | '' is unquote.
6 |
7 | ''s is unquote-splice.
8 |
9 | > ' foo
10 | foo
11 |
12 | > ' print "hi"
13 | print "hi"
14 |
15 | > 'q foo (1 + 2)
16 | foo (1 + 2)
17 |
18 | >'q foo '' (1 + 2)
19 | foo 3
20 |
21 | > 'q (1, '' (2 + 3), 4)
22 | (1, 5, 4)
23 |
24 | > 'q (1, ''s (2 + 3, 4), 5)
25 | (1, 5, 4, 5)
26 |
27 |
--------------------------------------------------------------------------------
/sample/malefemale.lark:
--------------------------------------------------------------------------------
1 | # (letrec ((female (lambda(n)
2 | # (if (= n 0) 1
3 | # (- n (male (female (- n 1)))))))
4 | # (male (lambda(n)
5 | # (if (= n 0) 0
6 | # (- n (female (male (- n 1))))))))
7 | # (display "i male(i) female(i)")(newline)
8 | # (do ((i 0 (+ i 1)))
9 | # ((> i 8) #f)
10 | # (display i) (display " ")(display (male i))(display " ")(display (female i))
11 | # (newline)))
12 |
13 | def: female is: n => (if: n = 0 then: 1 else: n - male female (n - 1));
14 | def: male is: n => (if: n = 0 then: 0 else: n - female male (n - 1));
15 |
16 | print "female";
17 | print female 0;
18 | print female 1;
19 | print female 2;
20 | print female 3;
21 | print female 4;
22 | print female 5;
23 | print female 6;
24 | print female 7;
25 | print female 8;
26 | print female 9;
27 |
28 | print "male";
29 | print male 0;
30 | print male 1;
31 | print male 2;
32 | print male 3;
33 | print male 4;
34 | print male 5;
35 | print male 6;
36 | print male 7;
37 | print male 8;
38 | print male 9;
39 |
--------------------------------------------------------------------------------
/sample/objects.lark:
--------------------------------------------------------------------------------
1 | global: makePoint is: [x y] => (
2 | method => (
3 | if: method = "x" then: () => x else: () => y
4 | )
5 | )
6 |
7 | def: a is: makePoint (1, 2)
8 |
9 | print (a "x") () # prints 1
10 | print (a "y") () # prints 2
11 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Arithmetic.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 | /**
6 | * Built-in arithmetic functions.
7 | */
8 | public class Arithmetic {
9 |
10 | public static CallableExpr add() {
11 | return new CallableExpr() {
12 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
13 | Expr arg = interpreter.eval(scope, argExpr);
14 | Expr error = validateBinaryArg(interpreter, "+", arg);
15 | if (error != null) return error;
16 |
17 | ListExpr argList = (ListExpr)arg;
18 |
19 | return new NumExpr(((NumExpr)argList.getList().get(0)).getValue() +
20 | ((NumExpr)argList.getList().get(1)).getValue());
21 | }
22 | };
23 | }
24 |
25 | public static CallableExpr subtract() {
26 | return new CallableExpr() {
27 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
28 | Expr arg = interpreter.eval(scope, argExpr);
29 | Expr error = validateBinaryArg(interpreter, "-", arg);
30 | if (error != null) return error;
31 |
32 | ListExpr argList = (ListExpr)arg;
33 |
34 | return new NumExpr(((NumExpr)argList.getList().get(0)).getValue() -
35 | ((NumExpr)argList.getList().get(1)).getValue());
36 | }
37 | };
38 | }
39 |
40 | public static CallableExpr multiply() {
41 | return new CallableExpr() {
42 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
43 | Expr arg = interpreter.eval(scope, argExpr);
44 | Expr error = validateBinaryArg(interpreter, "*", arg);
45 | if (error != null) return error;
46 |
47 | ListExpr argList = (ListExpr)arg;
48 |
49 | return new NumExpr(((NumExpr)argList.getList().get(0)).getValue() *
50 | ((NumExpr)argList.getList().get(1)).getValue());
51 | }
52 | };
53 | }
54 |
55 | public static CallableExpr divide() {
56 | return new CallableExpr() {
57 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
58 | Expr arg = interpreter.eval(scope, argExpr);
59 | Expr error = validateBinaryArg(interpreter, "/", arg);
60 | if (error != null) return error;
61 |
62 | ListExpr argList = (ListExpr)arg;
63 |
64 | return new NumExpr(((NumExpr)argList.getList().get(0)).getValue() /
65 | ((NumExpr)argList.getList().get(1)).getValue());
66 | }
67 | };
68 | }
69 |
70 | public static CallableExpr equals() {
71 | return new CallableExpr() {
72 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
73 |
74 | Expr arg = interpreter.eval(scope, argExpr);
75 |
76 | if (arg.getType() != ExprType.LIST) return interpreter.error("'=' requires two arguments.");
77 | List argList = ((ListExpr)arg).getList();
78 |
79 | if (argList.size() != 2) return interpreter.error("'=' requires two arguments.");
80 |
81 | if (argList.get(0).getType() != argList.get(1).getType()) return interpreter.error("'=' requires arguments of the same type.");
82 |
83 | boolean equals = false;
84 |
85 | switch (argList.get(0).getType()) {
86 | case BOOL:
87 | equals = ((BoolExpr)argList.get(0)).getValue() == ((BoolExpr)argList.get(1)).getValue();
88 | break;
89 |
90 | case NAME:
91 | equals = ((NameExpr)argList.get(0)).getName().equals(((NameExpr)argList.get(1)).getName());
92 | break;
93 |
94 | case NUMBER:
95 | equals = ((NumExpr)argList.get(0)).getValue() == ((NumExpr)argList.get(1)).getValue();
96 | break;
97 |
98 | case STRING:
99 | equals = ((StringExpr)argList.get(0)).getValue().equals(((StringExpr)argList.get(1)).getValue());
100 | break;
101 |
102 | default:
103 | return interpreter.error("'=' cannot be used to compare arguments of type " + argList.get(0).getType().toString());
104 | }
105 |
106 | return new BoolExpr(equals);
107 | }
108 | };
109 | }
110 |
111 | public static CallableExpr lessThan() {
112 | return new CallableExpr() {
113 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
114 | Expr arg = interpreter.eval(scope, argExpr);
115 | Expr error = validateBinaryArg(interpreter, "<", arg);
116 | if (error != null) return error;
117 |
118 | ListExpr argList = (ListExpr)arg;
119 |
120 | return new BoolExpr(((NumExpr)argList.getList().get(0)).getValue() <
121 | ((NumExpr)argList.getList().get(1)).getValue());
122 | }
123 | };
124 | }
125 |
126 | public static CallableExpr greaterThan() {
127 | return new CallableExpr() {
128 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
129 | Expr arg = interpreter.eval(scope, argExpr);
130 | Expr error = validateBinaryArg(interpreter, ">", arg);
131 | if (error != null) return error;
132 |
133 | ListExpr argList = (ListExpr)arg;
134 |
135 | return new BoolExpr(((NumExpr)argList.getList().get(0)).getValue() >
136 | ((NumExpr)argList.getList().get(1)).getValue());
137 | }
138 | };
139 | }
140 |
141 | public static CallableExpr lessThanOrEqual() {
142 | return new CallableExpr() {
143 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
144 | Expr arg = interpreter.eval(scope, argExpr);
145 | Expr error = validateBinaryArg(interpreter, "<=", arg);
146 | if (error != null) return error;
147 |
148 | ListExpr argList = (ListExpr)arg;
149 |
150 | return new BoolExpr(((NumExpr)argList.getList().get(0)).getValue() <=
151 | ((NumExpr)argList.getList().get(1)).getValue());
152 | }
153 | };
154 | }
155 |
156 | public static CallableExpr greaterThanOrEqual() {
157 | return new CallableExpr() {
158 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
159 | Expr arg = interpreter.eval(scope, argExpr);
160 | Expr error = validateBinaryArg(interpreter, ">=", arg);
161 | if (error != null) return error;
162 |
163 | ListExpr argList = (ListExpr)arg;
164 |
165 | return new BoolExpr(((NumExpr)argList.getList().get(0)).getValue() >=
166 | ((NumExpr)argList.getList().get(1)).getValue());
167 | }
168 | };
169 | }
170 |
171 | private static Expr validateBinaryArg(Interpreter interpreter, String op, Expr arg) {
172 | if (arg.getType() != ExprType.LIST) return interpreter.error("'" + op + "' requires two arguments.");
173 | ListExpr argList = (ListExpr)arg;
174 |
175 | if (argList.getList().size() != 2) return interpreter.error("'" + op + "' requires two arguments.");
176 |
177 | if (argList.getList().get(0).getType() != ExprType.NUMBER) return interpreter.error("'" + op + "' expects numeric arguments.");
178 | if (argList.getList().get(1).getType() != ExprType.NUMBER) return interpreter.error("'" + op + "' expects numeric arguments.");
179 |
180 | return null;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/BoolExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public class BoolExpr extends Expr {
4 | public BoolExpr(final boolean value) {
5 | mValue = value;
6 | }
7 |
8 | public boolean getValue() { return mValue; }
9 |
10 | @Override
11 | public ExprType getType() { return ExprType.BOOL; }
12 |
13 | @Override
14 | public String toString() { return Boolean.toString(mValue); }
15 |
16 | private final boolean mValue;
17 | }
18 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/CallExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public class CallExpr extends Expr {
4 |
5 | public CallExpr(final Expr left, final Expr right) {
6 | mLeft = left;
7 | mRight = right;
8 | }
9 |
10 | public Expr getLeft() { return mLeft; }
11 | public Expr getRight() { return mRight; }
12 |
13 | @Override
14 | public ExprType getType() { return ExprType.CALL; }
15 |
16 | @Override
17 | public String toString() {
18 | return mLeft.toString() + " " + mRight.toString();
19 | }
20 |
21 | private final Expr mLeft;
22 | private final Expr mRight;
23 | }
24 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/CallableExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public abstract class CallableExpr extends Expr {
4 |
5 | public abstract Expr call(Interpreter interpreter, Scope parentScope, Expr argExpr);
6 |
7 | @Override
8 | public ExprType getType() { return ExprType.FUNCTION; }
9 | }
10 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Expr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 | public abstract class Expr {
6 |
7 | public static Expr unit() {
8 | // an empty list is unit
9 | return new ListExpr(new ArrayList());
10 | }
11 |
12 | public abstract ExprType getType();
13 | }
14 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/ExprType.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public enum ExprType {
4 | BOOL,
5 | CALL,
6 | FUNCTION,
7 | LIST,
8 | NAME,
9 | NUMBER,
10 | STRING
11 | }
12 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/FunctionExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 | public class FunctionExpr extends CallableExpr {
6 | public FunctionExpr(Scope closure, boolean isMacro, List parameters, Expr body) {
7 | mClosure = closure;
8 | mIsMacro = isMacro;
9 | mParameters = new ArrayList(parameters);
10 | mBody = body;
11 | }
12 |
13 | @Override
14 | public Expr call(Interpreter interpreter, Scope parentScope, Expr argExpr) {
15 | // eagerly evaluate the arguments
16 | Expr arg = argExpr;
17 | if (!mIsMacro) {
18 | arg = interpreter.eval(parentScope, argExpr);
19 | }
20 |
21 | // make sure we have the right number of arguments
22 | if (mParameters.size() == 0) {
23 | if (!(arg instanceof ListExpr)) return interpreter.error("Function expects no arguments but got one.");
24 | if (((ListExpr)arg).getList().size() != 0) return interpreter.error("Function expects no arguments but got multiple.");
25 | } else if (mParameters.size() > 1) {
26 | if (!(arg instanceof ListExpr)) return interpreter.error("Function expects multiple arguments but got one.");
27 | if (((ListExpr)arg).getList().size() != mParameters.size()) return interpreter.error("Function did not get expected number of arguments.");
28 | }
29 |
30 | // create a new local scope for the function
31 | Scope scope = mClosure.create();
32 |
33 | // bind the arguments to the parameters
34 | if (mParameters.size() == 1) {
35 | scope.put(mParameters.get(0), arg);
36 | } else if (mParameters.size() > 1) {
37 | ListExpr args = (ListExpr)arg;
38 | for (int i = 0; i < mParameters.size(); i++) {
39 | scope.put(mParameters.get(i), args.getList().get(i));
40 | }
41 | }
42 |
43 | // evaluate the body in the new scope
44 | return interpreter.eval(scope, mBody);
45 | }
46 |
47 | private final Scope mClosure;
48 | private final boolean mIsMacro;
49 | private final List mParameters;
50 | private final Expr mBody;
51 | }
52 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/IntepreterHost.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public interface IntepreterHost {
4 | void print(final String text);
5 | void error(final String text);
6 | void warning(final String text);
7 | }
8 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Interpreter.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 | public class Interpreter {
6 | public Interpreter() {
7 | this(null);
8 | }
9 |
10 | public Interpreter(IntepreterHost host) {
11 | if (host != null) {
12 | mHost = host;
13 | } else {
14 | mHost = new SysOutHost();
15 | }
16 |
17 | mGlobal = new Scope(null);
18 |
19 | // register the special forms
20 | mGlobal.put("'", SpecialForms.quote());
21 | mGlobal.put("eval", SpecialForms.eval());
22 | mGlobal.put("do", SpecialForms.doForm());
23 | mGlobal.put("print", SpecialForms.print());
24 | mGlobal.put("=>", SpecialForms.createFunction());
25 | mGlobal.put("=>>", SpecialForms.createMacro());
26 | mGlobal.put("def:is:", SpecialForms.defIs());
27 | mGlobal.put("global:is:", SpecialForms.globalIs());
28 |
29 | mGlobal.put("if:then:else:", SpecialForms.ifThenElse());
30 |
31 | mGlobal.put("bool?", SpecialForms.boolPredicate());
32 | mGlobal.put("list?", SpecialForms.listPredicate());
33 | mGlobal.put("name?", SpecialForms.namePredicate());
34 | mGlobal.put("number?", SpecialForms.numberPredicate());
35 | mGlobal.put("string?", SpecialForms.stringPredicate());
36 |
37 | mGlobal.put("count", SpecialForms.count());
38 |
39 | mGlobal.put("+", Arithmetic.add());
40 | mGlobal.put("-", Arithmetic.subtract());
41 | mGlobal.put("*", Arithmetic.multiply());
42 | mGlobal.put("/", Arithmetic.divide());
43 |
44 | mGlobal.put("=", Arithmetic.equals());
45 | mGlobal.put("<", Arithmetic.lessThan());
46 | mGlobal.put(">", Arithmetic.greaterThan());
47 | mGlobal.put("<=", Arithmetic.lessThanOrEqual());
48 | mGlobal.put(">=", Arithmetic.greaterThanOrEqual());
49 | }
50 |
51 | public Scope getGlobalScope() {
52 | return mGlobal;
53 | }
54 |
55 | public Expr eval(Expr expr) {
56 | return eval(mGlobal, expr);
57 | }
58 |
59 | public Expr eval(Scope scope, Expr expr) {
60 | switch (expr.getType()) {
61 | case CALL:
62 | CallExpr call = (CallExpr)expr;
63 |
64 | // evaluate the expression for the function we're calling
65 | Expr function = eval(scope, call.getLeft());
66 |
67 | // must be callable
68 | if (!(function instanceof CallableExpr)) {
69 | return error("Called object is not callable.");
70 | }
71 |
72 | return ((CallableExpr)function).call(this, scope, call.getRight());
73 |
74 | case LIST:
75 | ListExpr list = (ListExpr)expr;
76 |
77 | // evaluate each of the items
78 | List results = new ArrayList();
79 | for (Expr listExpr : list.getList()) {
80 | results.add(eval(scope, listExpr));
81 | }
82 |
83 | return new ListExpr(results);
84 |
85 | case NAME:
86 | // look up a name in the scope
87 | String name = ((NameExpr)expr).getName();
88 | Expr value = scope.get(name);
89 | if (value == null) {
90 | warning("Could not find a value named '" + name + "'.");
91 | value = Expr.unit();
92 | }
93 | return value;
94 |
95 | default:
96 | // everything else is a literal, which evaluates to itself
97 | return expr;
98 | }
99 | }
100 |
101 | public Expr error(final String message) {
102 | mHost.error(message);
103 | return Expr.unit();
104 | }
105 |
106 | public void warning(final String message) {
107 | mHost.warning(message);
108 | }
109 |
110 | public void print(final String message) {
111 | mHost.print(message);
112 | }
113 |
114 | private static class SysOutHost implements IntepreterHost {
115 | public void print(final String text) {
116 | System.out.println(text);
117 | }
118 |
119 | public void error(final String text) {
120 | System.out.println("! " + text);
121 | }
122 |
123 | public void warning(final String text) {
124 | System.out.println("? " + text);
125 | }
126 | }
127 |
128 | private final Scope mGlobal;
129 |
130 | private IntepreterHost mHost;
131 | }
132 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Lark.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.io.*;
4 |
5 | /**
6 | * Main entry point class for running either a single Lark script, or the
7 | * interactive REPL.
8 | */
9 | public class Lark {
10 | public static void main(String[] args) throws IOException {
11 | if (args.length == 0) {
12 | runRepl();
13 | } else if (args.length == 1) {
14 | if (args[0].equals("-t")) {
15 | TestRunner runner = new TestRunner();
16 | runner.run();
17 | } else {
18 | LarkScript.run(args[0]);
19 | }
20 | } else {
21 | System.out.println("Lark expects zero or one argument.");
22 | }
23 | }
24 |
25 | private static void runRepl() throws IOException {
26 | System.out.println("lark v0.0.0");
27 | System.out.println("-----------");
28 | System.out.println("Type 'q' and press Enter to quit.");
29 |
30 | InputStreamReader converter = new InputStreamReader(System.in);
31 | BufferedReader in = new BufferedReader(converter);
32 |
33 | Interpreter interpreter = new Interpreter();
34 |
35 | // load the base scripts
36 | //### bob: hack. assumes relative path.
37 | LarkScript base = new LarkScript("base/init.lark");
38 | base.run(interpreter);
39 |
40 | while (true) {
41 | System.out.print("> ");
42 | String line = in.readLine();
43 | if (line.equals("q")) break;
44 |
45 | Lexer lexer = new Lexer(line);
46 | LarkParser parser = new LarkParser(lexer);
47 |
48 | try {
49 | Expr expr = parser.parse();
50 | Expr result = interpreter.eval(expr);
51 |
52 | System.out.println("= " + result.toString());
53 | } catch(ParseException ex) {
54 | System.out.println("Could not parse expression: " + ex.getMessage());
55 | }
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/LarkParser.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 | public class LarkParser extends Parser {
6 | public LarkParser(Lexer lexer) {
7 | super(lexer);
8 | }
9 |
10 | public Expr parse() throws ParseException {
11 | return semicolonList();
12 | }
13 |
14 | private Expr semicolonList() throws ParseException {
15 | List exprs = new ArrayList();
16 |
17 | do {
18 | // ignore trailing lines before closing a group
19 | if (isMatch(TokenType.RIGHT_PAREN)) break;
20 | if (isMatch(TokenType.RIGHT_BRACE)) break;
21 | if (isMatch(TokenType.RIGHT_BRACKET)) break;
22 | if (isMatch(TokenType.EOF)) break;
23 |
24 | exprs.add(keyword());
25 | } while(match(TokenType.LINE));
26 |
27 | // only create a list if we actually had a ;
28 | if (exprs.size() == 1) return exprs.get(0);
29 |
30 | return new ListExpr(exprs);
31 | }
32 |
33 | private Expr keyword() throws ParseException {
34 | if (!isMatch(TokenType.KEYWORD)) return commaList();
35 |
36 | List keywords = new ArrayList();
37 | List args = new ArrayList();
38 |
39 | while (match(TokenType.KEYWORD)) {
40 | keywords.add(getMatch()[0].getString());
41 | args.add(commaList());
42 | }
43 |
44 | return new CallExpr(new NameExpr(join(keywords)), new ListExpr(args));
45 | }
46 |
47 | private Expr commaList() throws ParseException {
48 | List exprs = new ArrayList();
49 |
50 | do {
51 | exprs.add(operator());
52 | } while (match(TokenType.COMMA));
53 |
54 | // only create a list if we actually had a ,
55 | if (exprs.size() == 1) return exprs.get(0);
56 |
57 | return new ListExpr(exprs);
58 | }
59 |
60 | private Expr operator() throws ParseException {
61 | Expr expr = call();
62 |
63 | while (match(TokenType.OPERATOR)) {
64 | String op = getMatch()[0].getString();
65 | Expr right = call();
66 |
67 | List args = new ArrayList();
68 | args.add(expr);
69 | args.add(right);
70 |
71 | expr = new CallExpr(new NameExpr(op), new ListExpr(args));
72 | }
73 |
74 | return expr;
75 | }
76 |
77 | private Expr call() throws ParseException {
78 | Stack stack = new Stack();
79 |
80 | // push as many calls as we can parse
81 | while (true) {
82 | Expr expr = dottedOrNull();
83 | if (expr == null) break;
84 | stack.push(expr);
85 | }
86 |
87 | if (stack.size() == 0) {
88 | throw new ParseException("Expected primary expression.");
89 | }
90 |
91 | // and then pop them back off to be right-associative
92 | Expr result = stack.pop();
93 | while (stack.size() > 0) {
94 | result = new CallExpr(stack.pop(), result);
95 | }
96 |
97 | return result;
98 | }
99 |
100 | private Expr dottedOrNull() throws ParseException {
101 | Expr expr = primaryOrNull();
102 |
103 | if (expr == null) return null;
104 |
105 | while (match(TokenType.DOT)) {
106 | Expr right = primaryOrNull();
107 |
108 | if (right == null) throw new ParseException("Expected expression after '.'");
109 |
110 | // swap the function and argument
111 | // a.b -> b(a)
112 | expr = new CallExpr(right, expr);
113 | }
114 |
115 | return expr;
116 | }
117 |
118 | private Expr primaryOrNull() throws ParseException {
119 | if (match(TokenType.NAME)) {
120 | String name = getMatch()[0].getString();
121 |
122 | // check for reserved names
123 | if (name.equals("true")) return new BoolExpr(true);
124 | if (name.equals("false")) return new BoolExpr(false);
125 |
126 | return new NameExpr(name);
127 |
128 | } else if (match(TokenType.NUMBER)) {
129 | return new NumExpr(getMatch()[0].getDouble());
130 |
131 | } else if (match(TokenType.STRING)) {
132 | return new StringExpr(getMatch()[0].getString());
133 |
134 | } else if (match(TokenType.LEFT_PAREN)) {
135 | // () is unit
136 | if (match(TokenType.RIGHT_PAREN)) {
137 | return Expr.unit();
138 | }
139 |
140 | // handle (operator) and (keyword:) so that you can create
141 | // name exprs for operators and keywords without actually
142 | // having to parse them as used.
143 | if (match(TokenType.OPERATOR, TokenType.RIGHT_PAREN)) {
144 | return new NameExpr(getMatch()[0].getString());
145 | }
146 | if (match(TokenType.KEYWORD, TokenType.RIGHT_PAREN)) {
147 | return new NameExpr(getMatch()[0].getString());
148 | }
149 |
150 | Expr expr = semicolonList();
151 |
152 | if (!match(TokenType.RIGHT_PAREN)) throw new ParseException("Missing closing ')'.");
153 |
154 | return expr;
155 | } else if (match(TokenType.LEFT_BRACE)) {
156 | // { a } -> do a
157 | Expr expr = semicolonList();
158 |
159 | if (!match(TokenType.RIGHT_BRACE)) throw new ParseException("Missing closing '}'.");
160 |
161 | return new CallExpr(new NameExpr("do"), expr);
162 | } else if (match(TokenType.LEFT_BRACKET)) {
163 |
164 | List exprs = new ArrayList();
165 |
166 | while (true) {
167 | Expr term = primaryOrNull();
168 | if (term == null) break;
169 |
170 | // ignore lines in a [] expression
171 | match(TokenType.LINE);
172 |
173 | exprs.add(term);
174 | }
175 |
176 | if (!match(TokenType.RIGHT_BRACKET)) throw new ParseException("Missing closing ']'.");
177 |
178 | return new ListExpr(exprs);
179 | }
180 |
181 | return null;
182 | }
183 |
184 | private String join(Collection> s) {
185 | StringBuilder builder = new StringBuilder();
186 | Iterator> iter = s.iterator();
187 | while (iter.hasNext()) {
188 | builder.append(iter.next());
189 | }
190 | return builder.toString();
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/LarkScript.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.io.*;
4 | import java.nio.charset.Charset;
5 |
6 | public class LarkScript {
7 | public static String run(String path) {
8 | try {
9 | LarkScript script = new LarkScript(path);
10 | Expr result = script.run();
11 |
12 | if (result == null) return "";
13 | return result.toString();
14 | }
15 | catch (FileNotFoundException ex) {
16 | System.out.println("Could not find the file '" + path + "'.");
17 | }
18 | catch (IOException ex) {
19 | System.out.println("Could not read the file '" + path + "'.");
20 | }
21 |
22 | return null;
23 | }
24 |
25 | public LarkScript(String path) throws IOException {
26 | mPath = path;
27 | mSource = readFile(path);
28 | }
29 |
30 | public String getPath() { return mPath; }
31 | public String getSource() { return mSource; }
32 |
33 | /* Runs this script in the given interpreter.
34 | */
35 | public Expr run(Interpreter interpreter) {
36 | if (mSource.length() > 0) {
37 | Lexer lexer = new Lexer(mSource);
38 | LarkParser parser = new LarkParser(lexer);
39 |
40 | try {
41 | Expr expr = parser.parse();
42 |
43 | // the body of a script is implicitly wrapped in a 'do'
44 | // so that the result is the last expression in the script
45 | expr = new CallExpr(new NameExpr("do"), expr);
46 | return interpreter.eval(expr);
47 |
48 | } catch (ParseException ex) {
49 | System.out.println("Error parsing '" + mPath + "': " + ex.getMessage());
50 | }
51 | }
52 |
53 | return null;
54 | }
55 |
56 | public Expr run(IntepreterHost host) {
57 | return run(new Interpreter(host));
58 | }
59 |
60 | public Expr run() {
61 | return run((IntepreterHost)null);
62 | }
63 |
64 | private static String readFile(String path) throws IOException {
65 | FileInputStream stream = new FileInputStream(path);
66 |
67 | try {
68 | InputStreamReader input = new InputStreamReader(stream, Charset.defaultCharset());
69 | Reader reader = new BufferedReader(input);
70 |
71 | StringBuilder builder = new StringBuilder();
72 | char[] buffer = new char[8192];
73 | int read;
74 |
75 | while ((read = reader.read(buffer, 0, buffer.length)) > 0) {
76 | builder.append(buffer, 0, read);
77 | }
78 |
79 | return builder.toString();
80 | } finally {
81 | stream.close();
82 | }
83 | }
84 |
85 | private final String mPath;
86 | private final String mSource;
87 | }
88 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Lexer.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 |
4 | public class Lexer {
5 |
6 | public Lexer(String text) {
7 | mText = text;
8 | mState = LexState.DEFAULT;
9 | mIndex = 0;
10 | mTokenStart = 0;
11 |
12 | // ignore starting lines
13 | mEatLines = true;
14 | }
15 |
16 | public Token readToken() {
17 | while (true) {
18 | Token token = readRawToken();
19 |
20 | switch (token.getType()) {
21 | // ignore lines after tokens that can't end an expression
22 | case LEFT_PAREN:
23 | case LEFT_BRACKET:
24 | case LEFT_BRACE:
25 | case COMMA:
26 | case DOT:
27 | case OPERATOR:
28 | case KEYWORD:
29 | mEatLines = true;
30 | return token;
31 |
32 | case LINE:
33 | if (!mEatLines) {
34 | // collapse multiple lines
35 | mEatLines = true;
36 | return token;
37 | }
38 | break;
39 |
40 | default:
41 | // a line after any other token is significant
42 | mEatLines = false;
43 | return token;
44 | }
45 | }
46 | }
47 |
48 | private Token readRawToken() {
49 | while (mIndex <= mText.length()) {
50 |
51 | // tack on a '\0' to the end of the string an lex it. that will let
52 | // us conveniently have a place to end any token that goes to the
53 | // end of the string
54 | char c = (mIndex < mText.length()) ? mText.charAt(mIndex) : '\0';
55 |
56 | switch (mState) {
57 | case DEFAULT:
58 | switch (c) {
59 | case '(': return singleCharToken(TokenType.LEFT_PAREN);
60 | case ')': return singleCharToken(TokenType.RIGHT_PAREN);
61 | case '[': return singleCharToken(TokenType.LEFT_BRACKET);
62 | case ']': return singleCharToken(TokenType.RIGHT_BRACKET);
63 | case '{': return singleCharToken(TokenType.LEFT_BRACE);
64 | case '}': return singleCharToken(TokenType.RIGHT_BRACE);
65 | case ',': return singleCharToken(TokenType.COMMA);
66 | case ';': return singleCharToken(TokenType.LINE);
67 | case '.': return singleCharToken(TokenType.DOT);
68 |
69 | case '"': startToken(LexState.IN_STRING); break;
70 | case '#': startToken(LexState.IN_COMMENT); break;
71 |
72 | case ':':
73 | // start a multi-character token so that ":::" is a single
74 | // keyword
75 | startToken(LexState.IN_KEYWORD);
76 | break;
77 |
78 | case '-':
79 | startToken(LexState.IN_MINUS);
80 | break;
81 |
82 | case '\n':
83 | case '\r':
84 | return singleCharToken(TokenType.LINE);
85 |
86 | // ignore whitespace
87 | case ' ':
88 | case '\t':
89 | case '\0':
90 | mIndex++;
91 | break;
92 |
93 | default:
94 | if (isAlpha(c)) {
95 | startToken(LexState.IN_NAME);
96 | } else if (isOperator(c)) {
97 | startToken(LexState.IN_OPERATOR);
98 | } else if (isDigit(c)) {
99 | startToken(LexState.IN_NUMBER);
100 | } else {
101 | //### bob: hack temp. unexpected character
102 | return new Token(TokenType.EOF);
103 | }
104 | break;
105 | }
106 | break;
107 |
108 | case IN_NAME:
109 | if (isAlpha(c) || isDigit(c) || isOperator(c)) {
110 | mIndex++;
111 | } else if (c == ':') {
112 | changeToken(LexState.IN_KEYWORD);
113 | } else {
114 | return createStringToken(TokenType.NAME);
115 | }
116 | break;
117 |
118 | case IN_OPERATOR:
119 | if (isOperator(c) || isAlpha(c) || isDigit(c)) {
120 | mIndex++;
121 | } else {
122 | return createStringToken(TokenType.OPERATOR);
123 | }
124 | break;
125 |
126 | case IN_KEYWORD:
127 | if (isOperator(c) || isAlpha(c) || isDigit(c) || (c == ':')) {
128 | mIndex++;
129 | } else {
130 | return createStringToken(TokenType.KEYWORD);
131 | }
132 | break;
133 |
134 | case IN_NUMBER:
135 | if (isDigit(c)) {
136 | mIndex++;
137 | } else if (c == '.') {
138 | changeToken(LexState.IN_DECIMAL);
139 | } else {
140 | return createNumToken(TokenType.NUMBER);
141 | }
142 | break;
143 |
144 | case IN_DECIMAL:
145 | if (isDigit(c)) {
146 | mIndex++;
147 | } else {
148 | return createNumToken(TokenType.NUMBER);
149 | }
150 | break;
151 |
152 | case IN_MINUS:
153 | if (isDigit(c)) {
154 | changeToken(LexState.IN_NUMBER);
155 | } else if (isOperator(c) || isAlpha(c)) {
156 | changeToken(LexState.IN_OPERATOR);
157 | } else {
158 | return createStringToken(TokenType.OPERATOR);
159 | }
160 | break;
161 |
162 | case IN_STRING:
163 | if (c == '"') {
164 | // eat the closing "
165 | mIndex++;
166 |
167 | // get the contained string without the quotes
168 | String text = mText.substring(mTokenStart + 1, mIndex - 1);
169 | mState = LexState.DEFAULT;
170 | return new Token(TokenType.STRING, text);
171 | } else if (c == '\0') {
172 | //### bob: need error handling. ran out of characters before
173 | // string was closed
174 | return new Token(TokenType.EOF);
175 | } else {
176 | mIndex++;
177 | }
178 | break;
179 |
180 | case IN_COMMENT:
181 | if ((c == '\n') || (c == '\r')) {
182 | // don't eat the newline here. that way, a comment on the
183 | // same line as other code still allows the newline to be
184 | // processed
185 | mState = LexState.DEFAULT;
186 | } else {
187 | mIndex++;
188 | }
189 | break;
190 | }
191 | }
192 |
193 | return new Token(TokenType.EOF);
194 | }
195 |
196 | private Token singleCharToken(TokenType type) {
197 | mIndex++;
198 | return new Token(type, mText.substring(mIndex - 1, mIndex));
199 | }
200 |
201 | private void startToken(LexState state) {
202 | mTokenStart = mIndex;
203 | changeToken(state);
204 | }
205 |
206 | private void changeToken(LexState state) {
207 | mState = state;
208 | mIndex++;
209 | }
210 |
211 | private Token createStringToken(TokenType type) {
212 | String text = mText.substring(mTokenStart, mIndex);
213 | mState = LexState.DEFAULT;
214 | return new Token(type, text);
215 | }
216 |
217 | private Token createNumToken(TokenType type) {
218 | String text = mText.substring(mTokenStart, mIndex);
219 | double value = Double.parseDouble(text);
220 | mState = LexState.DEFAULT;
221 | return new Token(type, value);
222 | }
223 |
224 | private boolean isAlpha(final char c) {
225 | return ((c >= 'a') && (c <= 'z')) ||
226 | ((c >= 'A') && (c <= 'Z')) ||
227 | (c == '_') || (c == '\'');
228 | }
229 |
230 | private boolean isDigit(final char c) {
231 | return (c >= '0') && (c <= '9');
232 | }
233 |
234 | private boolean isOperator(final char c) {
235 | return "`~!@#$%^&*-=+\\|/?<>".indexOf(c) != -1;
236 | }
237 |
238 | private enum LexState {
239 | DEFAULT,
240 | IN_NAME,
241 | IN_OPERATOR,
242 | IN_KEYWORD,
243 | IN_NUMBER,
244 | IN_DECIMAL,
245 | IN_MINUS,
246 | IN_STRING,
247 | IN_COMMENT
248 | }
249 |
250 | private final String mText;
251 | private LexState mState;
252 | private int mTokenStart;
253 | private int mIndex;
254 | private boolean mEatLines;
255 | }
256 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/ListExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 | public class ListExpr extends Expr {
6 |
7 | public ListExpr(final List list) {
8 | mList = new ArrayList(list);
9 | }
10 |
11 | public List getList() { return mList; }
12 |
13 | @Override
14 | public ExprType getType() { return ExprType.LIST; }
15 |
16 | @Override
17 | public String toString() {
18 | StringBuilder builder = new StringBuilder();
19 |
20 | builder.append("(");
21 |
22 | Iterator> iter = mList.iterator();
23 | while (iter.hasNext()) {
24 | builder.append(iter.next());
25 |
26 | if (iter.hasNext()) {
27 | builder.append(", ");
28 | }
29 | }
30 |
31 | builder.append(")");
32 |
33 | return builder.toString();
34 | }
35 |
36 | private final List mList;
37 | }
38 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/NameExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public class NameExpr extends Expr {
4 |
5 | public NameExpr(final String name) {
6 | mName = name;
7 | }
8 |
9 | public String getName() { return mName; }
10 |
11 | @Override
12 | public ExprType getType() { return ExprType.NAME; }
13 |
14 | @Override
15 | public String toString() { return mName; }
16 |
17 | private final String mName;
18 | }
19 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/NumExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.text.NumberFormat;
4 |
5 | public class NumExpr extends CallableExpr {
6 | public NumExpr(final double value) {
7 | mValue = value;
8 | }
9 |
10 | public double getValue() { return mValue; }
11 |
12 | @Override
13 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
14 | // an int is a "function" that takes a list and returns the element
15 | // at that (zero-based) index in the list
16 | // > 1 (4, 5, 6)
17 | // = 5
18 | // > (1, 2, 3).2
19 | // = 3
20 | Expr arg = interpreter.eval(scope, argExpr);
21 |
22 | if (!(arg instanceof ListExpr)) return interpreter.error("Argument to index function must be a list.");
23 |
24 | ListExpr list = (ListExpr)arg;
25 |
26 | int index = (int)mValue;
27 |
28 | if (index < 0) return interpreter.error("Index must be non-negative.");
29 | if (index >= list.getList().size()) return interpreter.error("Index is out of bounds.");
30 |
31 | return list.getList().get(index);
32 |
33 | //### bob: should also work for getting a character from a string?
34 | }
35 |
36 | @Override
37 | public ExprType getType() { return ExprType.NUMBER; }
38 |
39 | @Override
40 | public String toString() {
41 | return sFormat.format(mValue);
42 | }
43 |
44 | private static final NumberFormat sFormat;
45 |
46 | static {
47 | sFormat = NumberFormat.getInstance();
48 | sFormat.setGroupingUsed(false);
49 | sFormat.setMinimumFractionDigits(0);
50 | }
51 |
52 | private final double mValue;
53 | }
54 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/ParseException.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | @SuppressWarnings("serial")
4 | public class ParseException extends Exception {
5 | public ParseException(final String message) {
6 | super(message);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Parser.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 |
5 |
6 | public abstract class Parser {
7 | public Parser(Lexer lexer) {
8 | mLexer = lexer;
9 |
10 | mRead = new LinkedList();
11 | }
12 |
13 | protected Token[] getMatch()
14 | {
15 | return mLastMatch;
16 | }
17 |
18 | protected boolean isMatch(TokenType... types) {
19 | for (int i = 0; i < types.length; i++) {
20 | if (!lookAhead(i).getType().equals(types[i])) return false;
21 | }
22 |
23 | return true;
24 | }
25 |
26 | protected boolean match(TokenType... types) {
27 | // don't consume any unless all match
28 | if (!isMatch(types)) return false;
29 |
30 | mLastMatch = new Token[types.length];
31 |
32 | for (int i = 0; i < mLastMatch.length; i++) {
33 | mLastMatch[i] = mRead.poll();
34 | }
35 |
36 | return true;
37 | }
38 |
39 | private Token lookAhead(int distance) {
40 | // read in as many as needed
41 | while (distance >= mRead.size()) {
42 | mRead.add(mLexer.readToken());
43 | }
44 |
45 | // get the queued token
46 | return mRead.get(distance);
47 | }
48 |
49 | private final Lexer mLexer;
50 |
51 | private final LinkedList mRead;
52 | private Token[] mLastMatch;
53 | }
54 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Scope.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.Hashtable;
4 |
5 | /**
6 | * Represents a name scope, i.e. an environment where names can be defined and
7 | * looked up.
8 | */
9 | public class Scope {
10 |
11 | public Scope(final Scope parent) {
12 | mParent = parent;
13 | mBound = new Hashtable();
14 | }
15 |
16 | /**
17 | * Creates a new child scope of this scope.
18 | */
19 | public Scope create() {
20 | return new Scope(this);
21 | }
22 |
23 | /**
24 | * Gets the value bound to the given name in this scope, or any of its
25 | * parent scopes.
26 | *
27 | * @param name - the name of the value to look up.
28 | * @return the value bound to that name or null if not found.
29 | */
30 | public Expr get(String name) {
31 | // look it up in the current scope
32 | if (mBound.containsKey(name)) {
33 | return mBound.get(name);
34 | }
35 |
36 | // if we're at the global scope and haven't found it, it isn't defined
37 | if (mParent == null) return null;
38 |
39 | // walk up the scope chain
40 | return mParent.get(name);
41 | }
42 |
43 | public void put(String name, Expr value) {
44 | mBound.put(name, value);
45 | }
46 |
47 | private final Scope mParent;
48 | private final Hashtable mBound;
49 | }
50 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/SpecialForms.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class SpecialForms {
7 |
8 | public static CallableExpr quote() {
9 | return new CallableExpr() {
10 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
11 | return argExpr;
12 | }
13 | };
14 | }
15 |
16 | public static CallableExpr eval() {
17 | return new CallableExpr() {
18 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
19 | Expr arg = interpreter.eval(scope, argExpr);
20 | return interpreter.eval(scope, arg);
21 | }
22 | };
23 | }
24 |
25 | public static CallableExpr doForm() {
26 | return new CallableExpr() {
27 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
28 |
29 | // create a new local scope for the body
30 | Scope local = scope.create();
31 |
32 | // if the arg isn't a list, just eval it normally
33 | if (argExpr.getType() != ExprType.LIST) return interpreter.eval(local, argExpr);
34 |
35 | // evaluate each item in the arg list in order, returning the last one
36 | ListExpr argList = (ListExpr)argExpr;
37 |
38 | Expr result = null;
39 | for (Expr arg : argList.getList()) {
40 | result = interpreter.eval(local, arg);
41 | }
42 |
43 | return result;
44 | }
45 | };
46 | }
47 |
48 | public static CallableExpr print() {
49 | return new CallableExpr() {
50 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
51 | Expr arg = interpreter.eval(scope, argExpr);
52 | interpreter.print(arg.toString());
53 |
54 | return Expr.unit();
55 | }
56 | };
57 | }
58 |
59 | public static CallableExpr createFunction() {
60 | return new CallableExpr() {
61 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
62 | return createFunction(false, scope, argExpr);
63 | }
64 | };
65 | }
66 |
67 | public static CallableExpr createMacro() {
68 | return new CallableExpr() {
69 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
70 | return createFunction(true, scope, argExpr);
71 | }
72 | };
73 | }
74 |
75 | public static CallableExpr defIs() {
76 | return new CallableExpr() {
77 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
78 | return define(false, interpreter, scope, argExpr);
79 | }
80 | };
81 | }
82 |
83 | public static CallableExpr globalIs() {
84 | return new CallableExpr() {
85 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
86 | return define(true, interpreter, scope, argExpr);
87 | }
88 | };
89 | }
90 |
91 | public static CallableExpr ifThenElse() {
92 | return new CallableExpr() {
93 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
94 | if (argExpr.getType() != ExprType.LIST) return interpreter.error("'if:then:else:' expects an argument list.");
95 |
96 | ListExpr argListExpr = (ListExpr)argExpr;
97 | if (argListExpr.getList().size() != 3) return interpreter.error ("'if:then:else:' expects three arguments.");
98 |
99 | // evaluate the condition
100 | Expr condition = interpreter.eval(scope, argListExpr.getList().get(0));
101 |
102 | if (condition.getType() != ExprType.BOOL) return interpreter.error("'if:then:else:' condition must evaluate to true or false.");
103 |
104 | // evaluate the then branch
105 | if (((BoolExpr)condition).getValue()) {
106 | return interpreter.eval(scope, argListExpr.getList().get(1));
107 | } else {
108 | // condition was false
109 | return interpreter.eval(scope, argListExpr.getList().get(2));
110 | }
111 | }
112 | };
113 | }
114 |
115 | public static CallableExpr boolPredicate() {
116 | return new CallableExpr() {
117 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
118 | Expr arg = interpreter.eval(scope, argExpr);
119 |
120 | return new BoolExpr(arg.getType() == ExprType.BOOL);
121 | }
122 | };
123 | }
124 |
125 | public static CallableExpr listPredicate() {
126 | return new CallableExpr() {
127 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
128 | Expr arg = interpreter.eval(scope, argExpr);
129 |
130 | return new BoolExpr(arg.getType() == ExprType.LIST);
131 | }
132 | };
133 | }
134 |
135 | public static CallableExpr namePredicate() {
136 | return new CallableExpr() {
137 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
138 | Expr arg = interpreter.eval(scope, argExpr);
139 |
140 | return new BoolExpr(arg.getType() == ExprType.NAME);
141 | }
142 | };
143 | }
144 |
145 | public static CallableExpr numberPredicate() {
146 | return new CallableExpr() {
147 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
148 | Expr arg = interpreter.eval(scope, argExpr);
149 |
150 | return new BoolExpr(arg.getType() == ExprType.NUMBER);
151 | }
152 | };
153 | }
154 |
155 | public static CallableExpr stringPredicate() {
156 | return new CallableExpr() {
157 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
158 | Expr arg = interpreter.eval(scope, argExpr);
159 |
160 | return new BoolExpr(arg.getType() == ExprType.STRING);
161 | }
162 | };
163 | }
164 |
165 | public static CallableExpr count() {
166 | return new CallableExpr() {
167 | public Expr call(Interpreter interpreter, Scope scope, Expr argExpr) {
168 | Expr arg = interpreter.eval(scope, argExpr);
169 |
170 | if (arg.getType() != ExprType.LIST) return interpreter.error("Argument to 'count' must be a list.");
171 |
172 | ListExpr list = (ListExpr)arg;
173 | return new NumExpr(list.getList().size());
174 | }
175 | };
176 | }
177 |
178 | private static Expr define(boolean isGlobal, Interpreter interpreter, Scope scope, Expr argExpr) {
179 | if (!(argExpr instanceof ListExpr)) return interpreter.error("def:is: needs more than one argument.");
180 |
181 | ListExpr args = (ListExpr)argExpr;
182 |
183 | if (args.getList().size() != 2) return interpreter.error("def:is: expects two arguments.");
184 |
185 | // get the list of names being defined
186 | Expr nameArg = args.getList().get(0);
187 | List names = new ArrayList();
188 | if (nameArg.getType() == ExprType.NAME) {
189 | // defining a single name
190 | names.add(((NameExpr)nameArg).getName());
191 |
192 | } else if (nameArg.getType() == ExprType.LIST) {
193 | // defining a list of names
194 | ListExpr namesList = (ListExpr)nameArg;
195 | for (Expr name : namesList.getList()) {
196 | if (name.getType() != ExprType.NAME) {
197 | return interpreter.error("First argument to def:is: must be a name, list, or call.");
198 | }
199 | names.add(((NameExpr)name).getName());
200 | }
201 | } else {
202 | return interpreter.error("First argument to def:is: must be a name, list, or call.");
203 | }
204 |
205 | // evaluate the value(s)
206 | Expr body = args.getList().get(1);
207 | Expr value = interpreter.eval(scope, body);
208 |
209 | // make sure the body matches the names
210 | if (names.size() > 1) {
211 | if (value.getType() != ExprType.LIST) return interpreter.error("When defining multiple names, the value must be a list.");
212 | ListExpr valueList = (ListExpr)value;
213 | if (names.size() != valueList.getList().size()) return interpreter.error("When defining multiple names, the number of names and values must match.");
214 | }
215 |
216 | // define the names in the correct scope
217 | if (names.size() == 1) {
218 | defineName(isGlobal, interpreter, scope, names.get(0), value);
219 | } else {
220 | ListExpr values = (ListExpr)value;
221 | for (int i = 0; i < names.size(); i++) {
222 | defineName(isGlobal, interpreter, scope, names.get(i), values.getList().get(i));
223 | }
224 | }
225 |
226 | return Expr.unit();
227 | }
228 |
229 | private static void defineName(boolean isGlobal, Interpreter interpreter, Scope scope, String name, Expr value) {
230 | if (isGlobal) {
231 | interpreter.getGlobalScope().put(name, value);
232 | } else {
233 | scope.put(name, value);
234 | }
235 | }
236 |
237 | private static Expr createFunction(boolean isMacro, Scope scope, Expr arg) {
238 | //### bob: need lots of error-handling here
239 | ListExpr argList = (ListExpr)arg;
240 |
241 | // get the parameter name(s)
242 | List paramNames = new ArrayList();
243 | Expr parameters = argList.getList().get(0);
244 | if (parameters.getType() == ExprType.LIST) {
245 | ListExpr paramList = (ListExpr)parameters;
246 |
247 | for (Expr param : paramList.getList()) {
248 | paramNames.add(((NameExpr)param).getName());
249 | }
250 | } else {
251 | // not a list, so assume it's a single name
252 | paramNames.add(((NameExpr)parameters).getName());
253 | }
254 |
255 | // create the function
256 | return new FunctionExpr(scope, isMacro, paramNames, argList.getList().get(1));
257 | //### bob: need to support closures at some point
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/StringExpr.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public class StringExpr extends Expr {
4 | public StringExpr(final String value) {
5 | mValue = value;
6 | }
7 |
8 | public String getValue() { return mValue; }
9 |
10 | @Override
11 | public ExprType getType() { return ExprType.STRING; }
12 |
13 | @Override
14 | public String toString() { return mValue; }
15 |
16 | private final String mValue;
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/TestRunner.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | import java.util.*;
4 | import java.io.*;
5 |
6 | public class TestRunner {
7 | public void run() {
8 | System.out.println("running test suite...");
9 | System.out.println();
10 |
11 | File testDir = new File("test");
12 |
13 | for (File script : testDir.listFiles()) {
14 | if (script.getPath().endsWith(".lark")) {
15 | runTest(script);
16 | }
17 | }
18 |
19 | System.out.println();
20 | System.out.printf("%d out of %d tests passed", mPasses, mTests);
21 | }
22 |
23 | private void runTest(File path) {
24 | mOutput = 0;
25 | System.out.println(path.toString());
26 |
27 | try {
28 | LarkScript script = new LarkScript(path.getPath());
29 |
30 | mPassed = true;
31 |
32 | // parse the script to get the expected behavior
33 | mExpectedOutput = new LinkedList();
34 | String expectedResult = "";
35 |
36 | for (String line : script.getSource().split("\r\n|\r|\n")) {
37 | if (line.contains(OUTPUT_PREFIX)) {
38 | int start = line.indexOf(OUTPUT_PREFIX) + OUTPUT_PREFIX.length();
39 | mExpectedOutput.add(line.substring(start));
40 | }
41 | else if (line.contains(RESULT_PREFIX)) {
42 | int start = line.indexOf(RESULT_PREFIX) + RESULT_PREFIX.length();
43 | expectedResult = line.substring(start);
44 | }
45 | }
46 |
47 | Interpreter interpreter = new Interpreter(new TestHost());
48 |
49 | // load the base script
50 | //### bob: hack. assumes relative path.
51 | LarkScript base = new LarkScript("base/init.lark");
52 | base.run(interpreter);
53 |
54 | // run the script
55 | mRunning = true;
56 | Expr resultExpr = script.run(interpreter);
57 | mRunning = false;
58 |
59 | // check the result
60 | if (resultExpr == null) {
61 | System.out.println("- fail: got null expression");
62 | mPassed = false;
63 |
64 | } else if ((expectedResult.length() > 0) && !expectedResult.equals(resultExpr.toString())) {
65 | System.out.println("- fail: result was '" + resultExpr.toString() + "', expected '" + expectedResult + "'");
66 | mPassed = false;
67 | }
68 |
69 | // see if we missed output
70 | for (String expected : mExpectedOutput) {
71 | System.out.println("- fail: expected '" + expected + "' but got nothing");
72 | }
73 | }
74 | catch (IOException ex) {
75 | System.out.println("- fail: got exception loading test script");
76 | mPassed = false;
77 | }
78 |
79 | System.out.println("- passed " + mOutput + " lines of output");
80 |
81 | mTests++;
82 | if (mPassed) mPasses++;
83 | }
84 |
85 | private class TestHost implements IntepreterHost {
86 | @Override
87 | public void print(final String text) {
88 | if (mRunning) {
89 | if (mExpectedOutput.size() == 0) {
90 | System.out.println("- fail: got '" + text + "' output when no more was expected");
91 | mPassed = false;
92 | } else {
93 | String actual = mExpectedOutput.poll();
94 | if (!actual.equals(text)) {
95 | System.out.println("- fail: got '" + text + "' output when '" + actual + "' was expected");
96 | mPassed = false;
97 | } else {
98 | mOutput++;
99 | }
100 | }
101 | } else {
102 | System.out.println(text);
103 | }
104 | }
105 |
106 | @Override
107 | public void error(final String text) {
108 | if (!mRunning) return;
109 |
110 | System.out.println("- fail: got unexpected error '" + text + "'");
111 | mPassed = false;
112 | }
113 |
114 | @Override
115 | public void warning(final String text) {
116 | // ignore warnings
117 | }
118 | }
119 |
120 | private static final String OUTPUT_PREFIX = "# output: ";
121 | private static final String RESULT_PREFIX = "# result: ";
122 |
123 | private LinkedList mExpectedOutput;
124 | private boolean mRunning;
125 | private boolean mPassed;
126 | private int mTests;
127 | private int mPasses;
128 | private int mOutput;
129 | }
130 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/Token.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 |
4 | public final class Token {
5 | public Token(final TokenType type) {
6 | mType = type;
7 | mStringValue = "";
8 | mDoubleValue = 0;
9 | }
10 |
11 | public Token(final TokenType type, final String value) {
12 | mType = type;
13 | mStringValue = value;
14 | mDoubleValue = 0;
15 | }
16 |
17 | public Token(final TokenType type, final double value) {
18 | mType = type;
19 | mStringValue = "";
20 | mDoubleValue = value;
21 | }
22 |
23 | public TokenType getType() { return mType; }
24 |
25 | public String getString() { return mStringValue; }
26 | public double getDouble() { return mDoubleValue; }
27 |
28 | public String toString() {
29 | switch (mType)
30 | {
31 | case LEFT_PAREN: return "(";
32 | case RIGHT_PAREN: return ")";
33 | case LEFT_BRACKET: return "[";
34 | case RIGHT_BRACKET: return "]";
35 | case LEFT_BRACE: return "{";
36 | case RIGHT_BRACE: return "}";
37 | case COMMA: return ",";
38 | case LINE: return "(line)";
39 | case DOT: return ".";
40 |
41 | case NAME: return mStringValue + " (name)";
42 | case OPERATOR: return mStringValue + " (op)";
43 | case KEYWORD: return mStringValue + " (keyword)";
44 |
45 | case NUMBER: return Double.toString(mDoubleValue);
46 | case STRING: return "\"" + mStringValue + "\"";
47 |
48 | case EOF: return "(eof)";
49 |
50 | default: return "(unknown token?!)";
51 | }
52 | }
53 |
54 | private final TokenType mType;
55 | private final String mStringValue;
56 | private final double mDoubleValue;
57 | }
58 |
--------------------------------------------------------------------------------
/src/com/stuffwithstuff/lark/TokenType.java:
--------------------------------------------------------------------------------
1 | package com.stuffwithstuff.lark;
2 |
3 | public enum TokenType {
4 | LEFT_PAREN,
5 | RIGHT_PAREN,
6 | LEFT_BRACKET,
7 | RIGHT_BRACKET,
8 | LEFT_BRACE,
9 | RIGHT_BRACE,
10 | COMMA,
11 | DOT,
12 |
13 | NAME,
14 | OPERATOR,
15 | KEYWORD,
16 |
17 | NUMBER,
18 | STRING,
19 |
20 | LINE,
21 | EOF
22 | }
23 |
--------------------------------------------------------------------------------
/test/arithmetic.lark:
--------------------------------------------------------------------------------
1 | # output: 3
2 | # output: 6
3 | # output: 1
4 | # output: 2
5 | print (1 + 2)
6 | print (2 * 3)
7 | print (4 - 3)
8 | print (8 / 4)
--------------------------------------------------------------------------------
/test/def.lark:
--------------------------------------------------------------------------------
1 | # access an undefined name
2 | print a # output: ()
3 |
4 | # redefine
5 | def: b is: 1
6 | print b # output: 1
7 | def: b is: 2
8 | print b # output: 2
9 |
10 | # local scope shadows an outer scope
11 | def: a is: 3
12 | print a # output: 3
13 | {
14 | def: a is: 4
15 | print a # output: 4
16 | }
17 | print a # output: 3
18 |
19 | # define multiple names
20 | def: c, d is: 5, 6
21 | print c # output: 5
22 | print d # output: 6
23 |
24 | # evaluate value
25 | def: e is: 3 + 4
26 | print e # output: 7
27 |
28 | # define function
29 | #def: f g is: g + g
30 | #print f 3 # --output: 6
31 |
32 | #define multi-arg function
33 | #def: f (g, h) is: g + h
34 | #print f (2, 3) # --output: 5
35 |
36 | #define multiple functions
37 | #def: f g, h (i, j) is: (g + 7, i + j)
38 | #print f 2 # --output: 9
39 | #print h (3, 4) # --output: 7
40 |
--------------------------------------------------------------------------------
/test/do.lark:
--------------------------------------------------------------------------------
1 | # explicit syntax
2 | do (print 1, print 2, print 3)
3 | # output: 1
4 | # output: 2
5 | # output: 3
6 |
7 | # curly syntax
8 | { print 1, print 2, print 3 }
9 | # output: 1
10 | # output: 2
11 | # output: 3
12 |
13 | print { 1, 2, 3, 4 }
14 | # output: 4
15 |
--------------------------------------------------------------------------------
/test/eval.lark:
--------------------------------------------------------------------------------
1 | print (1 + 2)
2 | # output: 3
3 |
4 | print '(1 + 2)
5 | # output: + (1, 2)
6 |
7 | print eval '(1 + 2)
8 | # output: 3
9 |
10 | print eval ' '(1 + 2)
11 | # output: + (1, 2)
12 |
13 | print eval eval ' '(1 + 2)
14 | # output: 3
15 |
--------------------------------------------------------------------------------
/test/global.lark:
--------------------------------------------------------------------------------
1 | print a
2 | # output: ()
3 |
4 | global: a is: 1
5 | print a
6 | # output: 1
7 |
8 | {
9 | global: a is: 2
10 | print a
11 | # output: 2
12 | }
13 |
14 | print a
15 | # output: 2
16 |
--------------------------------------------------------------------------------
/test/if.lark:
--------------------------------------------------------------------------------
1 | if: true then: print "yes 1"
2 | # output: yes 1
3 |
4 | if: false then: print "no 2"
5 |
6 | if: true then: print "yes 3" else: print "no 3"
7 | # output: yes 3
8 |
9 | if: false then: print "no 4" else: print "yes 4"
10 | # output: yes 4
11 |
12 | print (if: true then: "yes 5" else: "no 5")
13 | # output: yes 5
14 |
--------------------------------------------------------------------------------
/test/newlines.lark:
--------------------------------------------------------------------------------
1 | # newlines separate a list
2 | print (1
3 | 2
4 | 3)
5 | # output: (1, 2, 3)
6 |
7 | # newlines separate a s-expr list
8 | print [1 2
9 | 3 4]
10 | # output: (1, 2, 3, 4)
11 |
12 | # multiple newlines are compacted
13 | print (1
14 |
15 |
16 |
17 |
18 | 2)
19 | # output: (1, 2)
20 |
21 | # newlines are eaten after things that don't end an expression
22 | print (1 +
23 | 2)
24 | # output: 3
25 |
26 | print (if: true then:
27 | 4)
28 | # output: 4
29 |
30 | "hi there".
31 | print
32 | # output: hi there
33 |
34 | print (
35 | 1, 2)
36 | # output: (1, 2)
37 |
38 | print [
39 | 3 4]
40 | # output: (3, 4)
41 |
42 | {
43 | print "hey"
44 | }
45 | # output: hey
--------------------------------------------------------------------------------
/test/predicates.lark:
--------------------------------------------------------------------------------
1 | print true.bool?
2 | # output: true
3 | print false.bool?
4 | # output: true
5 | print bool? 1
6 | # output: false
7 | print "s".bool?
8 | # output: false
9 | print ().bool?
10 | # output: false
11 | print (1, 2).bool?
12 | # output: false
13 | print (' name).bool?
14 | # output: false
15 |
16 | print true.number?
17 | # output: false
18 | print false.number?
19 | # output: false
20 | print number? 1
21 | # output: true
22 | print "s".number?
23 | # output: false
24 | print ().number?
25 | # output: false
26 | print (1, 2).number?
27 | # output: false
28 | print (' name).number?
29 | # output: false
30 |
31 | print true.list?
32 | # output: false
33 | print false.list?
34 | # output: false
35 | print list? 1
36 | # output: false
37 | print "s".list?
38 | # output: false
39 | print ().list?
40 | # output: true
41 | print (1, 2).list?
42 | # output: true
43 | print (' name).list?
44 | # output: false
45 |
46 | print true.name?
47 | # output: false
48 | print false.name?
49 | # output: false
50 | print name? 1
51 | # output: false
52 | print "s".name?
53 | # output: false
54 | print ().name?
55 | # output: false
56 | print (1, 2).name?
57 | # output: false
58 | print (' name).name?
59 | # output: true
60 |
61 | print true.string?
62 | # output: false
63 | print false.string?
64 | # output: false
65 | print string? 1
66 | # output: false
67 | print "s".string?
68 | # output: true
69 | print ().string?
70 | # output: false
71 | print (1, 2).string?
72 | # output: false
73 | print (' name).string?
74 | # output: false
75 |
--------------------------------------------------------------------------------
/test/print.lark:
--------------------------------------------------------------------------------
1 | print ()
2 | # output: ()
3 |
4 | print 123
5 | # output: 123
6 |
7 | print "hi there"
8 | # output: hi there
9 |
10 | print ' a-name
11 | # output: a-name
12 |
13 | print (1, "hello")
14 | # output: (1, hello)
15 |
16 | # result: ()
17 |
--------------------------------------------------------------------------------
/test/quote.lark:
--------------------------------------------------------------------------------
1 | print ' 1
2 | # output: 1
3 |
4 | print ' hi there
5 | # output: hi there
6 |
7 | print '(4 - 3)
8 | # output: - (4, 3)
9 |
10 | print '(if: foo then: bar)
11 | # output: if:then: (foo, bar)
12 |
--------------------------------------------------------------------------------
/test/sicp01-ex.lark:
--------------------------------------------------------------------------------
1 | # exercise 1.1:
2 | # ------------
3 |
4 | # 10
5 | print 10
6 | # output: 10
7 |
8 | # (+ 5 3 4)
9 | print (5 + 3 + 4)
10 | # output: 12
11 |
12 | # (- 9 1)
13 | print (9 - 1)
14 | # output: 8
15 |
16 | # (/ 6 2)
17 | print (6 / 2)
18 | # output: 3
19 |
20 | # (+ (* 2 4) (- 4 6))
21 | print ((2 * 4) + (4 - 6))
22 | # output: 6
23 |
24 | # (define a 3)
25 | print (global: a is: 3)
26 | # output: ()
27 |
28 | # (define b (+ a 1))
29 | print (global: b is: a + 1)
30 | # output: ()
31 |
32 | # (+ a b (* a b))
33 | print (a + b + (a * b))
34 | # output: 19
35 |
36 | # (= a b)
37 | print (a = b)
38 | # output: false
39 |
40 | # (if (and (> b a) (< b (* a b)))
41 | # b
42 | # a)
43 | print (if: (b > a) & (b < (a * b)) then: b else: a)
44 | # output: 4
45 |
46 | # (cond ((= a 4) 6)
47 | # ((= b 4) (+ 6 7 a))
48 | # (else 25))
49 | print (if: a = 4 then: 6 else:
50 | (if: b = 4 then: 6 + 7 + a else: 25))
51 | # output: 16
52 |
53 | # (+ 2 (if (> b a) b a))
54 | print (2 + (if: b > a then: b else: a))
55 | # output: 6
56 |
57 | # (* (cond ((> a b) a)
58 | # ((< a b) b)
59 | # (else -1))
60 | # (+ a 1))
61 | print ((if: a > b then: a else:
62 | (if: a < b then: b else: -1)) * (a + 1))
63 | # output: 16
64 |
65 |
66 | # exercise 1.2:
67 | # ------------
68 | print ((5 + 4 + (2 - (3 - (6 + (1 / 3))))) / (3 * (6 - 2) * (2 - 7)))
69 | # output: -0.239
70 |
71 |
72 | # exercise 1.3:
73 | # ------------
74 | def: square is: [x] => (x * x)
75 | def: twoLargest is: [x y z] => (
76 | if: x < y then: (
77 | if: x < z then: [y z] else: [x y]
78 | ) else: (
79 | if: y < z then: [x z] else: [x y]
80 | )
81 | )
82 | def: sumOfLargestSquares is: [x y z] => {
83 | def: a is: twoLargest [x y z]
84 | square a.0 + square a.1
85 | }
86 |
87 | print sumOfLargestSquares [1 2 3]
88 | # output: 13
89 | print sumOfLargestSquares [3 1 2]
90 | # output: 13
91 | print sumOfLargestSquares [2 2 2]
92 | # output: 8
93 |
94 |
95 | # exercise 1.4:
96 | # ------------
97 | # (define (a-plus-abs-b a b)
98 | # ((if (> b 0) + -) a b))
99 | def: aPlusAbsB is: [a b] => ((if: (b > 0) then: (+) else: (-)) (a, b))
100 | print aPlusAbsB (2, 3)
101 | # output: 5
102 | print aPlusAbsB (2, -3)
103 | # output: 5
104 |
105 |
106 | # exercise 1.5:
107 | # ------------
108 | # (define (p) (p))
109 | # (define (test x y)
110 | # (if (= x 0)
111 | # 0
112 | # y))
113 | # (test 0 (p))
114 |
115 | # applicative order will stack overflow trying to recursively evaluate (p).
116 | # normal order will safely return 0.
--------------------------------------------------------------------------------
/test/sicp01.lark:
--------------------------------------------------------------------------------
1 | # 1.1.1
2 | # -----
3 |
4 | # 486
5 | print 486
6 | # output: 486
7 |
8 | # (+ 137 349)
9 | print (137 + 349)
10 | # output: 486
11 |
12 | # (- 1000 334)
13 | print (1000 - 334)
14 | # output: 666
15 |
16 | # (* 5 99)
17 | print (5 * 99)
18 | # output: 495
19 |
20 | # (/ 10 5)
21 | print (10 / 5)
22 | # output: 2
23 |
24 | # (+ 2.7 10)
25 | print (2.7 + 10)
26 | # output: 12.7
27 |
28 | # (+ 21 35 12 7)
29 | print (21 + 35 + 12 + 7)
30 | # output: 75
31 |
32 | # (* 25 4 12)
33 | print (25 * 4 * 12)
34 | # output: 1200
35 |
36 | # (+ (* 3 5) (- 10 6))
37 | print ((3 * 5) + (10 - 6))
38 | # output: 19
39 |
40 | # (+ (* 3 (+ (* 2 4) (+ 3 5))) (+ (- 10 7) 6))
41 | print ((3 * (2 * 4 + 3 + 5)) + (10 - 7 + 6))
42 | # output: 57
43 |
44 | # 1.1.2
45 | # -----
46 |
47 | # (define size 2)
48 | global: size is: 2
49 |
50 | # size
51 | print size
52 | # output: 2
53 |
54 | # (* 5 size)
55 | print (5 * size)
56 | # output: 10
57 |
58 | # (define pi 3.14159)
59 | global: pi is: 3.14159
60 |
61 | # (define radius 10)
62 | global: radius is: 10
63 |
64 | # (* pi (* radius radius))
65 | print (pi * radius * radius)
66 | # output: 314.159
67 |
68 | # (define circumference (* 2 pi radius))
69 | global: circumference is: 2 * pi * radius
70 | # circumference
71 | print circumference
72 | # output: 62.832
73 |
74 | # 1.1.4
75 | # -----
76 |
77 | # (define (square x) (* x x))
78 | global: square is: x => (x * x)
79 |
80 | # (square 21)
81 | print square 21
82 | # output: 441
83 |
84 | # (square (+ 2 5))
85 | print square (2 + 5)
86 | # output: 49
87 |
88 | # (square (square 3))
89 | print square square 3
90 | # output: 81
91 |
92 | # (define (sum-of-squares x y)
93 | # (+ (square x) (square y)))
94 | global: sumOfSquares is: [x y] => (square x + square y)
95 |
96 | # (sum-of-squares 3 4)
97 | print sumOfSquares (3, 4)
98 | # output: 25
99 |
100 | # (define (f a)
101 | # (sum-of-squares (+ a 1) (* a 2)))
102 | global: f is: a => sumOfSquares (a + 1, a * 2)
103 |
104 | # (f 5)
105 | print f 5
106 | # output: 136
107 |
108 | # 1.1.6
109 | # -----
110 |
111 | # (define (abs x)
112 | # (cond ((> x 0) x)
113 | # ((= x 0) 0)
114 | # ((< x 0) (- x))))
115 | global: abs is: x => (
116 | if: x > 0 then: x else: neg x)
117 | print abs 123
118 | # output: 123
119 | print abs -123
120 | # output: 123
121 | print abs 0
122 | # output: 0
123 |
--------------------------------------------------------------------------------