├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── examples ├── closures.lel ├── concat.lel ├── conditional-test.lel ├── extension-test │ ├── example.txt │ └── read-file.lel ├── filter.lel ├── function-passing.lel ├── head-tail-nth.lel ├── helix.lel ├── lambdas.lel ├── lexical-scoping.lel ├── map.lel ├── module-test │ ├── a.lel │ ├── b.lel │ ├── c.lel │ └── main.lel ├── mutation.lel ├── negative.lel ├── return-last-statement.lel ├── split-join.lel ├── square.lel ├── stack-safety-check.lel ├── sublist.lel └── the-universe.lel ├── package-lock.json ├── package.json └── src ├── create-token.js ├── evaluate ├── create-scope.js ├── evaluate-expr │ ├── find-basepath.js │ ├── find-in-scope.js │ ├── index.js │ └── language-core │ │ ├── apply.js │ │ ├── call-function.js │ │ ├── call.js │ │ ├── filter.js │ │ ├── function │ │ ├── index.js │ │ └── scoped-function.js │ │ ├── if.js │ │ ├── import.js │ │ ├── index.js │ │ ├── lambda.js │ │ ├── let.js │ │ ├── list.js │ │ ├── map.js │ │ ├── mutate.js │ │ ├── standard-language-functions │ │ ├── boolean.js │ │ ├── general.js │ │ ├── index.js │ │ ├── list.js │ │ ├── math.js │ │ └── string.js │ │ ├── stdlib │ │ └── math.js │ │ └── use.js └── index.js ├── extensions ├── index.js └── read-file-sync.js ├── index.js ├── interpreter ├── index.js ├── read-lel.js └── validate.js ├── parse ├── clean.js └── index.js ├── patterns.js ├── repl ├── index.js ├── parentheses-balance.js └── repl.js ├── symbols.js ├── tokenise.js └── util ├── lel-promise-all.js ├── lel-promise.js ├── lel-reject.js └── lel-series.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | bin/ 4 | .DS_Store 5 | .tmp 6 | .git 7 | .settings 8 | .idea 9 | .vscode 10 | .eslintrc.json 11 | *.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | examples/ 3 | .eslint.json 4 | npm-debug.* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Frank Stokes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lisp-esque language (Lel) 2 | 3 | Lel is a lisp like programming language. It is not meant for practical purposes, but more as a tool to learn how to write a programming language. 4 | 5 | This project comes with a Lel interpreter for running Lel programs, and a REPL for executing Lel expressions in real time. 6 | 7 | ## Running 8 | 9 | ### Installation 10 | 11 | You can clone the repo. 12 | 13 | ```bash 14 | git clone https://github.com/francisrstokes/Lisp-esque-language.git 15 | cd Lisp-esque-language 16 | npm i 17 | ``` 18 | 19 | Or install from npm 20 | 21 | ```bash 22 | npm i -g lel-lang 23 | ``` 24 | 25 | ### REPL 26 | 27 | ```bash 28 | lel 29 | ``` 30 | 31 | ### Interpreter 32 | 33 | ```bash 34 | lel examples/helix.lel 35 | ``` 36 | 37 | ## Language features 38 | 39 | - Primitive types: 40 | - Number 41 | - String 42 | - Boolean 43 | - Function 44 | - List 45 | - Lexical scoping of functions and variables 46 | - Functions form closures 47 | - Anonymous functions through lambdas 48 | - Call and apply by reference for functions 49 | - Modules 50 | - Code can be split across files into isolated modules 51 | - Loaded with `import` keyword 52 | - Extendable 53 | - Allows extension of the language through js modules 54 | - Conditionals 55 | - List mapipulation 56 | - Stack safety 57 | - Recursive functions do not lead to stack overflow exceptions even without tail calls 58 | 59 | 60 | ### The Lel Language 61 | 62 | #### Variables 63 | 64 | Variables are declared with the `let` keyword. 65 | 66 | ```lisp 67 | (let x 10) 68 | ``` 69 | 70 | They can of course be assigned as the result of a function: 71 | 72 | ```lisp 73 | (let x (+ 4 6)) 74 | ``` 75 | 76 | A variable can be modified using the the `mutate` keyword, though it should be avoided where possible. 77 | 78 | ```lisp 79 | (let count 1) 80 | (print count "\n") 81 | (mutate count (+ count 1)) 82 | (print count "\n") 83 | 84 | ; -> 1 85 | ; 2 86 | ``` 87 | 88 | #### Functions 89 | 90 | Functions are declared with the `function` keyword. The value last statement in a function body is the final return value. 91 | 92 | ```lisp 93 | (function add-one (x) 94 | (+ x 1)) 95 | 96 | (print (add-one 1)) 97 | 98 | ; -> 2 99 | ``` 100 | 101 | #### Call 102 | 103 | Functions are first class in Lel and can be passed around in variables by reference. A function can be executed by reference using `call`: 104 | 105 | ```lisp 106 | ; Declare a function called say-hello 107 | (function say-hello (name) 108 | (print "hello " name "!")) 109 | 110 | ; Create a variable with a reference to this function 111 | (let referenced-hello say-hello) 112 | 113 | ; Call say-hello by reference 114 | (call referenced-hello "Francis") 115 | 116 | ; -> "hello Francis!" 117 | ``` 118 | 119 | All functions are lexically scoped and form [closures](https://en.wikipedia.org/wiki/Closure_(computer_programming)), meaning they have access to the scope they were declared in while also retaining their own scope. 120 | 121 | ```lisp 122 | (function create-adder (adding-value) 123 | (function new-adder (x) 124 | (+ x adding-value))) 125 | 126 | (let add-five (create-adder 5)) 127 | (let add-ten (create-adder 10)) 128 | 129 | (print (add-five 10) "\n") 130 | (print (add-ten 10) "\n") 131 | 132 | ; -> 15 133 | ; 20 134 | ``` 135 | 136 | #### Apply 137 | 138 | Apply is like call except the arguments are provided as a List, or a reference to a list: 139 | 140 | ```lisp 141 | (function say-hello (name) 142 | (print "hello " name "!")) 143 | 144 | (let args (list "Francis")) 145 | (print (apply say-hello args)) 146 | (print (apply say-hello (list "Mr. Stokes"))) 147 | 148 | ; -> "hello Francis!" 149 | ; "hello Mr. Stokes!" 150 | ``` 151 | 152 | #### Lambda 153 | 154 | A `Lambda` is a special kind of anonymous function that is not placed into scope when it's declared: 155 | 156 | ```lisp 157 | (let anon-func 158 | (lambda (x) 159 | (* x x))) 160 | 161 | (call anon-func 5) 162 | 163 | ; -> 25 164 | ``` 165 | 166 | They can also act like javascript's [Immediately-invoked function expressions](http://benalman.com/news/2010/11/immediately-invoked-function-expression/): 167 | 168 | ```lisp 169 | (call 170 | (lambda (x) 171 | (let xs (* x x x)) 172 | (print x " cubed is " xs "\n")) 173 | 5) 174 | 175 | ; -> 5 cubed is 125 176 | ``` 177 | 178 | Which might be better named as a `lambda call` in Lel. 179 | 180 | #### Conditionals 181 | 182 | The `if` keyword is used for condition checking. `if` is a function which takes 3 arguments, a boolean expression, a true expression and a false expression. The true and false expressions can also be lists of expressions. 183 | 184 | ```lisp 185 | (if (< 1 2) 186 | (print "1 is less than 2") 187 | (print "1 is not less than 2...?")) 188 | 189 | ; -> 1 is less than 2 190 | ``` 191 | 192 | And since `if` is a function, it always returns the evaluation of it's last statement, because of this `if` can be used like the `?` operator in many other languages. For example: 193 | 194 | ```lisp 195 | (let emotion 196 | (if (< 1 2) 197 | "love" 198 | "hate" 199 | )) 200 | 201 | (print "I " emotion " Lel!\n") 202 | 203 | ; -> I love Lel! 204 | ``` 205 | 206 | #### Types 207 | 208 | Lel has 5 types: 209 | 210 | - Number `10.5` 211 | - String `"Hello"` 212 | - Boolean `#true` or `#false` 213 | - List `(2 4 6 8 10)` 214 | - Function `(lambda (x) (* 3 (* x x)))` 215 | 216 | ##### Number 217 | 218 | The number type is a signed 64-bit double, just like javascript. 219 | 220 | ```lisp 221 | (let positive 5) 222 | (print positive) 223 | 224 | ; -> 5 225 | 226 | (let negative -5) 227 | (print negative) 228 | 229 | ; -> -5 230 | 231 | (let floating 42.2) 232 | (print floating) 233 | 234 | ; -> 42.2 235 | ``` 236 | 237 | ##### String 238 | 239 | A string is a string of characters contained in double quotes: 240 | 241 | ```lisp 242 | (print "hello") 243 | 244 | ; -> hello 245 | ``` 246 | 247 | Strings can be combined with `concat`: 248 | 249 | ```lisp 250 | (let hello-str "hello ") 251 | (print (concat hello-str "world")) 252 | 253 | ; -> hello world 254 | ``` 255 | 256 | ##### Boolean 257 | 258 | Boolean can be either true or false, and are represented with the identifiers `true` and `false`: 259 | 260 | ```lisp 261 | (let true-thing true) 262 | (let false-thing false) 263 | (print true-thing ", " false-thing) 264 | 265 | ; -> true, false 266 | ``` 267 | 268 | Booleans are also the return type of comparison functions, for example: 269 | 270 | ```lisp 271 | (print (> 2 1)) 272 | 273 | ; -> true 274 | ``` 275 | 276 | ##### List 277 | 278 | Lists hold groups of data similar to arrays. A variable holding a list can also be used as the arguments to a function. 279 | 280 | Lists are declared with the `list` keyword. 281 | 282 | ```lisp 283 | (let primes-under-ten 284 | (list 2 3 5 7)) 285 | ``` 286 | 287 | In Lel there is some syntactic sugar for creating list ranges: 288 | 289 | ```lisp 290 | (let ranged-list 291 | (list 1 .. 10)) 292 | 293 | ; -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 294 | ``` 295 | 296 | Ranges can be ascending and descending. 297 | 298 | Lists can be mapped over with the `map` keyword: 299 | 300 | ```lisp 301 | (function multiply-by-index (item index) 302 | (* item index)) 303 | (print (map primes-under-ten multiply-by-index)) 304 | 305 | ; -> (0 3 10 21) 306 | ``` 307 | 308 | Note that mapping functions must have a two argument signature. `map` can also take an inline lambda function. 309 | 310 | You can get the first element in the list with `head`: 311 | 312 | ```lisp 313 | (print (head (list 1 2 3))) 314 | 315 | ; -> 1 316 | ``` 317 | 318 | And you can get everything else in the list with `tail`: 319 | 320 | ```lisp 321 | (print (tail (list 1 2 3))) 322 | 323 | ; -> (2, 3) 324 | ``` 325 | 326 | You can get a list of all the elements from *start* to *end* indexes with `sublist`: 327 | 328 | ```lisp 329 | (let main-list (list 0 1 2 3 4 5)) 330 | (print (sublist main-list 1 4)) 331 | 332 | ; -> (1, 2, 3, 4) 333 | ``` 334 | 335 | You can also grab the nth element of the list with `nth`. The list is zero indexed, just like an array. 336 | 337 | ```lisp 338 | (print (nth (list 1 2 3) 1)) 339 | 340 | ; -> 2 341 | ``` 342 | 343 | You can get the number of elements in a list with `length`: 344 | 345 | ```lisp 346 | (print (length (list 1 2 3))) 347 | 348 | ; -> 3 349 | ``` 350 | 351 | Finally, you can combine lists with `concat` just like strings: 352 | 353 | ```lisp 354 | (let list-one 355 | (list 1 2 3 4 5)) 356 | (let list-two 357 | (list 6 7 8 9 10)) 358 | (print (concat list-one list-two)) 359 | 360 | ; -> (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 361 | ``` 362 | 363 | From these functions more operations are quite easily composable. For instance if you wanted the last element of a list, you could write a function like: 364 | 365 | ```lisp 366 | (let planets (list "earth" "mars" "saturn" "jupiter")) 367 | 368 | (function last-element (l) 369 | (let list-length 370 | (length l)) 371 | (let final-index 372 | (- list-length 1)) 373 | (nth l final-index)) 374 | 375 | (print (last-element planets) "\n") 376 | 377 | ; -> jupiter 378 | ``` 379 | 380 | ##### Function 381 | 382 | A function is just a data type like the others in Lel, and thus can be stored in a variable, in a list, passed as an argument into a function, be returned from a function. 383 | 384 | 385 | #### Modules 386 | 387 | Modules can be imported from a file with `import`. 388 | 389 | ```lisp 390 | ; FILE_A.lel 391 | (lambda () 392 | (print "hello from FILE_A.lel!\n")) 393 | ``` 394 | 395 | ```lisp 396 | ; FILE_B.lel 397 | (let from-file-a (import "./FILE_A.lel")) 398 | (call from-file-a) 399 | 400 | ; -> hello from FILE_A.lel! 401 | ``` 402 | 403 | The evaluated result of an import is the final value of the module (like a function), but an imported module will also inject the variables defined in it's scope into the the importing module. Scope injection can be avoided by only exporting a `lambda` function or primitive value, or by wrapping the code of the module inside a lambda call. 404 | 405 | Imported modules run in their own scope, but can recieve access via closures and dependency injection. 406 | 407 | #### Standard Libraries 408 | 409 | The standard libraries can be accessed with the `use` keyword. Libraries are loaded loaded into the runtime as language-level keywords. For example, the math library: 410 | 411 | ```lisp 412 | (use "math") 413 | 414 | (print (math-sin 4.2)) 415 | 416 | -> -0.8715757724135882 417 | ``` 418 | 419 | The current standard library includes 420 | 421 | - Math 422 | - math-sin 423 | - math-cos 424 | 425 | ## Tokenisation, Parsing, and Evaluation 426 | 427 | The lel tokeniser and parser are both written in javascript from scratch. The tokeniser recognises just 8 tokens: numbers, strings, booleans, left and right parentheses, comments and whitespace, and identifiers. 428 | 429 | The parser transforms the stream of tokens into an AST (abstract syntax tree) which describes those tokens in terms of the semantics of S-expressions. Every '(' signifies the start of a new branch in the tree and every matching ')' signifies the end of that branch. 430 | 431 | The interpreter evaluates the AST. Evaluation is a recursive process where each branch of the tree is an "expression", and is handed to a function called `evaluateExpr`. Sub expressions inside the expression are then evaluated by `evaluateExpr` until the result returns a primitive value of the language. This could easily lead to stack overflow exceptions when nesting of expressions is too high, so all expression evaluation is handled with asynchronous promises because promise evaluation does not work with the call stack. 432 | -------------------------------------------------------------------------------- /examples/closures.lel: -------------------------------------------------------------------------------- 1 | (function create-adder (x) 2 | (lambda (y) (+ x y))) 3 | 4 | (let add-five (create-adder 5)) 5 | (let add-ten (create-adder 10)) 6 | 7 | (print (call add-five 2) "\n") 8 | (print (call add-ten 2) "\n") -------------------------------------------------------------------------------- /examples/concat.lel: -------------------------------------------------------------------------------- 1 | ; concat joins either strings or lists 2 | 3 | (let hello-str "hello") 4 | (let concat-str (concat hello-str " world" "\n")) 5 | (print concat-str) 6 | 7 | (let list-one (list 1 2 3 4 5)) 8 | (let list-two (list 6 7 8 9 10)) 9 | (print (concat list-one list-two) "\n") -------------------------------------------------------------------------------- /examples/conditional-test.lel: -------------------------------------------------------------------------------- 1 | (function print-times (message times) 2 | (if (> times 0) 3 | ((print message) 4 | (print-times message (- times 1))) 5 | ((print "done!\n")))) 6 | 7 | (print-times "hello " 10) 8 | -------------------------------------------------------------------------------- /examples/extension-test/example.txt: -------------------------------------------------------------------------------- 1 | I'm from an external file :) 2 | -------------------------------------------------------------------------------- /examples/extension-test/read-file.lel: -------------------------------------------------------------------------------- 1 | (print (read-file-sync "./example.txt" "utf8")) -------------------------------------------------------------------------------- /examples/filter.lel: -------------------------------------------------------------------------------- 1 | (let numbers (list 1 2 3 4 5 6 7 8)) 2 | 3 | (print "Original list: " numbers "\n") 4 | 5 | (let filtered 6 | (filter numbers (lambda (x i) (> x 4)))) 7 | 8 | (print "Filtered on (> x 4): " filtered "\n") -------------------------------------------------------------------------------- /examples/function-passing.lel: -------------------------------------------------------------------------------- 1 | ; Normal function definition 2 | (function test (x) 3 | (+ x 1)) 4 | 5 | ; Evaluating this function in a regular way 6 | (print "Regular call: " (test 40) "\n") 7 | 8 | ; Function references can be stored in variables to be passed around 9 | (let stored-test test) 10 | 11 | ; To call a function by reference, use call 12 | (print "Call: " (call stored-test 41) "\n") 13 | 14 | ; to call a function with a list containing arguments, use apply 15 | (let args (list 42)) 16 | (print "Apply: " (apply stored-test args) "\n") 17 | 18 | (print "Apply with direct list: " (apply stored-test (list 43)) "\n") -------------------------------------------------------------------------------- /examples/head-tail-nth.lel: -------------------------------------------------------------------------------- 1 | (let primes (list 2 3 5 7 11)) 2 | 3 | (print "prime list: " primes "\n") 4 | (print "head: " (head primes) "\n") 5 | (print "tail: " (tail primes) "\n") 6 | (print "head of tail: " (head (tail primes)) "\n") 7 | (print "nth (3): " (nth primes 3) "\n") 8 | (print "length: " (length primes) "\n") 9 | -------------------------------------------------------------------------------- /examples/helix.lel: -------------------------------------------------------------------------------- 1 | (use "math") 2 | 3 | (print (math-sin 2.3) "\n") 4 | 5 | (function next (n current step lis) 6 | (if (> n 0) 7 | (next (- n 1) (+ current step) step (concat lis (list current))) 8 | lis 9 | ) 10 | ) 11 | 12 | (function left-pad (n str char) 13 | (if (> n 0) 14 | (left-pad (- n 1) (concat str char) char) 15 | str 16 | )) 17 | 18 | (function wait-clear (n) 19 | (if (> n 0) 20 | (wait-clear (- n 1)) 21 | (cls) 22 | )) 23 | 24 | (let nums 25 | (next 20 0 0.1 (list))) 26 | 27 | (function wave (t) 28 | (lambda (x i) 29 | (let size (+ 5 (* 2 (math-sin (/ t 10))))) 30 | (let sin-x (* size (math-sin (+ t i)))) 31 | (let num-spaces (+ size sin-x)) 32 | (let spaces (left-pad num-spaces "" " ")) 33 | (print spaces "*\n") 34 | )) 35 | 36 | (function animate (n) 37 | (if (> n 0) 38 | ( 39 | (map nums (wave ( / n 3))) 40 | (wait-clear 200) 41 | (animate (- n 1)) 42 | ) 43 | () 44 | )) 45 | 46 | (animate 1000) -------------------------------------------------------------------------------- /examples/lambdas.lel: -------------------------------------------------------------------------------- 1 | ; Functions declared with the 'function' keyword will always be placed into scope by name 2 | ; even if assigned directly to a variable 3 | 4 | (let hello-func-ref 5 | (function hello (name) 6 | (print "hello " name "\n"))) 7 | 8 | ; We can call hello-func-ref by reference, but hello is still placed into scope. 9 | (hello "Frank") 10 | 11 | ; Lambdas declare anonymous functions that still have access to the declaration scope but are not 12 | ; themselves placed into the scope 13 | 14 | (let anon-hello 15 | (lambda (name) 16 | (print "hello " name "\n"))) 17 | 18 | (call anon-hello "anonymous") 19 | 20 | ; Lambdas can also act as Imediately-invoked function expressions when call'd directly 21 | (call 22 | (lambda (x) 23 | (let xs (* x x x)) 24 | (print x " cubed is " xs "\n")) 25 | 5) -------------------------------------------------------------------------------- /examples/lexical-scoping.lel: -------------------------------------------------------------------------------- 1 | (let newline "\n") 2 | ; Variables declared on the outermost scope can be accessed from anywhere in the program 3 | ; provided they were declared first 4 | (let a 10) 5 | 6 | ; However a function executes inside it's own scope 7 | (function overwrite-a () 8 | (let a 100) 9 | a) 10 | 11 | (print a newline) 12 | (print (overwrite-a) newline) -------------------------------------------------------------------------------- /examples/map.lel: -------------------------------------------------------------------------------- 1 | (let nl "\n") 2 | 3 | ; map should take a list and a function reference, and apply that function to every element in the list, returning a new list 4 | 5 | (let one-to-four (list 1 2 3 4)) 6 | (function times-two (x i) (* x 2)) 7 | (let sq (lambda (x i) (* x x))) 8 | 9 | ; It should work with both list and function references 10 | (print "All references: " (map one-to-four times-two) nl) 11 | 12 | ; Direct lists 13 | (print "Direct List: " (map (list 1 2 3 4) sq) nl) 14 | 15 | ; And direct lambdas 16 | (print "Direct Lambda: " (map one-to-four (lambda (x i) (* x x x))) nl) 17 | -------------------------------------------------------------------------------- /examples/module-test/a.lel: -------------------------------------------------------------------------------- 1 | (function test-func (x) (* x x)) 2 | (function test-func-two (x) (* x x x)) -------------------------------------------------------------------------------- /examples/module-test/b.lel: -------------------------------------------------------------------------------- 1 | ; import a 3rd module 2 | (let c-func (import "./c.lel")) 3 | 4 | ; Export a lambda function 5 | (lambda () 6 | (print "Coming from b.lel!\n") 7 | (call c-func)) -------------------------------------------------------------------------------- /examples/module-test/c.lel: -------------------------------------------------------------------------------- 1 | ; Export a lambda function 2 | (lambda () (print "Coming from c.lel!\n")) 3 | -------------------------------------------------------------------------------- /examples/module-test/main.lel: -------------------------------------------------------------------------------- 1 | (print "Main running\n") 2 | (let b-func (import "./b.lel")) 3 | (call b-func) -------------------------------------------------------------------------------- /examples/mutation.lel: -------------------------------------------------------------------------------- 1 | ; Mutation should be avoided when possible, however sometimes it's the most pragmatic solution 2 | ; to a problem. 3 | 4 | (let count 1) 5 | (print count "\n") 6 | (mutate count (+ count 1)) 7 | (print count "\n") 8 | -------------------------------------------------------------------------------- /examples/negative.lel: -------------------------------------------------------------------------------- 1 | (let a -10) 2 | (print a "\n") 3 | -------------------------------------------------------------------------------- /examples/return-last-statement.lel: -------------------------------------------------------------------------------- 1 | (let a 500) 2 | 3 | (function test (x) 4 | (if (= x true) 5 | ( 6 | (let a 10.2) 7 | (let b 2) 8 | (* a b) 9 | ) 10 | ( 11 | (let a 20) 12 | (let b 4) 13 | (* a b) 14 | ) 15 | ) 16 | ) 17 | 18 | (print (test true) "\n") 19 | (print (test false) "\n") 20 | (print a "\n") -------------------------------------------------------------------------------- /examples/split-join.lel: -------------------------------------------------------------------------------- 1 | (let split-str (split "hello")) 2 | (print "split: " split-str "\n") 3 | 4 | (let joined-list (join split-str)) 5 | (print "join: " joined-list "\n") -------------------------------------------------------------------------------- /examples/square.lel: -------------------------------------------------------------------------------- 1 | (function 2 | square (x) 3 | (* x x)) 4 | 5 | (function 6 | square-square (x) 7 | (square (square (x)))) 8 | 9 | (let result (square-square 5)) 10 | 11 | (print result "\n") 12 | 13 | -------------------------------------------------------------------------------- /examples/stack-safety-check.lel: -------------------------------------------------------------------------------- 1 | (function looper (times) 2 | (if (> times 0) 3 | ( 4 | (print times "\n") 5 | (looper (- times 1)) 6 | ) 7 | (print "done\n") 8 | )) 9 | 10 | (looper 30000) -------------------------------------------------------------------------------- /examples/sublist.lel: -------------------------------------------------------------------------------- 1 | (let main-list 2 | (list 0 1 2 3 4 5 6 7 8 9)) 3 | 4 | (let sub-list 5 | (sublist main-list 3 7)) 6 | 7 | (print "main list: " main-list "\n") 8 | (print "sublist of 3 to 7: " sub-list "\n") -------------------------------------------------------------------------------- /examples/the-universe.lel: -------------------------------------------------------------------------------- 1 | (let theAnswer 2 | (+ 18 3 | (* 12 2))) 4 | 5 | (print 6 | "Life the universe and everything = " theAnswer "\n") -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lel-lang", 3 | "version": "0.0.6", 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "acorn": { 7 | "version": "5.1.1", 8 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", 9 | "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", 10 | "dev": true 11 | }, 12 | "acorn-jsx": { 13 | "version": "3.0.1", 14 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", 15 | "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", 16 | "dev": true, 17 | "dependencies": { 18 | "acorn": { 19 | "version": "3.3.0", 20 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", 21 | "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", 22 | "dev": true 23 | } 24 | } 25 | }, 26 | "ajv": { 27 | "version": "5.2.2", 28 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", 29 | "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", 30 | "dev": true 31 | }, 32 | "ajv-keywords": { 33 | "version": "1.5.1", 34 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", 35 | "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", 36 | "dev": true 37 | }, 38 | "ansi-escapes": { 39 | "version": "2.0.0", 40 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", 41 | "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", 42 | "dev": true 43 | }, 44 | "ansi-regex": { 45 | "version": "2.1.1", 46 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 47 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 48 | "dev": true 49 | }, 50 | "ansi-styles": { 51 | "version": "2.2.1", 52 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 53 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 54 | "dev": true 55 | }, 56 | "argparse": { 57 | "version": "1.0.9", 58 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", 59 | "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", 60 | "dev": true 61 | }, 62 | "array-union": { 63 | "version": "1.0.2", 64 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", 65 | "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", 66 | "dev": true 67 | }, 68 | "array-uniq": { 69 | "version": "1.0.3", 70 | "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", 71 | "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", 72 | "dev": true 73 | }, 74 | "arrify": { 75 | "version": "1.0.1", 76 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 77 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 78 | "dev": true 79 | }, 80 | "babel-code-frame": { 81 | "version": "6.22.0", 82 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", 83 | "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", 84 | "dev": true, 85 | "dependencies": { 86 | "esutils": { 87 | "version": "2.0.2", 88 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 89 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 90 | "dev": true 91 | } 92 | } 93 | }, 94 | "balanced-match": { 95 | "version": "1.0.0", 96 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 97 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 98 | "dev": true 99 | }, 100 | "bluebird": { 101 | "version": "3.5.0", 102 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.0.tgz", 103 | "integrity": "sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw=" 104 | }, 105 | "brace-expansion": { 106 | "version": "1.1.8", 107 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 108 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 109 | "dev": true 110 | }, 111 | "caller-path": { 112 | "version": "0.1.0", 113 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", 114 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", 115 | "dev": true 116 | }, 117 | "callsites": { 118 | "version": "0.2.0", 119 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", 120 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", 121 | "dev": true 122 | }, 123 | "chalk": { 124 | "version": "1.1.3", 125 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 126 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 127 | "dev": true 128 | }, 129 | "circular-json": { 130 | "version": "0.3.3", 131 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 132 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 133 | "dev": true 134 | }, 135 | "cli-cursor": { 136 | "version": "2.1.0", 137 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", 138 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", 139 | "dev": true 140 | }, 141 | "cli-width": { 142 | "version": "2.1.0", 143 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", 144 | "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", 145 | "dev": true 146 | }, 147 | "co": { 148 | "version": "4.6.0", 149 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 150 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", 151 | "dev": true 152 | }, 153 | "color-convert": { 154 | "version": "1.9.0", 155 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", 156 | "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", 157 | "dev": true 158 | }, 159 | "color-name": { 160 | "version": "1.1.3", 161 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 162 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 163 | "dev": true 164 | }, 165 | "concat-map": { 166 | "version": "0.0.1", 167 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 168 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 169 | "dev": true 170 | }, 171 | "concat-stream": { 172 | "version": "1.6.0", 173 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", 174 | "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", 175 | "dev": true 176 | }, 177 | "core-util-is": { 178 | "version": "1.0.2", 179 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 180 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 181 | "dev": true 182 | }, 183 | "cross-spawn": { 184 | "version": "5.1.0", 185 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", 186 | "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", 187 | "dev": true 188 | }, 189 | "debug": { 190 | "version": "2.6.8", 191 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", 192 | "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", 193 | "dev": true 194 | }, 195 | "deep-is": { 196 | "version": "0.1.3", 197 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 198 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 199 | "dev": true 200 | }, 201 | "del": { 202 | "version": "2.2.2", 203 | "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", 204 | "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", 205 | "dev": true 206 | }, 207 | "doctrine": { 208 | "version": "2.0.0", 209 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", 210 | "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", 211 | "dev": true, 212 | "dependencies": { 213 | "esutils": { 214 | "version": "2.0.2", 215 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 216 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 217 | "dev": true 218 | } 219 | } 220 | }, 221 | "escape-string-regexp": { 222 | "version": "1.0.5", 223 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 224 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 225 | "dev": true 226 | }, 227 | "eslint": { 228 | "version": "4.3.0", 229 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.3.0.tgz", 230 | "integrity": "sha1-/NfJY3a780yF7mftABKimWQrEI8=", 231 | "dev": true, 232 | "dependencies": { 233 | "estraverse": { 234 | "version": "4.2.0", 235 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 236 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 237 | "dev": true 238 | }, 239 | "esutils": { 240 | "version": "2.0.2", 241 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 242 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 243 | "dev": true 244 | } 245 | } 246 | }, 247 | "eslint-scope": { 248 | "version": "3.7.1", 249 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", 250 | "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", 251 | "dev": true, 252 | "dependencies": { 253 | "estraverse": { 254 | "version": "4.2.0", 255 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 256 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 257 | "dev": true 258 | } 259 | } 260 | }, 261 | "espree": { 262 | "version": "3.4.3", 263 | "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz", 264 | "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=", 265 | "dev": true 266 | }, 267 | "esquery": { 268 | "version": "1.0.0", 269 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", 270 | "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", 271 | "dev": true, 272 | "dependencies": { 273 | "estraverse": { 274 | "version": "4.2.0", 275 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 276 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 277 | "dev": true 278 | } 279 | } 280 | }, 281 | "esrecurse": { 282 | "version": "4.2.0", 283 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", 284 | "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", 285 | "dev": true, 286 | "dependencies": { 287 | "estraverse": { 288 | "version": "4.2.0", 289 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 290 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 291 | "dev": true 292 | } 293 | } 294 | }, 295 | "external-editor": { 296 | "version": "2.0.4", 297 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", 298 | "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", 299 | "dev": true 300 | }, 301 | "fast-deep-equal": { 302 | "version": "1.0.0", 303 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", 304 | "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", 305 | "dev": true 306 | }, 307 | "fast-levenshtein": { 308 | "version": "2.0.6", 309 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 310 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 311 | "dev": true 312 | }, 313 | "figures": { 314 | "version": "2.0.0", 315 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 316 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", 317 | "dev": true 318 | }, 319 | "file-entry-cache": { 320 | "version": "2.0.0", 321 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", 322 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", 323 | "dev": true 324 | }, 325 | "flat-cache": { 326 | "version": "1.2.2", 327 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", 328 | "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", 329 | "dev": true 330 | }, 331 | "fs.realpath": { 332 | "version": "1.0.0", 333 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 334 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 335 | "dev": true 336 | }, 337 | "functional-red-black-tree": { 338 | "version": "1.0.1", 339 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 340 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 341 | "dev": true 342 | }, 343 | "glob": { 344 | "version": "7.1.2", 345 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 346 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 347 | "dev": true 348 | }, 349 | "globals": { 350 | "version": "9.18.0", 351 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 352 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", 353 | "dev": true 354 | }, 355 | "globby": { 356 | "version": "5.0.0", 357 | "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", 358 | "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", 359 | "dev": true 360 | }, 361 | "graceful-fs": { 362 | "version": "4.1.11", 363 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 364 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 365 | "dev": true 366 | }, 367 | "has-ansi": { 368 | "version": "2.0.0", 369 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 370 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 371 | "dev": true 372 | }, 373 | "has-flag": { 374 | "version": "2.0.0", 375 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 376 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 377 | "dev": true 378 | }, 379 | "iconv-lite": { 380 | "version": "0.4.18", 381 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", 382 | "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", 383 | "dev": true 384 | }, 385 | "ignore": { 386 | "version": "3.3.3", 387 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", 388 | "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", 389 | "dev": true 390 | }, 391 | "imurmurhash": { 392 | "version": "0.1.4", 393 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 394 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 395 | "dev": true 396 | }, 397 | "inflight": { 398 | "version": "1.0.6", 399 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 400 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 401 | "dev": true 402 | }, 403 | "inherits": { 404 | "version": "2.0.3", 405 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 406 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 407 | "dev": true 408 | }, 409 | "inquirer": { 410 | "version": "3.2.1", 411 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.2.1.tgz", 412 | "integrity": "sha512-QgW3eiPN8gpj/K5vVpHADJJgrrF0ho/dZGylikGX7iqAdRgC9FVKYKWFLx6hZDBFcOLEoSqINYrVPeFAeG/PdA==", 413 | "dev": true, 414 | "dependencies": { 415 | "ansi-regex": { 416 | "version": "3.0.0", 417 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 418 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 419 | "dev": true 420 | }, 421 | "ansi-styles": { 422 | "version": "3.2.0", 423 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", 424 | "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", 425 | "dev": true 426 | }, 427 | "chalk": { 428 | "version": "2.0.1", 429 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.0.1.tgz", 430 | "integrity": "sha512-Mp+FXEI+FrwY/XYV45b2YD3E8i3HwnEAoFcM0qlZzq/RZ9RwWitt2Y/c7cqRAz70U7hfekqx6qNYthuKFO6K0g==", 431 | "dev": true 432 | }, 433 | "strip-ansi": { 434 | "version": "4.0.0", 435 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 436 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 437 | "dev": true 438 | }, 439 | "supports-color": { 440 | "version": "4.2.1", 441 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", 442 | "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", 443 | "dev": true 444 | } 445 | } 446 | }, 447 | "is-fullwidth-code-point": { 448 | "version": "2.0.0", 449 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 450 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 451 | "dev": true 452 | }, 453 | "is-path-cwd": { 454 | "version": "1.0.0", 455 | "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", 456 | "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", 457 | "dev": true 458 | }, 459 | "is-path-in-cwd": { 460 | "version": "1.0.0", 461 | "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", 462 | "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", 463 | "dev": true 464 | }, 465 | "is-path-inside": { 466 | "version": "1.0.0", 467 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", 468 | "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", 469 | "dev": true 470 | }, 471 | "is-promise": { 472 | "version": "2.1.0", 473 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 474 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 475 | "dev": true 476 | }, 477 | "is-resolvable": { 478 | "version": "1.0.0", 479 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", 480 | "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", 481 | "dev": true 482 | }, 483 | "isarray": { 484 | "version": "1.0.0", 485 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 486 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 487 | "dev": true 488 | }, 489 | "isexe": { 490 | "version": "2.0.0", 491 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 492 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 493 | "dev": true 494 | }, 495 | "js-tokens": { 496 | "version": "3.0.2", 497 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 498 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 499 | "dev": true 500 | }, 501 | "js-yaml": { 502 | "version": "3.9.0", 503 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz", 504 | "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==", 505 | "dev": true, 506 | "dependencies": { 507 | "esprima": { 508 | "version": "4.0.0", 509 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", 510 | "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", 511 | "dev": true 512 | } 513 | } 514 | }, 515 | "jschardet": { 516 | "version": "1.5.0", 517 | "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.0.tgz", 518 | "integrity": "sha512-+Q8JsoEQbrdE+a/gg1F9XO92gcKXgpE5UACqr0sIubjDmBEkd+OOWPGzQeMrWSLxd73r4dHxBeRW7edHu5LmJQ==", 519 | "dev": true 520 | }, 521 | "json-schema-traverse": { 522 | "version": "0.3.1", 523 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 524 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", 525 | "dev": true 526 | }, 527 | "json-stable-stringify": { 528 | "version": "1.0.1", 529 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 530 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 531 | "dev": true 532 | }, 533 | "jsonify": { 534 | "version": "0.0.0", 535 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 536 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", 537 | "dev": true 538 | }, 539 | "levn": { 540 | "version": "0.3.0", 541 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 542 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 543 | "dev": true 544 | }, 545 | "lodash": { 546 | "version": "4.17.4", 547 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 548 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", 549 | "dev": true 550 | }, 551 | "lru-cache": { 552 | "version": "4.1.1", 553 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", 554 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", 555 | "dev": true 556 | }, 557 | "mimic-fn": { 558 | "version": "1.1.0", 559 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", 560 | "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", 561 | "dev": true 562 | }, 563 | "minimatch": { 564 | "version": "3.0.4", 565 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 566 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 567 | "dev": true 568 | }, 569 | "minimist": { 570 | "version": "0.0.8", 571 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 572 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 573 | "dev": true 574 | }, 575 | "mkdirp": { 576 | "version": "0.5.1", 577 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 578 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 579 | "dev": true 580 | }, 581 | "ms": { 582 | "version": "2.0.0", 583 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 584 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 585 | "dev": true 586 | }, 587 | "mute-stream": { 588 | "version": "0.0.7", 589 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 590 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 591 | "dev": true 592 | }, 593 | "natural-compare": { 594 | "version": "1.4.0", 595 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 596 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 597 | "dev": true 598 | }, 599 | "object-assign": { 600 | "version": "4.1.1", 601 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 602 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 603 | "dev": true 604 | }, 605 | "once": { 606 | "version": "1.4.0", 607 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 608 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 609 | "dev": true 610 | }, 611 | "onetime": { 612 | "version": "2.0.1", 613 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", 614 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", 615 | "dev": true 616 | }, 617 | "optionator": { 618 | "version": "0.8.2", 619 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 620 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 621 | "dev": true 622 | }, 623 | "os-tmpdir": { 624 | "version": "1.0.2", 625 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 626 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 627 | "dev": true 628 | }, 629 | "path-is-absolute": { 630 | "version": "1.0.1", 631 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 632 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 633 | "dev": true 634 | }, 635 | "path-is-inside": { 636 | "version": "1.0.2", 637 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 638 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 639 | "dev": true 640 | }, 641 | "pify": { 642 | "version": "2.3.0", 643 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 644 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 645 | "dev": true 646 | }, 647 | "pinkie": { 648 | "version": "2.0.4", 649 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 650 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 651 | "dev": true 652 | }, 653 | "pinkie-promise": { 654 | "version": "2.0.1", 655 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 656 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 657 | "dev": true 658 | }, 659 | "pluralize": { 660 | "version": "4.0.0", 661 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-4.0.0.tgz", 662 | "integrity": "sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=", 663 | "dev": true 664 | }, 665 | "prelude-ls": { 666 | "version": "1.1.2", 667 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 668 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 669 | "dev": true 670 | }, 671 | "process-nextick-args": { 672 | "version": "1.0.7", 673 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 674 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 675 | "dev": true 676 | }, 677 | "progress": { 678 | "version": "2.0.0", 679 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", 680 | "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", 681 | "dev": true 682 | }, 683 | "pseudomap": { 684 | "version": "1.0.2", 685 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 686 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", 687 | "dev": true 688 | }, 689 | "readable-stream": { 690 | "version": "2.3.3", 691 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 692 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 693 | "dev": true 694 | }, 695 | "require-uncached": { 696 | "version": "1.0.3", 697 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", 698 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", 699 | "dev": true 700 | }, 701 | "resolve-from": { 702 | "version": "1.0.1", 703 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", 704 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", 705 | "dev": true 706 | }, 707 | "restore-cursor": { 708 | "version": "2.0.0", 709 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", 710 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", 711 | "dev": true 712 | }, 713 | "rimraf": { 714 | "version": "2.6.1", 715 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", 716 | "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", 717 | "dev": true 718 | }, 719 | "run-async": { 720 | "version": "2.3.0", 721 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 722 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 723 | "dev": true 724 | }, 725 | "rx-lite": { 726 | "version": "4.0.8", 727 | "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", 728 | "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", 729 | "dev": true 730 | }, 731 | "rx-lite-aggregates": { 732 | "version": "4.0.8", 733 | "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", 734 | "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", 735 | "dev": true 736 | }, 737 | "safe-buffer": { 738 | "version": "5.1.1", 739 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 740 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", 741 | "dev": true 742 | }, 743 | "semver": { 744 | "version": "5.4.1", 745 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", 746 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", 747 | "dev": true 748 | }, 749 | "shebang-command": { 750 | "version": "1.2.0", 751 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 752 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 753 | "dev": true 754 | }, 755 | "shebang-regex": { 756 | "version": "1.0.0", 757 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 758 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 759 | "dev": true 760 | }, 761 | "signal-exit": { 762 | "version": "3.0.2", 763 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 764 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 765 | "dev": true 766 | }, 767 | "slice-ansi": { 768 | "version": "0.0.4", 769 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", 770 | "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", 771 | "dev": true 772 | }, 773 | "sprintf-js": { 774 | "version": "1.0.3", 775 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 776 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 777 | "dev": true 778 | }, 779 | "string_decoder": { 780 | "version": "1.0.3", 781 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 782 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 783 | "dev": true 784 | }, 785 | "string-width": { 786 | "version": "2.1.1", 787 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 788 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 789 | "dev": true, 790 | "dependencies": { 791 | "ansi-regex": { 792 | "version": "3.0.0", 793 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 794 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 795 | "dev": true 796 | }, 797 | "strip-ansi": { 798 | "version": "4.0.0", 799 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 800 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 801 | "dev": true 802 | } 803 | } 804 | }, 805 | "strip-ansi": { 806 | "version": "3.0.1", 807 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 808 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 809 | "dev": true 810 | }, 811 | "strip-json-comments": { 812 | "version": "2.0.1", 813 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 814 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 815 | "dev": true 816 | }, 817 | "supports-color": { 818 | "version": "2.0.0", 819 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 820 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 821 | "dev": true 822 | }, 823 | "table": { 824 | "version": "4.0.1", 825 | "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", 826 | "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", 827 | "dev": true, 828 | "dependencies": { 829 | "ajv": { 830 | "version": "4.11.8", 831 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", 832 | "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", 833 | "dev": true 834 | } 835 | } 836 | }, 837 | "text-table": { 838 | "version": "0.2.0", 839 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 840 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 841 | "dev": true 842 | }, 843 | "through": { 844 | "version": "2.3.8", 845 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 846 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 847 | "dev": true 848 | }, 849 | "tmp": { 850 | "version": "0.0.31", 851 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", 852 | "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", 853 | "dev": true 854 | }, 855 | "tryit": { 856 | "version": "1.0.3", 857 | "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", 858 | "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", 859 | "dev": true 860 | }, 861 | "type-check": { 862 | "version": "0.3.2", 863 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 864 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 865 | "dev": true 866 | }, 867 | "typedarray": { 868 | "version": "0.0.6", 869 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 870 | "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", 871 | "dev": true 872 | }, 873 | "util-deprecate": { 874 | "version": "1.0.2", 875 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 876 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 877 | "dev": true 878 | }, 879 | "which": { 880 | "version": "1.2.14", 881 | "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", 882 | "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", 883 | "dev": true 884 | }, 885 | "wordwrap": { 886 | "version": "1.0.0", 887 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 888 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 889 | "dev": true 890 | }, 891 | "wrappy": { 892 | "version": "1.0.2", 893 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 894 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 895 | "dev": true 896 | }, 897 | "write": { 898 | "version": "0.2.1", 899 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 900 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 901 | "dev": true 902 | }, 903 | "yallist": { 904 | "version": "2.1.2", 905 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 906 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", 907 | "dev": true 908 | } 909 | } 910 | } 911 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lel-lang", 3 | "version": "0.0.6", 4 | "description": "Lel programming language(Lisp-esque language)", 5 | "main": "src/index.js", 6 | "bin": { 7 | "lel": "src/index.js" 8 | }, 9 | "dependencies": { 10 | "bluebird": "^3.5.0" 11 | }, 12 | "devDependencies": { 13 | "eslint": "^4.3.0" 14 | }, 15 | "scripts": {}, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/francisrstokes/Lisp-esque-language.git" 19 | }, 20 | "author": "Francis Stokes", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/francisrstokes/Lisp-esque-language/issues" 24 | }, 25 | "homepage": "https://github.com/francisrstokes/Lisp-esque-language#readme" 26 | } 27 | -------------------------------------------------------------------------------- /src/create-token.js: -------------------------------------------------------------------------------- 1 | module.exports = (type, value) => ({ 2 | isToken: true, 3 | type, 4 | value, 5 | toString: () => `${type}` 6 | }); -------------------------------------------------------------------------------- /src/evaluate/create-scope.js: -------------------------------------------------------------------------------- 1 | module.exports = (upperScope, basepath) => ({ 2 | upperScope, 3 | variables: {}, 4 | basepath 5 | }); -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/find-basepath.js: -------------------------------------------------------------------------------- 1 | const findBasepath = (scope) => { 2 | if (scope.upperScope) return findBasepath(scope.upperScope); 3 | return scope.basepath; 4 | } 5 | 6 | module.exports = findBasepath; 7 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/find-in-scope.js: -------------------------------------------------------------------------------- 1 | const findInScope = (scope, name) => { 2 | if (!scope) return null; 3 | if (name in scope.variables) return scope.variables[name]; 4 | return findInScope(scope.upperScope, name); 5 | }; 6 | 7 | module.exports = findInScope; -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/index.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../symbols'); 2 | const createToken = require('../../create-token'); 3 | const core = require('./language-core'); 4 | const callFunction = require('./language-core/call-function'); 5 | const standard = require('./language-core/standard-language-functions'); 6 | const findInScope = require('./find-in-scope'); 7 | 8 | const lelPromise = require('../../util/lel-promise'); 9 | const lelPromiseAll = require('../../util/lel-promise-all'); 10 | const lelSeries = require('../../util/lel-series'); 11 | 12 | const evaluateExpr = (scope, expr) => 13 | lelPromise((resolve, reject) => { 14 | // Return the value of primitives directly in their tokenised form 15 | if (expr.isToken && 16 | (expr.type === symbols.RANGE 17 | || expr.type === symbols.STRING 18 | || expr.type === symbols.NUMBER 19 | || expr.type === symbols.BOOLEAN 20 | || expr.type === symbols.FUNCTION_REFERENCE 21 | || expr.type === symbols.LIST)) resolve(expr); 22 | 23 | // Identifiers will be a function reference or a variable 24 | if (expr.type === symbols.IDENTIFIER) { 25 | const identifierType = expr.value; 26 | 27 | // Pass back variable value. Explicitly check null instead of other 28 | // falsey values that might really be contained in the variable 29 | const variableInScope = findInScope(scope, identifierType); 30 | if (variableInScope !== null) resolve(variableInScope); 31 | } 32 | 33 | // Evaluate empty block 34 | if (Array.isArray(expr) && expr.length === 0) resolve(createToken(symbols.LIST, [])); 35 | 36 | // List of expressions? 37 | if (Array.isArray(expr)) { 38 | // Evaluate a block in series 39 | if (Array.isArray(expr[0])) { 40 | const blockEvaluators = expr.map(blockExpr => () => evaluateExpr(scope, blockExpr)); 41 | return lelSeries(blockEvaluators).then(values => resolve(values[values.length-1])); 42 | } 43 | 44 | // The rest of the expressions are based on identifiers 45 | const identifierToken = expr[0]; 46 | if (identifierToken.type !== symbols.IDENTIFIER) { 47 | return reject(new Error(`Expected IDENTIFIER symbol, got ${indentifierToken.type}\nExpr: ${JSON.stringify(expr)}`)); 48 | } 49 | 50 | // Core language functions 51 | const coreFunc = core.get(identifierToken.value); 52 | if (coreFunc) { 53 | return coreFunc(evaluateExpr, scope, expr).then(resolve); 54 | } 55 | 56 | // Standard languages functions that manipulate primitives 57 | if (identifierToken.value in standard) { 58 | return lelPromiseAll(expr.slice(1).map(subExpr => evaluateExpr(scope, subExpr))) 59 | .then(args => 60 | standard[identifierToken.value](...args) 61 | .then(resolve) 62 | ); 63 | } 64 | 65 | // Run a scoped function if one is found 66 | const scopedFunction = findInScope(scope, identifierToken.value); 67 | if (scopedFunction && scopedFunction.type === symbols.FUNCTION_REFERENCE) { 68 | return lelPromiseAll(expr.slice(1).map(subExpr => evaluateExpr(scope, subExpr))) 69 | .then(args => 70 | callFunction(evaluateExpr, scope, args, scopedFunction.value) 71 | .then(resolve) 72 | ); 73 | } 74 | 75 | // Try and evaluate as a single expression 76 | return evaluateExpr(scope, expr[0]).then(resolve); 77 | } 78 | 79 | return reject(new Error(`Unrecognised expression: ${JSON.stringify(expr)}`)); 80 | }); 81 | 82 | module.exports = evaluateExpr; 83 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/apply.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const createLambda = require('./lambda'); 3 | const callFunction = require('./call-function'); 4 | const createToken = require('../../../create-token'); 5 | 6 | const lelPromise = require('../../../util/lel-promise'); 7 | 8 | const performFunctionCall = (resolve, reject, evaluateExpr, scope, expr, functionDescriptor) => 9 | (args) => { 10 | if (args.type !== symbols.LIST) { 11 | reject(new Error(`Referenced arguments must be a LIST apply (${functionDescriptor.name}). Got ${args.type} for expression ${expr}`)); 12 | } 13 | callFunction(evaluateExpr, scope, args.value, functionDescriptor).then(resolve); 14 | }; 15 | 16 | const getFunctionArgs = (resolve, reject, evaluateExpr, scope, expr) => 17 | (fReference) => { 18 | if (fReference && fReference.isToken && fReference.type === symbols.FUNCTION_REFERENCE) { 19 | const functionDescriptor = fReference.value; 20 | if (expr[2]) { 21 | evaluateExpr(scope, expr[2]) 22 | .then(performFunctionCall(resolve, reject, evaluateExpr, scope, expr, functionDescriptor)); 23 | } else { 24 | const emptyList = createToken(symbols.LIST, []); 25 | performFunctionCall(resolve, reject, evaluateExpr, scope, expr, functionDescriptor)(emptyList); 26 | } 27 | } else { 28 | reject(new Error(`First argument must be a FUNCTION_REFERENCE. Got ${expr[1].type} for expression ${expr}`)); 29 | } 30 | }; 31 | 32 | module.exports = (evaluateExpr, scope, expr) => 33 | lelPromise((resolve, reject) => { 34 | const fRefIsArray = Array.isArray(expr[1]); 35 | const fRefIsIndentifier = expr[1].isToken && expr[1].type === symbols.IDENTIFIER; 36 | 37 | let fReference; 38 | if (expr[1] && (fRefIsArray || fRefIsIndentifier)) { 39 | evaluateExpr(scope, expr[1]) 40 | .then(getFunctionArgs(resolve, reject, evaluateExpr, scope, expr)); 41 | } else { 42 | reject(new Error(`${expr[1]} is not a valid function reference`)); 43 | } 44 | }) -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/call-function.js: -------------------------------------------------------------------------------- 1 | const createScope = require('../../create-scope'); 2 | const lelPromise = require('../../../util/lel-promise'); 3 | const lelSeries = require('../../../util/lel-series'); 4 | 5 | module.exports = (evaluateExpr, scope, args, functionDescriptor) => 6 | lelPromise((resolve, reject) => { 7 | // Every time the function runs it gets it's own scope, meaning variables set inside this function 8 | // will not persist across different calls. 9 | const executionScope = createScope(functionDescriptor.scope); 10 | 11 | if (args.length !== functionDescriptor.expectedArguments.length) { 12 | reject(new Error(`Expected ${functionDescriptor.expectedArguments.length} arguments for function ${functionDescriptor.name} but got ${args.legnth}`)); 13 | } 14 | 15 | // Place arguments into the execution scope 16 | args.forEach((argument, i) => 17 | executionScope.variables[functionDescriptor.expectedArguments[i]] = argument 18 | ); 19 | 20 | const bodyEvaluators = functionDescriptor 21 | .bodyExpressions 22 | .map(fExpr => () => evaluateExpr(executionScope, fExpr)); 23 | 24 | lelSeries(bodyEvaluators).then(values => resolve(values[values.length-1])); 25 | }); 26 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/call.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const createLambda = require('./lambda'); 3 | const callFunction = require('./call-function'); 4 | const lelPromise = require('../../../util/lel-promise'); 5 | const lelPromiseAll = require('../../../util/lel-promise-all'); 6 | 7 | const getFunctionArguments = (resolve, reject, evaluateExpr, scope, expr) => 8 | (fReference) => { 9 | if (fReference && fReference.isToken && fReference.type === symbols.FUNCTION_REFERENCE) { 10 | const functionDescriptor = fReference.value; 11 | if (expr[2]) { 12 | lelPromiseAll(expr.slice(2).map(subExpr => evaluateExpr(scope, subExpr))) 13 | .then(performFunctionCall(resolve, reject, evaluateExpr, scope, expr, functionDescriptor)); 14 | } else { 15 | performFunctionCall(resolve, reject, evaluateExpr, scope, expr, functionDescriptor)([]); 16 | } 17 | } else { 18 | reject(new Error(`First argument must be a FUNCTION_REFERENCE. Got ${expr[1].type} for expression ${expr}`)); 19 | } 20 | }; 21 | 22 | const performFunctionCall = (resolve, reject, evaluateExpr, scope, expr, functionDescriptor) => 23 | (args) => 24 | callFunction(evaluateExpr, scope, args, functionDescriptor).then(resolve); 25 | 26 | module.exports = (evaluateExpr, scope, expr) => 27 | lelPromise((resolve, reject) => { 28 | const fRefIsArray = Array.isArray(expr[1]); 29 | const fRefIsIndentifier = expr[1].isToken && expr[1].type === symbols.IDENTIFIER; 30 | 31 | let fReference; 32 | if (expr[1] && (fRefIsArray || fRefIsIndentifier)) { 33 | evaluateExpr(scope, expr[1]) 34 | .then(getFunctionArguments(resolve, reject, evaluateExpr, scope, expr)); 35 | } else { 36 | reject(new Error(`${expr[1]} is not a valid function reference`)); 37 | } 38 | }); -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/filter.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const createToken = require('../../../create-token'); 3 | const callFunction = require('./call-function'); 4 | 5 | const lelPromise = require('../../../util/lel-promise'); 6 | const lelSeries = require('../../../util/lel-series'); 7 | 8 | const getFilteringFunction = 9 | (resolve, reject, evaluateExpr, scope, expr) => 10 | (list) => { 11 | if (list.type !== symbols.LIST) { 12 | reject(new Error(`Invalid list passed to filter. Got ${expr}`)); 13 | } 14 | 15 | evaluateExpr(scope, expr[2]) 16 | .then(performFiltering(resolve, reject, evaluateExpr, scope, expr, list)); 17 | }; 18 | 19 | const performFiltering = 20 | (resolve, reject, evaluateExpr, scope, expr, list) => 21 | (filteringFunction) => { 22 | if (filteringFunction.type !== symbols.FUNCTION_REFERENCE) { 23 | reject(new Error(`Invalid function passed to filter. Got ${expr}`)); 24 | } 25 | 26 | const mapCalls = list.value.map( 27 | (listElement, i) => 28 | () => callFunction(evaluateExpr, scope, [listElement, createToken(symbols.NUMBER, i)], filteringFunction.value) 29 | ); 30 | lelSeries(mapCalls) 31 | .then(values => { 32 | const newList = list.value.filter((element, index) => values[index].value); 33 | resolve(createToken(symbols.LIST, newList)) 34 | }); 35 | }; 36 | 37 | module.exports = (evaluateExpr, scope, expr) => 38 | lelPromise((resolve, reject) => 39 | evaluateExpr(scope, expr[1]) 40 | .then(getFilteringFunction(resolve, reject, evaluateExpr, scope, expr)) 41 | ); 42 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/function/index.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../../symbols'); 2 | const scopedFunction = require('./scoped-function'); 3 | const createScope = require('../../../create-scope'); 4 | const createToken = require('../../../../create-token'); 5 | const lelPromise = require('../../../../util/lel-promise'); 6 | 7 | module.exports = (evaluateExpr, scope, expr) => 8 | lelPromise((resolve, reject) => { 9 | if (expr[1].type !== symbols.IDENTIFIER) { 10 | reject(new Error(`Function name must be an IDENTIFIER. Got ${expr[1].type} for ${expr[1].value}`)); 11 | } 12 | const fName = expr[1].value; 13 | const expectedArguments = expr[2] 14 | .map(token => { 15 | if (token.type !== symbols.IDENTIFIER) { 16 | reject(new Error(`Function declaration arguments must be an IDENTIFIER. Got ${token.type} for function ${fName}`)); 17 | } 18 | return token.value; 19 | }); 20 | 21 | const fBody = expr.slice(3); 22 | if (fBody.length < 1) { 23 | reject(new Error(`Function body must contain at least one statement. Got none for function ${fName}`)); 24 | } 25 | 26 | const functionScope = createScope(scope); 27 | scope.variables[fName] = createToken(symbols.FUNCTION_REFERENCE, scopedFunction(fName, fBody, expectedArguments, functionScope)); 28 | resolve(scope.variables[fName]); 29 | }); 30 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/function/scoped-function.js: -------------------------------------------------------------------------------- 1 | module.exports = (name, bodyExpressions = [], expectedArguments = [], scope = {}) => ({ 2 | name, 3 | isFunction: true, 4 | bodyExpressions, 5 | expectedArguments, 6 | scope 7 | }); -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/if.js: -------------------------------------------------------------------------------- 1 | const lelPromise = require('../../../util/lel-promise'); 2 | 3 | module.exports = (evaluateExpr, scope, expr) => 4 | lelPromise((resolve, reject) => { 5 | if (expr.length < 4) { 6 | reject(new Error(`Conditional expressions require 3 arguments. Got ${expr.length} at expression ${JSON.stringify(expr)}`)); 7 | } 8 | 9 | evaluateExpr(scope, expr[1]) 10 | .then(conditionResult => { 11 | const branch = (conditionResult.value) ? expr[2] : expr[3]; 12 | evaluateExpr(scope, branch).then(resolve); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/import.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const symbols = require('../../../symbols'); 3 | const createScope = require('../../create-scope'); 4 | const findBasepath = require('../find-basepath'); 5 | 6 | const validate = require('../../../interpreter/validate'); 7 | const tokenise = require('../../../tokenise'); 8 | const parse = require('../../../parse'); 9 | const readLelFile = require('../../../interpreter/read-lel'); 10 | 11 | const lelPromise = require('../../../util/lel-promise'); 12 | 13 | // Can't import interpreter from here due to circular dependencies, so just put one together from 14 | // the modules. Have to use evaluateExpr directly, and build a scope for it to execute in. 15 | const interpreter = (filename) => 16 | readLelFile(filename) 17 | .then(tokenise) 18 | .then(validate) 19 | .then(parse) 20 | .catch(err => { 21 | console.error(err); 22 | process.exit(1); 23 | }); 24 | 25 | const injectModuleToScope = (scope, moduleScope, filePath) => 26 | lelPromise((resolve, reject, evaluated) => { 27 | const scopeKeys = Object.keys(scope.variables); 28 | const clash = Object.keys(moduleScope.variables).find(msImport => scopeKeys.includes(msImport)); 29 | if (clash) { 30 | return reject(new Error(`Cannot overwrite variable in scope ${clash} from module ${filePath}`)); 31 | } 32 | scope.variables = Object.assign({}, scope.variables, moduleScope.variables); 33 | resolve(evaluated); 34 | }); 35 | 36 | module.exports = (evaluateExpr, scope, expr) => 37 | lelPromise((resolve, reject) => { 38 | // Evaluate the filename 39 | evaluateExpr(scope, expr[1]) 40 | .then(filename => { 41 | if (filename.type === symbols.STRING) { 42 | const filepath = path.join(findBasepath(scope), filename.value); 43 | const basepath = path.parse(path.resolve(filepath)).dir; 44 | 45 | interpreter(filepath) 46 | .then(ast => { 47 | const moduleScope = createScope(null, basepath); 48 | evaluateExpr(moduleScope, ast) 49 | .then(injectModuleToScope(scope, moduleScope, filepath)) 50 | .then(resolve); 51 | }) 52 | .catch(console.error); 53 | } else { 54 | reject(new Error(`Import path must resolve to a string. Got ${filename}`)); 55 | } 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/index.js: -------------------------------------------------------------------------------- 1 | const extensions = require('../../../extensions'); 2 | const lelPromise = require('../../../util/lel-promise'); 3 | 4 | const useFunction = (libName) => { 5 | try { 6 | const lib = require(`./stdlib/${libName}.js`); 7 | Object.keys(lib) 8 | .forEach(exportedFunction => runtime[exportedFunction] = lib[exportedFunction]); 9 | } catch (err) { 10 | if (err.code == 'MODULE_NOT_FOUND') { 11 | throw new Error(`No standard library '${libName} in Lel.`); 12 | return; 13 | } 14 | throw new Error(`Error: ${err.message}`); 15 | } 16 | }; 17 | 18 | const getFunction = (keyword) => runtime[keyword]; 19 | 20 | const runtime = Object.assign( 21 | { 22 | if: require('./if'), 23 | let: require('./let'), 24 | function: require('./function'), 25 | lambda: require('./lambda'), 26 | list: require('./list'), 27 | map: require('./map'), 28 | filter: require('./filter'), 29 | call: require('./call'), 30 | apply: require('./apply'), 31 | mutate: require('./mutate'), 32 | import: require('./import'), 33 | use: require('./use')(useFunction) 34 | }, 35 | // Extensions can override standard language functionality 36 | extensions 37 | ); 38 | 39 | module.exports = { 40 | get: getFunction, 41 | use: useFunction 42 | }; 43 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/lambda.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const scopedFunction = require('./function/scoped-function'); 3 | const createScope = require('../../create-scope'); 4 | const createToken = require('../../../create-token'); 5 | 6 | const lelPromise = require('../../../util/lel-promise'); 7 | const lelSeries = require('../../../util/lel-series'); 8 | 9 | module.exports = (evaluateExpr, scope, expr) => 10 | lelPromise((resolve, reject) => { 11 | const fName = 'lambda_function'; 12 | const expectedArguments = expr[1] 13 | .map(token => { 14 | if (token.type !== symbols.IDENTIFIER) { 15 | reject(new Error(`Function declaration arguments must be an IDENTIFIER. Got ${token.type} for function ${fName}`)); 16 | } 17 | return token.value; 18 | }); 19 | 20 | const fBody = expr.slice(2); 21 | if (fBody.length < 1) { 22 | reject(new Error(`Function body must contain at least one statement. Got none for function ${fName}`)); 23 | } 24 | 25 | const functionScope = createScope(scope); 26 | resolve(createToken(symbols.FUNCTION_REFERENCE, scopedFunction(fName, fBody, expectedArguments, functionScope))); 27 | }); 28 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/let.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const lelPromise = require('../../../util/lel-promise'); 3 | 4 | module.exports = (evaluateExpr, scope, expr) => 5 | lelPromise((resolve, reject) => { 6 | if (expr[1].type !== symbols.IDENTIFIER) { 7 | reject(new Error(`Variable name must be an IDENTIFER. Got ${expr[1].type} for ${expr[1].value}`)); 8 | } 9 | const name = expr[1].value; 10 | evaluateExpr(scope, expr[2]) 11 | .then(value => { 12 | if (name in scope.variables) { 13 | reject(new Error(`Can't implicitly mutate previously assigned scoped variable '${name}'`)); 14 | } 15 | scope.variables[name] = value; 16 | resolve(value); 17 | }); 18 | }); -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/list.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const createToken = require('../../../create-token'); 3 | const lelPromise = require('../../../util/lel-promise'); 4 | const lelPromiseAll = require('../../../util/lel-promise-all'); 5 | 6 | const expandRanges = values => { 7 | const foundRange = values.some(value => value.type === symbols.RANGE); 8 | if (foundRange) { 9 | if (values.length !== 3) { 10 | throw new Error('List with range operator requires exactly 3 arguments'); 11 | } 12 | 13 | const prev = values[0]; 14 | const next = values[2]; 15 | if (prev.type !== symbols.NUMBER || next.type !== symbols.NUMBER) { 16 | throw new Error('Cannot make range from non-number'); 17 | } 18 | 19 | const rangeDirection = (prev.value < next.value); 20 | const start = (rangeDirection) ? prev.value : next.value; 21 | const end = (rangeDirection) ? next.value : prev.value; 22 | const expandedRange = []; 23 | 24 | for (let j = start; j <= end; j++) { 25 | expandedRange.push(createToken(symbols.NUMBER, j)); 26 | } 27 | if (!rangeDirection) expandedRange.reverse(); 28 | return createToken(symbols.LIST, expandedRange); 29 | } 30 | 31 | return createToken(symbols.LIST, values); 32 | }; 33 | 34 | module.exports = (evaluateExpr, scope, expr) => 35 | lelPromise((resolve, reject) => { 36 | lelPromiseAll(expr.slice(1).map(subExpr => evaluateExpr(scope, subExpr))) 37 | .then(expandRanges) 38 | .then(resolve); 39 | }); 40 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/map.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const createToken = require('../../../create-token'); 3 | const callFunction = require('./call-function'); 4 | 5 | const lelPromise = require('../../../util/lel-promise'); 6 | const lelSeries = require('../../../util/lel-series'); 7 | 8 | const getMappingFunction = 9 | (resolve, reject, evaluateExpr, scope, expr) => 10 | (list) => { 11 | if (list.type !== symbols.LIST) { 12 | reject(new Error(`Invalid list passed to map. Got ${expr}`)); 13 | } 14 | 15 | evaluateExpr(scope, expr[2]).then(performMapping(resolve, reject, evaluateExpr, scope, expr, list)); 16 | }; 17 | 18 | const performMapping = 19 | (resolve, reject, evaluateExpr, scope, expr, list) => 20 | (mappingFunction) => { 21 | if (mappingFunction.type !== symbols.FUNCTION_REFERENCE) { 22 | reject(new Error(`Invalid function passed to map. Got ${expr}`)); 23 | } 24 | 25 | const mapCalls = list.value.map( 26 | (listElement, i) => 27 | () => callFunction(evaluateExpr, scope, [listElement, createToken(symbols.NUMBER, i)], mappingFunction.value) 28 | ); 29 | lelSeries(mapCalls).then(values => resolve(createToken(symbols.LIST, values))); 30 | }; 31 | 32 | module.exports = (evaluateExpr, scope, expr) => 33 | lelPromise((resolve, reject) => 34 | evaluateExpr(scope, expr[1]).then(getMappingFunction(resolve, reject, evaluateExpr, scope, expr)) 35 | ); 36 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/mutate.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const lelPromise = require('../../../util/lel-promise'); 3 | 4 | module.exports = (evaluateExpr, scope, expr) => 5 | lelPromise((resolve, reject) => { 6 | if (expr[1].type !== symbols.IDENTIFIER) { 7 | reject(new Error(`Variable name must be an IDENTIFIER. Got ${expr[1].type} for ${expr[1].value}`)); 8 | } 9 | evaluateExpr(scope, expr[2]) 10 | .then(value => { 11 | const name = expr[1].value; 12 | if (!(name in scope.variables)) { 13 | reject(new Error(`No variable '${name}' to mutate in the local scope`)); 14 | } 15 | 16 | scope.variables[name] = value; 17 | resolve(value); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/standard-language-functions/boolean.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../../../../create-token'); 2 | const symbols = require('../../../../symbols'); 3 | 4 | module.exports = { 5 | '=': (x, y) => { 6 | return Promise.resolve(createToken(symbols.BOOLEAN, x.value === y.value)); 7 | }, 8 | '<': (x, y) => { 9 | return Promise.resolve(createToken(symbols.BOOLEAN, x.value < y.value)); 10 | }, 11 | '>': (x, y) => { 12 | return Promise.resolve(createToken(symbols.BOOLEAN, x.value > y.value)); 13 | }, 14 | '<=': (x, y) => { 15 | return Promise.resolve(createToken(symbols.BOOLEAN, x.value <= y.value)); 16 | }, 17 | '>=': (x, y) => { 18 | return Promise.resolve(createToken(symbols.BOOLEAN, x.value >= y.value)); 19 | }, 20 | // NOT 21 | } -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/standard-language-functions/general.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../../../../create-token'); 2 | const symbols = require('../../../../symbols'); 3 | 4 | const EMPTY_STRING = createToken(symbols.STRING, ''); 5 | const EMPTY_LIST = createToken(symbols.LIST); 6 | 7 | const prettyString = (token) => { 8 | if ([symbols.NUMBER, symbols.BOOLEAN, symbols.STRING].includes(token.type)) { 9 | return token.value.toString(); 10 | } else if (token.type === symbols.LIST) { 11 | return `(${token.value.map(prettyString).join(', ')})`; 12 | } 13 | return token.toString(); 14 | }; 15 | 16 | module.exports = { 17 | exit: (codeToken) => { 18 | const code = (codeToken && codeToken.type === symbols.NUMBER) 19 | ? codeToken.value 20 | : 0; 21 | process.exit(code); 22 | }, 23 | print: (...items) => { 24 | const out = items 25 | .map(prettyString) 26 | .join(''); 27 | process.stdout.write(out); 28 | return Promise.resolve(createToken(symbols.STRING, out)); 29 | }, 30 | cls: () => { 31 | process.stdout.write('\033c'); 32 | return Promise.resolve(EMPTY_LIST); 33 | }, 34 | concat: (...concatables) => { 35 | if (concatables.length) { 36 | const type = concatables[0].type; 37 | const allSameType = concatables.filter(concatable => concatable.type !== type).length === 0; 38 | 39 | if (type === symbols.LIST && allSameType) { 40 | const concatenatedLists = concatables.reduce((acc, list) => 41 | [...acc, ...list.value] 42 | , [] 43 | ); 44 | return Promise.resolve(createToken(symbols.LIST, concatenatedLists)); 45 | } else { 46 | return Promise.resolve(concatables.reduce( 47 | (acc, cur) => { 48 | const next = ([symbols.STRING, symbols.NUMBER, symbols.BOOLEAN].indexOf(cur.type) !== -1) 49 | ? cur.value 50 | : cur.toString(); 51 | return createToken(symbols.STRING, acc.value + next); 52 | }, EMPTY_STRING 53 | )); 54 | } 55 | } 56 | return Promise.resolve(createToken(symbols.LIST, [])); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/standard-language-functions/index.js: -------------------------------------------------------------------------------- 1 | const general = require('./general'); 2 | const boolean = require('./boolean'); 3 | const math = require('./math'); 4 | const list = require('./list'); 5 | const string = require('./string'); 6 | 7 | module.exports = Object.assign( 8 | {}, 9 | general, 10 | boolean, 11 | math, 12 | list, 13 | string 14 | ); 15 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/standard-language-functions/list.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../../../../create-token'); 2 | const symbols = require('../../../../symbols'); 3 | const lelReject = require('../../../../util/lel-reject'); 4 | 5 | module.exports = { 6 | // List functions 7 | join: (list) => { 8 | if (list.type !== symbols.LIST) { 9 | return lelReject(new Error(`Can't join non-lists. Got ${list.type}`)); 10 | } 11 | return Promise.resolve(createToken( 12 | symbols.STRING, 13 | list.value 14 | .map(listItem => listItem.value) 15 | .join('') 16 | )); 17 | }, 18 | length: (list) => { 19 | if (list.type === symbols.LIST) { 20 | return Promise.resolve(createToken(symbols.NUMBER, list.value.length)); 21 | } 22 | return lelReject(new Error(`length operates on LIST type. Got ${list.type}`)); 23 | }, 24 | head: (list) => { 25 | if (list.type === symbols.LIST) { 26 | const listHead = list.value.slice(0,1)[0]; 27 | return Promise.resolve((typeof listHead !== 'undefined') 28 | ? listHead 29 | : createToken(symbols.LIST, [])); 30 | } 31 | return lelReject(new Error(`head operates on LIST type. Got ${list.type}`)); 32 | }, 33 | tail: (list) => { 34 | // Check is list 35 | if (list.type === symbols.LIST) { 36 | return Promise.resolve(createToken(symbols.LIST, list.value.slice(1))); 37 | } 38 | return lelReject(new Error(`tail operates on LIST type. Got ${list.type}`)); 39 | }, 40 | nth: (list, n) => { 41 | if (list.type === symbols.LIST) { 42 | if (n.value >= 0 && n.value < list.value.length) { 43 | return Promise.resolve(list.value.slice(n.value, n.value + 1)[0]); 44 | } 45 | return lelReject(new Error(`nth: bad index ${n.value}. Given list has ${list.value.length} elements`)); 46 | } 47 | return lelReject(new Error(`nth operates on LIST type. Got ${list.type}`)); 48 | }, 49 | sublist: (list, start, end) => { 50 | if (list.type === symbols.LIST) { 51 | const listLength = list.value.length; 52 | const s = start.value; 53 | const e = end.value; 54 | 55 | if (s > e) { 56 | return lelReject(new Error(`start index cannot be greater than the end index for a sublist`)); 57 | } 58 | 59 | if (s < 0 || e > listLength - 1) { 60 | return lelReject(new Error(`sublist indexes out of range. Got start (${s}) end (${e}) for list of length ${listLength}`)); 61 | } 62 | 63 | return Promise.resolve(createToken(symbols.LIST, list.value.slice(s, e+1))); 64 | } 65 | return lelReject(new Error(`sublist operates on LIST type. Got ${list.type}`)); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/standard-language-functions/math.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../../../../create-token'); 2 | const symbols = require('../../../../symbols'); 3 | 4 | const ZERO = createToken(symbols.NUMBER, 0); 5 | const ONE = createToken(symbols.NUMBER, 1); 6 | 7 | const lelReject = require('../../../../util/lel-reject'); 8 | 9 | module.exports = { 10 | '+': (...numbers) => { 11 | if (numbers.filter(n => n.type !== symbols.NUMBER).length !== 0) { 12 | return Promise 13 | .reject(new Error(`+ only operates on NUMBER type`)) 14 | .catch(console.error); 15 | } 16 | return Promise.resolve(numbers.reduce( 17 | (acc, cur) => 18 | createToken(symbols.NUMBER, acc.value + cur.value) 19 | , ZERO)); 20 | }, 21 | '-': (x, y) => { 22 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 23 | return Promise 24 | .reject(new Error(`- only operates on NUMBER type`)) 25 | .catch(console.error); 26 | } 27 | return Promise.resolve(createToken(symbols.NUMBER, x.value - y.value)); 28 | }, 29 | '/': (x, y) => { 30 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 31 | return Promise 32 | .reject(new Error(`/ only operates on NUMBER type`)) 33 | .catch(console.error); 34 | } 35 | if (y.value === 0) { 36 | return Promise 37 | .reject(new Error(`Divison by zero error`)) 38 | .catch(console.error); 39 | } 40 | return Promise.resolve(createToken(symbols.NUMBER, x.value / y.value)); 41 | }, 42 | '*': (...numbers) => { 43 | if (numbers.filter(n => n.type !== symbols.NUMBER).length !== 0) { 44 | return Promise 45 | .reject(new Error(`* only operates on NUMBER type`)) 46 | .catch(console.error); 47 | } 48 | return Promise.resolve(numbers.reduce( 49 | (acc, cur) => 50 | createToken(symbols.NUMBER, acc.value * cur.value) 51 | , ONE)); 52 | }, 53 | '**': (x, y) => { 54 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 55 | return Promise 56 | .reject(new Error(`** only operates on NUMBER type`)) 57 | .catch(console.error); 58 | } 59 | return Promise.resolve(createToken(symbols.NUMBER, Math.pow(x.value, y.value))); 60 | }, 61 | '%': (x, y) => { 62 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 63 | return Promise 64 | .reject(new Error(`% only operates on NUMBER type`)) 65 | .catch(console.error); 66 | } 67 | if (y.value === 0) { 68 | return Promise 69 | .reject(new Error(`Divison by zero error`)) 70 | .catch(console.error); 71 | } 72 | return Promise.resolve(createToken(symbols.NUMBER, x.value % y.value)); 73 | }, 74 | '<<': (x, y) => { 75 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 76 | return lelReject(new Error(`<< only operates on NUMBER type`)); 77 | } 78 | return Promise.resolve(createToken(symbols.NUMBER, x.value << y.value)); 79 | }, 80 | '>>': (x, y) => { 81 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 82 | return lelReject(new Error(`>> only operates on NUMBER type`)); 83 | } 84 | return Promise.resolve(createToken(symbols.NUMBER, x.value >> y.value)); 85 | }, 86 | '|': (x, y) => { 87 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 88 | return lelReject(new Error(`| only operates on NUMBER type`)); 89 | } 90 | return Promise.resolve(createToken(symbols.NUMBER, x.value | y.value)); 91 | }, 92 | '&': (x, y) => { 93 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 94 | return lelReject(new Error(`& only operates on NUMBER type`)); 95 | } 96 | return Promise.resolve(createToken(symbols.NUMBER, x.value & y.value)); 97 | }, 98 | '^': (x, y) => { 99 | if (x.type !== symbols.NUMBER || y.type !== symbols.NUMBER) { 100 | return lelReject(new Error(`^ only operates on NUMBER type`)); 101 | } 102 | return Promise.resolve(createToken(symbols.NUMBER, x.value ^ y.value)); 103 | }, 104 | '~': (x) => { 105 | if (x.type !== symbols.NUMBER) { 106 | return lelReject(new Error(`^ only operates on NUMBER type`)); 107 | } 108 | return Promise.resolve(createToken(symbols.NUMBER, ~x.value)); 109 | } 110 | } -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/standard-language-functions/string.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../../../../create-token'); 2 | const symbols = require('../../../../symbols'); 3 | 4 | module.exports = { 5 | split: (string) => { 6 | if (string.type !== symbols.STRING) { 7 | return Promise 8 | .reject(new Error(`Can't split non-strings. Got ${string.type}`)) 9 | .catch(console.error); 10 | } 11 | return Promise.resolve(createToken( 12 | symbols.LIST, 13 | string.value 14 | .split('') 15 | .map(char => createToken(symbols.STRING, char))) 16 | ); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/stdlib/math.js: -------------------------------------------------------------------------------- 1 | const createToken = require('../../../../create-token'); 2 | const symbols = require('../../../../symbols'); 3 | const lelPromise = require('../../../../util/lel-promise'); 4 | const lelPromiseAll = require('../../../../util/lel-promise-all'); 5 | 6 | module.exports = { 7 | 'math-sin': (evaluateExpr, scope, expr) => 8 | lelPromise((resolve, reject) => { 9 | return evaluateExpr(scope, expr[1]).then(number => { 10 | if (number.type !== symbols.NUMBER) { 11 | return reject(new Error(`math-sin only operates on NUMBER type`)); 12 | } 13 | return resolve(createToken(symbols.NUMBER, Math.sin(number.value))) 14 | }); 15 | }), 16 | 'math-cos': (evaluateExpr, scope, expr) => 17 | lelPromise((resolve, reject) => { 18 | return evaluateExpr(scope, expr[1]).then(number => { 19 | if (number.type !== symbols.NUMBER) { 20 | return reject(new Error(`math-cos only operates on NUMBER type`)); 21 | } 22 | return resolve(createToken(symbols.NUMBER, Math.cos(number.value))) 23 | }); 24 | }) 25 | }; 26 | -------------------------------------------------------------------------------- /src/evaluate/evaluate-expr/language-core/use.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../../../symbols'); 2 | const lelPromise = require('../../../util/lel-promise'); 3 | 4 | module.exports = (use) => 5 | (evaluateExpr, scope, expr) => 6 | lelPromise((resolve, reject) => { 7 | // Evaluate the libName 8 | evaluateExpr(scope, expr[1]) 9 | .then(libName => { 10 | if (libName.type === symbols.STRING) { 11 | use(libName.value); 12 | return resolve(libName); 13 | } else { 14 | reject(new Error(`use: library name must resolve to a string. Got ${libName}`)); 15 | } 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/evaluate/index.js: -------------------------------------------------------------------------------- 1 | const evaluateExpr = require('./evaluate-expr'); 2 | const lelPromise = require('../util/lel-promise'); 3 | const lelSeries = require('../util/lel-series'); 4 | 5 | module.exports = (ast, basepath = __dirname) => { 6 | const rootScope = require('./create-scope')(null, basepath); 7 | return lelPromise((resolve, reject) => { 8 | const astEvaluators = ast.map((expr) => () => evaluateExpr(rootScope, expr)); 9 | lelSeries(astEvaluators).then(resolve); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/extensions/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'read-file-sync': require('./read-file-sync') 3 | }; -------------------------------------------------------------------------------- /src/extensions/read-file-sync.js: -------------------------------------------------------------------------------- 1 | const {readFileSync} = require('fs'); 2 | const path = require('path'); 3 | const findBasepath = require('../evaluate/evaluate-expr/find-basepath'); 4 | 5 | const symbols = require('../symbols'); 6 | const createToken = require('../create-token'); 7 | const lelPromise = require('../util/lel-promise'); 8 | const lelPromiseAll = require('../util/lel-promise-all'); 9 | 10 | module.exports = (evaluateExpr, scope, expr) => 11 | lelPromise((resolve, reject) => { 12 | if (!(expr[1] && expr[2])) { 13 | reject(new Error(`Requires filename and encoding. Got ${expr[1]} and ${expr[2]}`)); 14 | } 15 | 16 | lelPromiseAll([evaluateExpr(scope, expr[1]), evaluateExpr(scope, expr[2])]) 17 | .then(results => { 18 | if (results[0].type === symbols.STRING && results[1].type === symbols.STRING) { 19 | resolve(readFileSync(path.resolve(findBasepath(scope), results[0].value), results[1].value)); 20 | } else { 21 | reject(new Error(`Requires filename and encoding to be strings. Got ${results[0]} and ${results[1]}`)); 22 | } 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | const argv = process.argv; 4 | const repl = require('./repl'); 5 | const interpreter = require('./interpreter'); 6 | 7 | if (argv.length < 3) { 8 | repl(); 9 | } else { 10 | interpreter(argv[2]).then(() => process.exit(0)); 11 | } -------------------------------------------------------------------------------- /src/interpreter/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const validate = require('./validate'); 4 | const tokenise = require('../tokenise'); 5 | const parse = require('../parse'); 6 | const evaluate = require('../evaluate'); 7 | const readLelFile = require('./read-lel'); 8 | 9 | module.exports = 10 | (filename) => 11 | readLelFile(filename) 12 | .then(tokenise) 13 | .then(validate) 14 | .then(parse) 15 | .then(ast => { 16 | const basepath = path.parse(path.resolve(filename)).dir; 17 | return evaluate(ast, basepath); 18 | }) 19 | .catch(err => { 20 | console.error(err); 21 | process.exit(1); 22 | }); -------------------------------------------------------------------------------- /src/interpreter/read-lel.js: -------------------------------------------------------------------------------- 1 | const {promisify} = require('util'); 2 | const readFileAsync = promisify(require('fs').readFile); 3 | 4 | module.exports = (filename) => 5 | readFileAsync(filename, 'utf8') 6 | .catch(console.error); -------------------------------------------------------------------------------- /src/interpreter/validate.js: -------------------------------------------------------------------------------- 1 | const { 2 | LPAREN, 3 | RPAREN 4 | } = require('../symbols'); 5 | 6 | // Ensure matching parentheses 7 | module.exports = (tokens) => { 8 | const left = tokens.filter(token => token.type === LPAREN).length; 9 | const right = tokens.filter(token => token.type === RPAREN).length; 10 | if (left !== right) { 11 | throw new Error('Unmatched parentheses.'); 12 | } 13 | return tokens; 14 | }; -------------------------------------------------------------------------------- /src/parse/clean.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../symbols'); 2 | 3 | const unescapeCharacters = (str) => 4 | str 5 | .replace(/\\n/g, '\n') 6 | .replace(/\\r/g, '\r') 7 | .replace(/\\f/g, '\f') 8 | .replace(/\\b/g, '\b') 9 | .replace(/\\t/g, '\t') 10 | .replace(/\\v/g, '\v') 11 | .replace(/\\;/, ';'); 12 | 13 | const cleanString = (str) => unescapeCharacters(str.slice(1, str.length - 1)); 14 | const cleanBool = (bool) => (bool === 'true') ? true : false; 15 | const cleanNumber = (number) => parseFloat(number); 16 | 17 | module.exports = (token) => { 18 | if (token.type === symbols.STRING) { 19 | token.value = cleanString(token.value); 20 | } else if (token.type === symbols.NUMBER) { 21 | token.value = cleanNumber(token.value); 22 | } else if (token.type === symbols.BOOLEAN) { 23 | token.value = cleanBool(token.value); 24 | } 25 | return token; 26 | }; 27 | -------------------------------------------------------------------------------- /src/parse/index.js: -------------------------------------------------------------------------------- 1 | const symbols = require('../symbols'); 2 | const createToken = require('../create-token'); 3 | const cleanToken = require('./clean'); 4 | 5 | let depthPointer = 0; 6 | const addTokenToExprTree = (ast, token) => { 7 | let level = ast; 8 | for (let i = 0; i < depthPointer; i++) { 9 | //set the level to the rightmost deepest branch 10 | level = level[level.length - 1]; 11 | } 12 | level.push(token); 13 | }; 14 | 15 | const popExpr = () => depthPointer--; 16 | const pushExpr = (ast) => { 17 | addTokenToExprTree(ast, []); 18 | depthPointer++; 19 | }; 20 | 21 | module.exports = (_tokens) => { 22 | // Reset depth pointer 23 | depthPointer = 0; 24 | const tokens = _tokens 25 | .map(t => Object.assign({}, t)) 26 | .map(cleanToken); 27 | let ast = []; 28 | 29 | for (let i = 0; i < tokens.length; i++) { 30 | const token = tokens[i]; 31 | 32 | if (token.type === symbols.LPAREN) { 33 | pushExpr(ast); 34 | continue; 35 | } else if (token.type === symbols.RPAREN) { 36 | popExpr(); 37 | continue; 38 | } 39 | 40 | addTokenToExprTree(ast, token); 41 | } 42 | return ast; 43 | }; 44 | -------------------------------------------------------------------------------- /src/patterns.js: -------------------------------------------------------------------------------- 1 | const symbols = require('./symbols'); 2 | 3 | const number = [/^\-?[0-9]+\.?[0-9]*$/, symbols.NUMBER]; 4 | const string = [/^\"[^\n\"]*\"$/, symbols.STRING]; 5 | const whitespace = [/^[\s\n]+$/, symbols.SKIP]; 6 | const comment = [/^;.+?\n$/, symbols.SKIP]; 7 | const identifier = [/^[a-zA-Z\+\-\/\*\%\_\>\<\&\^\~\|=]*$/, symbols.IDENTIFIER]; 8 | const boolTrue = ['true', symbols.BOOLEAN]; 9 | const boolFalse = ['false', symbols.BOOLEAN]; 10 | const lparen = ['(', symbols.LPAREN]; 11 | const rparen = [')', symbols.RPAREN]; 12 | const range = ['..', symbols.RANGE]; 13 | 14 | module.exports = { 15 | ambiguous: [ 16 | [/^\-$/, number] 17 | ], 18 | exact: [ 19 | boolTrue, 20 | boolFalse, 21 | lparen, 22 | rparen, 23 | range 24 | ], 25 | tokens: [ 26 | whitespace, 27 | comment, 28 | number, 29 | string, 30 | identifier 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /src/repl/index.js: -------------------------------------------------------------------------------- 1 | const repl = require('./repl')(); 2 | const parenthesesBalance = require('./parentheses-balance'); 3 | const readline = require('readline'); 4 | const rl = readline.createInterface({ 5 | input: process.stdin, 6 | output: process.stdout 7 | }); 8 | 9 | module.exports = () => { 10 | process.stdout.write('Lel REPL - Francis Stokes 2017\n> '); 11 | let expr = ''; 12 | 13 | rl.on('line', (input) => { 14 | expr += input; 15 | const balance = parenthesesBalance(expr); 16 | if (expr !== '') { 17 | if (balance === 0) { 18 | repl(expr); 19 | expr = ''; 20 | } else if (balance === -1) { 21 | // Not enough closing parens, wait for the expression to be completed 22 | process.stdout.write('> '); 23 | expr += ' '; 24 | } else { 25 | console.error(`Too many ')'!`); 26 | expr = ''; 27 | process.stdout.write('> '); 28 | } 29 | } else { 30 | process.stdout.write('> '); 31 | } 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/repl/parentheses-balance.js: -------------------------------------------------------------------------------- 1 | module.exports = (str) => { 2 | const l = (str.match(/\(/g) || []).length; 3 | const r = (str.match(/\)/g) || []).length; 4 | return (l === r) 5 | ? 0 6 | : (l > r) 7 | ? -1 8 | : 1; 9 | }; -------------------------------------------------------------------------------- /src/repl/repl.js: -------------------------------------------------------------------------------- 1 | const tokenise = require('../tokenise'); 2 | const parse = require('../parse'); 3 | const createScope = require('../evaluate/create-scope'); 4 | const evaluateExpr = require('../evaluate/evaluate-expr'); 5 | const {print} = require('../evaluate/evaluate-expr/language-core/standard-language-functions'); 6 | 7 | module.exports = () => { 8 | const rootScope = createScope(null, process.cwd()); 9 | return (exprStr) => { 10 | Promise 11 | .resolve(exprStr) 12 | .then(tokenise) 13 | .then(parse) 14 | .then(ast => { 15 | evaluateExpr(rootScope, ast[0]) 16 | .then(result => { 17 | console.log(); 18 | print(result).then(r => { 19 | process.stdout.write('\n> '); 20 | }); 21 | }); 22 | }) 23 | .catch(console.error); 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/symbols.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Whitespace skips 3 | SKIP: 'SKIP', 4 | 5 | // List operator expanded in parser 6 | RANGE: 'RANGE', 7 | 8 | // Expression start/end 9 | LPAREN: 'LPAREN', 10 | RPAREN: 'RPAREN', 11 | 12 | // Primitives 13 | NUMBER: 'NUMBER', 14 | STRING: 'STRING', 15 | BOOLEAN: 'BOOLEAN', 16 | IDENTIFIER: 'IDENTIFIER', 17 | 18 | // Used internally for passing functions 19 | FUNCTION_REFERENCE: 'FUNCTION_REFERENCE', 20 | 21 | // Used internally for describing lists 22 | LIST: 'LIST' 23 | }; 24 | -------------------------------------------------------------------------------- /src/tokenise.js: -------------------------------------------------------------------------------- 1 | const createToken = require('./create-token'); 2 | const patterns = require('./patterns'); 3 | const { SKIP, EOF } = require('./symbols'); 4 | 5 | module.exports = (inString) => { 6 | const tokens = []; 7 | const chars = inString.split(''); 8 | 9 | let check = ''; 10 | 11 | for (let i = 0; i < chars.length; i++) { 12 | check += chars[i]; 13 | 14 | // Check against exact patterns first 15 | if (check.length === 1) { 16 | const matchedExactPattern = patterns.exact.some(ep => { 17 | const [exactStr, symbol] = ep; 18 | if (i + exactStr.length <= chars.length) { 19 | const exactCheck = check + chars.slice(i + 1, i + exactStr.length).join(''); 20 | 21 | if (exactCheck === exactStr) { 22 | // Set the new i pointer 23 | i += exactStr.length - 1; 24 | 25 | // Add the token to the list 26 | if (symbol !== SKIP) { 27 | tokens.push(createToken(symbol, exactCheck)); 28 | } 29 | // Reset the check string 30 | check = ''; 31 | // Exit from the token search 32 | return true; 33 | } 34 | } 35 | return false; 36 | }); 37 | 38 | if (matchedExactPattern) continue; 39 | } 40 | 41 | // Perform an ambiguous check to prioritise a pattern match 42 | if (check.length === 1 && i < chars.length - 1) { 43 | patterns.ambiguous.some(ap => { 44 | if (ap[0].test(check)) { 45 | return ap.slice(1).some(tokenPattern => { 46 | if (tokenPattern[0].test(check) || tokenPattern[0].test(check + chars[i + 1])) { 47 | check += chars[++i]; 48 | return true; 49 | } 50 | }); 51 | } 52 | return false; 53 | }); 54 | } 55 | 56 | // Test for tokens until a sucessful one is found 57 | patterns.tokens.some(tr => { 58 | const [regex, label] = tr; 59 | 60 | // Test if it's a token match 61 | if (regex.test(check)) { 62 | if (i === chars.length - 1) { 63 | // Add the token to the list 64 | if (label !== SKIP) { 65 | tokens.push(createToken(label, check)); 66 | } 67 | return true; 68 | } 69 | // Peek ahead at the next charcters while it's still matching the same token 70 | let peekCheck = check; 71 | for (let j = i + 1; j < chars.length; j++) { 72 | peekCheck += chars[j]; 73 | 74 | // Does checking with another character still match? 75 | if (!regex.test(peekCheck)) { 76 | // If not, consider everything up until the last peekCheck the token 77 | check = peekCheck.slice(0, peekCheck.length - 1); 78 | 79 | // Set tokeniser index to this point 80 | i = j - 1; 81 | 82 | // Add the token to the list 83 | if (label !== SKIP) { 84 | tokens.push(createToken(label, check)); 85 | } 86 | // Reset the check string 87 | check = ''; 88 | // Exit from the token search 89 | return true; 90 | } 91 | } 92 | } 93 | }); 94 | } 95 | 96 | return tokens; 97 | }; 98 | -------------------------------------------------------------------------------- /src/util/lel-promise-all.js: -------------------------------------------------------------------------------- 1 | const defaultErr = (err) => console.error(err.message); 2 | 3 | module.exports = (promiseFuncs, catchAllFunc = defaultErr) => { 4 | const p = Promise.all(promiseFuncs).catch(catchAllFunc); 5 | return { 6 | then: (func) => p.then(func).catch(catchAllFunc), 7 | finally: (func) => p.finally(func).catch(catchAllFunc) 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/util/lel-promise.js: -------------------------------------------------------------------------------- 1 | const defaultErr = (err) => console.error(err.message); 2 | 3 | module.exports = (promiseFunc, catchAllFunc = defaultErr) => { 4 | const p = new Promise(promiseFunc).catch(catchAllFunc); 5 | return { 6 | then: (func) => p.then(func).catch(catchAllFunc), 7 | finally: (func) => p.finally(func).catch(catchAllFunc) 8 | }; 9 | }; -------------------------------------------------------------------------------- /src/util/lel-reject.js: -------------------------------------------------------------------------------- 1 | const defaultErr = (err) => console.error(err.message); 2 | 3 | module.exports = (err, catchAllFunc = defaultErr) => 4 | Promise.reject(err).catch(catchAllFunc); -------------------------------------------------------------------------------- /src/util/lel-series.js: -------------------------------------------------------------------------------- 1 | const {mapSeries} = require('bluebird'); 2 | const defaultErr = (err) => console.error(err.message); 3 | 4 | module.exports = (promiseFunctions, catchAllFunc = defaultErr) => 5 | mapSeries(promiseFunctions, (promiseGetter) => promiseGetter()) 6 | .catch(catchAllFunc); --------------------------------------------------------------------------------