├── .github
└── workflows
│ └── run-tests.yml
├── .gitignore
├── README.md
├── UNLICENSE
├── pom.xml
└── src
└── main
├── antlr4
└── tl
│ └── antlr4
│ └── TL.g4
├── java
└── tl
│ └── antlr4
│ ├── EvalException.java
│ ├── EvalVisitor.java
│ ├── Function.java
│ ├── Main.java
│ ├── ReturnValue.java
│ ├── Scope.java
│ └── TLValue.java
└── tl
└── test.tl
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 | on: [push, pull_request]
3 | jobs:
4 | run-tests:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - uses: actions/checkout@v2
8 | - uses: actions/setup-java@v1
9 | with:
10 | java-version: 1.7
11 | - run: mvn -q antlr4:antlr4 install exec:java
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | target/
3 | *.iml
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tiny Language for ANTLR 4
2 |
3 | In [some blog posts](http://web.archive.org/web/20140519034030/http://bkiers.blogspot.nl/2011/03/creating-your-own-programming-language.html)
4 | I wrote a while ago, I demonstrated how to create a small dynamically typed programming language
5 | called *Tiny Language* using [ANTLR](http://www.antlr.org/) 3.
6 |
7 | However, ANTLR 4 is now the leaner (and meaner) version of the popular parser generator. Since the
8 | changes from v3 to v4 are significant, making *Tiny Language* work using ANTLR 4 is non trivial.
9 | Most notably, ANTLR 4 does not have any tree rewriting anymore. The new version generates listeners
10 | (and/or visitors) that can be used to *walk* the plain parse tree.
11 |
12 | ## Get up and running
13 |
14 | First, clone this repository:
15 |
16 | ```bash
17 | git clone https://github.com/bkiers/tiny-language-antlr4.git
18 | cd tiny-language-antlr4
19 | ```
20 |
21 | Then generate the lexer, parser and visitor classes using the antlr4 Maven plugin:
22 |
23 | ```bash
24 | mvn antlr4:antlr4
25 | ```
26 |
27 | Compile all classes:
28 |
29 | ```bash
30 | mvn install
31 | ```
32 |
33 | and finally run the `Main` class (which executes the [`test.tl`](src/main/tl/test.tl) file):
34 |
35 | ```bash
36 | mvn -q exec:java
37 | ```
38 |
39 | Or, combine all the previous commands in a single liner:
40 |
41 | ```bash
42 | mvn -q antlr4:antlr4 install exec:java
43 | ```
44 |
45 | which should print:
46 |
47 | ```
48 | All Assertions have passed.
49 | ```
50 |
51 | ## No Maven?
52 |
53 | If you're unfamiliar with Maven, and are reluctant to install it, here's how
54 | to perform all the steps from the (*nix) command line (assuming you're in the
55 | root folder of the project `tiny-language-antlr4`):
56 |
57 | Download ANTLR 4:
58 |
59 | ```bash
60 | wget http://www.antlr.org/download/antlr-4.7.1-complete.jar
61 | ```
62 |
63 | Generate the lexer, parser and visitor classes and move them to the other
64 | `.java` project sources:
65 |
66 | ```bash
67 | java -cp antlr-4.7.1-complete.jar \
68 | org.antlr.v4.Tool src/main/antlr4/tl/antlr4/TL.g4 \
69 | -package tl.antlr4 \
70 | -visitor
71 |
72 | mv src/main/antlr4/tl/antlr4/*.java src/main/java/tl/antlr4
73 | ```
74 |
75 | Compile all `.java` source files:
76 |
77 | ```bash
78 | javac -cp antlr-4.7.1-complete.jar src/main/java/tl/antlr4/*.java
79 | ```
80 |
81 | Run the `Main` class:
82 |
83 | ```bash
84 | java -cp src/main/java:antlr-4.7.1-complete.jar tl.antlr4.Main
85 | ```
86 |
87 | ## (Un)license
88 |
89 | [The Unlicense](http://unlicense.org)
90 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 | 4.0.0
6 | nl.big-o
7 | tiny-language-antlr4
8 | jar
9 | 1.0-SNAPSHOT
10 | tiny-language-antlr4
11 |
12 |
13 | 4.7.1
14 |
15 |
16 |
17 |
18 |
19 | org.antlr
20 | antlr4-runtime
21 | ${antlr4.version}
22 | compile
23 |
24 |
25 |
26 | org.antlr
27 | antlr4-maven-plugin
28 | ${antlr4.version}
29 | compile
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | org.apache.maven.plugins
39 | maven-compiler-plugin
40 | 3.1
41 |
42 | 1.7
43 | 1.7
44 |
45 |
46 |
47 |
48 | org.antlr
49 | antlr4-maven-plugin
50 | ${antlr4.version}
51 |
52 |
53 | -visitor
54 |
55 |
56 |
57 |
58 |
59 | antlr4
60 |
61 |
62 |
63 |
64 |
65 | org.codehaus.mojo
66 | exec-maven-plugin
67 | 1.2.1
68 |
69 |
70 |
71 | java
72 |
73 |
74 |
75 |
76 | tl.antlr4.Main
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/main/antlr4/tl/antlr4/TL.g4:
--------------------------------------------------------------------------------
1 | grammar TL;
2 |
3 | parse
4 | : block EOF
5 | ;
6 |
7 | block
8 | : ( statement | functionDecl )* ( Return expression ';' )?
9 | ;
10 |
11 | statement
12 | : assignment ';'
13 | | functionCall ';'
14 | | ifStatement
15 | | forStatement
16 | | whileStatement
17 | ;
18 |
19 | assignment
20 | : Identifier indexes? '=' expression
21 | ;
22 |
23 | functionCall
24 | : Identifier '(' exprList? ')' #identifierFunctionCall
25 | | Println '(' expression? ')' #printlnFunctionCall
26 | | Print '(' expression ')' #printFunctionCall
27 | | Assert '(' expression ')' #assertFunctionCall
28 | | Size '(' expression ')' #sizeFunctionCall
29 | ;
30 |
31 | ifStatement
32 | : ifStat elseIfStat* elseStat? End
33 | ;
34 |
35 | ifStat
36 | : If expression Do block
37 | ;
38 |
39 | elseIfStat
40 | : Else If expression Do block
41 | ;
42 |
43 | elseStat
44 | : Else Do block
45 | ;
46 |
47 | functionDecl
48 | : Def Identifier '(' idList? ')' block End
49 | ;
50 |
51 | forStatement
52 | : For Identifier '=' expression To expression Do block End
53 | ;
54 |
55 | whileStatement
56 | : While expression Do block End
57 | ;
58 |
59 | idList
60 | : Identifier ( ',' Identifier )*
61 | ;
62 |
63 | exprList
64 | : expression ( ',' expression )*
65 | ;
66 |
67 | expression
68 | : '-' expression #unaryMinusExpression
69 | | '!' expression #notExpression
70 | | expression '^' expression #powerExpression
71 | | expression op=( '*' | '/' | '%' ) expression #multExpression
72 | | expression op=( '+' | '-' ) expression #addExpression
73 | | expression op=( '>=' | '<=' | '>' | '<' ) expression #compExpression
74 | | expression op=( '==' | '!=' ) expression #eqExpression
75 | | expression '&&' expression #andExpression
76 | | expression '||' expression #orExpression
77 | | expression '?' expression ':' expression #ternaryExpression
78 | | expression In expression #inExpression
79 | | Number #numberExpression
80 | | Bool #boolExpression
81 | | Null #nullExpression
82 | | functionCall indexes? #functionCallExpression
83 | | list indexes? #listExpression
84 | | Identifier indexes? #identifierExpression
85 | | String indexes? #stringExpression
86 | | '(' expression ')' indexes? #expressionExpression
87 | | Input '(' String? ')' #inputExpression
88 | ;
89 |
90 | list
91 | : '[' exprList? ']'
92 | ;
93 |
94 | indexes
95 | : ( '[' expression ']' )+
96 | ;
97 |
98 | Println : 'println';
99 | Print : 'print';
100 | Input : 'input';
101 | Assert : 'assert';
102 | Size : 'size';
103 | Def : 'def';
104 | If : 'if';
105 | Else : 'else';
106 | Return : 'return';
107 | For : 'for';
108 | While : 'while';
109 | To : 'to';
110 | Do : 'do';
111 | End : 'end';
112 | In : 'in';
113 | Null : 'null';
114 |
115 | Or : '||';
116 | And : '&&';
117 | Equals : '==';
118 | NEquals : '!=';
119 | GTEquals : '>=';
120 | LTEquals : '<=';
121 | Pow : '^';
122 | Excl : '!';
123 | GT : '>';
124 | LT : '<';
125 | Add : '+';
126 | Subtract : '-';
127 | Multiply : '*';
128 | Divide : '/';
129 | Modulus : '%';
130 | OBrace : '{';
131 | CBrace : '}';
132 | OBracket : '[';
133 | CBracket : ']';
134 | OParen : '(';
135 | CParen : ')';
136 | SColon : ';';
137 | Assign : '=';
138 | Comma : ',';
139 | QMark : '?';
140 | Colon : ':';
141 |
142 | Bool
143 | : 'true'
144 | | 'false'
145 | ;
146 |
147 | Number
148 | : Int ( '.' Digit* )?
149 | ;
150 |
151 | Identifier
152 | : [a-zA-Z_] [a-zA-Z_0-9]*
153 | ;
154 |
155 | String
156 | : ["] ( ~["\r\n\\] | '\\' ~[\r\n] )* ["]
157 | | ['] ( ~['\r\n\\] | '\\' ~[\r\n] )* [']
158 | ;
159 |
160 | Comment
161 | : ( '//' ~[\r\n]* | '/*' .*? '*/' ) -> skip
162 | ;
163 |
164 | Space
165 | : [ \t\r\n\u000C] -> skip
166 | ;
167 |
168 | fragment Int
169 | : [1-9] Digit*
170 | | '0'
171 | ;
172 |
173 | fragment Digit
174 | : [0-9]
175 | ;
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/EvalException.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | import org.antlr.v4.runtime.ParserRuleContext;
4 |
5 | public class EvalException extends RuntimeException {
6 | public EvalException(ParserRuleContext ctx) {
7 | this("Illegal expression: " + ctx.getText(), ctx);
8 | }
9 |
10 | public EvalException(String msg, ParserRuleContext ctx) {
11 | super(msg + " line:" + ctx.start.getLine());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/EvalVisitor.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.IOException;
5 | import java.io.InputStreamReader;
6 | import java.nio.file.Files;
7 | import java.nio.file.Paths;
8 | import java.util.ArrayList;
9 | import java.util.HashMap;
10 | import java.util.List;
11 | import java.util.Map;
12 |
13 | import org.antlr.v4.runtime.ParserRuleContext;
14 | import org.antlr.v4.runtime.tree.ParseTree;
15 | import org.antlr.v4.runtime.tree.TerminalNode;
16 |
17 | import tl.antlr4.TLParser.*;
18 |
19 | public class EvalVisitor extends TLBaseVisitor {
20 | private static ReturnValue returnValue = new ReturnValue();
21 | private Scope scope;
22 | private Map functions;
23 |
24 | EvalVisitor(Scope scope, Map functions) {
25 | this.scope = scope;
26 | this.functions = new HashMap<>(functions);
27 | }
28 |
29 | // functionDecl
30 | @Override
31 | public TLValue visitFunctionDecl(FunctionDeclContext ctx) {
32 | List params = ctx.idList() != null ? ctx.idList().Identifier() : new ArrayList();
33 | ParseTree block = ctx.block();
34 | String id = ctx.Identifier().getText() + params.size();
35 | // TODO: throw exception if function is already defined?
36 | functions.put(id, new Function(scope, params, block));
37 | return TLValue.VOID;
38 | }
39 |
40 | // list: '[' exprList? ']'
41 | @Override
42 | public TLValue visitList(ListContext ctx) {
43 | List list = new ArrayList<>();
44 | if (ctx.exprList() != null) {
45 | for(ExpressionContext ex: ctx.exprList().expression()) {
46 | list.add(this.visit(ex));
47 | }
48 | }
49 | return new TLValue(list);
50 | }
51 |
52 |
53 | // '-' expression #unaryMinusExpression
54 | @Override
55 | public TLValue visitUnaryMinusExpression(UnaryMinusExpressionContext ctx) {
56 | TLValue v = this.visit(ctx.expression());
57 | if (!v.isNumber()) {
58 | throw new EvalException(ctx);
59 | }
60 | return new TLValue(-1 * v.asDouble());
61 | }
62 |
63 | // '!' expression #notExpression
64 | @Override
65 | public TLValue visitNotExpression(NotExpressionContext ctx) {
66 | TLValue v = this.visit(ctx.expression());
67 | if(!v.isBoolean()) {
68 | throw new EvalException(ctx);
69 | }
70 | return new TLValue(!v.asBoolean());
71 | }
72 |
73 | // expression '^' expression #powerExpression
74 | @Override
75 | public TLValue visitPowerExpression(PowerExpressionContext ctx) {
76 | TLValue lhs = this.visit(ctx.expression(0));
77 | TLValue rhs = this.visit(ctx.expression(1));
78 | if (lhs.isNumber() && rhs.isNumber()) {
79 | return new TLValue(Math.pow(lhs.asDouble(), rhs.asDouble()));
80 | }
81 | throw new EvalException(ctx);
82 | }
83 |
84 | // expression op=( '*' | '/' | '%' ) expression #multExpression
85 | @Override
86 | public TLValue visitMultExpression(MultExpressionContext ctx) {
87 | switch (ctx.op.getType()) {
88 | case TLLexer.Multiply:
89 | return multiply(ctx);
90 | case TLLexer.Divide:
91 | return divide(ctx);
92 | case TLLexer.Modulus:
93 | return modulus(ctx);
94 | default:
95 | throw new RuntimeException("unknown operator type: " + ctx.op.getType());
96 | }
97 | }
98 |
99 | // expression op=( '+' | '-' ) expression #addExpression
100 | @Override
101 | public TLValue visitAddExpression(AddExpressionContext ctx) {
102 | switch (ctx.op.getType()) {
103 | case TLLexer.Add:
104 | return add(ctx);
105 | case TLLexer.Subtract:
106 | return subtract(ctx);
107 | default:
108 | throw new RuntimeException("unknown operator type: " + ctx.op.getType());
109 | }
110 | }
111 |
112 | // expression op=( '>=' | '<=' | '>' | '<' ) expression #compExpression
113 | @Override
114 | public TLValue visitCompExpression(CompExpressionContext ctx) {
115 | switch (ctx.op.getType()) {
116 | case TLLexer.LT:
117 | return lt(ctx);
118 | case TLLexer.LTEquals:
119 | return ltEq(ctx);
120 | case TLLexer.GT:
121 | return gt(ctx);
122 | case TLLexer.GTEquals:
123 | return gtEq(ctx);
124 | default:
125 | throw new RuntimeException("unknown operator type: " + ctx.op.getType());
126 | }
127 | }
128 |
129 | // expression op=( '==' | '!=' ) expression #eqExpression
130 | @Override
131 | public TLValue visitEqExpression(EqExpressionContext ctx) {
132 | switch (ctx.op.getType()) {
133 | case TLLexer.Equals:
134 | return eq(ctx);
135 | case TLLexer.NEquals:
136 | return nEq(ctx);
137 | default:
138 | throw new RuntimeException("unknown operator type: " + ctx.op.getType());
139 | }
140 | }
141 |
142 | public TLValue multiply(MultExpressionContext ctx) {
143 | TLValue lhs = this.visit(ctx.expression(0));
144 | TLValue rhs = this.visit(ctx.expression(1));
145 | if(lhs == null || rhs == null) {
146 | System.err.println("lhs "+ lhs+ " rhs "+rhs);
147 | throw new EvalException(ctx);
148 | }
149 |
150 | // number * number
151 | if(lhs.isNumber() && rhs.isNumber()) {
152 | return new TLValue(lhs.asDouble() * rhs.asDouble());
153 | }
154 |
155 | // string * number
156 | if(lhs.isString() && rhs.isNumber()) {
157 | StringBuilder str = new StringBuilder();
158 | int stop = rhs.asDouble().intValue();
159 | for(int i = 0; i < stop; i++) {
160 | str.append(lhs.asString());
161 | }
162 | return new TLValue(str.toString());
163 | }
164 |
165 | // list * number
166 | if(lhs.isList() && rhs.isNumber()) {
167 | List total = new ArrayList<>();
168 | int stop = rhs.asDouble().intValue();
169 | for(int i = 0; i < stop; i++) {
170 | total.addAll(lhs.asList());
171 | }
172 | return new TLValue(total);
173 | }
174 |
175 | throw new EvalException(ctx);
176 | }
177 |
178 | private TLValue divide(MultExpressionContext ctx) {
179 | TLValue lhs = this.visit(ctx.expression(0));
180 | TLValue rhs = this.visit(ctx.expression(1));
181 | if (lhs.isNumber() && rhs.isNumber()) {
182 | return new TLValue(lhs.asDouble() / rhs.asDouble());
183 | }
184 | throw new EvalException(ctx);
185 | }
186 |
187 | private TLValue modulus(MultExpressionContext ctx) {
188 | TLValue lhs = this.visit(ctx.expression(0));
189 | TLValue rhs = this.visit(ctx.expression(1));
190 | if (lhs.isNumber() && rhs.isNumber()) {
191 | return new TLValue(lhs.asDouble() % rhs.asDouble());
192 | }
193 | throw new EvalException(ctx);
194 | }
195 |
196 | private TLValue add(AddExpressionContext ctx) {
197 | TLValue lhs = this.visit(ctx.expression(0));
198 | TLValue rhs = this.visit(ctx.expression(1));
199 |
200 | if(lhs == null || rhs == null) {
201 | throw new EvalException(ctx);
202 | }
203 |
204 | // number + number
205 | if(lhs.isNumber() && rhs.isNumber()) {
206 | return new TLValue(lhs.asDouble() + rhs.asDouble());
207 | }
208 |
209 | // list + any
210 | if(lhs.isList()) {
211 | List list = lhs.asList();
212 | list.add(rhs);
213 | return new TLValue(list);
214 | }
215 |
216 | // string + any
217 | if(lhs.isString()) {
218 | return new TLValue(lhs.asString() + "" + rhs.toString());
219 | }
220 |
221 | // any + string
222 | if(rhs.isString()) {
223 | return new TLValue(lhs.toString() + "" + rhs.asString());
224 | }
225 |
226 | return new TLValue(lhs.toString() + rhs.toString());
227 | }
228 |
229 | private TLValue subtract(AddExpressionContext ctx) {
230 | TLValue lhs = this.visit(ctx.expression(0));
231 | TLValue rhs = this.visit(ctx.expression(1));
232 | if (lhs.isNumber() && rhs.isNumber()) {
233 | return new TLValue(lhs.asDouble() - rhs.asDouble());
234 | }
235 | if (lhs.isList()) {
236 | List list = lhs.asList();
237 | list.remove(rhs);
238 | return new TLValue(list);
239 | }
240 | throw new EvalException(ctx);
241 | }
242 |
243 | private TLValue gtEq(CompExpressionContext ctx) {
244 | TLValue lhs = this.visit(ctx.expression(0));
245 | TLValue rhs = this.visit(ctx.expression(1));
246 | if (lhs.isNumber() && rhs.isNumber()) {
247 | return new TLValue(lhs.asDouble() >= rhs.asDouble());
248 | }
249 | if(lhs.isString() && rhs.isString()) {
250 | return new TLValue(lhs.asString().compareTo(rhs.asString()) >= 0);
251 | }
252 | throw new EvalException(ctx);
253 | }
254 |
255 | private TLValue ltEq(CompExpressionContext ctx) {
256 | TLValue lhs = this.visit(ctx.expression(0));
257 | TLValue rhs = this.visit(ctx.expression(1));
258 | if (lhs.isNumber() && rhs.isNumber()) {
259 | return new TLValue(lhs.asDouble() <= rhs.asDouble());
260 | }
261 | if(lhs.isString() && rhs.isString()) {
262 | return new TLValue(lhs.asString().compareTo(rhs.asString()) <= 0);
263 | }
264 | throw new EvalException(ctx);
265 | }
266 |
267 | private TLValue gt(CompExpressionContext ctx) {
268 | TLValue lhs = this.visit(ctx.expression(0));
269 | TLValue rhs = this.visit(ctx.expression(1));
270 | if (lhs.isNumber() && rhs.isNumber()) {
271 | return new TLValue(lhs.asDouble() > rhs.asDouble());
272 | }
273 | if(lhs.isString() && rhs.isString()) {
274 | return new TLValue(lhs.asString().compareTo(rhs.asString()) > 0);
275 | }
276 | throw new EvalException(ctx);
277 | }
278 |
279 | private TLValue lt(CompExpressionContext ctx) {
280 | TLValue lhs = this.visit(ctx.expression(0));
281 | TLValue rhs = this.visit(ctx.expression(1));
282 | if (lhs.isNumber() && rhs.isNumber()) {
283 | return new TLValue(lhs.asDouble() < rhs.asDouble());
284 | }
285 | if(lhs.isString() && rhs.isString()) {
286 | return new TLValue(lhs.asString().compareTo(rhs.asString()) < 0);
287 | }
288 | throw new EvalException(ctx);
289 | }
290 |
291 | private TLValue eq(EqExpressionContext ctx) {
292 | TLValue lhs = this.visit(ctx.expression(0));
293 | TLValue rhs = this.visit(ctx.expression(1));
294 | if (lhs == null) {
295 | throw new EvalException(ctx);
296 | }
297 | return new TLValue(lhs.equals(rhs));
298 | }
299 |
300 | private TLValue nEq(EqExpressionContext ctx) {
301 | TLValue lhs = this.visit(ctx.expression(0));
302 | TLValue rhs = this.visit(ctx.expression(1));
303 | return new TLValue(!lhs.equals(rhs));
304 | }
305 |
306 | // expression '&&' expression #andExpression
307 | @Override
308 | public TLValue visitAndExpression(AndExpressionContext ctx) {
309 | TLValue lhs = this.visit(ctx.expression(0));
310 | TLValue rhs = this.visit(ctx.expression(1));
311 |
312 | if(!lhs.isBoolean() || !rhs.isBoolean()) {
313 | throw new EvalException(ctx);
314 | }
315 | return new TLValue(lhs.asBoolean() && rhs.asBoolean());
316 | }
317 |
318 | // expression '||' expression #orExpression
319 | @Override
320 | public TLValue visitOrExpression(OrExpressionContext ctx) {
321 | TLValue lhs = this.visit(ctx.expression(0));
322 | TLValue rhs = this.visit(ctx.expression(1));
323 |
324 | if(!lhs.isBoolean() || !rhs.isBoolean()) {
325 | throw new EvalException(ctx);
326 | }
327 | return new TLValue(lhs.asBoolean() || rhs.asBoolean());
328 | }
329 |
330 | // expression '?' expression ':' expression #ternaryExpression
331 | @Override
332 | public TLValue visitTernaryExpression(TernaryExpressionContext ctx) {
333 | TLValue condition = this.visit(ctx.expression(0));
334 | if (condition.asBoolean()) {
335 | return this.visit(ctx.expression(1));
336 | } else {
337 | return this.visit(ctx.expression(2));
338 | }
339 | }
340 |
341 | // expression In expression #inExpression
342 | @Override
343 | public TLValue visitInExpression(InExpressionContext ctx) {
344 | TLValue lhs = this.visit(ctx.expression(0));
345 | TLValue rhs = this.visit(ctx.expression(1));
346 |
347 | if (rhs.isList()) {
348 | for(TLValue val: rhs.asList()) {
349 | if (val.equals(lhs)) {
350 | return new TLValue(true);
351 | }
352 | }
353 | return new TLValue(false);
354 | }
355 | throw new EvalException(ctx);
356 | }
357 |
358 | // Number #numberExpression
359 | @Override
360 | public TLValue visitNumberExpression(NumberExpressionContext ctx) {
361 | return new TLValue(Double.valueOf(ctx.getText()));
362 | }
363 |
364 | // Bool #boolExpression
365 | @Override
366 | public TLValue visitBoolExpression(BoolExpressionContext ctx) {
367 | return new TLValue(Boolean.valueOf(ctx.getText()));
368 | }
369 |
370 | // Null #nullExpression
371 | @Override
372 | public TLValue visitNullExpression(NullExpressionContext ctx) {
373 | return TLValue.NULL;
374 | }
375 |
376 | private TLValue resolveIndexes(TLValue val, List indexes) {
377 | for (ExpressionContext ec: indexes) {
378 | TLValue idx = this.visit(ec);
379 | if (!idx.isNumber() || (!val.isList() && !val.isString()) ) {
380 | throw new EvalException("Problem resolving indexes on "+val+" at "+idx, ec);
381 | }
382 | int i = idx.asDouble().intValue();
383 | if (val.isString()) {
384 | val = new TLValue(val.asString().substring(i, i+1));
385 | } else {
386 | val = val.asList().get(i);
387 | }
388 | }
389 | return val;
390 | }
391 |
392 | private void setAtIndex(ParserRuleContext ctx, List indexes, TLValue val, TLValue newVal) {
393 | if (!val.isList()) {
394 | throw new EvalException(ctx);
395 | }
396 | for (int i = 0; i < indexes.size() - 1; i++) {
397 | TLValue idx = this.visit(indexes.get(i));
398 | if (!idx.isNumber()) {
399 | throw new EvalException(ctx);
400 | }
401 | val = val.asList().get(idx.asDouble().intValue());
402 | }
403 | TLValue idx = this.visit(indexes.get(indexes.size() - 1));
404 | if (!idx.isNumber()) {
405 | throw new EvalException(ctx);
406 | }
407 | val.asList().set(idx.asDouble().intValue(), newVal);
408 | }
409 |
410 | // functionCall indexes? #functionCallExpression
411 | @Override
412 | public TLValue visitFunctionCallExpression(FunctionCallExpressionContext ctx) {
413 | TLValue val = this.visit(ctx.functionCall());
414 | if (ctx.indexes() != null) {
415 | List exps = ctx.indexes().expression();
416 | val = resolveIndexes(val, exps);
417 | }
418 | return val;
419 | }
420 |
421 | // list indexes? #listExpression
422 | @Override
423 | public TLValue visitListExpression(ListExpressionContext ctx) {
424 | TLValue val = this.visit(ctx.list());
425 | if (ctx.indexes() != null) {
426 | List exps = ctx.indexes().expression();
427 | val = resolveIndexes(val, exps);
428 | }
429 | return val;
430 | }
431 |
432 | // Identifier indexes? #identifierExpression
433 | @Override
434 | public TLValue visitIdentifierExpression(IdentifierExpressionContext ctx) {
435 | String id = ctx.Identifier().getText();
436 | TLValue val = scope.resolve(id);
437 |
438 | if (ctx.indexes() != null) {
439 | List exps = ctx.indexes().expression();
440 | val = resolveIndexes(val, exps);
441 | }
442 | return val;
443 | }
444 |
445 | // String indexes? #stringExpression
446 | @Override
447 | public TLValue visitStringExpression(StringExpressionContext ctx) {
448 | String text = ctx.getText();
449 | text = text.substring(1, text.length() - 1).replaceAll("\\\\(.)", "$1");
450 | TLValue val = new TLValue(text);
451 | if (ctx.indexes() != null) {
452 | List exps = ctx.indexes().expression();
453 | val = resolveIndexes(val, exps);
454 | }
455 | return val;
456 | }
457 |
458 | // '(' expression ')' indexes? #expressionExpression
459 | @Override
460 | public TLValue visitExpressionExpression(ExpressionExpressionContext ctx) {
461 | TLValue val = this.visit(ctx.expression());
462 | if (ctx.indexes() != null) {
463 | List exps = ctx.indexes().expression();
464 | val = resolveIndexes(val, exps);
465 | }
466 | return val;
467 | }
468 |
469 | // Input '(' String? ')' #inputExpression
470 | @Override
471 | public TLValue visitInputExpression(InputExpressionContext ctx) {
472 | TerminalNode inputString = ctx.String();
473 | try {
474 | if (inputString != null) {
475 | String text = inputString.getText();
476 | text = text.substring(1, text.length() - 1).replaceAll("\\\\(.)", "$1");
477 | return new TLValue(new String(Files.readAllBytes(Paths.get(text))));
478 | } else {
479 | BufferedReader buffer = new BufferedReader(new InputStreamReader(System.in));
480 | return new TLValue(buffer.readLine());
481 | }
482 | } catch (IOException e) {
483 | throw new RuntimeException(e);
484 | }
485 | }
486 |
487 |
488 | // assignment
489 | // : Identifier indexes? '=' expression
490 | // ;
491 | @Override
492 | public TLValue visitAssignment(AssignmentContext ctx) {
493 | TLValue newVal = this.visit(ctx.expression());
494 | if (ctx.indexes() != null) {
495 | TLValue val = scope.resolve(ctx.Identifier().getText());
496 | List exps = ctx.indexes().expression();
497 | setAtIndex(ctx, exps, val, newVal);
498 | } else {
499 | String id = ctx.Identifier().getText();
500 | scope.assign(id, newVal);
501 | }
502 | return TLValue.VOID;
503 | }
504 |
505 | // Identifier '(' exprList? ')' #identifierFunctionCall
506 | @Override
507 | public TLValue visitIdentifierFunctionCall(IdentifierFunctionCallContext ctx) {
508 | List params = ctx.exprList() != null ? ctx.exprList().expression() : new ArrayList();
509 | String id = ctx.Identifier().getText() + params.size();
510 | Function function;
511 | if ((function = functions.get(id)) != null) {
512 | List args = new ArrayList<>(params.size());
513 | for (ExpressionContext param: params) {
514 | args.add(this.visit(param));
515 | }
516 | return function.invoke(args, functions);
517 | }
518 | throw new EvalException(ctx);
519 | }
520 |
521 | // Println '(' expression? ')' #printlnFunctionCall
522 | @Override
523 | public TLValue visitPrintlnFunctionCall(PrintlnFunctionCallContext ctx) {
524 | if (ctx.expression() != null) {
525 | System.out.println(this.visit(ctx.expression()));
526 | } else {
527 | System.out.println();
528 | }
529 | return TLValue.VOID;
530 | }
531 |
532 | // Print '(' expression ')' #printFunctionCall
533 | @Override
534 | public TLValue visitPrintFunctionCall(PrintFunctionCallContext ctx) {
535 | System.out.print(this.visit(ctx.expression()));
536 | return TLValue.VOID;
537 | }
538 |
539 | // Assert '(' expression ')' #assertFunctionCall
540 | @Override
541 | public TLValue visitAssertFunctionCall(AssertFunctionCallContext ctx) {
542 | TLValue value = this.visit(ctx.expression());
543 |
544 | if(!value.isBoolean()) {
545 | throw new EvalException(ctx);
546 | }
547 |
548 | if(!value.asBoolean()) {
549 | throw new AssertionError("Failed Assertion "+ctx.expression().getText()+" line:"+ctx.start.getLine());
550 | }
551 |
552 | return TLValue.VOID;
553 | }
554 |
555 | // Size '(' expression ')' #sizeFunctionCall
556 | @Override
557 | public TLValue visitSizeFunctionCall(SizeFunctionCallContext ctx) {
558 | TLValue value = this.visit(ctx.expression());
559 |
560 | if(value.isString()) {
561 | return new TLValue(value.asString().length());
562 | }
563 |
564 | if(value.isList()) {
565 | return new TLValue(value.asList().size());
566 | }
567 |
568 | throw new EvalException(ctx);
569 | }
570 |
571 | // ifStatement
572 | // : ifStat elseIfStat* elseStat? End
573 | // ;
574 | //
575 | // ifStat
576 | // : If expression Do block
577 | // ;
578 | //
579 | // elseIfStat
580 | // : Else If expression Do block
581 | // ;
582 | //
583 | // elseStat
584 | // : Else Do block
585 | // ;
586 | @Override
587 | public TLValue visitIfStatement(IfStatementContext ctx) {
588 |
589 | // if ...
590 | if(this.visit(ctx.ifStat().expression()).asBoolean()) {
591 | return this.visit(ctx.ifStat().block());
592 | }
593 |
594 | // else if ...
595 | for(int i = 0; i < ctx.elseIfStat().size(); i++) {
596 | if(this.visit(ctx.elseIfStat(i).expression()).asBoolean()) {
597 | return this.visit(ctx.elseIfStat(i).block());
598 | }
599 | }
600 |
601 | // else ...
602 | if(ctx.elseStat() != null) {
603 | return this.visit(ctx.elseStat().block());
604 | }
605 |
606 | return TLValue.VOID;
607 | }
608 |
609 | // block
610 | // : (statement | functionDecl)* (Return expression)?
611 | // ;
612 | @Override
613 | public TLValue visitBlock(BlockContext ctx) {
614 |
615 | scope = new Scope(scope, false); // create new local scope
616 | for (FunctionDeclContext fdx: ctx.functionDecl()) {
617 | this.visit(fdx);
618 | }
619 | for (StatementContext sx: ctx.statement()) {
620 | this.visit(sx);
621 | }
622 | ExpressionContext ex;
623 | if ((ex = ctx.expression()) != null) {
624 | returnValue.value = this.visit(ex);
625 | scope = scope.parent();
626 | throw returnValue;
627 | }
628 | scope = scope.parent();
629 | return TLValue.VOID;
630 | }
631 |
632 | // forStatement
633 | // : For Identifier '=' expression To expression OBrace block CBrace
634 | // ;
635 | @Override
636 | public TLValue visitForStatement(ForStatementContext ctx) {
637 | int start = this.visit(ctx.expression(0)).asDouble().intValue();
638 | int stop = this.visit(ctx.expression(1)).asDouble().intValue();
639 | for(int i = start; i <= stop; i++) {
640 | scope.assign(ctx.Identifier().getText(), new TLValue(i));
641 | TLValue returnValue = this.visit(ctx.block());
642 | if(returnValue != TLValue.VOID) {
643 | return returnValue;
644 | }
645 | }
646 | return TLValue.VOID;
647 | }
648 |
649 | // whileStatement
650 | // : While expression OBrace block CBrace
651 | // ;
652 | @Override
653 | public TLValue visitWhileStatement(WhileStatementContext ctx) {
654 | while( this.visit(ctx.expression()).asBoolean() ) {
655 | TLValue returnValue = this.visit(ctx.block());
656 | if (returnValue != TLValue.VOID) {
657 | return returnValue;
658 | }
659 | }
660 | return TLValue.VOID;
661 | }
662 |
663 | }
664 |
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/Function.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 |
6 | import org.antlr.v4.runtime.tree.ParseTree;
7 | import org.antlr.v4.runtime.tree.TerminalNode;
8 |
9 | import tl.antlr4.TLParser.ExpressionContext;
10 |
11 | public class Function {
12 |
13 | private Scope parentScope;
14 | private List params;
15 | private ParseTree block;
16 |
17 | Function(Scope parentScope, List params, ParseTree block) {
18 | this.parentScope = parentScope;
19 | this.params = params;
20 | this.block = block;
21 | }
22 |
23 | public TLValue invoke(List args, Map functions) {
24 | if (args.size() != this.params.size()) {
25 | throw new RuntimeException("Illegal Function call");
26 | }
27 | Scope scopeNext = new Scope(parentScope, true); // create function scope
28 |
29 | for (int i = 0; i < this.params.size(); i++) {
30 | TLValue value = args.get(i);
31 | scopeNext.assignParam(this.params.get(i).getText(), value);
32 | }
33 | EvalVisitor evalVistorNext = new EvalVisitor(scopeNext,functions);
34 |
35 | TLValue ret = TLValue.VOID;
36 | try {
37 | evalVistorNext.visit(this.block);
38 | } catch (ReturnValue returnValue) {
39 | ret = returnValue.value;
40 | }
41 | return ret;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/Main.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | import java.util.Collections;
4 | import java.util.Map;
5 |
6 | import org.antlr.v4.runtime.CharStreams;
7 | import org.antlr.v4.runtime.CommonTokenStream;
8 | import org.antlr.v4.runtime.tree.ParseTree;
9 |
10 | public class Main {
11 | public static void main(String[] args) {
12 | try {
13 | TLLexer lexer = new TLLexer(CharStreams.fromFileName("src/main/tl/test.tl"));
14 | TLParser parser = new TLParser(new CommonTokenStream(lexer));
15 | parser.setBuildParseTree(true);
16 | ParseTree tree = parser.parse();
17 |
18 | Scope scope = new Scope();
19 | Map functions = Collections.emptyMap();
20 | EvalVisitor visitor = new EvalVisitor(scope, functions);
21 | visitor.visit(tree);
22 | } catch (Exception e) {
23 | if (e.getMessage() != null) {
24 | System.err.println(e.getMessage());
25 | } else {
26 | e.printStackTrace();
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/ReturnValue.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | public class ReturnValue extends RuntimeException {
4 | public TLValue value;
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/Scope.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | public class Scope {
7 |
8 | private Scope parent;
9 | private Map variables;
10 | private boolean isFunction;
11 |
12 | Scope() {
13 | // only for the global scope, the parent is null
14 | this(null, false);
15 | }
16 |
17 | Scope(Scope p, boolean function) {
18 | parent = p;
19 | variables = new HashMap<>();
20 | isFunction = function;
21 | }
22 |
23 | public void assignParam(String var, TLValue value) {
24 | variables.put(var, value);
25 | }
26 |
27 | public void assign(String var, TLValue value) {
28 | if(resolve(var, !isFunction) != null) {
29 | // There is already such a variable, re-assign it
30 | this.reAssign(var, value);
31 | }
32 | else {
33 | // A newly declared variable
34 | variables.put(var, value);
35 | }
36 | }
37 |
38 | private boolean isGlobalScope() {
39 | return parent == null;
40 | }
41 |
42 | public Scope parent() {
43 | return parent;
44 | }
45 |
46 | private void reAssign(String identifier, TLValue value) {
47 | if(variables.containsKey(identifier)) {
48 | // The variable is declared in this scope
49 | variables.put(identifier, value);
50 | }
51 | else if(parent != null) {
52 | // The variable was not declared in this scope, so let
53 | // the parent scope re-assign it
54 | parent.reAssign(identifier, value);
55 | }
56 | }
57 |
58 | public TLValue resolve(String var) {
59 | return resolve(var, true);
60 | }
61 |
62 | private TLValue resolve(String var, boolean checkParent) {
63 | TLValue value = variables.get(var);
64 | if(value != null) {
65 | // The variable resides in this scope
66 | return value;
67 | }
68 | else if(checkParent && !isGlobalScope()) {
69 | // Let the parent scope look for the variable
70 | return parent.resolve(var, !parent.isFunction);
71 | }
72 | else {
73 | // Unknown variable
74 | return null;
75 | }
76 | }
77 |
78 | @Override
79 | public String toString() {
80 | StringBuilder sb = new StringBuilder();
81 | for(Map.Entry var: variables.entrySet()) {
82 | sb.append(var.getKey()).append("->").append(var.getValue()).append(",");
83 | }
84 | return sb.toString();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/tl/antlr4/TLValue.java:
--------------------------------------------------------------------------------
1 | package tl.antlr4;
2 |
3 | import java.util.List;
4 |
5 | public class TLValue implements Comparable {
6 |
7 | public static final TLValue NULL = new TLValue();
8 | public static final TLValue VOID = new TLValue();
9 |
10 | private Object value;
11 |
12 | private TLValue() {
13 | // private constructor: only used for NULL and VOID
14 | value = new Object();
15 | }
16 |
17 | TLValue(Object v) {
18 | if(v == null) {
19 | throw new RuntimeException("v == null");
20 | }
21 | value = v;
22 | // only accept boolean, list, number or string types
23 | if(!(isBoolean() || isList() || isNumber() || isString())) {
24 | throw new RuntimeException("invalid data type: " + v + " (" + v.getClass() + ")");
25 | }
26 | }
27 |
28 | public Boolean asBoolean() {
29 | return (Boolean)value;
30 | }
31 |
32 | public Double asDouble() {
33 | return ((Number)value).doubleValue();
34 | }
35 |
36 | public Long asLong() {
37 | return ((Number)value).longValue();
38 | }
39 |
40 | @SuppressWarnings("unchecked")
41 | public List asList() {
42 | return (List)value;
43 | }
44 |
45 | public String asString() {
46 | return (String)value;
47 | }
48 |
49 | @Override
50 | public int compareTo(TLValue that) {
51 | if(this.isNumber() && that.isNumber()) {
52 | if(this.equals(that)) {
53 | return 0;
54 | }
55 | else {
56 | return this.asDouble().compareTo(that.asDouble());
57 | }
58 | }
59 | else if(this.isString() && that.isString()) {
60 | return this.asString().compareTo(that.asString());
61 | }
62 | else {
63 | throw new RuntimeException("illegal expression: can't compare `" + this + "` to `" + that + "`");
64 | }
65 | }
66 |
67 | @Override
68 | public boolean equals(Object o) {
69 | if(this == VOID || o == VOID) {
70 | throw new RuntimeException("can't use VOID: " + this + " ==/!= " + o);
71 | }
72 | if(this == o) {
73 | return true;
74 | }
75 | if(o == null || this.getClass() != o.getClass()) {
76 | return false;
77 | }
78 | TLValue that = (TLValue)o;
79 | if(this.isNumber() && that.isNumber()) {
80 | double diff = Math.abs(this.asDouble() - that.asDouble());
81 | return diff < 0.00000000001;
82 | }
83 | else {
84 | return this.value.equals(that.value);
85 | }
86 | }
87 |
88 | @Override
89 | public int hashCode() {
90 | return value.hashCode();
91 | }
92 |
93 | public boolean isBoolean() {
94 | return value instanceof Boolean;
95 | }
96 |
97 | public boolean isNumber() {
98 | return value instanceof Number;
99 | }
100 |
101 | public boolean isList() {
102 | return value instanceof List>;
103 | }
104 |
105 | public boolean isNull() {
106 | return this == NULL;
107 | }
108 |
109 | public boolean isVoid() {
110 | return this == VOID;
111 | }
112 |
113 | public boolean isString() {
114 | return value instanceof String;
115 | }
116 |
117 | @Override
118 | public String toString() {
119 | return isNull() ? "NULL" : isVoid() ? "VOID" : String.valueOf(value);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/main/tl/test.tl:
--------------------------------------------------------------------------------
1 | /*
2 | A script for testing Tiny Language syntax.
3 | */
4 |
5 | // boolean expressions
6 | assert(true || false);
7 | assert(!false);
8 | assert(true && true);
9 | assert(!true || !false);
10 | assert(true && (true || false));
11 |
12 | // relational expressions
13 | assert(1 < 2);
14 | assert(666 >= 666);
15 | assert(-5 > -6);
16 | assert(0 >= -1);
17 | assert('a' < 's');
18 | assert('sw' <= 'sw');
19 |
20 | // add
21 | assert(1 + 999 == 1000);
22 | assert([1] + 1 == [1,1]);
23 | assert(2 - -2 == 4);
24 | assert(-1 + 1 == 0);
25 | assert(1 - 50 == -49);
26 | assert([1,2,3,4,5] - 4 == [1,2,3,5]);
27 |
28 | // multiply
29 | assert(3 * 50 == 150);
30 | assert(4 / 2 == 2);
31 | assert(1 / 4 == 0.25);
32 | assert(999999 % 3 == 0);
33 | assert(-5 * -5 == 25);
34 | assert([1,2,3] * 2 == [1,2,3,1,2,3]);
35 | assert('ab'*3 == "ababab");
36 |
37 | // power
38 | assert(2^10 == 1024);
39 | assert(3^3 == 27);
40 | assert(4^3^2 == 262144); // power is right associative
41 | assert((4^3)^2 == 4096);
42 |
43 | // for- and while statements
44 | a = 0;
45 | for i=1 to 10 do
46 | a = a + i;
47 | end
48 | assert(a == (1+2+3+4+5+6+7+8+9+10));
49 |
50 | b = -10;
51 | c = 0;
52 | while b < 0 do
53 | c = c + b;
54 | b = b + 1;
55 | end
56 | assert(c == -(1+2+3+4+5+6+7+8+9+10));
57 |
58 | // if
59 | a = 123;
60 | if a > 200 do
61 | assert(false);
62 | end
63 |
64 | if a < 100 do
65 | assert(false);
66 | else if a > 124 do
67 | assert(false);
68 | else if a < 124 do
69 | assert(true);
70 | else do
71 | assert(false);
72 | end
73 |
74 | if false do
75 | assert(false);
76 | else do
77 | assert(true);
78 | end
79 |
80 | // functions
81 | def twice(n)
82 | temp = n + n;
83 | return temp;
84 | end
85 |
86 | def squared(n)
87 | return n*n;
88 | end
89 |
90 | def squaredAndTwice(n)
91 | return twice(squared(n));
92 | end
93 |
94 | def list()
95 | return [7,8,9];
96 | end
97 |
98 | assert(squared(666) == 666^2);
99 | assert(twice(squared(5)) == 50);
100 | assert(squaredAndTwice(10) == 200);
101 | assert(squared(squared(squared(2))) == ((2^2)^2)^2);
102 | assert(list() == [7,8,9]);
103 | assert(size(list()) == 3);
104 | assert(list()[1] == 8);
105 |
106 | // naive bubble sort
107 | def sort(list)
108 | while !sorted(list) do
109 | end
110 | end
111 | def sorted(list)
112 | n = size(list);
113 | for i=0 to n-2 do
114 | if list[i] > list[i+1] do
115 | temp = list[i+1];
116 | list[i+1] = list[i];
117 | list[i] = temp;
118 | return false;
119 | end
120 | end
121 | return true;
122 | end
123 | numbers = [3,5,1,4,2];
124 | sort(numbers);
125 | assert(numbers == [1,2,3,4,5]);
126 |
127 | // recursive calls
128 | def fib(n)
129 | if n < 2 do
130 | return n;
131 | else do
132 | return fib(n-2) + fib(n-1);
133 | end
134 | end
135 | assert(fib(4) == 3);
136 | sequence = [];
137 | for i = 0 to 10 do
138 | sequence = sequence + fib(i);
139 | end
140 | assert(sequence == [0,1,1,2,3,5,8,13,21,34,55]);
141 |
142 | def fib2(n)
143 | if n < 2 do
144 | return n;
145 | else do
146 | a = fib2(n-2);
147 | c = fib2(n-1);
148 | return a + c;
149 | end
150 | end
151 | assert(fib2(4) == 3);
152 |
153 | // lists and lookups, `in` operator
154 | n = [[1,0,0],[0,1,0],[0,0,1]];
155 | p = [-1, 'abc', true];
156 |
157 | assert('abc' in p);
158 | assert([0,1,0] in n);
159 | assert(n[0][2] == 0);
160 | assert(n[1][1] == n[2][2]);
161 | assert(p[2]);
162 | assert(p[1][2] == 'c');
163 |
164 | println("All Assertions have passed.");
165 |
--------------------------------------------------------------------------------