├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bin └── ait-lang.js ├── ffi.js ├── lib ├── aitFault.js ├── parser │ ├── ast-walkers.js │ ├── ast.js │ ├── parse.js │ └── test │ │ ├── parse-defintion.js │ │ ├── parse-load.js │ │ ├── parse-primitive.js │ │ ├── parse-quotation.js │ │ ├── parse-variable.js │ │ └── parse-word.js ├── runtime │ ├── browser.js │ ├── lexicon.js │ ├── moduleLoader.js │ ├── node.js │ ├── runtime.js │ ├── stack.js │ └── test │ │ └── stack.js └── stdlib │ ├── aggregate.js │ ├── controlflow.js │ ├── index.js │ ├── internal.js │ ├── iterators.js │ ├── logic.js │ ├── math.js │ ├── quotations.js │ ├── recursion.js │ ├── stack.js │ ├── string.js │ ├── test │ ├── _runTest.js │ ├── aggregate.js │ ├── controlflow.js │ ├── definitions.js │ ├── iterators.js │ ├── logic.js │ ├── math.js │ ├── quotations.js │ ├── recursion.js │ ├── stack.js │ ├── string.js │ └── variables.js │ └── utils │ └── program.js ├── package-lock.json ├── package.json ├── runtimes ├── browser.js └── node.js └── util └── atom-language-ait ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── grammars └── ait.cson ├── package.json ├── settings └── language-ait.cson └── snippets └── language-ait.cson /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "node": true 6 | }, 7 | "extends": "defaults", 8 | "parser": "babel-eslint", 9 | "rules": { 10 | "strict": 0, 11 | "import/no-duplicates": 2, 12 | "no-duplicate-imports": 0, 13 | "no-use-before-define": 0, 14 | "func-names": 0, 15 | "prefer-arrow-callback": 0, 16 | "no-case-declarations": 0, 17 | "comma-dangle": 0, 18 | "no-console": 0, 19 | "consistent-return": 0, 20 | "no-param-reassign": 0, 21 | "space-before-function-paren": 0, 22 | "import/no-extraneous-dependencies": [2, { devDependencies: true }], 23 | "flowtype/define-flow-type": 1, 24 | "flowtype/use-flow-type": 1 25 | }, 26 | "parserOptions": { 27 | "sourceType": "module" 28 | }, 29 | "plugins": [ 30 | "import", 31 | "flowtype" 32 | ], 33 | "globals": { 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | suppress_comment= \\(.\\|\n\\)*\\@FlowIgnore 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | examples/**/*.png 4 | *.log 5 | dist/ 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Stian Veum Møllersen 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 | # Ait 2 | 3 | > Ait or Aisteach (ie) - Strange, unusual, curious, weird, funny 4 | 5 | Programming language for visual and creative experiments on the web. Heavily 6 | influenced by the Forth-family of languages. 7 | 8 | The language and runtime is still in a very **experimental** state. 9 | 10 | ## Installation 11 | 12 | Requirements: [Node v6 or above](https://nodejs.org) 13 | 14 | ### Recommended approach 15 | 16 | Install with npm `npm install [-g] ait-lang`, use `-g` if you want to add it as a global executable. 17 | 18 | ### Build from source 19 | 20 | Clone repo and run `npm i && npm run build` and an executable should appear in `ait-lang/bin`. 21 | 22 | ## Usage 23 | 24 | After installing (or building from source) you can use ait-lang to interpret your `.ait`-files like this: 25 | 26 | ``` 27 | > ait-lang source.ait 28 | ``` 29 | 30 | ### Browser 31 | 32 | You can also run `ait-lang` in your browser. It involves a bit more setting up (this is still a work in progress): 33 | 34 | ```js 35 | const fs = require('fs'); 36 | 37 | const Browser = require('ait-lang/runtimes/browser'); 38 | const src = fs.readFileSync('YOUR SOURCE HERE', 'utf8'); 39 | 40 | const runtime = Browser(); 41 | // Load some liberaries 42 | Object.assign(runtime.lexicon, require('ait-canvas')); 43 | Object.assign(runtime.lexicon, require('ait-dom')); 44 | 45 | runtime.evaluate(src); 46 | 47 | // Grab the canvas from the runtime 48 | // It's a bit roundabout, working on a better solution 49 | document.body.appendChild(runtime.scope['__aitCanvasContext'].body.canvas); 50 | ``` 51 | 52 | ### Examples 53 | 54 | The [Ait Playground](https://github.com/mollerse/ait-playground/) has some examples you could follow. It also has some examples of usage in the browser. 55 | 56 | ## Syntax and semantics 57 | 58 | TODO 59 | 60 | ## Standard Library 61 | 62 | These are the words currently shipping with the `ait-lang`-runtime. 63 | 64 | ### Math 65 | 66 | The normal operators found in JavaScript works as words in Ait: 67 | `+`, `-`, `*`, `/`. `%` exists as `mod` and unary `-` exists as `neg`. 68 | 69 | Most of the methods and constants normally found on [`Math`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math) in JavaScript exist in Ait: 70 | `PI`, `E`, `random`, `abs`, `max`, `min`, `sqrt`, `cbrt`, `pow`, `floor`, `ceil`, `round`, `exp`, `sign`, `log`, `log10`, `cos`, `acos`, `cosh`, `acosh`, `sin`, `asin`, `sinh`, `asinh`, `tan`, `atan`, `atan2`, `tanh`, `atanh`. 71 | 72 | In addition some helper words have been added: 73 | 74 | #### random2 75 | `lower upper random2` 76 | 77 | Generates a random float between `lower` and `upper`. 78 | 79 | #### succ 80 | `n succ` 81 | 82 | Generates the successor to `n`. Equivalent to `n 1 +`. 83 | 84 | #### pred 85 | `n pred` 86 | 87 | Generates the predecessor to `n`. Equivalent to `n 1 -`. 88 | 89 | ### Logic 90 | #### > 91 | `a b >` 92 | 93 | `true` if a is greater than b, `false` otherwise. 94 | 95 | Desc 96 | #### < 97 | `a b <` 98 | 99 | `true` if a is smaller than b, `false` otherwise. 100 | 101 | Desc 102 | #### = 103 | `a b =` 104 | 105 | `true` if a is equal to b, `false` otherwise. This uses `===` in JavaScript to determine equality. 106 | 107 | Desc 108 | #### != 109 | `a b !=` 110 | 111 | `true` if a is not equal to b, `false` otherwise. This uses `!==` in JavaScript to determine equality. 112 | 113 | Desc 114 | #### >= 115 | `a b >` 116 | 117 | `true` if a is greater than or equal to b, `false` otherwise. 118 | 119 | Desc 120 | #### <= 121 | `a b >` 122 | 123 | `true` if a is smaller than or equal to b, `false` otherwise. 124 | 125 | Desc 126 | #### || 127 | `a b ||` 128 | 129 | `true` if a or b is `true`, `false` otherwise. 130 | 131 | Works the same way as JavaScripts `||` in that it will coerce operands to boolean values before comparing. 132 | 133 | Desc 134 | #### && 135 | `a b &&` 136 | 137 | `true` if a and b is `true`, `false` otherwise. 138 | 139 | Works the same way as JavaScripts `&&` in that it will coerce operands to boolean values before comparing. 140 | 141 | 142 | Desc 143 | #### small 144 | `[aggr] small` or `n small` 145 | 146 | `true` if aggregate `aggr` has 0 or 1 elements or if `n` is a small number (`-1`, `0` or `1`). 147 | 148 | Desc 149 | #### zero 150 | `[aggr] zero` or `n zero` 151 | 152 | `true` if aggregate `aggr` has 0 element or if `n` is the number `0`. 153 | 154 | ### String manipulation 155 | Words for manipulating strings. 156 | 157 | This module is unstable. 158 | 159 | #### replace 160 | `"string" "replacement" replace` 161 | 162 | Replaces the first `%s`s in the string `"string"` with replacement `"replacement"`. 163 | 164 | ##### Example 165 | ``` 166 | "%s extra" "nothing" replace 167 | ``` 168 | 169 | Resulting in `"nothing extra"` being left on the stack. 170 | 171 | #### replace2 172 | `"string" "replacement" replace2` 173 | 174 | Replaces the two first `%s`s in the string `"string"` with replacement `"replacement"`. 175 | 176 | #### replace3 177 | `"string" "replacement" replace3` 178 | 179 | Replaces the three first `%s`s in the string `"string"` with replacement `"replacement"`. 180 | 181 | ### Stack shufflers 182 | #### . 183 | `a .` 184 | 185 | Pops the top element off the stack and prints it to stdout. 186 | 187 | #### dup 188 | `a dup` 189 | 190 | Duplicates the top element of the stack 191 | 192 | #### dupd 193 | `a b dupd` 194 | 195 | `dip`'ed version of `dup`. Short-hand for `a b [dup] dip`. 196 | 197 | #### pop 198 | `a pop` 199 | 200 | Pops the top element off the stack. 201 | 202 | #### popd 203 | `a b popd` 204 | 205 | `dip`'ed version of `pop`. Short-hand for `a b [pop] dip`. 206 | 207 | #### swap 208 | `a b swap` 209 | 210 | Swaps the top two elements of the stack. 211 | 212 | #### swapd 213 | `a b c swapd` 214 | 215 | `dip`'ed version of `swap`. Short-hand for `a b c [swap] dip`. 216 | 217 | #### rollup 218 | `a b c rollup` 219 | 220 | Rolls the 3rd element of the stack up to the top of the stack. 221 | 222 | ##### Example 223 | ``` 224 | 1 2 3 rollup 225 | ``` 226 | 227 | Results in the stack looking like this: `2 3 1`. 228 | 229 | #### rollupd 230 | `a b c d rollupd` 231 | 232 | `dip`'ed version of `rollup`. Short-hand for `a b c d [rollup] dip`. 233 | 234 | #### rolldown 235 | `a b c rolldown` 236 | 237 | Rolls the 1st element of the stack up down 2 slots. 238 | 239 | ##### Example 240 | ``` 241 | 1 2 3 rolldown 242 | ``` 243 | 244 | Results in the stack looking like this: `3 1 2`. 245 | 246 | #### rolldownd 247 | `a b c d rolldownd` 248 | 249 | `dip`'ed version of `rolldown`. Short-hand for `a b c d [rolldown] dip`. 250 | 251 | #### rotate 252 | `a b c rotate` 253 | 254 | Rotates the 1st and 3rd element of the stack. 255 | 256 | ##### Example 257 | ``` 258 | 1 2 3 rotate 259 | ``` 260 | 261 | Results in the stack looking like this: `3 2 1`. 262 | 263 | #### rotated 264 | `a b c d rotated` 265 | 266 | `dip`'ed version of `rotate`. Short-hand for `a b c d [rotate] dip`. 267 | 268 | #### stack 269 | `stack` 270 | 271 | Pushes a copy of the current stack onto the stack. 272 | 273 | #### unstack 274 | `[aggr] unstack` 275 | 276 | Replaces the current stack with aggregate `aggr`. 277 | 278 | ### Quotations 279 | 280 | Words for working with quotations 281 | 282 | #### exec 283 | `[quot] exec` 284 | 285 | Executes quotation `quot`. 286 | 287 | ##### Example 288 | ``` 289 | [1 1 +] exec 290 | ``` 291 | 292 | Results in `2` being left on the stack. 293 | 294 | #### nullary 295 | `[quot] nullary` 296 | 297 | Executes quotation `quot` with arity 0. Reading as many elements of the stack as it needs, but does not consume any. 298 | 299 | ##### Example 300 | ``` 301 | 1 1 [+] nullary 302 | ``` 303 | 304 | Results in `1 1 2` being left on the stack. 305 | 306 | #### unary 307 | `[quot] unary` 308 | 309 | Executes quotation `quot` with arity 1. Reading as many elements of the stack as it needs, but only consumes 1. 310 | 311 | ##### Example 312 | ``` 313 | 1 1 [+] unary 314 | ``` 315 | 316 | Results in `1 2` being left on the stack. 317 | 318 | #### binary 319 | `[quot] binary` 320 | 321 | Executes quotation `quot` with arity 2. Reading as many elements of the stack as it needs, but only consumes 2. 322 | 323 | ##### Example 324 | ``` 325 | 1 1 [+] unary 326 | ``` 327 | 328 | Results in `2` being left on the stack. 329 | 330 | #### ternary 331 | `[quot] ternary` 332 | 333 | Executes quotation `quot` with arity 3. Reading as many elements of the stack as it needs, but only consumes 3. 334 | 335 | ##### Example 336 | ``` 337 | 1 2 3 4 [ + + + ] ternary 338 | ``` 339 | 340 | Resulting in `1 10` being left on the stack. 341 | 342 | #### dip 343 | `[quot] dip` 344 | 345 | Takes the top element of the stack, executes quotation `quot` and puts top element back on the stack. 346 | 347 | ##### Example 348 | ``` 349 | 1 2 3 [+] dip 350 | ``` 351 | 352 | Results in `3 3` being left on the stack. 353 | 354 | #### cleave 355 | `el [qout1] [qout2] cleave` 356 | 357 | Executes quotations `quot1` and `quot2` each with element `el` as the top of the stack. 358 | 359 | ##### Example 360 | ``` 361 | 1 [1 +] [2 +] 362 | ``` 363 | 364 | Resulting in `2 3` being left on the stack. 365 | 366 | 367 | ### Aggregates 368 | 369 | Words for working with aggregates 370 | 371 | #### cons 372 | `el [aggr] cons` 373 | 374 | Conses `el` onto aggregate `aggr`. 375 | 376 | ##### Example 377 | ``` 378 | 1 0 [] cons cons 379 | ``` 380 | 381 | Constructs a two-element vector from `0` and `1`, resulting in `[1 0]` being left on the stack. 382 | 383 | #### swons 384 | `[aggr] el swons` 385 | 386 | Short-hand for `[aggr] el swap cons`. 387 | 388 | #### append 389 | `el [aggr] append` 390 | 391 | Appends `el` at the end of aggregate `aggr`. 392 | 393 | ##### Example 394 | ``` 395 | 0 [1] append 396 | ``` 397 | 398 | Appends `0` at the end of `[1]` resulting in `[1 0]` being left on the stack. 399 | 400 | #### swappend 401 | `[aggr] el swappend` 402 | 403 | Short-hand for `[aggr] el swap append`. 404 | 405 | 406 | #### uncons 407 | `[aggr] uncons` 408 | 409 | Unconses the first element of aggregate `aggr`, leaving `el` and `aggr` on the stack. 410 | 411 | ##### Example 412 | ``` 413 | [1 0] uncons 414 | ``` 415 | 416 | Unconses `1` from `[1 0]` leaving the stack with `1 [0]`. 417 | 418 | #### unswons 419 | `[aggr] unswons` 420 | 421 | Short-hand for `[aggr] uncons swap`. Leaves result of uncons on the stack in the reverse order. 422 | 423 | ##### Example 424 | ``` 425 | [1 0] unswons 426 | ``` 427 | 428 | Unconses `1` from `[1 0]` leaving the stack with `[0] 1`. 429 | 430 | #### concat 431 | `[aggr1] [aggr2] concat` 432 | 433 | Concats two aggregates. Follows the rules of `Array.prototype.concat` in JavaScript. 434 | 435 | ##### Example 436 | ``` 437 | [0] [1] concat 438 | ``` 439 | 440 | Concats aggregate `[0]` and aggregate `[1]` resulting in `[0 1]` being left on the stack. 441 | 442 | #### enconcat 443 | `el [aggr1] [aggr2] enconcat` 444 | 445 | Concats aggregates `aggr1` and `aggr2` with `el` in between. 446 | 447 | ##### Example 448 | ``` 449 | 1 [0] [2] enconcat 450 | ``` 451 | 452 | Concats aggregates `[0]` and `[2]` with `1` in between, resulting in `[0 1 2]` being left on the stack. 453 | 454 | #### first 455 | `[aggr] first` 456 | 457 | Gets the first element of aggregate `aggr`. 458 | 459 | ##### Example 460 | ``` 461 | [0 1 2] first 462 | ``` 463 | 464 | Leaves `0` on the stack. 465 | 466 | #### last 467 | `[aggr] last` 468 | 469 | Gets the last element of aggregate `aggr`. 470 | 471 | ##### Example 472 | ``` 473 | [0 1 2] last 474 | ``` 475 | 476 | Leaves `2` on the stack. 477 | 478 | #### rest 479 | `[aggr] rest` 480 | 481 | Drops the first element and gives you the rest of aggreate `aggr`. 482 | 483 | ##### Example 484 | ``` 485 | [0 1 2] rest 486 | ``` 487 | 488 | Leaves `[1 2]` on the stack. 489 | 490 | #### of 491 | `i [aggr] of` 492 | 493 | Gets the element of aggregate `aggr` at index `i`. Equivalent to `i [aggr] swap at`. 494 | 495 | ##### Example 496 | ``` 497 | 1 [0 1 2] of 498 | ``` 499 | 500 | Leaves `1` on the stack. 501 | 502 | #### at 503 | `[aggr] i at` 504 | 505 | Gets the element at index `i` of aggregate `aggr`. Equivalent to `[aggr] i swap of`. 506 | 507 | ##### Example 508 | ``` 509 | [0 1 2] 1 at 510 | ``` 511 | 512 | Leaves `1` on the stack. 513 | 514 | #### ins 515 | `el [aggr] i ins` 516 | 517 | Inserts `el` in aggregate `aggr` at index `i`. Follows the rules of assigning to index in JavaScript, meaning you can get sparse aggregates. 518 | 519 | ##### Example 520 | ``` 521 | 3 [0 1] 1 ins 522 | ``` 523 | 524 | Inserts `3` into `[0 1]` at index `1` resulting in `[0 3]` being left on the stack. 525 | 526 | #### size 527 | `[aggr] size` 528 | 529 | Gets the size or length of aggregate `aggr`. 530 | 531 | ##### Example 532 | ``` 533 | [0 1 2] size 534 | ``` 535 | 536 | Leaves `3` on the stack. 537 | 538 | #### drop 539 | `[aggr] n drop` 540 | 541 | Drops the `n` first elements of aggregate `[aggr]`. 542 | 543 | ##### Example 544 | ``` 545 | [0 1 2] 1 drop 546 | ``` 547 | 548 | Leaves `[1 2]` on the stack. 549 | 550 | #### take 551 | `[aggr] n take` 552 | 553 | Takes the `n` first elements of aggregate `[aggr]`. 554 | 555 | ##### Example 556 | ``` 557 | [0 1 2] 2 take 558 | ``` 559 | 560 | Leaves `[0 1]` on the stack. 561 | 562 | #### step 563 | `[aggr] [s] step` 564 | 565 | Executes quotation `s` with arity 1 once for every element of aggregate `aggr`. Quotation `s` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 566 | 567 | ##### Example 568 | ``` 569 | [1 2 3] [ 1 + ] step 570 | ``` 571 | 572 | Results in `2 3 4` being left on the stack. 573 | 574 | #### map 575 | `[aggr] [mapper] map` 576 | 577 | Executes quotation `mapper` with arity 1 producing a new element for each element in aggregate `aggr`. 578 | 579 | Quotation `mapper` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 580 | 581 | ##### Example 582 | ``` 583 | [1 2 3] [ 1 + ] map 584 | ``` 585 | 586 | Results in `[2 3 4]` being left on the stack. 587 | 588 | #### map2 589 | `[aggr1] [aggr2] [mapper] map2` 590 | 591 | Executes quotation `mapper` with arity 2 producing a new element for each element in aggregate `aggr1` and `aggr2`. Aggregate `aggr1` must have an equal or greater number of elements than aggregate `aggr2`. 592 | 593 | Quotation `mapper` has access to the variables `_i`, which is the index of the current step, `_a1`, which is the first aggregate, and `_a2` which is the second aggregate. 594 | 595 | ##### Example 596 | ``` 597 | [2 4 6] [1 2 3] [ / ] map2 598 | ``` 599 | 600 | Results in `[2 2 2]` being left on the stack. 601 | 602 | #### fold 603 | `[aggr] v0 [folder] fold` 604 | 605 | Executes quotation `folder` with arity 2 producing a new value for each element in aggregate `aggr` with `v0` as the initial value. 606 | 607 | Quotation `folder` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 608 | 609 | 610 | ##### Example 611 | ``` 612 | [1 2 3] 0 [ + ] fold 613 | ``` 614 | 615 | Results in `6` being left on the stack. 616 | 617 | #### sort 618 | `[aggr] sort` 619 | 620 | Sorts the aggregate `aggr` in place using numerical comparison. 621 | 622 | ##### Example 623 | ``` 624 | [3 1 2] sort 625 | ``` 626 | 627 | Results in `[1 2 3]` being left on the stack. 628 | 629 | #### sortBy 630 | `[aggr] index sortBy` 631 | 632 | Sorts aggregate `aggr` in place using numerical comparison on `index` of each element. 633 | 634 | ##### Example 635 | ``` 636 | [[1 3] [3 2] [2 1]] 1 sortBy 637 | ``` 638 | 639 | Results in `[[2 1] [3 2] [1 3]]` being left on the stack. 640 | 641 | #### filter 642 | `[aggr] [test] filter` 643 | 644 | Filters the aggregate `aggr` by executing `test` with arity 1 for each element in aggregate. 645 | 646 | Quotation `test` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 647 | 648 | ##### Example 649 | ``` 650 | [1 2 3 4 5 6] [2 mod 0 =] filter 651 | ``` 652 | 653 | Results in `[2 4 6]` being left on the stack. 654 | 655 | #### split 656 | `[aggr] [test] split` 657 | 658 | Splits aggregate `aggr` into two aggregates according to the result of executing `test` for each element. 659 | 660 | Quotation `test` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 661 | 662 | ##### Example 663 | ``` 664 | [1 2 3 4 5 6] [3 <] split 665 | ``` 666 | 667 | Results in `[1 2] [3 4 5 6]` being left on the stack. 668 | 669 | #### some 670 | `[aggr] [test] some` 671 | 672 | Executes quotation `test` with arity 1 for each element in aggregate `aggr` and yields `true` if atleast one element passes the test. 673 | 674 | Quotation `test` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 675 | 676 | ##### Example 677 | ``` 678 | [1 2 3 4 5] [3 =] some 679 | ``` 680 | 681 | Results in `true` being left on the stack. 682 | 683 | #### all 684 | `[aggr] [test] all` 685 | 686 | Executes quotation `test` with arity 1 for each element in aggregate `aggr` and yields `true` if all elements passes the test. 687 | 688 | Quotation `test` has access to the variables `_i`, which is the index of the current step, and `_a` which is the aggregate. 689 | 690 | ##### Example 691 | ``` 692 | [1 2 3 4 5] [3 =] all 693 | ``` 694 | 695 | Results in `false` being left on the stack. 696 | 697 | ### Control flow 698 | 699 | Words for managing control flow. 700 | 701 | #### cond 702 | `test [quote]` 703 | 704 | Executes `quote` if `test` is `true`. 705 | 706 | ##### Example 707 | ``` 708 | 0 10 random2 5 < ['less than five'] 709 | ``` 710 | 711 | Tells if a random number between `0` and `10` is lower than `5`. 712 | 713 | #### branch 714 | `test [t] [f]` 715 | 716 | Executes `t` if `test` is `true` or `f` if `test` is `false`. 717 | 718 | ##### Example 719 | ``` 720 | 0 10 random2 5 < ['less than five'] ['bigger than or equal to five'] ifte 721 | ``` 722 | 723 | Tells if a random number between `0` and `10` is lower or higher than `5`. 724 | 725 | #### ifte 726 | `[test] [t] [f] ifte` 727 | 728 | Executes `test` with arity 0 to produce a boolean. Executes `t` if boolean is `true` or `f` if boolean is `false`. 729 | 730 | Works like `branch` except that `test` can be a quotation. 731 | 732 | ##### Example 733 | ``` 734 | 0 10 random2 [5 <] ['less than five'] ['bigger than or equal to five'] ifte 735 | ``` 736 | 737 | Tells if a random number between `0` and `10` is lower or higher than `5`. 738 | 739 | ### Iteration 740 | 741 | Words for doing iterations and other looping. 742 | 743 | #### while 744 | `[ test ] [ step ] while` 745 | 746 | Executes `test` with arity 0 to yield a boolean. Executes `step` if boolean is true. 747 | 748 | ##### Example 749 | ``` 750 | 0 751 | [ 10 < ] 752 | [ 1 + ] 753 | while 754 | ``` 755 | 756 | Will count up from 0 to 9 and stop, leaving 9 on the stack. 757 | 758 | #### times 759 | `count [ step ] times` 760 | 761 | Executes `step` `count` times. 762 | 763 | Inside `step` you will have access to the `_i`-variable, which has the value of the current step. 764 | 765 | ##### Example 766 | ``` 767 | 0 10 [ 1 + ] times 768 | ``` 769 | 770 | Will add `1` to the top of the stack `10` times, leaving `10` on top of the stack. 771 | 772 | ### Recursion 773 | Words for doing anonymous recursion 774 | 775 | #### linrec 776 | `[ test ] [ done ] [ generate ] [ collect ] linrec` 777 | 778 | `linrec` takes 4 quotations off the stack and preforms linear recursion: 779 | - `test` is executed with arity 0 to yield a boolean. 780 | - `done` is executed if `test` yields `true`. 781 | - `generate` is executed if `test` yields `false` and will continue the recursion with whatever `generate` leaves on the stack. 782 | - `collect` is executed after the recursion-step is done. 783 | 784 | ##### Example 785 | ``` 786 | 7 [ zero ] [ succ ] [ dup pred ] [ * ] linrec . 787 | ``` 788 | 789 | Will compute the factorial of `7`, which is `5040`. 790 | 791 | #### tailrec 792 | `[ test ] [ done ] [ step ] tailrec` 793 | 794 | `tailrec` takes 3 quotations off the stack and preforms tail recursion: 795 | - `test` is executed with arity 0 to yield a boolean. 796 | - `done` is executed if `test` yields `true`. 797 | - `step` is executed if `test` yields `false` and will continue the recursion with whatever `generate` leaves on the stack. 798 | 799 | ##### Example 800 | ``` 801 | 0 [1 2 3 4 5] [zero] [ pop ] [ uncons [+] dip ] tailrec . 802 | ``` 803 | 804 | Will do a tail recursive sum of the elements in an aggregate. 805 | 806 | #### binrec 807 | `[ test ] [ done ] [ generate ] [ collect ] binrec` 808 | 809 | `binrec` takes 4 quotations off the stack and preforms linear recursion: 810 | - `test` is executed with arity 0 to yield a boolean. 811 | - `done` is executed if `test` yields `true`. 812 | - `generate` is executed if `test` yields `false` and will produce 2 values. Binrec then recurses twice with each value on the top of the stack. 813 | - `collect` is executed after the recursion-step is done. 814 | 815 | ##### Example 816 | 817 | ``` 818 | [2 4 6 8 1 3 5 7 9] [ small ] [] [ uncons [>] split ] [ enconcat ] binrec . 819 | ``` 820 | 821 | Will preform quicksort on the aggregate. 822 | -------------------------------------------------------------------------------- /bin/ait-lang.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { relative, join, dirname } = require('path'); 3 | const Node = require(__dirname + '/../runtimes/node'); 4 | 5 | const srcFile = process.argv[2]; 6 | const root = dirname(join(process.cwd(), relative(process.cwd(), srcFile))); 7 | const runtime = Node(root); 8 | runtime.evaluate(srcFile); 9 | -------------------------------------------------------------------------------- /ffi.js: -------------------------------------------------------------------------------- 1 | module.exports.aitFFI__F = require('./dist/runtime/lexicon').jsWord; 2 | module.exports.aitFFILookupVariable = require('./dist/runtime/lexicon').lookupVariable; 3 | module.exports.aitFFIStoreRootVariable = require('./dist/runtime/lexicon').storeRootVariable; 4 | module.exports.aitFFIStoreVariable = require('./dist/runtime/lexicon').storeVariable; 5 | module.exports.aitFFIWrapValue = require('./dist/parser/ast-walkers').wrapValueFFI; 6 | module.exports.aitFFIWrapQuotation = require('./dist/parser/ast-walkers').wrapQuotationFFI; 7 | module.exports.aitFFIWrapAggregate = require('./dist/parser/ast-walkers').wrapQuotationFFI; 8 | module.exports.aitFFIUnwrapValue = require('./dist/parser/ast-walkers').unwrapValueFFI; 9 | module.exports.aitFFIPrint = require('./dist/parser/ast-walkers').prettyPrint; 10 | -------------------------------------------------------------------------------- /lib/aitFault.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type AitFault = { 4 | type: 'aitFault', 5 | cause: string 6 | }; 7 | 8 | export default function aitFault(cause: string): AitFault { 9 | return { 10 | type: 'aitFault', 11 | cause 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /lib/parser/ast-walkers.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { 3 | ASTNode, 4 | LoadDirective, 5 | Definition, 6 | Var, 7 | GlobalVar, 8 | Word, 9 | Value, 10 | Quotation, 11 | AitNumber, 12 | AitString 13 | } from './ast'; 14 | 15 | import { aitValue, quotation } from './ast'; 16 | 17 | function isType(type: string, node: ASTNode): boolean { 18 | return node.type === type; 19 | } 20 | 21 | function findAstNode(pred: ASTNode => boolean, ast: Array) { 22 | function extractVars(nodes) { 23 | let ret = []; 24 | for (var i = 0; i < nodes.length; i++) { 25 | const node = nodes[i]; 26 | if (pred(node)) { 27 | ret.push(node); 28 | } else if (node.type === 'quotation') { 29 | ret = ret.concat(extractVars(node.body)); 30 | } else if (node.type === 'defintion') { 31 | ret = ret.concat(extractVars(node.body.body)); 32 | } 33 | } 34 | return ret; 35 | } 36 | 37 | return extractVars(ast); 38 | } 39 | 40 | export function unwrapValueInternal(node: ASTNode): mixed { 41 | if ( 42 | isLoadDirective(node) || 43 | isWord(node) || 44 | isString(node) || 45 | isNumber(node) || 46 | isBool(node) || 47 | isAitValue(node) || 48 | isVar(node) || 49 | isGlobalVar(node) 50 | ) { 51 | return node.body; 52 | } else if (isDefinition(node)) { 53 | return ((node: any): Definition).body.body.map(unwrapValueInternal).reverse(); 54 | } else if (isQuotation(node)) { 55 | return ((node: any): Quotation).body.map(unwrapValueInternal).reverse(); 56 | } 57 | } 58 | 59 | export function unwrapValueFFI(node: ASTNode) { 60 | if (isWord(node) || isString(node) || isNumber(node) || isBool(node) || isAitValue(node)) { 61 | return node.body; 62 | } else if (isQuotation(node)) { 63 | return node; 64 | } else if (isVar(node) || isGlobalVar(node)) { 65 | throw new Error(`FFI Error: Operating on variable declarations are not supported`); 66 | } else if (isDefinition(node)) { 67 | throw new Error(`FFI Error: Operating on definitions are not supported`); 68 | } else if (isLoadDirective(node)) { 69 | throw new Error(`FFI Error: Operating on load directives are not supported`); 70 | } else { 71 | throw new Error(`Internal Error: Missing unwrapper for ASTNode type "${node.type}"`); 72 | } 73 | } 74 | 75 | export function wrapValueFFI(value: mixed) { 76 | return aitValue(value); 77 | } 78 | 79 | export function wrapQuotationFFI(value: Array) { 80 | return quotation(value); 81 | } 82 | 83 | export function prettyPrint(node: ASTNode): string { 84 | if (isWord(node) || isString(node) || isNumber(node)) { 85 | const n = ((node: any): Word | AitString | AitNumber); 86 | return `${n.body}`; 87 | } else if (isBool(node)) { 88 | return node.body ? 'true' : 'false'; 89 | } else if (isDefinition(node)) { 90 | const def = ((node: any): Definition); 91 | const defBody = def.body.body.reverse().map(prettyPrint); 92 | return `${def.body.keyword}:\n ${defBody.join(defBody.length > 3 ? '\n ' : ' ')} ;`; 93 | } else if (isQuotation(node)) { 94 | const quote = ((node: any): Quotation); 95 | const quoteBody = quote.body.map(prettyPrint).reverse(); 96 | return `[ ${quoteBody.join(quoteBody.length > 3 ? '\n ' : ' ')} ]`; 97 | } else if (isVar(node)) { 98 | const v = ((node: any): Var); 99 | return `-> ${v.body}`; 100 | } else if (isGlobalVar(node)) { 101 | const v = ((node: any): GlobalVar); 102 | return `->> ${v.body}`; 103 | } else if (isAitValue(node)) { 104 | // @FlowIgnore everything has toString and this is unsafe by design 105 | return node.body.toString(); 106 | } else { 107 | return ''; 108 | } 109 | } 110 | 111 | export const isLoadDirective: (node: ASTNode) => boolean = isType.bind(null, 'load'); 112 | export const isDefinition: (node: ASTNode) => boolean = isType.bind(null, 'definition'); 113 | export const isBool: (node: ASTNode) => boolean = isType.bind(null, 'bool'); 114 | export const isNumber: (node: ASTNode) => boolean = isType.bind(null, 'number'); 115 | export const isString: (node: ASTNode) => boolean = isType.bind(null, 'string'); 116 | export const isWord: (node: ASTNode) => boolean = isType.bind(null, 'word'); 117 | export const isVar: (node: ASTNode) => boolean = isType.bind(null, 'var'); 118 | export const isGlobalVar: (node: ASTNode) => boolean = isType.bind(null, 'globalVar'); 119 | export const isAitValue: (node: ASTNode) => boolean = isType.bind(null, 'value'); 120 | export const isQuotation: (node: ASTNode) => boolean = isType.bind(null, 'quotation'); 121 | export const isValue: (node: ASTNode) => boolean = node => 122 | isBool(node) || 123 | isNumber(node) || 124 | isString(node) || 125 | isWord(node) || 126 | isVar(node) || 127 | isGlobalVar(node) || 128 | isAitValue(node) || 129 | isQuotation(node); 130 | 131 | //Exports downcasted through any-casting to circumvent Flow limitations 132 | export const findLoadDirectives = ((findAstNode.bind(null, isLoadDirective): any): ( 133 | ast: Array 134 | ) => Array); 135 | export const findDefinitions = ((findAstNode.bind(null, isDefinition): any): ( 136 | ast: Array 137 | ) => Array); 138 | export const findVariables = ((findAstNode.bind(null, isVar): any): ( 139 | ast: Array 140 | ) => Array); 141 | export const findGlobalVariables = ((findAstNode.bind(null, isGlobalVar): any): ( 142 | ast: Array 143 | ) => Array); 144 | export const findWords = ((findAstNode.bind(null, isWord): any): ( 145 | ast: Array 146 | ) => Array); 147 | export const findValues = ((findAstNode.bind(null, isValue): any): ( 148 | ast: Array 149 | ) => Array); 150 | -------------------------------------------------------------------------------- /lib/parser/ast.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | export type AitString = { 3 | type: 'string', 4 | body: string 5 | }; 6 | 7 | export type AitBool = { 8 | type: 'bool', 9 | body: boolean 10 | }; 11 | 12 | export type AitNumber = { 13 | type: 'number', 14 | body: number 15 | }; 16 | 17 | export type Word = { 18 | type: 'word', 19 | body: string 20 | }; 21 | 22 | export type Quotation = { 23 | type: 'quotation', 24 | body: Array 25 | }; 26 | 27 | export type Definition = { 28 | type: 'definition', 29 | body: {| 30 | keyword: string, 31 | body: Array 32 | |} 33 | }; 34 | 35 | export type LoadDirective = { 36 | type: 'load', 37 | body: string 38 | }; 39 | 40 | export type Var = { 41 | type: 'var', 42 | body: string 43 | }; 44 | 45 | export type GlobalVar = { 46 | type: 'globalVar', 47 | body: string 48 | }; 49 | 50 | // Generic value type for FFI 51 | export type AitValue = { 52 | type: 'value', 53 | body: mixed 54 | }; 55 | 56 | export type Value = AitBool | AitString | AitNumber | Word | Quotation | Var | GlobalVar | AitValue; 57 | export type ASTNode = Value | Definition | LoadDirective; 58 | 59 | export function primitiveString(body: string): AitString { 60 | return { 61 | type: 'string', 62 | body 63 | }; 64 | } 65 | 66 | export function primitiveBoolean(body: 'true' | 'false'): AitBool { 67 | return { 68 | type: 'bool', 69 | body: body === 'true' 70 | }; 71 | } 72 | 73 | export function primitiveNumber(body: string): AitNumber { 74 | return { 75 | type: 'number', 76 | body: Number(body) 77 | }; 78 | } 79 | 80 | export function word(body: string): Word { 81 | return { 82 | type: 'word', 83 | body 84 | }; 85 | } 86 | 87 | export function quotation(body: Array): Quotation { 88 | return { 89 | type: 'quotation', 90 | body: body.reverse() 91 | }; 92 | } 93 | 94 | export function quotationNonReversed(body: Array): Quotation { 95 | return { 96 | type: 'quotation', 97 | body 98 | }; 99 | } 100 | 101 | export function definition([keyword, body]: [string, Array]): Definition { 102 | return { 103 | type: 'definition', 104 | body: { keyword, body: body.reverse() } 105 | }; 106 | } 107 | 108 | export function loadDirective(body: string): LoadDirective { 109 | return { 110 | type: 'load', 111 | body 112 | }; 113 | } 114 | 115 | export function aitVar(body: string): Var { 116 | return { 117 | type: 'var', 118 | body 119 | }; 120 | } 121 | 122 | export function aitGlobalVar(body: string): GlobalVar { 123 | return { 124 | type: 'globalVar', 125 | body 126 | }; 127 | } 128 | 129 | export function aitValue(body: mixed): AitValue { 130 | return { 131 | type: 'value', 132 | body 133 | }; 134 | } 135 | -------------------------------------------------------------------------------- /lib/parser/parse.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { lazy, string, regex, alt, seq, eof, formatError } from 'parsimmon'; 4 | import { 5 | word, 6 | primitiveString, 7 | primitiveBoolean, 8 | primitiveNumber, 9 | quotation, 10 | definition, 11 | loadDirective, 12 | aitVar, 13 | aitGlobalVar 14 | } from './ast'; 15 | import aitFault from '../aitFault'; 16 | 17 | import type { ASTNode } from './ast'; 18 | import type { AitFault } from '../aitFault'; 19 | 20 | const newline = regex(/\n/); 21 | const notNewlines = regex(/[^\n]*/); 22 | const comment = string('#').then(notNewlines).skip(newline.or(eof)); 23 | const whitespace = regex(/\s+/); 24 | const ignore = alt(whitespace, comment).many(); 25 | const lexme = p => p.skip(ignore); 26 | 27 | const lbracket = lexme(string('[')); 28 | const rbracket = lexme(string(']')); 29 | const blockterminator = lexme(string(';')); 30 | const path = lexme(regex(/[a-zA-Z0-9._~!$&'()*+,;=:@%/-]+/)).desc('file-path'); 31 | const str = lexme(regex(/["']((?:\\.|.|\n)*?)["']/, 1)).desc('quoted string'); 32 | const booleanTrue = lexme(string('true')).desc('boolean true literal'); 33 | const booleanFalse = lexme(string('false')).desc('boolean false literal'); 34 | const number = lexme(regex(/[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?|[-+]?Infinity/)).desc( 35 | 'number literal' 36 | ); 37 | const wordLiteral = lexme(regex(/[a-zA-Z0-9.<>+=\\*%!\|&/_-]+/)).desc('word literal'); 38 | const load = lexme(string('@load')); 39 | const arrow = lexme(string('->')); 40 | const fatArrow = lexme(string('->>')); 41 | const colon = lexme(string(':')).desc('colon-char'); 42 | 43 | const primitive = lazy('primivite values', function() { 44 | return alt(primitiveStringParser, primitiveBooleanParser, primitiveNumberParser); 45 | }); 46 | 47 | const value = lazy('top level-values', function() { 48 | return alt(primitive, quotationParser); 49 | }); 50 | 51 | const words = lazy('words', function() { 52 | return alt(value, setGlobalVarParser, setVarParser, wordLiteralParser); 53 | }); 54 | 55 | const expressions = lazy('top-level expressions', function() { 56 | return alt(definitionParser, words, loadDirectiveParser); 57 | }); 58 | 59 | const primitiveStringParser = str.map(primitiveString); 60 | 61 | const primitiveBooleanParser = alt(booleanTrue, booleanFalse).map(primitiveBoolean); 62 | 63 | const primitiveNumberParser = number.map(primitiveNumber); 64 | 65 | const quotationParser = lbracket.then(words.many()).skip(rbracket).map(quotation); 66 | const wordLiteralParser = wordLiteral.map(word); 67 | 68 | const definitionParser = seq(wordLiteral.skip(colon), words.many(), blockterminator).map( 69 | definition 70 | ); 71 | 72 | const loadDirectiveParser = load.then(path).skip(blockterminator).map(loadDirective); 73 | 74 | const setVarParser = arrow.then(wordLiteral).map(aitVar); 75 | const setGlobalVarParser = fatArrow.then(wordLiteral).map(aitGlobalVar); 76 | 77 | const final = ignore.then(expressions.many()); 78 | 79 | export default function parse(src: string): Array | AitFault { 80 | const result = final.parse(src); 81 | if (result.status) { 82 | return (result.value: Array).reverse(); 83 | } 84 | return aitFault(formatError(src, result)); 85 | } 86 | -------------------------------------------------------------------------------- /lib/parser/test/parse-defintion.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import test from 'ava'; 4 | import parse from '../parse'; 5 | import { word, quotation, primitiveNumber, definition } from '../ast'; 6 | 7 | test('should parse simple definitions', function(t) { 8 | t.plan(1); 9 | const result = parse('bla: word ;')[0]; 10 | if (result.type === 'definition') { 11 | t.deepEqual(result, definition(['bla', [word('word')]])); 12 | } 13 | }); 14 | 15 | test('should parse definitions', function(t) { 16 | t.plan(1); 17 | const result = parse( 18 | ` 19 | bla: 20 | 1 2 21 | + 22 | word ; 23 | ` 24 | )[0]; 25 | if (result.type === 'definition') { 26 | t.deepEqual( 27 | result, 28 | definition(['bla', [primitiveNumber('1'), primitiveNumber('2'), word('+'), word('word')]]) 29 | ); 30 | } 31 | }); 32 | 33 | test('should parse definitions with symbolic names', function(t) { 34 | t.plan(1); 35 | const result = parse( 36 | ` 37 | a')[0]; 10 | if (result.type === 'var') { 11 | t.deepEqual(result, aitVar('a')); 12 | } 13 | }); 14 | 15 | test('should parse global variables', function(t) { 16 | t.plan(1); 17 | const result = parse('->> a')[0]; 18 | if (result.type === 'globalVar') { 19 | t.deepEqual(result, aitGlobalVar('a')); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /lib/parser/test/parse-word.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import test from 'ava'; 4 | import parse from '../parse'; 5 | import { word } from '../ast'; 6 | 7 | test('should parse single words', function(t) { 8 | const result = parse('test')[0]; 9 | if (result.type === 'word') { 10 | t.deepEqual(result, word('test')); 11 | } 12 | }); 13 | 14 | test('should parse symbolic words', function(t) { 15 | const result = parse('+')[0]; 16 | if (result.type === 'word') { 17 | t.deepEqual(result, word('+')); 18 | } 19 | }); 20 | 21 | test('should parse strange words', function(t) { 22 | const result = parse('vec3 map+ -map 21 | }; 22 | 23 | export type Lexicon = { [key: string]: AitWord | JSWord }; 24 | 25 | export function aitWord({ body: definition }: Definition): AitWord { 26 | return { 27 | type: 'ait', 28 | name: definition.keyword, 29 | fn: definition.body 30 | }; 31 | } 32 | 33 | export function jsWord(arity: number, name: string, fn: Function): JSWord { 34 | return { 35 | type: 'js', 36 | name, 37 | fn, 38 | arity 39 | }; 40 | } 41 | 42 | export function lookupVariable(runtime: Runtime, name: string) { 43 | return runtime.scope[name]; 44 | } 45 | 46 | export function storeVariable(runtime: Runtime, name: string, value: Value) { 47 | runtime.scope[name] = value; 48 | } 49 | 50 | export function storeRootVariable(runtime: Runtime, name: string, value: Value) { 51 | runtime.rootScope[name] = value; 52 | } 53 | 54 | export function lexicon(): Lexicon { 55 | return stdlib; 56 | } 57 | 58 | export function populateLexicon(src: Array): Lexicon { 59 | const populatedLexicon = {}; 60 | 61 | const wordDefinitions = findDefinitions(src); 62 | 63 | wordDefinitions.forEach(function(definition) { 64 | const def = aitWord(definition); 65 | populatedLexicon[def.name] = def; 66 | }); 67 | 68 | return populatedLexicon; 69 | } 70 | -------------------------------------------------------------------------------- /lib/runtime/moduleLoader.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import path from 'path'; 4 | import { readFileSync } from 'fs'; 5 | 6 | import aitFault from '../aitFault'; 7 | import parse from '../parser/parse'; 8 | 9 | import type { AitFault } from '../aitFault'; 10 | import type { JSWord, AitWord } from './lexicon'; 11 | import { aitWord } from './lexicon'; 12 | 13 | function isRelative(path) { 14 | return path[0] === '.'; 15 | } 16 | 17 | function resolveModulePath(module: string, root: string): string | AitFault { 18 | // Redefine modulepath to absolute path if it is a relative path 19 | if (isRelative(module)) { 20 | const rootPath = path.parse(root); 21 | module = path.join(rootPath.dir, rootPath.base, module); 22 | } 23 | 24 | try { 25 | // See if it resolves to a module 26 | return require.resolve(module); 27 | } catch (e) { 28 | try { 29 | // Since it did not resolve to a module, see if it resolves to an ait-file 30 | return require.resolve(`${module}.ait`); 31 | } catch (ee) { 32 | return aitFault(`Could not load module ${module}`); 33 | } 34 | } 35 | } 36 | 37 | export default function loadModule( 38 | module: string, 39 | root: string 40 | ): Array | AitFault { 41 | const fullPath = resolveModulePath(module, root); 42 | 43 | if (typeof fullPath !== 'string') { 44 | return fullPath; 45 | } 46 | 47 | const ext = path.parse(fullPath).ext; 48 | 49 | if (ext === '.js') { 50 | /* eslint global-require: [0], import/no-dynamic-require: [0] */ 51 | // @FlowIgnore: Non-standard way of using require 52 | const moduleSource = require(fullPath); 53 | 54 | let jsModule: Array = []; 55 | 56 | if (typeof moduleSource !== 'object') { 57 | return aitFault( 58 | `Ait does not support single-function js-modules. You tried to load ${module}.` 59 | ); 60 | } 61 | 62 | for (let key of Object.keys(moduleSource)) { 63 | const word = moduleSource[key]; 64 | if (word.type === 'js' && typeof word.fn === 'function' && typeof word.name === 'string') { 65 | jsModule.push(word); 66 | } else { 67 | return aitFault(`${key} of module ${module} is not a valid FFI JSWord.`); 68 | } 69 | } 70 | return jsModule; 71 | } else if (ext === '.ait') { 72 | const moduleSource = readFileSync(fullPath, 'utf8'); 73 | 74 | const parsedAitModule = parse(moduleSource); 75 | 76 | if (!Array.isArray(parsedAitModule)) { 77 | return parsedAitModule; 78 | } 79 | 80 | let aitModule: Array = []; 81 | 82 | for (let node of parsedAitModule) { 83 | if (node.type === 'definition') { 84 | aitModule.push(aitWord(node)); 85 | } else if (node.type === 'load') { 86 | const maybeModule = loadModule(node.body, root); 87 | if (!Array.isArray(maybeModule)) { 88 | return maybeModule; 89 | } else { 90 | aitModule.concat(maybeModule); 91 | } 92 | } 93 | } 94 | return aitModule; 95 | } 96 | 97 | return aitFault(`File type ${ext} is not supported. Try loading an .ait or .js file!`); 98 | } 99 | -------------------------------------------------------------------------------- /lib/runtime/node.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Runtime } from './runtime'; 3 | 4 | import { readFileSync } from 'fs'; 5 | import BaseRuntime from './runtime'; 6 | import moduleLoader from './moduleLoader'; 7 | 8 | class NodeRuntimeImpl extends BaseRuntime implements Runtime { 9 | type: string; 10 | root: string; 11 | 12 | constructor(root: string) { 13 | super(); 14 | this.type = 'node'; 15 | this.root = root; 16 | } 17 | 18 | evaluate(path: string) { 19 | const src = readFileSync(path, 'utf8'); 20 | super.evaluate(src); 21 | } 22 | 23 | loadModule(module) { 24 | const words = moduleLoader(module, this.root); 25 | 26 | if (!Array.isArray(words)) { 27 | throw new Error(words.cause); 28 | } 29 | 30 | words.forEach(word => { 31 | this.lexicon[word.name] = word; 32 | }); 33 | } 34 | } 35 | 36 | export default function node(cwd: string): Runtime { 37 | return new NodeRuntimeImpl(cwd); 38 | } 39 | -------------------------------------------------------------------------------- /lib/runtime/runtime.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import clone from 'clone'; 3 | import type { Stack } from './stack'; 4 | import type { Lexicon, JSWord } from './lexicon'; 5 | import type { Value, Word } from '../parser/ast'; 6 | 7 | import { lexicon, populateLexicon } from './lexicon'; 8 | import { 9 | findLoadDirectives, 10 | findValues, 11 | isBool, 12 | isNumber, 13 | isString, 14 | isQuotation, 15 | isVar, 16 | isGlobalVar, 17 | isWord, 18 | isAitValue 19 | } from '../parser/ast-walkers'; 20 | import parse from '../parser/parse'; 21 | import stackFactory from './stack'; 22 | 23 | export interface Runtime { 24 | type: string, 25 | stack: Stack, 26 | scope: Object, 27 | rootScope: Object, 28 | lexicon: Lexicon, 29 | reset(): void, 30 | loadModule(name: string): void, 31 | unstack(newStack: Array): void, 32 | pushScope(): void, 33 | popScope(): void, 34 | evaluate(src: string): void 35 | } 36 | 37 | export default class BaseRuntime implements Runtime { 38 | stack: Stack; 39 | lexicon: Lexicon; 40 | rootScope: Object; 41 | scope: Object; 42 | program: Array; 43 | type: string; 44 | 45 | constructor() { 46 | this.stack = stackFactory(); 47 | this.rootScope = Object.create({}); 48 | this.scope = this.rootScope; 49 | this.type = 'base'; 50 | this.lexicon = lexicon(); 51 | } 52 | 53 | unstack(newStack: Array) { 54 | if (!Array.isArray(newStack)) { 55 | throw new Error('Aggregate is required for unstack'); 56 | } 57 | this.stack.restack(newStack); 58 | } 59 | 60 | reset() { 61 | this.unstack([]); 62 | this.lexicon = lexicon(); 63 | } 64 | 65 | pushScope() { 66 | const newScope = Object.create({}); 67 | Object.setPrototypeOf(newScope, this.scope); 68 | this.scope = newScope; 69 | } 70 | 71 | popScope() { 72 | this.scope = Object.getPrototypeOf(this.scope); 73 | } 74 | 75 | // eslint-disable-next-line no-unused-vars 76 | loadModule(module: string) { 77 | throw new Error( 78 | `This runtime does not support loading modules, skipping load directive for ${module}` 79 | ); 80 | } 81 | 82 | evaluate(source: string) { 83 | const srcOrError = parse(source); 84 | 85 | if (!Array.isArray(srcOrError)) { 86 | throw new Error(srcOrError.cause); 87 | } 88 | 89 | const src = srcOrError; // We know that it isn't an error anymore 90 | 91 | Object.assign(this.lexicon, populateLexicon(src)); 92 | 93 | findLoadDirectives(src).forEach(loadDirective => { 94 | this.loadModule(loadDirective.body); 95 | }); 96 | 97 | this.program = findValues(src); 98 | this.executeProgram(); 99 | } 100 | 101 | executeProgram() { 102 | while (this.program.length) { 103 | const current = this.program.pop(); 104 | 105 | if (!current) { 106 | console.warn('something is wrong, got something that isnt an ASTValue'); 107 | } 108 | 109 | if ( 110 | isBool(current) || 111 | isNumber(current) || 112 | isString(current) || 113 | isAitValue(current) || 114 | isQuotation(current) 115 | ) { 116 | this.stack.push(current); 117 | continue; 118 | } else if (isVar(current)) { 119 | this.scope[current.body] = this.stack.pop(); 120 | continue; 121 | } else if (isGlobalVar(current)) { 122 | this.rootScope[current.body] = this.stack.pop(); 123 | continue; 124 | } else if (isWord(current)) { 125 | const maybeProgram = this.handleWord(((current: any): Word)); 126 | 127 | if (Array.isArray(maybeProgram)) { 128 | this.program[this.program.length] = { 129 | type: 'word', 130 | body: 'popScope' 131 | }; 132 | for (let i = 0; i < maybeProgram.length; i++) { 133 | this.program[this.program.length] = maybeProgram[i]; 134 | } 135 | this.program[this.program.length] = { 136 | type: 'word', 137 | body: 'pushScope' 138 | }; 139 | } else if (maybeProgram != null) { 140 | this.program[this.program.length] = maybeProgram; 141 | } 142 | continue; 143 | } else { 144 | throw new Error(`Internal error: Missing handler for ASTNode of type ${current.type}`); 145 | } 146 | } 147 | } 148 | 149 | handleWord(word: Word): Array | void { 150 | const wordDefiniton = this.lexicon[word.body]; 151 | const variableDefinition = this.scope[word.body]; 152 | 153 | if (wordDefiniton == null && variableDefinition == null) { 154 | throw new Error(`Could not find definition of word "${word.body}"`); 155 | } 156 | 157 | if (variableDefinition != null) { 158 | this.stack.push(variableDefinition); 159 | } else if (wordDefiniton.type === 'ait') { 160 | return clone(wordDefiniton.fn); 161 | } else if (wordDefiniton.type === 'js') { 162 | return this.executeJSWord(wordDefiniton); 163 | } 164 | } 165 | 166 | executeJSWord(word: JSWord): Array | void { 167 | let args = []; 168 | for (let i = 0; i < word.arity; i++) { 169 | args.push(this.stack.pop()); 170 | } 171 | 172 | return word.fn.apply(this, args); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /lib/runtime/stack.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export interface Stack { 4 | resolve(): Array, 5 | fork(i: number): void, 6 | push(v: mixed): void, 7 | pop(): mixed, 8 | length(): number, 9 | stack(): Array, 10 | restack(s: Array): void 11 | } 12 | 13 | interface Consumable { 14 | _pop(): mixed, 15 | pop(): mixed, 16 | push(v: mixed): void, 17 | length(): number, 18 | _length(): number, 19 | stack(): Array, 20 | _stack(): Array, 21 | restack(s: Array): void, 22 | resolve(): Array 23 | } 24 | 25 | class Fork implements Consumable { 26 | toConsume: number; 27 | consumable: Consumable; 28 | consumed: Array; 29 | internalStack: Array; 30 | 31 | constructor(toConsume: number, consumable: Consumable) { 32 | this.toConsume = toConsume; 33 | this.consumable = consumable; 34 | this.consumed = []; 35 | this.internalStack = []; 36 | } 37 | 38 | length() { 39 | return this._length(); 40 | } 41 | 42 | _length(): number { 43 | return ( 44 | this.consumable._length() + 45 | Math.max(this.consumed.length, this.toConsume) + 46 | this.internalStack.length 47 | ); 48 | } 49 | 50 | hasOwnElements() { 51 | return this.internalStack.length > 0; 52 | } 53 | 54 | hasElements() { 55 | return this.consumable._length() > 0; 56 | } 57 | 58 | _pop() { 59 | if (this.hasOwnElements()) { 60 | return this.internalStack.pop(); 61 | } else if (this.hasElements()) { 62 | const el = this.consumable._pop(); 63 | this.consumed.unshift(el); 64 | return el; 65 | } else { 66 | throw new Error('Cannot pop on empty stack'); 67 | } 68 | } 69 | 70 | pop() { 71 | return this._pop(); 72 | } 73 | 74 | push(el) { 75 | this.internalStack.push(el); 76 | } 77 | 78 | consumeAll() { 79 | for (var i = 0; i < this.toConsume; i++) { 80 | if (this.consumed.length > 0) { 81 | this.consumed.pop(); 82 | } else if (this.hasElements()) { 83 | this.consumable._pop(); 84 | } else { 85 | throw new Error('Cannot pop on empty stack'); 86 | } 87 | } 88 | } 89 | 90 | _stack() { 91 | return this.consumable._stack().concat(this.consumed, this.internalStack); 92 | } 93 | 94 | stack() { 95 | return this._stack(); 96 | } 97 | 98 | resolve() { 99 | this.consumeAll(); 100 | 101 | let topOfOwnStack = []; 102 | if (this.hasOwnElements()) { 103 | topOfOwnStack = this.internalStack.pop(); 104 | if (Array.isArray(topOfOwnStack)) { 105 | topOfOwnStack = [topOfOwnStack]; 106 | } 107 | } 108 | 109 | return this.consumable._stack().concat(this.consumed, topOfOwnStack); 110 | } 111 | 112 | restack(newStack) { 113 | this.internalStack = newStack; 114 | } 115 | } 116 | 117 | function forkFactory(toConsume: number, consumable: Consumable): Fork { 118 | return new Fork(toConsume, consumable); 119 | } 120 | 121 | class ForkableStack implements Consumable, Stack { 122 | internalStack: Array; 123 | forks: Array; 124 | 125 | constructor() { 126 | this.internalStack = []; 127 | this.forks = []; 128 | } 129 | 130 | push(v: mixed) { 131 | if (this.hasForks()) { 132 | 1; 133 | this.topFork().push(v); 134 | } else { 135 | this.internalStack.push(v); 136 | } 137 | } 138 | 139 | _pop() { 140 | if (this.hasElements()) { 141 | return this.internalStack.pop(); 142 | } 143 | throw new Error('Cannot pop on empty stack'); 144 | } 145 | 146 | pop() { 147 | if (this.hasForks()) { 148 | return this.topFork().pop(); 149 | } else { 150 | return this._pop(); 151 | } 152 | } 153 | 154 | hasForks() { 155 | return this.forks.length > 0; 156 | } 157 | 158 | topFork() { 159 | return this.forks[this.forks.length - 1]; 160 | } 161 | 162 | hasElements() { 163 | return this.internalStack.length > 0; 164 | } 165 | 166 | fork(i: number = 0) { 167 | const fork = forkFactory(i, this.hasForks() ? this.topFork() : this); 168 | this.forks.push(fork); 169 | } 170 | 171 | resolve() { 172 | if (!this.hasForks()) { 173 | return []; 174 | } 175 | 176 | const forkToResolve = this.topFork(); 177 | const newConsumable = forkToResolve.resolve(); 178 | this.forks.pop(); 179 | 180 | if (this.hasForks()) { 181 | this.topFork().restack(newConsumable); 182 | } else { 183 | this.restack(newConsumable); 184 | } 185 | 186 | return this.stack(); 187 | } 188 | 189 | restack(newStack: Array) { 190 | this.internalStack = newStack; 191 | } 192 | 193 | _length() { 194 | return this.internalStack.length; 195 | } 196 | 197 | length() { 198 | if (this.hasForks()) { 199 | return this.topFork().length(); 200 | } else { 201 | return this._length(); 202 | } 203 | } 204 | 205 | _stack() { 206 | return this.internalStack; 207 | } 208 | 209 | stack() { 210 | if (this.hasForks()) { 211 | return this.topFork().stack(); 212 | } else { 213 | return this._stack(); 214 | } 215 | } 216 | } 217 | 218 | export default function stackFactory(): Stack { 219 | return new ForkableStack(); 220 | } 221 | -------------------------------------------------------------------------------- /lib/runtime/test/stack.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import stack from '../stack'; 3 | 4 | test('should pop same as pushed', function(t) { 5 | t.plan(1); 6 | const s = stack(); 7 | const v = []; 8 | s.push(v); 9 | t.is(s.pop(), v); 10 | }); 11 | 12 | test('should throw when poping on empty', function(t) { 13 | t.plan(1); 14 | const s = stack(); 15 | t.throws(() => s.pop()); 16 | }); 17 | 18 | test('`1 [2 3] nullary` == 1 3', function(t) { 19 | t.plan(1); 20 | const s = stack(); 21 | s.push(1); 22 | s.fork(0); 23 | s.push(2); 24 | s.push(3); 25 | s.resolve(); 26 | t.deepEqual(s.stack(), [1, 3]); 27 | }); 28 | 29 | test('`1 2 [3 4] unary` == 1 4', function(t) { 30 | t.plan(1); 31 | const s = stack(); 32 | s.push(1); 33 | s.push(2); 34 | s.fork(1); 35 | s.push(3); 36 | s.push(4); 37 | s.resolve(); 38 | t.deepEqual(s.stack(), [1, 4]); 39 | }); 40 | 41 | test('`[3 4] unary` == runtime error', function(t) { 42 | t.plan(1); 43 | const s = stack(); 44 | s.fork(1); 45 | s.push(3); 46 | s.push(4); 47 | t.throws(() => s.resolve()); 48 | }); 49 | 50 | test('`1 2 3 [4 5] binary` == 1 5', function(t) { 51 | t.plan(1); 52 | const s = stack(); 53 | s.push(1); 54 | s.push(2); 55 | s.push(3); 56 | s.fork(2); 57 | s.push(4); 58 | s.push(5); 59 | s.resolve(); 60 | t.deepEqual(s.stack(), [1, 5]); 61 | }); 62 | 63 | test('`3 [4 5] binary` == runtime error', function(t) { 64 | t.plan(1); 65 | const s = stack(); 66 | s.push(3); 67 | s.fork(2); 68 | s.push(4); 69 | s.push(5); 70 | t.throws(() => s.resolve()); 71 | }); 72 | 73 | test('`3 4 [5 6] ternary` == runtime error', function(t) { 74 | t.plan(1); 75 | const s = stack(); 76 | s.push(3); 77 | s.push(4); 78 | s.fork(3); 79 | s.push(5); 80 | s.push(6); 81 | t.throws(() => s.resolve()); 82 | }); 83 | 84 | test('`1 2 [+] nullary` == 1 2 3', function(t) { 85 | t.plan(1); 86 | const s = stack(); 87 | s.push(1); 88 | s.push(2); 89 | s.fork(0); 90 | s.push(s.pop() + s.pop()); 91 | s.resolve(); 92 | t.deepEqual(s.stack(), [1, 2, 3]); 93 | }); 94 | 95 | test('`1 2 [+] unary` == 1 3', function(t) { 96 | t.plan(1); 97 | const s = stack(); 98 | s.push(1); 99 | s.push(2); 100 | s.fork(1); 101 | s.push(s.pop() + s.pop()); 102 | s.resolve(); 103 | t.deepEqual(s.stack(), [1, 3]); 104 | }); 105 | 106 | test('`1 2 [+] binary` == 3', function(t) { 107 | t.plan(1); 108 | const s = stack(); 109 | s.push(1); 110 | s.push(2); 111 | s.fork(2); 112 | s.push(s.pop() + s.pop()); 113 | s.resolve(); 114 | t.deepEqual(s.stack(), [3]); 115 | }); 116 | 117 | test('`1 2 [ [+] binary ] nullary` == 1 2 3', function(t) { 118 | t.plan(1); 119 | const s = stack(); 120 | s.push(1); 121 | s.push(2); 122 | s.fork(0); 123 | s.fork(2); 124 | s.push(s.pop() + s.pop()); 125 | s.resolve(); 126 | s.resolve(); 127 | t.deepEqual(s.stack(), [1, 2, 3]); 128 | }); 129 | 130 | test('`1 2 [ [+] binary ] unary` == 1 3', function(t) { 131 | t.plan(1); 132 | const s = stack(); 133 | s.push(1); 134 | s.push(2); 135 | s.fork(1); 136 | s.fork(2); 137 | s.push(s.pop() + s.pop()); 138 | s.resolve(); 139 | s.resolve(); 140 | t.deepEqual(s.stack(), [1, 3]); 141 | }); 142 | 143 | test('`1 2 [ [+] nullary ] unary` == 1 3', function(t) { 144 | t.plan(1); 145 | const s = stack(); 146 | s.push(1); 147 | s.push(2); 148 | s.fork(1); 149 | s.fork(0); 150 | s.push(s.pop() + s.pop()); 151 | s.resolve(); 152 | s.resolve(); 153 | t.deepEqual(s.stack(), [1, 3]); 154 | }); 155 | 156 | test('`1 2 [ [+] nullary ] binary` == 3', function(t) { 157 | t.plan(1); 158 | const s = stack(); 159 | s.push(1); 160 | s.push(2); 161 | s.fork(2); 162 | s.fork(0); 163 | s.push(s.pop() + s.pop()); 164 | s.resolve(); 165 | s.resolve(); 166 | t.deepEqual(s.stack(), [3]); 167 | }); 168 | 169 | test('`1 2 [ [+] nullary 4 ] binary` == 4', function(t) { 170 | t.plan(1); 171 | const s = stack(); 172 | s.push(1); 173 | s.push(2); 174 | s.fork(2); 175 | s.fork(0); 176 | s.push(s.pop() + s.pop()); 177 | s.resolve(); 178 | s.push(4); 179 | s.resolve(); 180 | t.deepEqual(s.stack(), [4]); 181 | }); 182 | 183 | test('`1 2 [ [ + ] nullary 1 ] unary` == 1 1', function(t) { 184 | t.plan(1); 185 | const s = stack(); 186 | s.push(1); 187 | s.push(2); 188 | s.fork(1); 189 | s.fork(0); 190 | s.push(s.pop() + s.pop()); 191 | s.resolve(); 192 | s.push(1); 193 | s.resolve(); 194 | t.deepEqual(s.stack(), [1, 1]); 195 | }); 196 | 197 | test('`1 2 [ 3 [ + ] nullary ] unary` == 1 5', function(t) { 198 | t.plan(1); 199 | const s = stack(); 200 | s.push(1); 201 | s.push(2); 202 | s.fork(1); 203 | s.push(3); 204 | s.fork(0); 205 | s.push(s.pop() + s.pop()); 206 | s.resolve(); 207 | s.resolve(); 208 | t.deepEqual(s.stack(), [1, 5]); 209 | }); 210 | 211 | test('`1 2 3 4 [ + + + ] nullary` == 1 2 3 4 10', function(t) { 212 | t.plan(1); 213 | const s = stack(); 214 | s.push(1); 215 | s.push(2); 216 | s.push(3); 217 | s.push(4); 218 | s.fork(0); 219 | s.push(s.pop() + s.pop()); 220 | s.push(s.pop() + s.pop()); 221 | s.push(s.pop() + s.pop()); 222 | s.resolve(); 223 | t.deepEqual(s.stack(), [1, 2, 3, 4, 10]); 224 | }); 225 | 226 | test('`[1 2 3 4] 0 [ at ] nullary` == [1 2 3 4] 0 1', function(t) { 227 | t.plan(1); 228 | const s = stack(); 229 | s.push([1, 2, 3, 4]); 230 | s.push(0); 231 | s.fork(0); 232 | const index = s.pop(); 233 | const aggr = s.pop(); 234 | s.push(aggr[index]); 235 | s.resolve(); 236 | t.deepEqual(s.stack(), [[1, 2, 3, 4], 0, 1]); 237 | }); 238 | 239 | test('`[[1] [2] [3] [4]] 0 [ at ] nullary` == [[1] [2] [3] [4]] 0 [1]', function(t) { 240 | t.plan(1); 241 | const s = stack(); 242 | s.push([[1], [2], [3], [4]]); 243 | s.push(0); 244 | s.fork(0); 245 | const index = s.pop(); 246 | const aggr = s.pop(); 247 | s.push(aggr[index]); 248 | s.resolve(); 249 | t.deepEqual(s.stack(), [[[1], [2], [3], [4]], 0, [1]]); 250 | }); 251 | 252 | test('`[1 2] [ dup pop ] nullary` == [1 2] [1 2]', function(t) { 253 | t.plan(1); 254 | const s = stack(); 255 | s.push([1, 2]); 256 | s.fork(0); 257 | const aggr = s.pop(); 258 | s.push(aggr); 259 | s.push([...aggr]); 260 | s.pop(); 261 | s.resolve(); 262 | t.deepEqual(s.stack(), [[1, 2], [1, 2]]); 263 | }); 264 | 265 | // This test fails, known bug 266 | /* 267 | When resolving forks that consume all of the elements of its parent fork and 268 | produces no new items it will restack the parent fork with elements from the 269 | grand-parent stack. If the parent-fork also does not produce any items it will 270 | wrongly duplicate the top of the grand-parent stack. 271 | 272 | Joy throws an error when this happens. 273 | */ 274 | // test('`1 [ dup pop [ pop ] unary ] nullary` == runtime error', function(t) { 275 | // t.plan(1); 276 | // const s = stack(); 277 | // s.push(1); 278 | // s.fork(0); 279 | // const v = s.pop(); 280 | // s.push(v); 281 | // s.push(v); 282 | // s.pop(); 283 | // s.fork(1); 284 | // s.pop(); 285 | // t.throws(() => s.resolve()); 286 | // }); 287 | 288 | //This is weird behavior, but in line with how Joy does it. 289 | test('`1 1 [ dup pop [pop] unary ] nullary` == [1 1 1]', function(t) { 290 | t.plan(1); 291 | const s = stack(); 292 | s.push(1); 293 | s.push(1); 294 | s.fork(0); 295 | const n = s.pop(); 296 | s.push(n); 297 | s.push(n); 298 | s.pop(); 299 | s.fork(1); 300 | s.pop(); 301 | s.resolve(); 302 | s.resolve(); 303 | t.deepEqual(s.stack(), [1, 1, 1]); 304 | }); 305 | -------------------------------------------------------------------------------- /lib/stdlib/aggregate.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { 3 | value, 4 | quotation, 5 | aggregate, 6 | assignVariable, 7 | assignVariableAggr, 8 | assignVariableEmptyAggr, 9 | exec, 10 | execWithArity, 11 | word 12 | } from './utils/program'; 13 | 14 | function cons(aggr, a) { 15 | aggr.body.push(a); 16 | return aggr; 17 | } 18 | 19 | function swons(a, aggr) { 20 | aggr.body.push(a); 21 | return aggr; 22 | } 23 | 24 | function append(aggr, a) { 25 | aggr.body.unshift(a); 26 | return aggr; 27 | } 28 | 29 | function swappend(a, aggr) { 30 | aggr.body.unshift(a); 31 | return aggr; 32 | } 33 | 34 | function uncons(aggr) { 35 | const a = aggr.body.pop(); 36 | return [aggr, a]; 37 | } 38 | 39 | function unswons(aggr) { 40 | const a = aggr.body.pop(); 41 | return [a, aggr]; 42 | } 43 | 44 | function concat(b, a) { 45 | return aggregate((b.type == 'quotation' ? b.body : [b]).concat(a.body)); 46 | } 47 | 48 | function enconcat(b, a, x) { 49 | return aggregate(b.body.concat(x, a.body)); 50 | } 51 | 52 | function first(aggr) { 53 | return aggr.body[aggr.body.length - 1]; 54 | } 55 | 56 | function last(aggr) { 57 | return aggr.body[0]; 58 | } 59 | 60 | function rest(aggr) { 61 | aggr.body.pop(); 62 | return aggr; 63 | } 64 | 65 | function of(aggr, i) { 66 | return aggr.body[aggr.body.length - i.body - 1]; 67 | } 68 | 69 | function at(i, aggr) { 70 | return aggr.body[aggr.body.length - i.body - 1]; 71 | } 72 | 73 | function ins(i, aggr, el) { 74 | const index = aggr.body.length - i.body - 1; 75 | if (index < 0) { 76 | const padding = Array(-index); 77 | aggr.body = padding.concat(aggr.body); 78 | } 79 | aggr.body[aggr.body.length - i.body - 1] = el; 80 | return aggr; 81 | } 82 | 83 | function size(aggr) { 84 | return value(aggr.body.length); 85 | } 86 | 87 | function drop(n, aggr) { 88 | aggr.body.length = n.body > aggr.body.length ? 0 : aggr.body.length - n.body; 89 | return aggr; 90 | } 91 | 92 | function take(n, aggr) { 93 | if (n.body == 0) { 94 | return aggregate([]); 95 | } 96 | return aggregate(aggr.body.slice(-n.body)); 97 | } 98 | 99 | function step(stepper, aggr) { 100 | const ret = []; 101 | for (let i = 0; i < aggr.body.length; i++) { 102 | ret.push.apply(ret, exec(stepper)); 103 | ret.push(aggr.body[i]); 104 | ret.push.apply(ret, assignVariable(aggr.body.length - i - 1, '_i')); 105 | } 106 | ret.push.apply(ret, assignVariableAggr(aggr, '_a')); 107 | return ret; 108 | } 109 | 110 | function map(mapper, aggr) { 111 | const ret = []; 112 | for (let i = 0; i < aggr.body.length; i++) { 113 | ret.push(word('swappend')); 114 | ret.push.apply(ret, execWithArity(1, mapper)); 115 | ret.push(aggr.body[i]); 116 | ret.push.apply(ret, assignVariable(aggr.body.length - i - 1, '_i')); 117 | } 118 | ret.push(aggregate([])); 119 | ret.push.apply(ret, assignVariableAggr(aggr, '_a')); 120 | return ret; 121 | } 122 | 123 | function map2(mapper, aggr1, aggr2) { 124 | const ret = []; 125 | for (let i = 0; i < aggr1.body.length; i++) { 126 | ret.push(word('swappend')); 127 | ret.push.apply(ret, execWithArity(2, mapper)); 128 | ret.push(aggr1.body[i], aggr2.body[i + (aggr2.body.length - aggr1.body.length)]); 129 | ret.push.apply(ret, assignVariable(aggr1.body.length - i - 1, '_i')); 130 | } 131 | ret.push.apply(ret, assignVariableAggr(aggr1, '_a1')); 132 | ret.push.apply(ret, assignVariableAggr(aggr2, '_a2')); 133 | ret.push(aggregate([])); 134 | return ret; 135 | } 136 | 137 | function fold(folder, v0, aggr) { 138 | const ret = []; 139 | for (let i = 0; i < aggr.body.length; i++) { 140 | ret.push.apply(ret, execWithArity(2, folder)); 141 | ret.push(aggr.body[i]); 142 | ret.push.apply(ret, assignVariable(aggr.body.length - i - 1, '_i')); 143 | } 144 | ret.push(v0); 145 | ret.push.apply(ret, assignVariableAggr(aggr, '_a')); 146 | return ret; 147 | } 148 | 149 | function sort(aggr) { 150 | aggr.body.sort((a, b) => b.body - a.body); 151 | return aggr; 152 | } 153 | 154 | function sortBy(prop, aggr) { 155 | aggr.body.sort( 156 | (a, b) => 157 | b.body[b.body.length - prop.body - 1].body - a.body[a.body.length - prop.body - 1].body 158 | ); 159 | return aggr; 160 | } 161 | 162 | function filter(test, aggr) { 163 | const ret = []; 164 | for (let i = 0; i < aggr.body.length; i++) { 165 | ret.push(word('branch'), quotation([word('pop')]), quotation([word('swappend')])); 166 | ret.push.apply(ret, execWithArity(0, test)); 167 | ret.push(aggr.body[i]); 168 | ret.push.apply(ret, assignVariable(aggr.body.length - i - 1, '_i')); 169 | } 170 | ret.push(quotation([])); 171 | ret.push.apply(ret, assignVariableAggr(aggr, '_a')); 172 | return ret; 173 | } 174 | 175 | function split(test, aggr) { 176 | const ret = [word('_f'), word('_t')]; 177 | for (let i = 0; i < aggr.body.length; i++) { 178 | ret.push( 179 | word('branch'), 180 | quotation([word('pop'), word('append'), word('_f')]), 181 | quotation([word('pop'), word('append'), word('_t')]) 182 | ); 183 | ret.push.apply(ret, execWithArity(0, test)); 184 | ret.push(aggr.body[i]); 185 | ret.push.apply(ret, assignVariable(aggr.body.length - i - 1, '_i')); 186 | } 187 | ret.push.apply(ret, assignVariableEmptyAggr('_t')); 188 | ret.push.apply(ret, assignVariableEmptyAggr('_f')); 189 | ret.push.apply(ret, assignVariableAggr(aggr, '_a')); 190 | return ret; 191 | } 192 | 193 | function some(test, aggr) { 194 | test.body.unshift(word('||')); 195 | return fold(test, value(false), aggr); 196 | } 197 | 198 | function all(test, aggr) { 199 | test.body.unshift(word('&&')); 200 | return fold(test, value(true), aggr); 201 | } 202 | 203 | export default { 204 | cons: jsWord(2, 'cons', cons), 205 | swons: jsWord(2, 'swons', swons), 206 | append: jsWord(2, 'append', append), 207 | swappend: jsWord(2, 'swappend', swappend), 208 | uncons: jsWord(1, 'uncons', uncons), 209 | unswons: jsWord(1, 'unswons', unswons), 210 | concat: jsWord(2, 'concat', concat), 211 | enconcat: jsWord(3, 'enconcat', enconcat), 212 | first: jsWord(1, 'first', first), 213 | last: jsWord(1, 'last', last), 214 | rest: jsWord(1, 'rest', rest), 215 | of: jsWord(2, 'of', of), 216 | at: jsWord(2, 'at', at), 217 | ins: jsWord(3, 'ins', ins), 218 | size: jsWord(1, 'size', size), 219 | drop: jsWord(2, 'drop', drop), 220 | take: jsWord(2, 'take', take), 221 | step: jsWord(2, 'step', step), 222 | map: jsWord(2, 'map', map), 223 | map2: jsWord(3, 'map2', map2), 224 | fold: jsWord(3, 'fold', fold), 225 | sort: jsWord(1, 'sort', sort), 226 | sortBy: jsWord(2, 'sortBy', sortBy), 227 | filter: jsWord(2, 'filter', filter), 228 | split: jsWord(2, 'split', split), 229 | some: jsWord(2, 'some', some), 230 | all: jsWord(2, 'all', all) 231 | }; 232 | -------------------------------------------------------------------------------- /lib/stdlib/controlflow.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { execWithArity, word, exec } from './utils/program'; 3 | 4 | function ifte(falseBranch, trueBranch, testQuotation) { 5 | const ret = [word('branch'), falseBranch, trueBranch]; 6 | ret.push.apply(ret, execWithArity(0, testQuotation)); 7 | 8 | return ret; 9 | } 10 | 11 | function branch(falseBranch, trueBranch, test) { 12 | if (test.body) { 13 | return exec(trueBranch); 14 | } else { 15 | return exec(falseBranch); 16 | } 17 | } 18 | 19 | function cond(quotation, test) { 20 | if (test.body) { 21 | return exec(quotation); 22 | } 23 | } 24 | 25 | export default { 26 | ifte: jsWord(3, 'ifte', ifte), 27 | branch: jsWord(3, 'branch', branch), 28 | cond: jsWord(2, 'cond', cond) 29 | }; 30 | -------------------------------------------------------------------------------- /lib/stdlib/index.js: -------------------------------------------------------------------------------- 1 | import math from './math'; 2 | import stack from './stack'; 3 | import string from './string'; 4 | import iterators from './iterators'; 5 | import logic from './logic'; 6 | import aggregate from './aggregate'; 7 | import quotations from './quotations'; 8 | import controlflow from './controlflow'; 9 | import recursion from './recursion'; 10 | import internal from './internal'; 11 | 12 | export default Object.assign( 13 | {}, 14 | math, 15 | stack, 16 | string, 17 | iterators, 18 | logic, 19 | aggregate, 20 | quotations, 21 | controlflow, 22 | recursion, 23 | internal 24 | ); 25 | -------------------------------------------------------------------------------- /lib/stdlib/internal.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | 3 | function pushScope() { 4 | this.pushScope(); 5 | } 6 | function popScope() { 7 | this.popScope(); 8 | } 9 | function forkStack(n) { 10 | this.stack.fork(n.body); 11 | } 12 | function resolveStack() { 13 | this.stack.resolve(); 14 | } 15 | 16 | export default { 17 | pushScope: jsWord(0, 'pushScope', pushScope), 18 | popScope: jsWord(0, 'popScope', popScope), 19 | forkStack: jsWord(1, 'forkStack', forkStack), 20 | resolveStack: jsWord(0, 'resolveStack', resolveStack) 21 | }; 22 | -------------------------------------------------------------------------------- /lib/stdlib/iterators.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { execWithArity, exec, word, quotation, assignVariable } from './utils/program'; 3 | 4 | function whileLoop(loopQuote, checkQuote) { 5 | const rec = []; 6 | rec.push(word('while'), loopQuote, checkQuote); 7 | rec.push.apply(rec, exec(loopQuote)); 8 | 9 | const ret = [word('cond'), quotation(rec)]; 10 | ret.push.apply(ret, execWithArity(0, checkQuote)); 11 | 12 | return ret; 13 | } 14 | 15 | function times(loopQuote, n) { 16 | let ret = []; 17 | 18 | for (let i = 0; i < n.body; i++) { 19 | ret.push.apply(ret, exec(loopQuote)); 20 | ret.push.apply(ret, assignVariable(n.body - i - 1, '_i')); 21 | } 22 | 23 | return ret; 24 | } 25 | 26 | export default { 27 | while: jsWord(2, 'while', whileLoop), 28 | times: jsWord(2, 'times', times) 29 | }; 30 | -------------------------------------------------------------------------------- /lib/stdlib/logic.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { value } from './utils/program'; 3 | 4 | function small(top) { 5 | if (top.type == 'quotation') { 6 | return value(top.body.length < 2); 7 | } 8 | return value(top.body === 0 || top.body === 1 || top.body === -1); 9 | } 10 | 11 | function zero(top) { 12 | if (top.type == 'quotation') { 13 | return value(top.body.length === 0); 14 | } 15 | return value(top.body === 0); 16 | } 17 | 18 | export default { 19 | '>': jsWord(2, '>', (b, a) => value(a.body > b.body)), 20 | '<': jsWord(2, '<', (b, a) => value(a.body < b.body)), 21 | '=': jsWord(2, '=', (b, a) => { 22 | return value(a.body === b.body); 23 | }), 24 | '!=': jsWord(2, '!=', (b, a) => value(a.body !== b.body)), 25 | '>=': jsWord(2, '>=', (b, a) => value(a.body >= b.body)), 26 | '<=': jsWord(2, '<=', (b, a) => value(a.body <= b.body)), 27 | '||': jsWord(2, '||', (b, a) => value(a.body || b.body)), 28 | '&&': jsWord(2, '&&', (b, a) => value(a.body && b.body)), 29 | small: jsWord(1, 'small', small), 30 | zero: jsWord(1, 'zero', zero) 31 | }; 32 | -------------------------------------------------------------------------------- /lib/stdlib/math.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { value } from './utils/program'; 3 | 4 | export default { 5 | // CONSTANTS 6 | PI: jsWord(0, 'PI', () => value(Math.PI)), 7 | E: jsWord(0, 'E', () => value(Math.E)), 8 | // RANDOM 9 | random: jsWord(0, 'random', () => value(Math.random())), 10 | random2: jsWord(2, 'random2', (upper, lower) => 11 | value(lower.body + Math.random() * (upper.body - lower.body)) 12 | ), 13 | // ARITHMETICS 14 | '*': jsWord(2, '*', (b, a) => value(a.body * b.body)), 15 | '/': jsWord(2, '/', (b, a) => value(a.body / b.body)), 16 | '+': jsWord(2, '+', (b, a) => value(a.body + b.body)), 17 | '-': jsWord(2, '-', (b, a) => value(a.body - b.body)), 18 | mod: jsWord(2, 'mod', (b, a) => value(a.body % b.body)), 19 | neg: jsWord(1, 'neg', a => value(-a.body)), 20 | abs: jsWord(1, 'abs', a => value(Math.abs(a.body))), 21 | max: jsWord(2, 'max', (b, a) => value(Math.max(a.body, b.body))), 22 | min: jsWord(2, 'min', (b, a) => value(Math.min(a.body, b.body))), 23 | sqrt: jsWord(1, 'sqrt', a => value(Math.sqrt(a.body))), 24 | cbrt: jsWord(1, 'cbrt', a => value(Math.cbrt(a.body))), 25 | pow: jsWord(2, 'pow', (b, a) => value(Math.pow(a.body, b.body))), 26 | floor: jsWord(1, 'floor', a => value(Math.floor(a.body))), 27 | ceil: jsWord(1, 'ceil', a => value(Math.ceil(a.body))), 28 | round: jsWord(1, 'round', a => value(Math.round(a.body))), 29 | exp: jsWord(1, 'exp', a => value(Math.exp(a.body))), 30 | sign: jsWord(1, 'sign', a => value(Math.sign(a.body))), 31 | log: jsWord(1, 'log', a => value(Math.log(a.body))), 32 | log10: jsWord(1, 'log10', a => value(Math.log10(a.body))), 33 | // TRIGONOMETRY 34 | cos: jsWord(1, 'cos', a => value(Math.cos(a.body))), 35 | acos: jsWord(1, 'acos', a => value(Math.acos(a.body))), 36 | cosh: jsWord(1, 'cosh', a => value(Math.cosh(a.body))), 37 | acosh: jsWord(1, 'acosh', a => value(Math.acosh(a.body))), 38 | sin: jsWord(1, 'sin', a => value(Math.sin(a.body))), 39 | asin: jsWord(1, 'asin', a => value(Math.asin(a.body))), 40 | sinh: jsWord(1, 'sinh', a => value(Math.sinh(a.body))), 41 | asinh: jsWord(1, 'asinh', a => value(Math.asinh(a.body))), 42 | tan: jsWord(1, 'tan', a => value(Math.tan(a.body))), 43 | atan: jsWord(1, 'atan', a => value(Math.atan(a.body))), 44 | atan2: jsWord(2, 'atan2', (b, a) => value(Math.atan2(a.body, b.body))), 45 | tanh: jsWord(1, 'tanh', a => value(Math.tanh(a.body))), 46 | atanh: jsWord(1, 'atanh', a => value(Math.atanh(a.body))), 47 | // Specials 48 | succ: jsWord(1, 'succ', a => value(a.body + 1)), 49 | pred: jsWord(1, 'pred', a => value(a.body - 1)) 50 | }; 51 | -------------------------------------------------------------------------------- /lib/stdlib/quotations.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { execWithArity, word, quotation, exec } from './utils/program'; 3 | 4 | function _exec(quotation) { 5 | return exec(quotation); 6 | } 7 | 8 | function nullary(quotation) { 9 | return execWithArity(0, quotation); 10 | } 11 | 12 | function unary(quotation) { 13 | return execWithArity(1, quotation); 14 | } 15 | 16 | function binary(quotation) { 17 | return execWithArity(2, quotation); 18 | } 19 | 20 | function ternary(quotation) { 21 | return execWithArity(3, quotation); 22 | } 23 | 24 | function dip(q, top) { 25 | const ret = [top]; 26 | ret.push.apply(ret, exec(q)); 27 | return ret; 28 | } 29 | 30 | function cleave(q2, q1, top) { 31 | const ret = [word('dip')]; 32 | const inner = []; 33 | inner.push.apply(inner, exec(q1)); 34 | inner.push(top); 35 | ret.push(quotation(inner)); 36 | ret.push.apply(ret, exec(q2)); 37 | ret.push(top); 38 | 39 | return ret; 40 | } 41 | 42 | export default { 43 | exec: jsWord(1, 'exec', _exec), 44 | nullary: jsWord(1, 'nullary', nullary), 45 | unary: jsWord(1, 'unary', unary), 46 | binary: jsWord(1, 'binary', binary), 47 | ternary: jsWord(1, 'ternary', ternary), 48 | dip: jsWord(2, 'dip', dip), 49 | cleave: jsWord(3, 'cleave', cleave) 50 | }; 51 | -------------------------------------------------------------------------------- /lib/stdlib/recursion.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { execWithArity, quotation, word, exec } from './utils/program'; 3 | 4 | function linrec(collect, generate, done, test) { 5 | const rec = []; 6 | rec.push.apply(rec, exec(collect)); 7 | rec.push(word('linrec'), collect, generate, done, test); 8 | rec.push.apply(rec, exec(generate)); 9 | 10 | const ret = [word('branch')]; 11 | ret.push(quotation(rec)); 12 | ret.push(done); 13 | ret.push.apply(ret, execWithArity(0, test)); 14 | 15 | return ret; 16 | } 17 | 18 | function tailrec(step, done, test) { 19 | const rec = []; 20 | rec.push(word('tailrec'), step, done, test); 21 | rec.push.apply(rec, exec(step)); 22 | 23 | const ret = [word('branch')]; 24 | ret.push(quotation(rec)); 25 | ret.push(done); 26 | ret.push.apply(ret, execWithArity(0, test)); 27 | 28 | return ret; 29 | } 30 | 31 | function binrec(collect, generate, done, test) { 32 | const rec = []; 33 | rec.push.apply(rec, exec(collect)); 34 | rec.push( 35 | word('dip'), 36 | quotation([word('binrec'), collect, generate, done, test]), 37 | word('binrec'), 38 | collect, 39 | generate, 40 | done, 41 | test 42 | ); 43 | rec.push.apply(rec, exec(generate)); 44 | 45 | const ret = [word('branch')]; 46 | ret.push(quotation(rec)); 47 | ret.push(done); 48 | ret.push.apply(ret, execWithArity(0, test)); 49 | 50 | return ret; 51 | } 52 | 53 | export default { 54 | linrec: jsWord(4, 'linrec', linrec), 55 | tailrec: jsWord(3, 'tailrec', tailrec), 56 | binrec: jsWord(4, 'binrec', binrec) 57 | }; 58 | -------------------------------------------------------------------------------- /lib/stdlib/stack.js: -------------------------------------------------------------------------------- 1 | import clone from 'clone'; 2 | import { jsWord } from '../runtime/lexicon'; 3 | import { prettyPrint } from '../parser/ast-walkers'; 4 | import { quotation } from './utils/program'; 5 | 6 | const pop = _ => {}; // eslint-disable-line no-unused-vars 7 | const popd = (a, _) => a; // eslint-disable-line no-unused-vars 8 | const swap = (a, b) => [b, a]; 9 | const swapd = (z, a, b) => [z, b, a]; 10 | const rollup = (a, b, c) => [c, a, b]; 11 | const rollupd = (z, a, b, c) => [z, c, a, b]; 12 | const rolldown = (a, b, c) => [b, c, a]; 13 | const rolldownd = (z, a, b, c) => [z, b, c, a]; 14 | const rotate = (a, b, c) => [c, b, a]; 15 | const rotated = (z, a, b, c) => [z, c, b, a]; 16 | 17 | function dup(top) { 18 | if (top.type == 'quotation') { 19 | // We have to clone the body to avoid accidental shared state 20 | return [clone(top), top]; 21 | } 22 | 23 | return [top, top]; 24 | } 25 | 26 | function dupd(a, b) { 27 | if (b.type == 'quotation') { 28 | // We have to clone the body to avoid accidental shared state 29 | return [a, b, clone(b)]; 30 | } 31 | 32 | return [a, b, b]; 33 | } 34 | 35 | function unstack(newStack) { 36 | this.unstack(newStack.body); 37 | } 38 | 39 | function stack() { 40 | // This has to be a copy, otherwise it will contain itself 41 | return quotation(clone(this.stack.stack(), false).reverse()); 42 | } 43 | 44 | const dot = top => console.log(prettyPrint(top)); 45 | 46 | export default { 47 | '.': jsWord(1, '.', dot), 48 | dup: jsWord(1, 'dup', dup), 49 | dupd: jsWord(2, 'dupd', dupd), 50 | pop: jsWord(1, 'pop', pop), 51 | popd: jsWord(2, 'popd', popd), 52 | swap: jsWord(2, 'swap', swap), 53 | swapd: jsWord(3, 'swapd', swapd), 54 | rollup: jsWord(3, 'rollup', rollup), 55 | rollupd: jsWord(4, 'rollupd', rollupd), 56 | rolldown: jsWord(3, 'rolldown', rolldown), 57 | rolldownd: jsWord(4, 'rolldownd', rolldownd), 58 | rotate: jsWord(3, 'rotate', rotate), 59 | rotated: jsWord(4, 'rotated', rotated), 60 | stack: jsWord(0, 'stack', stack), 61 | unstack: jsWord(1, 'unstack', unstack) 62 | }; 63 | -------------------------------------------------------------------------------- /lib/stdlib/string.js: -------------------------------------------------------------------------------- 1 | import { jsWord } from '../runtime/lexicon'; 2 | import { value } from './utils/program'; 3 | 4 | export default { 5 | replace: jsWord(2, 'replace', function(string, replacement) { 6 | return value(string.body.replace(/%s/, replacement.body)); 7 | }), 8 | replace2: jsWord(3, 'replace2', function(string, r1, r2) { 9 | return value(string.body.replace(/%s/, r1.body).replace(/%s/, r2.body)); 10 | }), 11 | replace3: jsWord(4, 'replace3', function(string, r1, r2, r3) { 12 | return value(string.body.replace(/%s/, r1.body).replace(/%s/, r2.body).replace(/%s/, r3.body)); 13 | }) 14 | }; 15 | -------------------------------------------------------------------------------- /lib/stdlib/test/_runTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import BaseRuntime from '../../runtime/runtime'; 3 | import { unwrapValueInternal } from '../../parser/ast-walkers'; 4 | 5 | function internalTestProgram(fn, program, expectedStack) { 6 | fn(`Evaluating "${program}" should yield ${JSON.stringify(expectedStack)}`, function(t) { 7 | t.plan(1); 8 | const runtime = new BaseRuntime(); 9 | runtime.evaluate(program); 10 | t.deepEqual(runtime.stack.stack().map(unwrapValueInternal), expectedStack); 11 | }); 12 | } 13 | 14 | export function testProgram(program, expectedStack) { 15 | internalTestProgram(test, program, expectedStack); 16 | } 17 | 18 | testProgram.only = function(program, expectedStack) { 19 | internalTestProgram(test.only, program, expectedStack); 20 | }; 21 | 22 | function internalTestProgramShouldThrow(fn, program) { 23 | fn(`Evaluating "${program}" should throw`, function(t) { 24 | t.plan(1); 25 | const runtime = new BaseRuntime(); 26 | t.throws(() => runtime.evaluate(program)); 27 | }); 28 | } 29 | 30 | export function testProgramShouldThrow(program) { 31 | internalTestProgramShouldThrow(test, program); 32 | } 33 | 34 | testProgramShouldThrow.only = function(program) { 35 | internalTestProgramShouldThrow(test.only, program); 36 | }; 37 | 38 | function internalTestProgramAssertOnTop(fn, program, assertion) { 39 | fn( 40 | `Evaluating "${program}" should yield top of stack satisfying assertion ${assertion.toString()}`, 41 | function(t) { 42 | t.plan(1); 43 | const runtime = new BaseRuntime(); 44 | runtime.evaluate(program); 45 | t.true(assertion(unwrapValueInternal(runtime.stack.stack().pop()))); 46 | } 47 | ); 48 | } 49 | 50 | export function testProgramAssertOnTop(program, assertion) { 51 | internalTestProgramAssertOnTop(test, program, assertion); 52 | } 53 | 54 | testProgramAssertOnTop.only = function(program, assertion) { 55 | internalTestProgramAssertOnTop(test.only, program, assertion); 56 | }; 57 | -------------------------------------------------------------------------------- /lib/stdlib/test/aggregate.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | // 'cons' 4 | testProgram('[] [] cons', [[[]]]); 5 | testProgram('[1] [2] cons', [[[1], 2]]); 6 | testProgram('[2] [1] cons', [[[2], 1]]); 7 | testProgram('1 [2] cons', [[1, 2]]); 8 | testProgram('2 [1] cons', [[2, 1]]); 9 | testProgram('2 ["asdf"] cons', [[2, 'asdf']]); 10 | // 'swons' 11 | testProgram('[] [] swons', [[[]]]); 12 | testProgram('[1] [2] swons', [[[2], 1]]); 13 | testProgram('[2] [1] swons', [[[1], 2]]); 14 | testProgram('[2] 1 swons', [[1, 2]]); 15 | testProgram('[1] 2 swons', [[2, 1]]); 16 | testProgram('["asdf"] 2 swons', [[2, 'asdf']]); 17 | // 'append' 18 | testProgram('[] [] append', [[[]]]); 19 | testProgram('[1] [2] append', [[2, [1]]]); 20 | testProgram('[2] [1] append', [[1, [2]]]); 21 | testProgram('1 [2] append', [[2, 1]]); 22 | testProgram('2 [1] append', [[1, 2]]); 23 | testProgram('2 ["asdf"] append', [['asdf', 2]]); 24 | // 'swappend' 25 | testProgram('[] [] swappend', [[[]]]); 26 | testProgram('[1] [2] swappend', [[1, [2]]]); 27 | testProgram('[2] [1] swappend', [[2, [1]]]); 28 | testProgram('[2] 1 swappend', [[2, 1]]); 29 | testProgram('[1] 2 swappend', [[1, 2]]); 30 | testProgram('["asdf"] 2 swappend', [['asdf', 2]]); 31 | // 'uncons' 32 | testProgram('[1 2] uncons', [1, [2]]); 33 | testProgram('[1] uncons', [1, []]); 34 | testProgram('[[1 2] 3] uncons', [[1, 2], [3]]); 35 | // 'unswons' 36 | testProgram('[1 2] unswons', [[2], 1]); 37 | testProgram('[1] unswons', [[], 1]); 38 | testProgram('[[1 2] 3] unswons', [[3], [1, 2]]); 39 | // 'concat' 40 | testProgram('[] [] concat', [[]]); 41 | testProgram('[1] [2] concat', [[1, 2]]); 42 | testProgram('[2] [1] concat', [[2, 1]]); 43 | testProgram('[1] 2 concat', [[1, 2]]); 44 | testProgram('[1] "asdf" concat', [[1, 'asdf']]); 45 | testProgram('[] 1 concat', [[1]]); 46 | // 'enconcat' 47 | testProgram('2 [] [] enconcat', [[2]]); 48 | testProgram('2 [1] [3] enconcat', [[1, 2, 3]]); 49 | testProgram('2 [3] [1] enconcat', [[3, 2, 1]]); 50 | testProgram('[2] [1] [3] enconcat', [[1, [2], 3]]); 51 | testProgram('"asdf" [1] [] enconcat', [[1, 'asdf']]); 52 | // 'first' 53 | testProgram('[1] first', [1]); 54 | testProgram('[1 2] first', [1]); 55 | testProgram('[1 2 3] first', [1]); 56 | testProgram('[[1 2 3]] first', [[1, 2, 3]]); 57 | // 'last' 58 | testProgram('[1] last', [1]); 59 | testProgram('[1 2] last', [2]); 60 | testProgram('[1 2 3] last', [3]); 61 | testProgram('[[1 2 3]] last', [[1, 2, 3]]); 62 | // 'rest' 63 | testProgram('[1] rest', [[]]); 64 | testProgram('[1 2] rest', [[2]]); 65 | testProgram('[1 2 3] rest', [[2, 3]]); 66 | // 'at' 67 | testProgram('[1 2] 1 at', [2]); 68 | testProgram('[1 2] 0 at', [1]); 69 | testProgram('[[1 2] 3] 0 at', [[1, 2]]); 70 | // 'of' 71 | testProgram('1 [1 2] of', [2]); 72 | testProgram('0 [1 2] of', [1]); 73 | testProgram('0 [[1 2] 3] of', [[1, 2]]); 74 | // 'ins' 75 | testProgram('3 [1 2] 1 ins', [[1, 3]]); 76 | testProgram('3 [1 2] 0 ins', [[3, 2]]); 77 | testProgram('2 [1 4 3] 1 ins', [[1, 2, 3]]); 78 | testProgram('2 [] 2 ins', [[, , 2]]); // eslint-disable-line 79 | testProgram('2 [1 2] 2 ins', [[1, 2, 2]]); 80 | // 'size' 81 | testProgram('[] size', [0]); 82 | testProgram('[1] size', [1]); 83 | testProgram('[1 2] size', [2]); 84 | testProgram('[1 2 3] size', [3]); 85 | // 'drop' 86 | testProgram('[] 0 drop', [[]]); 87 | testProgram('[1] 1 drop', [[]]); 88 | testProgram('[1] 0 drop', [[1]]); 89 | testProgram('[1 2] 1 drop', [[2]]); 90 | testProgram('[1 2 3] 1 drop', [[2, 3]]); 91 | testProgram('[] 1 drop', [[]]); 92 | testProgram('[1] 2 drop', [[]]); 93 | // 'take' 94 | testProgram('[] 0 take', [[]]); 95 | testProgram('[1] 1 take', [[1]]); 96 | testProgram('[1] 0 take', [[]]); 97 | testProgram('[1 2] 1 take', [[1]]); 98 | testProgram('[1 2 3] 1 take', [[1]]); 99 | testProgram('[1 2 3] 2 take', [[1, 2]]); 100 | testProgram('[] 1 take', [[]]); 101 | testProgram('[1] 2 take', [[1]]); 102 | // 'step' 103 | testProgram('[1] [ 1 + ] step', [2]); 104 | testProgram('[1 2 3] [ 1 + ] step', [2, 3, 4]); 105 | testProgram('[] [ 1 + ] step', []); 106 | testProgram('[1 2 3] [ dup ] step', [1, 1, 2, 2, 3, 3]); 107 | testProgram('[1 2 3] [] step', [1, 2, 3]); 108 | testProgram('[1 2 3] [ _i ] step', [1, 0, 2, 1, 3, 2]); 109 | testProgram('[1 2 3] [ _a ] step', [1, [1, 2, 3], 2, [1, 2, 3], 3, [1, 2, 3]]); 110 | // 'map' 111 | testProgram('[1] [ 1 + ] map', [[2]]); 112 | testProgram('[1 2 3] [ 1 + ] map', [[2, 3, 4]]); 113 | testProgram('[1 2 3] [ _i + ] map', [[1, 3, 5]]); 114 | testProgram('[1 2 3] [ _a _i at + ] map', [[2, 4, 6]]); 115 | testProgram('[] [ 1 + ] map', [[]]); 116 | // 'map2' 117 | testProgram('[1] [1] [ + ] map2', [[2]]); 118 | testProgram('[1 2 3] [1 2 3] [ + ] map2', [[2, 4, 6]]); 119 | testProgram('[2 4 6] [1 2 3] [ / ] map2', [[2, 2, 2]]); 120 | testProgram('[1] [] [ + ] map2', [[]]); 121 | testProgram('[1 2 3] [1 2] [ + ] map2', [[2, 4]]); 122 | testProgram('[3 4 5] [1 2] [ - ] map2', [[2, 2]]); 123 | testProgram('[1 2 3] [1 2] [ _i + + ] map2', [[2, 5]]); 124 | testProgram('[1 2 3] [1 2] [ _a1 _i at _a2 _i at + + + ] map2', [[4, 8]]); 125 | // 'sort' 126 | testProgram('[1] sort', [[1]]); 127 | testProgram('[1 2 3] sort', [[1, 2, 3]]); 128 | testProgram('[] sort', [[]]); 129 | testProgram('[3 1 2] sort', [[1, 2, 3]]); 130 | testProgram('["c" "a" "b"] sort', [['c', 'a', 'b']]); 131 | testProgram('["a" 1 "c"] sort', [['a', 1, 'c']]); 132 | // 'sortBy' 133 | testProgram('[1] "" sortBy', [[1]]); 134 | testProgram('[[1] [3] [2]] 0 sortBy', [[[1], [2], [3]]]); 135 | testProgram('[[1 3] [3 2] [2 1]] 1 sortBy', [[[2, 1], [3, 2], [1, 3]]]); 136 | testProgram('[] [0] sortBy', [[]]); 137 | // 'fold' 138 | testProgram('[1] [] [ 1 + concat ] fold', [[2]]); 139 | testProgram('[1 2 3] [] [ 1 + concat ] fold', [[2, 3, 4]]); 140 | testProgram('[] [] [ 1 + concat ] fold', [[]]); 141 | testProgram('[1 2 3] 0 [ + ] fold', [6]); 142 | testProgram('[1 2 3] 0 [ _i + + ] fold', [9]); 143 | testProgram('[1 2 3] 0 [ _a _i at + + ] fold', [12]); 144 | testProgram('[1 2 3] false [ = 3 || ] fold', [true]); 145 | // 'filter' 146 | testProgram('[1 2 3 4 5 6] [2 mod 0 =] filter', [[2, 4, 6]]); 147 | testProgram('[] [true] filter', [[]]); 148 | testProgram('[1 2 3] [ _i 1 + = ] filter', [[1, 2, 3]]); 149 | testProgram('[1 2 3] [ _a _i at = ] filter', [[1, 2, 3]]); 150 | // 'split' 151 | testProgram('[1 2 3 4 5 6] [3 <] split', [[1, 2], [3, 4, 5, 6]]); 152 | testProgram('2 [6 8 1 3 5 7 9] [>] split', [2, [1], [6, 8, 3, 5, 7, 9]]); 153 | testProgram('[1 2 3 4 5 6] [ _i 2 mod 1 = ] split', [[2, 4, 6], [1, 3, 5]]); 154 | testProgram('[1 2 3 4 5 6] [ _a _i at = ] split', [[1, 2, 3, 4, 5, 6], []]); 155 | testProgram('[] [ true ] split', [[], []]); 156 | // 'some' 157 | testProgram('[1 2 3 4 5] [3 =] some', [true]); 158 | testProgram('[1 2 4 5] [3 =] some', [false]); 159 | testProgram('[1 2 4 5] [ _i = ] some', [false]); 160 | // 'all' 161 | testProgram('[1 2 3 4 5] [3 =] all', [false]); 162 | testProgram('[3 3 3 3] [3 =] all', [true]); 163 | testProgram('[1 2 4 5] [ _i != ] all', [true]); 164 | 165 | // bug-tests 166 | 167 | testProgram( 168 | ` 169 | halfWidth: 500 2 / ; 170 | negativeHalfWidth: halfWidth -1 * ; 171 | 172 | negativeHalfWidth halfWidth 173 | `, 174 | [-250, 250] 175 | ); 176 | 177 | testProgram( 178 | ` 179 | colorwheel: ["teal" "teal" "teal" "teal"] ; 180 | randomColor: 0 colorwheel size random2 floor colorwheel of ; 181 | 182 | randomColor 183 | `, 184 | ['teal'] 185 | ); 186 | 187 | testProgram( 188 | ` 189 | gen: 190 | [] 191 | 10 192 | [ _i [] cons swappend ] 193 | times ; 194 | 195 | gen 196 | `, 197 | [[[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]]] 198 | ); 199 | 200 | testProgram( 201 | ` 202 | point: [] cons cons ; 203 | 204 | 0 0 point 205 | 0 1 point 206 | `, 207 | [[0, 0], [0, 1]] 208 | ); 209 | 210 | testProgram( 211 | ` 212 | filterFn: [ 3 > ] ; 213 | [1 2 3 4 5 6] filterFn filter 214 | filterFn 215 | `, 216 | [[4, 5, 6], [3, '>']] 217 | ); 218 | -------------------------------------------------------------------------------- /lib/stdlib/test/controlflow.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | // 'ifte' 4 | testProgram('[true] [0] [1] ifte', [0]); 5 | testProgram('[false] [0] [1] ifte', [1]); 6 | // 'branch' 7 | testProgram('true [0] [1] branch', [0]); 8 | testProgram('1 [0] [1] branch', [0]); 9 | testProgram('"asdf" [0] [1] branch', [0]); 10 | testProgram('true [] [] branch', []); 11 | testProgram('false [0] [1] branch', [1]); 12 | testProgram('0 [0] [1] branch', [1]); 13 | testProgram('"" [0] [1] branch', [1]); 14 | // 'cond' 15 | testProgram('true [0] cond', [0]); 16 | testProgram('false [0] cond', []); 17 | testProgram('"asdf" [0] cond', [0]); 18 | testProgram('true [] cond', []); 19 | testProgram('0 [0] cond', []); 20 | testProgram('"" [0] cond', []); 21 | -------------------------------------------------------------------------------- /lib/stdlib/test/definitions.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | testProgram( 4 | ` 5 | test: 1 2 3 ; 6 | test 7 | `, 8 | [1, 2, 3] 9 | ); 10 | 11 | testProgram( 12 | ` 13 | test: 1 2 3 ; 14 | test [ 4 5 6 ] exec 15 | `, 16 | [1, 2, 3, 4, 5, 6] 17 | ); 18 | 19 | testProgram( 20 | ` 21 | test: 4 5 6 ; 22 | [ 1 2 3 ] exec test 23 | `, 24 | [1, 2, 3, 4, 5, 6] 25 | ); 26 | -------------------------------------------------------------------------------- /lib/stdlib/test/iterators.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | // 'while' 4 | testProgram('0 [ 10 < ] [ 1 + ] while', [10]); 5 | testProgram('[[1 2 3]] 0 [ 1 < ] [ [ at ] nullary swap 1 + ] while', [[[1, 2, 3]], [1, 2, 3], 1]); 6 | testProgram('false [] [] while', []); 7 | testProgram('0 10 [ 1 + ] times', [10]); 8 | 9 | //Testing loop counter as variable 10 | testProgram('10 [ _i ] times', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); 11 | -------------------------------------------------------------------------------- /lib/stdlib/test/logic.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | testProgram('1 2 <', [true]); 4 | testProgram('2 1 <', [false]); 5 | testProgram('2 2 =', [true]); 6 | testProgram('2 1 =', [false]); 7 | testProgram('2 2 !=', [false]); 8 | testProgram('2 1 !=', [true]); 9 | testProgram('2 1 >=', [true]); 10 | testProgram('1 2 >=', [false]); 11 | testProgram('2 1 <=', [false]); 12 | testProgram('1 2 <=', [true]); 13 | testProgram('true false ||', [true]); 14 | testProgram('false false ||', [false]); 15 | testProgram('false true ||', [true]); 16 | testProgram('true true ||', [true]); 17 | testProgram('true false &&', [false]); 18 | testProgram('false false &&', [false]); 19 | testProgram('false true &&', [false]); 20 | testProgram('true true &&', [true]); 21 | testProgram('[] small', [true]); 22 | testProgram('[1] small', [true]); 23 | testProgram('[1 2] small', [false]); 24 | testProgram('-2 small', [false]); 25 | testProgram('-1 small', [true]); 26 | testProgram('0 small', [true]); 27 | testProgram('1 small', [true]); 28 | testProgram('2 small', [false]); 29 | testProgram('"0" small', [false]); 30 | testProgram('"1" small', [false]); 31 | testProgram('true small', [false]); 32 | testProgram('false small', [false]); 33 | testProgram('[] zero', [true]); 34 | testProgram('[1] zero', [false]); 35 | testProgram('-1 zero', [false]); 36 | testProgram('0 zero', [true]); 37 | testProgram('1 zero', [false]); 38 | testProgram('"0" zero', [false]); 39 | testProgram('"1" zero', [false]); 40 | testProgram('true zero', [false]); 41 | testProgram('false zero', [false]); 42 | -------------------------------------------------------------------------------- /lib/stdlib/test/math.js: -------------------------------------------------------------------------------- 1 | import { testProgram, testProgramAssertOnTop } from './_runTest'; 2 | 3 | //PI 4 | testProgram('PI', [Math.PI]); 5 | //E 6 | testProgram('E', [Math.E]); 7 | //random 8 | testProgramAssertOnTop('random', v => v < 1 && v > 0); 9 | //random2 10 | testProgramAssertOnTop('-5 5 random2', v => v < 5 && v > -5); 11 | //'*' 12 | testProgram('2 3 *', [6]); 13 | //'/' 14 | testProgram('2 3 /', [2 / 3]); 15 | testProgram('6 2 /', [3]); 16 | //'+' 17 | testProgram('2 3 +', [5]); 18 | //'-' 19 | testProgram('2 3 -', [-1]); 20 | testProgram('4 3 -', [1]); 21 | //mod 22 | testProgram('7 2 mod', [1]); 23 | //neg 24 | testProgram('5 neg', [-5]); 25 | testProgram('-5 neg', [5]); 26 | //abs 27 | testProgram('-5 abs', [5]); 28 | testProgram('5 abs', [5]); 29 | //max 30 | testProgram('2 3 max', [3]); 31 | //min 32 | testProgram('2 3 min', [2]); 33 | //sqrt 34 | testProgram('9 sqrt', [3]); 35 | //cbrt 36 | testProgram('27 cbrt', [3]); 37 | //pow 38 | testProgram('2 10 pow', [1024]); 39 | //floor 40 | testProgram('1.123123 floor', [1]); 41 | //ceil 42 | testProgram('1.123123 ceil', [2]); 43 | //round 44 | testProgram('1.12312131 round', [Math.round(1.12312131)]); 45 | //exp 46 | testProgram('0 exp', [1]); 47 | //sign 48 | testProgram('10 sign', [1]); 49 | testProgram('-10 sign', [-1]); 50 | testProgram('0 sign', [0]); 51 | testProgram('-0 sign', [-0]); 52 | testProgram('+0 sign', [0]); 53 | //log 54 | testProgram('1 log', [0]); 55 | //log10 56 | testProgram('10 log10', [1]); 57 | //cos 58 | testProgram('PI 2 * cos', [1]); 59 | //acos 60 | testProgram('1 acos', [0]); 61 | testProgram('1.1 acos', [NaN]); 62 | //cosh 63 | testProgram('PI cosh', [Math.cosh(Math.PI)]); 64 | //acosh 65 | testProgram('1.1 acosh', [Math.acosh(1.1)]); 66 | testProgram('0.1 acosh', [NaN]); 67 | //sin 68 | testProgram('PI 2 / sin', [1]); 69 | //asin 70 | testProgram('0 asin', [0]); 71 | testProgram('1.1 asin', [NaN]); 72 | //sinh 73 | testProgram('PI sinh', [Math.sinh(Math.PI)]); 74 | //asinh 75 | testProgram('1.1 asinh', [Math.asinh(1.1)]); 76 | testProgram('0.1 asinh', [Math.asinh(0.1)]); 77 | //tan 78 | testProgram('PI tan', [Math.tan(Math.PI)]); 79 | //atan 80 | testProgram('1.10 atan', [Math.atan(1.1)]); 81 | //atan2 82 | testProgram('5 6 atan2', [Math.atan2(5, 6)]); 83 | //tanh 84 | testProgram('5 tanh', [Math.tanh(5)]); 85 | //atanh 86 | testProgram('0 atanh', [0]); 87 | testProgram('1.1 atanh', [NaN]); 88 | //succ 89 | testProgram('1 succ', [2]); 90 | testProgram('2 succ', [3]); 91 | //pred 92 | testProgram('1 pred', [0]); 93 | testProgram('2 pred', [1]); 94 | -------------------------------------------------------------------------------- /lib/stdlib/test/quotations.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | testProgram('[2 3] exec', [2, 3]); 4 | testProgram('1 [2 3] nullary', [1, 3]); 5 | testProgram('1 2 [3 4] unary', [1, 4]); 6 | testProgram('1 2 3 [4 5] binary', [1, 5]); 7 | testProgram('1 2 [+] nullary', [1, 2, 3]); 8 | testProgram('[1 2] [ dup pop ] nullary', [[1, 2], [1, 2]]); 9 | testProgram('[1 2] [ dup pop pop ] nullary', [[1, 2]]); 10 | testProgram('1 1 [ dup pop [pop] unary ] nullary', [1, 1, 1]); 11 | testProgram('1 2 [+] unary', [1, 3]); 12 | testProgram('1 2 [+] binary', [3]); 13 | testProgram('1 2 [ [+] unary ] nullary', [1, 2, 3]); 14 | testProgram('1 2 [ [+] binary ] nullary', [1, 2, 3]); 15 | testProgram('1 2 [ [+] binary ] unary', [1, 3]); 16 | testProgram('1 2 [ [+] nullary ] unary', [1, 3]); 17 | testProgram('1 2 [ [+] nullary ] binary', [3]); 18 | testProgram('1 2 [ [+] nullary 4 ] binary', [4]); 19 | testProgram('1 2 [ [ + ] nullary 1 ] unary', [1, 1]); 20 | testProgram('false 2 [ 2 = || ] binary', [true]); 21 | testProgram('1 2 [ 3 [ + ] nullary ] unary', [1, 5]); 22 | testProgram('1 2 3 4 [ + + + ] nullary', [1, 2, 3, 4, 10]); 23 | testProgram('[1 2 3 4] 0 [ at ] nullary', [[1, 2, 3, 4], 0, 1]); 24 | testProgram('[] 1 [ [ size 0 = ] dip swap ] nullary', [[], 1, true]); 25 | // dip 26 | testProgram('0 2 [1 +] dip', [1, 2]); 27 | testProgram('[1] 2 [3 swons] dip', [[3, 1], 2]); 28 | // cleave 29 | testProgram('1 [1 +] [2 +] cleave', [2, 3]); 30 | -------------------------------------------------------------------------------- /lib/stdlib/test/recursion.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | // linrec 4 | testProgram('7 [zero] [succ] [dup pred] [*] linrec', [5040]); 5 | testProgram('0 [zero] [succ] [dup pred] [*] linrec', [1]); 6 | // tailrec 7 | testProgram('0 6 [zero] [+] [ pred dup [+] dip ] tailrec', [15]); 8 | testProgram('0 [1 2 3 4 5] [zero] [ pop ] [ uncons [+] dip ] tailrec', [15]); 9 | // binrec 10 | testProgram('[2 4 6 8 1 3 5 7 9] [small] [] [uncons [>] split] [enconcat] binrec', [ 11 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 12 | ]); 13 | -------------------------------------------------------------------------------- /lib/stdlib/test/stack.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | testProgram('1 2 dup', [1, 2, 2]); 4 | testProgram('[1] [2] dup', [[1], [2], [2]]); 5 | testProgram('1 2 3 dupd', [1, 2, 2, 3]); 6 | testProgram('[1] [2] [3] dupd', [[1], [2], [2], [3]]); 7 | 8 | testProgram('1 2 pop', [1]); 9 | testProgram('[1] [2] pop', [[1]]); 10 | testProgram('1 2 3 popd', [1, 3]); 11 | testProgram('[1] [2] [3] popd', [[1], [3]]); 12 | 13 | testProgram('[1] [2] swap', [[2], [1]]); 14 | testProgram('1 2 swap', [2, 1]); 15 | testProgram('isEmpty: size 0 = ; [] 1 swap isEmpty', [1, true]); 16 | testProgram('[1] [2] [3] swapd', [[2], [1], [3]]); 17 | testProgram('1 2 3 swapd', [2, 1, 3]); 18 | 19 | testProgram('[1] [2] [3] rollup', [[2], [3], [1]]); 20 | testProgram('1 2 3 rollup', [2, 3, 1]); 21 | testProgram('[1] [2] [3] [4] rollupd', [[2], [3], [1], [4]]); 22 | testProgram('1 2 3 4 rollupd', [2, 3, 1, 4]); 23 | 24 | testProgram('[1] [2] [3] rolldown', [[3], [1], [2]]); 25 | testProgram('1 2 3 rolldown', [3, 1, 2]); 26 | testProgram('[1] [2] [3] [4] rolldownd', [[3], [1], [2], [4]]); 27 | testProgram('1 2 3 4 rolldownd', [3, 1, 2, 4]); 28 | 29 | testProgram('[1] [2] [3] rotate', [[3], [2], [1]]); 30 | testProgram('1 2 3 rotate', [3, 2, 1]); 31 | testProgram('[1] [2] [3] [4] rotated', [[3], [2], [1], [4]]); 32 | testProgram('1 2 3 4 rotated', [3, 2, 1, 4]); 33 | 34 | testProgram('1 2 3 4 stack', [1, 2, 3, 4, [1, 2, 3, 4]]); 35 | testProgram('1 2 3 4 [] unstack', []); 36 | 37 | // Accidental mutation tests for dup 38 | testProgram('[] dup 1 swons', [[], [1]]); 39 | testProgram('"asdft %s" dup "derp" swap replace', ['asdft %s', 'asdft derp']); 40 | testProgram('"asdf" dup "1" +', ['asdf', 'asdf1']); 41 | // Accidental mutation tests for dupd 42 | testProgram('[] "dummy" dupd [1 swons] dip', [[], [1], 'dummy']); 43 | testProgram('"asdft %s" "dummy" dupd ["derp" swap replace] dip', [ 44 | 'asdft %s', 45 | 'asdft derp', 46 | 'dummy' 47 | ]); 48 | testProgram('"asdf" "dummy" dupd ["1" +] dip', ['asdf', 'asdf1', 'dummy']); 49 | -------------------------------------------------------------------------------- /lib/stdlib/test/string.js: -------------------------------------------------------------------------------- 1 | import { testProgram } from './_runTest'; 2 | 3 | testProgram('"a" "%s" replace', ['a']); 4 | testProgram('"a" "b" "%s%s" replace2', ['ba']); 5 | testProgram('"a" "b" "c" "%s%s%s" replace3', ['cba']); 6 | testProgram('"a" "b" "%s" replace2', ['b']); 7 | testProgram('"a" "b" "c" "%s" replace3', ['c']); 8 | testProgram('"a" "b" "c" "%s%s" replace3', ['cb']); 9 | -------------------------------------------------------------------------------- /lib/stdlib/test/variables.js: -------------------------------------------------------------------------------- 1 | import { testProgram, testProgramShouldThrow } from './_runTest'; 2 | 3 | testProgram( 4 | ` 5 | 1 -> a 6 | a 7 | `, 8 | [1] 9 | ); 10 | 11 | testProgram( 12 | ` 13 | [ 1 -> a a ] exec 14 | `, 15 | [1] 16 | ); 17 | 18 | testProgram( 19 | ` 20 | 1 -> a 21 | [ a ] exec 22 | `, 23 | [1] 24 | ); 25 | 26 | testProgram( 27 | ` 28 | 1 -> a 29 | [ [a] exec ] exec 30 | `, 31 | [1] 32 | ); 33 | 34 | testProgram( 35 | ` 36 | 1 -> a 37 | [ 2 -> b [a b] exec ] exec 38 | `, 39 | [1, 2] 40 | ); 41 | 42 | testProgram( 43 | ` 44 | 1 -> a 45 | [ 2 -> a [a] exec ] exec 46 | `, 47 | [2] 48 | ); 49 | 50 | testProgram( 51 | ` 52 | [1] -> a 53 | 2 a append 54 | `, 55 | [[1, 2]] 56 | ); 57 | 58 | testProgram( 59 | ` 60 | [1] -> a 61 | 2 a append 62 | a 63 | `, 64 | [[1, 2], [1, 2]] 65 | ); 66 | 67 | testProgram( 68 | ` 69 | makeA: -> a a ; 70 | 24 makeA 71 | `, 72 | [24] 73 | ); 74 | 75 | testProgram( 76 | ` 77 | makeA: -> a [a] ; 78 | 24 makeA 79 | 2 -> a 80 | exec 81 | `, 82 | [2] 83 | ); 84 | 85 | // Testing modification of variable state 86 | testProgram( 87 | ` 88 | [ 1 2 3 ] -> A 89 | 6 A 1 ins pop 90 | A 91 | `, 92 | [[1, 6, 3]] 93 | ); 94 | 95 | testProgram( 96 | ` 97 | [ [1] [2] [3] ] -> A 98 | 6 1 A of 0 ins pop 99 | A 100 | `, 101 | [[[1], [6], [3]]] 102 | ); 103 | 104 | testProgram( 105 | ` 106 | 0 -> a 107 | 6 108 | [_i a + ->> a] 109 | times 110 | a 111 | `, 112 | [15] 113 | ); 114 | 115 | testProgram( 116 | ` 117 | add1: a 1 + ->> a ; 118 | sub1: a 1 - ->> a ; 119 | 0 -> a 120 | add1 add1 sub1 sub1 add1 121 | a 122 | `, 123 | [1] 124 | ); 125 | 126 | testProgram( 127 | ` 128 | init: 0 ->> a ; 129 | add1: a 1 + ->> a ; 130 | sub1: a 1 - ->> a ; 131 | 132 | init 133 | add1 add1 sub1 sub1 add1 134 | a 135 | `, 136 | [1] 137 | ); 138 | 139 | testProgramShouldThrow( 140 | ` 141 | add1: a 1 + ->> a ; 142 | 143 | add1 144 | `, 145 | [1] 146 | ); 147 | 148 | testProgramShouldThrow( 149 | ` 150 | makeA: -> a a ; 151 | 24 makeA 152 | a 153 | ` 154 | ); 155 | 156 | testProgramShouldThrow( 157 | ` 158 | makeA: -> a [a] ; 159 | 24 makeA 160 | exec 161 | ` 162 | ); 163 | 164 | testProgramShouldThrow( 165 | ` 166 | [ 1 -> a a ] exec 167 | b 168 | ` 169 | ); 170 | 171 | testProgramShouldThrow( 172 | ` 173 | [ 1 -> a a ] exec 174 | a 175 | ` 176 | ); 177 | -------------------------------------------------------------------------------- /lib/stdlib/utils/program.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import clone from 'clone'; 3 | import type { Value, Quotation } from '../../parser/ast'; 4 | import { 5 | quotationNonReversed as quotation, 6 | word, 7 | aitValue as value, 8 | aitVar 9 | } from '../../parser/ast'; 10 | 11 | export function exec(quot: Quotation): Array { 12 | return clone(quot.body, false); 13 | } 14 | 15 | export function execWithArity(arity: number, quot: Quotation): Array { 16 | const ret = [word('resolveStack')]; 17 | ret.push.apply(ret, clone(quot.body, false)); 18 | ret.push(word('forkStack'), value(arity)); 19 | return ret; 20 | } 21 | 22 | export function assignVariableEmptyAggr(name: string): Array { 23 | return [aitVar(name), quotation([])]; 24 | } 25 | 26 | export function assignVariableAggr(val: Quotation, name: string): Array { 27 | return [aitVar(name), val]; 28 | } 29 | 30 | export function assignVariable(val: mixed, name: string): Array { 31 | return [aitVar(name), value(val)]; 32 | } 33 | 34 | export { word, quotation, quotation as aggregate, value }; 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ait-lang", 3 | "version": "0.3.3", 4 | "description": "Compiler and runtime for the Ait language", 5 | "main": "runtimes/node.js", 6 | "bin": "bin/ait-lang.js", 7 | "browser": "runtimes/browser.js", 8 | "scripts": { 9 | "flow": "flow", 10 | "test": "ava", 11 | "test:watch": "ava -w", 12 | "lint": "eslint lib/**/*.js", 13 | "build": "BABEL_ENV=commonjs babel -s -d dist lib --ignore=test/*.js", 14 | "clean": "rimraf dist", 15 | "prepublish": "npm run clean && npm run build", 16 | "format": "prettier --single-quote --print-width 100 --write \"lib/**/*.js\"" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/mollerse/ait-lang.git" 21 | }, 22 | "keywords": [ 23 | "language", 24 | "web", 25 | "canvas" 26 | ], 27 | "files": [ 28 | "dist", 29 | "runtimes", 30 | "util", 31 | "ffi.js", 32 | "bin" 33 | ], 34 | "author": "Stian Møllersen", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/mollerse/ait-lang/issues" 38 | }, 39 | "homepage": "https://github.com/mollerse/ait-lang#readme", 40 | "dependencies": { 41 | "clone": "2.1.1", 42 | "parsimmon": "1.2.0" 43 | }, 44 | "engines": { 45 | "node": ">=6.0.0" 46 | }, 47 | "devDependencies": { 48 | "ava": "0.22.0", 49 | "babel-cli": "6.26.0", 50 | "babel-eslint": "7.2.3", 51 | "babel-plugin-transform-flow-strip-types": "6.22.0", 52 | "babel-preset-es2015": "6.24.1", 53 | "babel-register": "6.26.0", 54 | "eslint": "4.4.1", 55 | "eslint-config-defaults": "9.0.0", 56 | "eslint-plugin-flowtype": "2.35.0", 57 | "eslint-plugin-import": "2.7.0", 58 | "flow-bin": "0.53.1", 59 | "prettier": "1.5.3", 60 | "rimraf": "2.6.1" 61 | }, 62 | "ava": { 63 | "files": [ 64 | "lib/**/test/*.js" 65 | ], 66 | "source": [ 67 | "lib/**/*.{js}" 68 | ], 69 | "require": [ 70 | "babel-register" 71 | ], 72 | "babel": "inherit" 73 | }, 74 | "babel": { 75 | "presets": [ 76 | "es2015" 77 | ], 78 | "plugins": [ 79 | "transform-flow-strip-types" 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /runtimes/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/runtime/browser').default; 2 | -------------------------------------------------------------------------------- /runtimes/node.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../dist/runtime/node').default; 2 | -------------------------------------------------------------------------------- /util/atom-language-ait/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 - First Release 2 | * Basic highlighting 3 | -------------------------------------------------------------------------------- /util/atom-language-ait/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Stian Møllersen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /util/atom-language-ait/README.md: -------------------------------------------------------------------------------- 1 | # Ait language package 2 | 3 | Basic highlighting for the Ait language 4 | -------------------------------------------------------------------------------- /util/atom-language-ait/grammars/ait.cson: -------------------------------------------------------------------------------- 1 | # If this is your first time writing a language grammar, check out: 2 | # - http://manual.macromates.com/en/language_grammars 3 | 4 | 'scopeName': 'source.ait' 5 | 'name': 'Ait' 6 | 'fileTypes': [ 7 | 'ait' 8 | ] 9 | 'patterns': [ 10 | { 11 | 'match': '#.*$' 12 | 'name': 'comment.ait' 13 | } 14 | { 15 | 'captures': 16 | '1': 17 | 'name': 'keyword.load.ait' 18 | '2': 19 | 'name': 'constant.path.ait' 20 | '3': 21 | 'name': 'keyword.semicolon.ait' 22 | 'match': '^(@.+)\\s(.+)(;)$' 23 | } 24 | { 25 | 'captures': 26 | '1': 27 | 'name': 'constant.definition.ait' 28 | '2': 29 | 'name': 'keyword.colon.ait' 30 | 'match': '^(.*)(:)\\s' 31 | 'name': 'definition.word.ait' 32 | } 33 | { 34 | 'match': '[;\\[\\](){}]' 35 | 'name': 'keyword.ait' 36 | } 37 | { 38 | 'captures': 39 | '1': 40 | 'name': 'entity.number.ait' 41 | 'match': '(-?\\d+(\\.\\d+)?)(?![\\w<>+-=*%])' 42 | } 43 | { 44 | 'match': '[a-zA-Z0-9<>+-=\\\\*%]+' 45 | 'name': 'entity.word.ait' 46 | } 47 | { 48 | 'match': '["|\']((?:\\.|.)*?)["|\']' 49 | 'name': 'string.ait' 50 | } 51 | ] 52 | -------------------------------------------------------------------------------- /util/atom-language-ait/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "language-ait", 3 | "version": "1.0.0", 4 | "description": "Highlighting for the Ait langauge", 5 | "keywords": [ 6 | "language", 7 | "grammar" 8 | ], 9 | "repository": "https://github.com/mollerse/atom-language-ait", 10 | "license": "MIT", 11 | "engines": { 12 | "atom": ">=1.0.0 <2.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /util/atom-language-ait/settings/language-ait.cson: -------------------------------------------------------------------------------- 1 | # If you want some examples of settings, check out: 2 | # https://github.com/atom/language-gfm/blob/master/settings/gfm.cson 3 | 4 | '.source.ait': 5 | 'editor': 6 | 'commentStart': '#' 7 | -------------------------------------------------------------------------------- /util/atom-language-ait/snippets/language-ait.cson: -------------------------------------------------------------------------------- 1 | # If you want some example snippets, check out: 2 | # https://github.com/atom/language-gfm/blob/master/snippets/gfm.cson 3 | --------------------------------------------------------------------------------