├── .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 | --------------------------------------------------------------------------------