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