├── .gitignore ├── atom-fflat-language ├── LICENSE ├── README.md ├── grammars │ └── fflat.cson └── package.json ├── bin ├── gui.js └── repl.js ├── docs ├── 404.html ├── 99-problems-solved.md ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── api │ ├── base.md │ ├── core.md │ ├── experimental.md │ ├── flags.md │ ├── math.md │ ├── node.md │ ├── objects.md │ ├── types.md │ └── vocab.md ├── assets │ ├── ENV.png │ ├── css │ │ └── style.scss │ ├── parent+child.png │ └── stack+queue.png ├── code-structure.md ├── compare.md ├── compared-to-json.md ├── concurrency.md ├── control-structures-conditionals-and-blocks.md ├── data-stuctures.md ├── demo.md ├── dice-rolls-for-dungons-and-dragons-and-other-games.md ├── dictionary-locals.md ├── examples.md ├── examples │ ├── euler20.ff │ ├── fridge-game.ff │ └── site-war.ff ├── expressions-evaluation.md ├── f-by-example.md ├── function-definitions-expanding-functions.md ├── history.md ├── immutability.md ├── index.md ├── interesting.md ├── intro.md ├── javascript-interop.md ├── learn-fflat-in-minutes.md ├── manifesto.md ├── module-loading.md ├── operator-basis.md ├── project-euler-solutions.md ├── signatures.md ├── site-war.md ├── spec.md ├── the-fridge-game.md ├── the-repl.md ├── the-stack-and-the-repl.md ├── type-casting.md ├── values.md └── why.md ├── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── readme.md ├── scripts ├── gen-spec.js └── generate-docs.js ├── src ├── constants.ts ├── core │ ├── base.ts │ ├── core.ts │ ├── experimental.ts │ ├── flags.ts │ ├── index.ts │ ├── math.ts │ ├── node.ts │ ├── objects.ts │ ├── types.ts │ └── vocab.ts ├── engine │ ├── env.ts │ └── vocabulary.ts ├── ff-lib │ ├── boot.ff │ ├── combinators.ff │ ├── core.ff │ ├── datetime.ff │ ├── lambdas.ff │ ├── loader.ff │ ├── math-sym.ff │ ├── math.ff │ ├── node.ff │ ├── shuffle.ff │ ├── sort.ff │ ├── strings.ff │ ├── switch-patterns.ff │ ├── testing.ff │ ├── types.ff │ └── usr.ff ├── parser │ ├── index.ts │ ├── parser.ts │ └── tokenizer.ts ├── stack.ts ├── types │ ├── complex-infinity.ts │ ├── complex.ts │ ├── decimal.ts │ ├── dynamo.ts │ ├── future.ts │ ├── index.ts │ ├── return-values.ts │ ├── stack-values.ts │ ├── vocabulary-values.ts │ └── words.ts └── utils │ ├── fflat-error.ts │ ├── gamma.ts │ ├── index.ts │ ├── json.ts │ ├── kleene-logic.ts │ ├── logger.ts │ ├── pattern.ts │ ├── pprint.ts │ ├── regex-logic.ts │ ├── rewrite.ts │ ├── stringConversion.ts │ ├── types.ts │ └── utils.ts ├── test ├── __snapshots__ │ └── json.spec.ts.snap ├── async.spec.ts ├── boolean.spec.ts ├── complex.spec.ts ├── core-ff.spec.ts ├── core.spec.ts ├── dates.spec.ts ├── dict.spec.ts ├── f-flat.spec.ts ├── fast-tests.spec.ts ├── flags.spec.ts ├── helpers │ └── setup.ts ├── json.spec.ts ├── lists.spec.ts ├── math.spec.ts ├── objects.spec.ts ├── pattern-matching.spec.ts ├── quotes.spec.ts ├── regexp.spec.ts ├── scoping.spec.ts ├── shuffle-ff.spec.ts ├── strings.spec.ts ├── symbols.spec.ts ├── template-strings.spec.ts └── types.spec.ts ├── todo.md ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | .nyc_output 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 29 | node_modules 30 | 31 | # Optional npm cache directory 32 | .npm 33 | 34 | # Optional REPL history 35 | .node_repl_history 36 | 37 | dist/ 38 | .esm-cache/ 39 | **/.DS_Store 40 | .vscode 41 | docs/_site/ 42 | .idea/ 43 | -------------------------------------------------------------------------------- /atom-fflat-language/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Maximilian Irro 4 | Copyright (c) 2016 Jayson Harshbarger 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /atom-fflat-language/README.md: -------------------------------------------------------------------------------- 1 | # F♭ language support in Atom 2 | 3 | Adds syntax highlighting to Forth files in [Atom](https://atom.io). 4 | -------------------------------------------------------------------------------- /atom-fflat-language/grammars/fflat.cson: -------------------------------------------------------------------------------- 1 | 'fileTypes': [ 2 | 'ff' 3 | ] 4 | 'foldingStartMarker': '/\\*\\*|\\{\\s*$' 5 | 'foldingStopMarker': '\\*\\*/|^\\s*\\}' 6 | 'name': 'F-Flat' 7 | 'patterns': [ 8 | { 9 | 'include': '#fflat' 10 | } 11 | ] 12 | 'repository': 13 | 'comment': 14 | 'patterns': [ 15 | { 16 | 'begin': '//' 17 | 'beginCaptures': 18 | '0': 19 | 'name': 'punctuation.definition.comment.js' 20 | 'end': '\\n' 21 | 'name': 'comment.line.double-slash.js' 22 | } 23 | { 24 | 'begin': '/\\*' 25 | 'captures': 26 | '0': 27 | 'name': 'punctuation.definition.comment.js' 28 | 'end': '\\*/' 29 | 'name': 'comment.block.js' 30 | } 31 | ] 32 | 'constant': 33 | 'match': '\\b(?:true|false|null)\\b' 34 | 'name': 'constant.language.json' 35 | 'fflat': 36 | 'patterns': [ 37 | { 38 | 'include': '#constant' 39 | } 40 | { 41 | 'include': '#comment' 42 | } 43 | { 44 | 'include': '#string' 45 | } 46 | { 47 | 'include': '#word' 48 | } 49 | { 50 | 'include': '#number' 51 | } 52 | ] 53 | 'number': 54 | 'comment': 'handles integer and decimal numbers' 55 | 'match': '-?(?=[1-9]|0(?!\\d))\\d+(\\.\\d+)?([eE][+-]?\\d+)?' 56 | 'name': 'constant.numeric.json' 57 | 'string': 58 | 'patterns': [ 59 | { 60 | 'begin': '\'' 61 | 'beginCaptures': 62 | '0': 63 | 'name': 'punctuation.definition.string.begin.js' 64 | 'end': '\'' 65 | 'endCaptures': 66 | '0': 67 | 'name': 'punctuation.definition.string.end.js' 68 | 'name': 'string.quoted.single.js' 69 | 'patterns': [ 70 | { 71 | 'match': '\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)' 72 | 'name': 'constant.character.escape.js' 73 | } 74 | { 75 | 'match': "[^']*[^\\n\\r'\\\\]$" 76 | 'name': 'invalid.illegal.string.js' 77 | } 78 | ] 79 | } 80 | { 81 | 'begin': '"' 82 | 'beginCaptures': 83 | '0': 84 | 'name': 'punctuation.definition.string.begin.js' 85 | 'end': '"' 86 | 'endCaptures': 87 | '0': 88 | 'name': 'punctuation.definition.string.end.js' 89 | 'name': 'string.quoted.double.js' 90 | 'patterns': [ 91 | { 92 | 'match': '\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]|37[0-7]?|[4-7][0-7]?|.)' 93 | 'name': 'constant.character.escape.js' 94 | } 95 | { 96 | 'match': '[^"]*[^\\n\\r"\\\\]$' 97 | 'name': 'invalid.illegal.string.js' 98 | } 99 | ] 100 | } 101 | { 102 | 'begin': '((\\w+)?(html|HTML|Html))\\s*(`)' 103 | 'beginCaptures': 104 | '1': 105 | 'name': 'entity.name.function.js' 106 | '4': 107 | 'name': 'punctuation.definition.string.begin.js' 108 | 'end': '`' 109 | 'endCaptures': 110 | '0': 111 | 'name': 'punctuation.definition.string.end.js' 112 | 'name': 'string.quoted.template.html.js' 113 | 'patterns': [ 114 | { 115 | 'match': '\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)' 116 | 'name': 'constant.character.escape.js' 117 | } 118 | { 119 | 'include': '#interpolated_js' 120 | } 121 | { 122 | 'include': 'text.html.basic' 123 | } 124 | ] 125 | } 126 | { 127 | 'begin': '((\\w+)?(css|CSS|Css))\\s*(`)' 128 | 'beginCaptures': 129 | '1': 130 | 'name': 'entity.name.function.js' 131 | '4': 132 | 'name': 'punctuation.definition.string.begin.js' 133 | 'end': '`' 134 | 'endCaptures': 135 | '0': 136 | 'name': 'punctuation.definition.string.end.js' 137 | 'name': 'string.quoted.template.css.js' 138 | 'patterns': [ 139 | { 140 | 'match': '\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)' 141 | 'name': 'constant.character.escape.js' 142 | } 143 | { 144 | 'include': '#interpolated_js' 145 | } 146 | { 147 | 'include': 'source.css' 148 | } 149 | ] 150 | } 151 | { 152 | 'begin': '`' 153 | 'beginCaptures': 154 | '0': 155 | 'name': 'punctuation.definition.string.begin.js' 156 | 'end': '`' 157 | 'endCaptures': 158 | '0': 159 | 'name': 'punctuation.definition.string.end.js' 160 | 'name': 'string.quoted.template.js' 161 | 'patterns': [ 162 | { 163 | 'match': '\\\\(x\\h{2}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.)' 164 | 'name': 'constant.character.escape.js' 165 | } 166 | { 167 | 'include': '#interpolated_js' 168 | } 169 | ] 170 | } 171 | ] 172 | 'word': 173 | 'patterns': [ 174 | { 175 | 'match': '\\b([a-zA-Z_$][a-zA-Z_$0-9]*)\\b' 176 | 'name': 'variable.other.module.js' 177 | } 178 | ] 179 | 'scopeName': 'source.fflat' 180 | -------------------------------------------------------------------------------- /atom-fflat-language/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "language-fflat", 3 | "version": "0.0.1", 4 | "description": "Syntax highlighting for fflat", 5 | "engines": { 6 | "atom": "*", 7 | "node": "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /bin/gui.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const blessed = require('blessed'); 4 | const { createStack, createRootEnv } = require('../dist/stack'); 5 | const { log, formatArray, bar } = require('../dist/utils'); 6 | 7 | const inspectOptions = { 8 | showHidden: false, 9 | depth: null, 10 | colors: true, 11 | indent: true, 12 | maxArrayLength: 1e20 13 | }; 14 | 15 | const screen = blessed.screen({ 16 | title: 'f-flat', 17 | smartCSR: true, 18 | autoPadding: true 19 | }); 20 | 21 | const inputBox = blessed.textbox({ 22 | parent: screen, 23 | top: '100%-5', 24 | left: 4, 25 | width: '100%', 26 | height: 1, 27 | border: false, 28 | style: { 29 | fg: 'white', 30 | border: { 31 | fg: '#f0f0f0' 32 | } 33 | }, 34 | inputOnFocus: false, 35 | input: true, 36 | keys: true, 37 | clickable: true 38 | }); 39 | 40 | const outputBox = blessed.log({ 41 | parent: screen, 42 | top: '2', 43 | left: 'center', 44 | width: '100%', 45 | height: '100%-7', 46 | border: false, 47 | scrollable: true, 48 | alwaysScroll: true, 49 | style: { 50 | scrollbar: { 51 | bg: '#222' 52 | } 53 | }, 54 | scrollbar: { 55 | ch: ' ', 56 | inverse: true 57 | }, 58 | keys: true, 59 | vi: true, 60 | mouse: true, 61 | tags: true 62 | }); 63 | 64 | /* const bar = blessed.progressbar({ 65 | parent: screen, 66 | border: 'none', 67 | style: { 68 | fg: 'blue', 69 | bg: 'default', 70 | bar: { 71 | bg: 'default', 72 | fg: 'blue' 73 | }, 74 | border: { 75 | fg: 'default', 76 | bg: 'default' 77 | } 78 | }, 79 | ch: ':', 80 | top: '100%-3', 81 | width: '100%', 82 | height: 3, 83 | right: 0, 84 | bottom: 0, 85 | filled: 0 86 | }); */ 87 | 88 | blessed.text({ 89 | parent: screen, 90 | top: '100%-5', 91 | left: 1, 92 | height: 1, 93 | width: 2, 94 | content: 'F>' 95 | }); 96 | 97 | blessed.Line({ 98 | parent: screen, 99 | type: 'line', 100 | orientation: 'horizontal', 101 | top: '100%-6', 102 | left: 1, 103 | width: '100%-2', 104 | style: { 105 | fg: '#666' 106 | } 107 | }); 108 | 109 | const p = blessed.textbox({ 110 | parent: screen, 111 | top: '100%-1', 112 | left: 5, 113 | height: 1, 114 | width: '100%', 115 | inputOnFocus: false, 116 | input: true, 117 | keys: true, 118 | clickable: true, 119 | border: false 120 | }); 121 | 122 | screen.key(['escape', 'q', 'C-c'], () => process.exit(0)); 123 | 124 | const f = newStack(); 125 | 126 | const history = []; 127 | let historyIndex = 0; 128 | 129 | inputBox.on('submit', data => { 130 | inputBox.clearValue(); 131 | run(data); 132 | }); 133 | 134 | inputBox.on('keypress', (character, key) => { 135 | if (historyIndex < 0) { 136 | historyIndex = 0; 137 | } 138 | if (key.name === 'up') { 139 | if (history.length > 0) { 140 | const expression = history[history.length - (1 + historyIndex)]; 141 | if (expression) { 142 | inputBox.setValue(expression); 143 | historyIndex++; 144 | screen.render(); 145 | } 146 | } 147 | } else if (key.name === 'down') { 148 | const expression = history[history.length - (historyIndex - 1)]; 149 | if (expression) { 150 | inputBox.setValue(expression); 151 | historyIndex--; 152 | screen.render(); 153 | } else { 154 | inputBox.setValue(''); 155 | historyIndex--; 156 | screen.render(); 157 | } 158 | } 159 | }); 160 | 161 | let beforeBinding; 162 | 163 | inputBox.focus(); 164 | inputBox.readInput(); 165 | screen.render(); 166 | 167 | /// 168 | 169 | function newStack() { 170 | log.level = 'warn'; 171 | return createStack('', createRootEnv().createChild(undefined)); 172 | } 173 | 174 | function run(buffer) { 175 | if (!buffer.length) { 176 | return; 177 | } 178 | 179 | history.push(buffer); 180 | 181 | p.focus(); 182 | screen.render(); 183 | 184 | beforeBinding = addBefore(); 185 | 186 | setTimeout(() => { 187 | f.next(buffer).then( 188 | () => fin(), 189 | err => fin(err) 190 | ); 191 | }); 192 | 193 | function fin(err) { 194 | bar.terminate(); 195 | const formattedStack = formatArray(f.stack, null, inspectOptions, ' '); 196 | const formattedError = err ? `\n{red-fg}${err.message}{/red-fg}` : ''; 197 | outputBox.setContent(formattedStack + formattedError); 198 | inputBox.focus(); 199 | inputBox.readInput(); 200 | screen.render(); 201 | } 202 | } 203 | 204 | function addBefore() { 205 | if (beforeBinding) { 206 | beforeBinding.detach(); 207 | } 208 | 209 | let qMax = f.stack.length + f.queue.length; 210 | let c = 0; 211 | 212 | return f.before.add(cb); 213 | 214 | function cb() { 215 | c++; 216 | if (c % 1000 === 0) { 217 | const q = f.stack.length + f.queue.length; 218 | if (q > qMax) qMax = q; 219 | 220 | let lastAction; 221 | 222 | // todo: fix this, sometimes last action is a symbol 223 | try { 224 | lastAction = String(f.lastAction); 225 | } catch (e) { 226 | lastAction = ''; 227 | } 228 | 229 | bar.update(f.stack.length / qMax, { 230 | stack: f.stack.length, 231 | queue: f.queue.length, 232 | depth: f.depth, 233 | lastAction 234 | }); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | # gem "jekyll", "~> 4.0.0" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | gem "minima", "~> 2.5" 13 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 14 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 15 | gem "github-pages", group: :jekyll_plugins 16 | # If you have any plugins, put them here! 17 | group :jekyll_plugins do 18 | gem "jekyll-feed", "~> 0.12" 19 | end 20 | 21 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 22 | # and associated library. 23 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do 24 | gem "tzinfo", "~> 1.2" 25 | gem "tzinfo-data" 26 | end 27 | 28 | # Performance-booster for watching directories on Windows 29 | gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform? 30 | 31 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | title: F-Flat 2 | baseurl: "/f-flat_node" # the subpath of your site, e.g. /blog 3 | url: "https://hypercubed.github.io/" # the base hostname & protocol for your site, e.g. http://example.com 4 | twitter_username: hypercubed 5 | github_username: hypercubed 6 | show_downloads: false 7 | 8 | # Build settings 9 | theme: jekyll-theme-midnight 10 | 11 | # Exclude from processing. 12 | # The following items will not be processed, by default. 13 | # Any item listed under the `exclude:` key here will be automatically added to 14 | # the internal "default list". 15 | # 16 | # Excluded items can be processed by explicitly listing the directories or 17 | # their entries' file path in the `include:` list. 18 | # 19 | # exclude: 20 | # - .sass-cache/ 21 | # - .jekyll-cache/ 22 | # - gemfiles/ 23 | # - Gemfile 24 | # - Gemfile.lock 25 | # - node_modules/ 26 | # - vendor/bundle/ 27 | # - vendor/cache/ 28 | # - vendor/gems/ 29 | # - vendor/ruby/ 30 | -------------------------------------------------------------------------------- /docs/api/experimental.md: -------------------------------------------------------------------------------- 1 | # Internal Experimental Words 2 |
[src]
3 | 4 | ## `stringify` 5 | 6 | `a -> str` 7 |
[src]
8 | 9 | ## `spawn` 10 | 11 | evalues the quote in a child environment, returns a future 12 | 13 | `[A] -> future` 14 |
[src]
15 | 16 | ## `await` 17 | 18 | evalues the quote in a child environment, waits for result 19 | 20 | `[A] -> [a]` 21 |
[src]
22 | 23 | ## `suspend` 24 | 25 | stops execution, push queue to stack, loses other state 26 | 27 | `->` 28 | 29 | ``` 30 | f♭> [ 1 2 * suspend 3 4 * ] in 31 | [ [ 2 3 4 * ] ] 32 | ``` 33 |
[src]
34 | 35 | ## `all` 36 | 37 | executes each element in a child environment 38 | 39 | `[A] -> [a]` 40 |
[src]
41 | 42 | ## `race` 43 | 44 | executes each element in a child environment, returns first to finish 45 | 46 | [A] -> [b]` 47 |
[src]
48 | 49 | ## `js-raw` 50 | 51 | evalues a string as raw javascript 52 | 53 | `str -> a*` 54 |
[src]
55 | -------------------------------------------------------------------------------- /docs/api/flags.md: -------------------------------------------------------------------------------- 1 | # Internal Flags 2 |
[src]
3 | 4 | ## `set-system-property` 5 | 6 | sets a system level flag 7 | - flags: `'auto-undo'`, `'log-level'`, `'decimal-precision'` 8 | 9 | `str a ->` 10 | 11 | 12 | ``` 13 | f♭> 'log-level' 'trace' set-system-property 14 | [ ] 15 | ``` 16 |
[src]
17 | 18 | ## `get-system-property` 19 | 20 | gets a system level flag 21 | - flags: `'auto-undo'`, `'log-level'`, `'decimal-precision'` 22 | 23 | `str -> a` 24 | 25 | ``` 26 | f♭> 'log-level' get-system-property 27 | [ 'warn' ] 28 | ``` 29 |
[src]
30 | -------------------------------------------------------------------------------- /docs/api/math.md: -------------------------------------------------------------------------------- 1 | # Internal Math Words 2 |
[src]
3 | 4 | ## `re` 5 | 6 | `z -> x` 7 | 8 | Real part of a value 9 | 10 |
[src]
11 | 12 | ## `im` 13 | 14 | `z -> y` 15 | 16 | Imaginary part of a value 17 | 18 |
[src]
19 | 20 | ## `arg` 21 | 22 | `z -> a` 23 | 24 | Argument (polar angle) of a complex number 25 | 26 |
[src]
27 | 28 | ## `abs` 29 | 30 | `x -> x'` 31 | 32 | Absolute value and complex magnitude 33 | 34 |
[src]
35 | 36 | ## `cos` 37 | 38 | `x -> x'` 39 | 40 | Cosine of argument in radians 41 | 42 |
[src]
43 | 44 | ## `sin` 45 | 46 | `x -> x'` 47 | 48 | Sine of argument in radians 49 | 50 |
[src]
51 | 52 | ## `tan` 53 | 54 | `x -> x'` 55 | 56 | Tangent of argument in radians 57 | 58 |
[src]
59 | 60 | ## `asin` 61 | 62 | `x -> x'` 63 | 64 | Inverse sine in radians 65 | 66 |
[src]
67 | 68 | ## `atan` 69 | 70 | `x -> x'` 71 | 72 | Inverse tangent in radians 73 | 74 |
[src]
75 | 76 | ## `round` 77 | 78 | `x -> n` 79 | 80 | Round to nearest decimal or integer 81 | 82 |
[src]
83 | 84 | ## `floor` 85 | 86 | `x -> n` 87 | 88 | Round toward negative infinity 89 | 90 |
[src]
91 | 92 | ## `ceil` 93 | 94 | `x -> n` 95 | 96 | Round toward positive infinity 97 | 98 |
[src]
99 | 100 | ## `sqrt` 101 | 102 | `x -> x'` 103 | 104 | Square root 105 |
[src]
106 | 107 | ## `conj` 108 | 109 | `z -> z'` 110 | 111 | Complex conjugate 112 | 113 |
[src]
114 | 115 | ## `exp` 116 | 117 | `x -> x'` 118 | 119 | Exponential 120 | 121 |
[src]
122 | 123 | ## `gamma` 124 | 125 | `x -> x'` 126 | 127 | Gamma function 128 | 129 |
[src]
130 | 131 | ## `atan2` 132 | 133 | `x₁ x₂ -> x₃` 134 | 135 | Four-quadrant inverse tangent 136 | 137 |
[src]
138 | 139 | ## `erf` 140 | 141 | `x -> x'` 142 | 143 | Error function 144 | 145 |
[src]
146 | 147 | ## `bit-and` 148 | 149 | `x₁ x₂ -> x₃` 150 | 151 | bitwise and 152 | 153 |
[src]
154 | 155 | ## `bit-or` 156 | 157 | `x₁ x₂ -> x₃` 158 | 159 | bitwise or 160 | 161 |
[src]
162 | 163 | ## `bit-xor` 164 | 165 | `x₁ x₂ -> x₃` 166 | 167 | bitwise xor 168 | 169 |
[src]
170 | 171 | ## `bit-not` 172 | 173 | `x -> x'` 174 | 175 | bitwise not 176 | 177 |
[src]
178 | 179 | ## `rand` 180 | 181 | `-> x` 182 | 183 | pseudo-random number in the range [0, 1) 184 | 185 |
[src]
186 | 187 | ## `infinity` 188 | 189 | `-> -Infinity` 190 | 191 | pushes the value Infinity 192 |
[src]
193 | 194 | ## `-infinity` 195 | 196 | `-> -Infinity` 197 | 198 | pushes the value -Infinity 199 |
[src]
200 | 201 | ## `complexinfinity` 202 | 203 | `-> ComplexInfinity` 204 | 205 | pushes the value complexinfinity 206 |
[src]
207 | -------------------------------------------------------------------------------- /docs/api/node.md: -------------------------------------------------------------------------------- 1 | # Internal Words for Node Environment 2 |
[src]
3 | 4 | ## `args` 5 | 6 | `-> [str*]` 7 | 8 | Returns an array containing of command line arguments passed when the process was launched 9 |
[src]
10 | 11 | ## `println` 12 | 13 | `a ->` 14 | 15 | Prints the value followed by (newline) 16 | 17 |
[src]
18 | 19 | ## `print` 20 | 21 | `a ->` 22 | 23 | Prints the value 24 | 25 |
[src]
26 | 27 | ## `exit` 28 | 29 | `->` 30 | 31 | terminate the process synchronously with an a status code 32 | 33 |
[src]
34 | 35 | ## `rand-u32` 36 | 37 | `-> x` 38 | 39 | Generates cryptographically strong pseudo-random with a givennumber of bytes to generate 40 | 41 |
[src]
42 | 43 | ## `dirname` 44 | 45 | `str₁ -> str₂` 46 | 47 | returns the directory name of a path, similar to the Unix dirname command. 48 | See https://nodejs.org/api/path.html#path_path_dirname_path 49 | 50 |
[src]
51 | 52 | ## `path-join` 53 | 54 | `[ str* ] -> str` 55 | 56 | joins all given path segments together using the platform specific separator as a delimiter 57 | See https://nodejs.org/api/path.html#path_path_join_paths 58 | 59 |
[src]
60 | 61 | ## `resolve` 62 | 63 | `str₁ -> str₂` 64 | 65 | returns a URL href releative to the current base 66 | 67 |
[src]
68 | 69 | ## `exists` 70 | 71 | `str -> bool` 72 | 73 | Returns true if the file exists, false otherwise. 74 | 75 |
[src]
76 | 77 | ## `read` 78 | 79 | `str₁ -> str₂` 80 | 81 | Pushes the content of a file as a utf8 string 82 | 83 |
[src]
84 | 85 | ## `cwd` 86 | 87 | `-> str` 88 | 89 | Pushes the current working directory 90 | 91 |
[src]
92 | 93 | ## `get-env` 94 | 95 | `str₁ -> str₂` 96 | 97 | Gets a environment variable 98 | 99 |
[src]
100 | -------------------------------------------------------------------------------- /docs/api/objects.md: -------------------------------------------------------------------------------- 1 | # Internal Object Words 2 |
[src]
3 | 4 | ## `object` 5 | 6 | `[ a: b ... ] -> { a: b ... }` 7 | 8 | convert a quotation to an object 9 | 10 |
[src]
11 | 12 | ## `object?` 13 | 14 | `a -> bool` 15 | 16 | retruns true of the item is an object 17 |
[src]
18 | 19 | ## `has?` 20 | 21 | `{A} a: -> bool` 22 | 23 | returns true if an item contains a key 24 | 25 |
[src]
26 | 27 | ## `keys` 28 | 29 | `{A} -> [str*]` 30 | 31 | returns an array of keys 32 | 33 |
[src]
34 | 35 | ## `vals` 36 | 37 | `{A} -> [b*]` 38 | 39 | returns an array of values 40 | 41 |
[src]
42 | -------------------------------------------------------------------------------- /docs/api/types.md: -------------------------------------------------------------------------------- 1 | # Internal Type Words 2 |
[src]
3 | 4 | ## `type` 5 | 6 | `a -> str` 7 | 8 | retruns the type of an item 9 | 10 |
[src]
11 | 12 | ## `number` 13 | 14 | `a -> x` 15 | 16 | converts to a number 17 | 18 |
[src]
19 | 20 | ## `complex` 21 | 22 | `a -> z` 23 | 24 | converts to a complex number 25 | 26 |
[src]
27 | 28 | ## `string` 29 | 30 | converts to a string 31 | 32 |
[src]
33 | 34 | ## `itoa` 35 | 36 | `x -> str` 37 | 38 | returns a string created from UTF-16 character code 39 | 40 |
[src]
41 | 42 | ## `atoi` 43 | 44 | `str -> x` 45 | 46 | returns an integer between 0 and 65535 representing the UTF-16 code of the first character of a string 47 | 48 |
[src]
49 | 50 | ## `atob` 51 | 52 | `str -> str` 53 | 54 | decodes a string of data which has been encoded using base-64 encoding 55 | 56 |
[src]
57 | 58 | ## `btoa` 59 | 60 | `str -> str` 61 | 62 | creates a base-64 encoded ASCII string from a String 63 | 64 |
[src]
65 | 66 | ## `hash` 67 | 68 | `a -> x` 69 | 70 | creates a numeric hash from a String 71 | 72 |
[src]
73 | 74 | ## `hex-hash` 75 | 76 | `a -> str` 77 | 78 | creates a hexidecimal hash from a String 79 | 80 |
[src]
81 | 82 | ## `base` 83 | 84 | `x -> str` 85 | 86 | Convert an integer to a string in the given base 87 | 88 |
[src]
89 | 90 | ## `boolean` 91 | 92 | `a -> bool` 93 | 94 | converts a value to a boolean 95 | 96 |
[src]
97 | 98 | ## `:` (key) 99 | 100 | `a -> a:` 101 | 102 | converts a string to a key 103 | 104 |
[src]
105 | 106 | ## `#` (symbol) 107 | 108 | `a -> #a` 109 | 110 | converts a string to a unique symbol 111 | 112 |
[src]
113 | 114 | ## `array` 115 | 116 | `a -> [A]` 117 | 118 | converts a value to an array 119 | 120 |
[src]
121 | 122 | ## `of` 123 | 124 | `a b -> c` 125 | 126 | converts the rhs value to the type of the lhs 127 | 128 |
[src]
129 | 130 | ## `is?` 131 | 132 | `a b -> bool` 133 | 134 | returns true if to values are the same value 135 | 136 |
[src]
137 | 138 | ## `nothing?` 139 | 140 | `a -> bool` 141 | 142 | returns true if the value is null or undefined 143 | 144 |
[src]
145 | 146 | ## `date` 147 | 148 | `a -> date` 149 | 150 | convert a value to a date/time 151 | 152 |
[src]
153 | 154 | ## `now` 155 | 156 | `-> date` 157 | 158 | returns the current date/time 159 | 160 |
[src]
161 | 162 | ## `clock` 163 | 164 | `-> x` 165 | 166 | returns a high resoltion time elapsed 167 | 168 |
[src]
169 | 170 | ## `regexp` 171 | 172 | `a -> regexp` 173 | 174 | convert string to regular expresion 175 | 176 |
[src]
177 | -------------------------------------------------------------------------------- /docs/api/vocab.md: -------------------------------------------------------------------------------- 1 | Converts a stack item to a "function" definition 2 |
[src]
3 | 4 | # Internal Vocabulary Words 5 |
[src]
6 | 7 | ## `defer` 8 | 9 | Stores a value in the dictionary that raises an error when executed. 10 | Used to allocate a word before it is used, for example in mutually-recursive 11 | 12 | `a: ->` 13 | 14 | ``` 15 | f♭> a: defer 16 | [ ] 17 | ``` 18 |
[src]
19 | 20 | ## `;` (def) 21 | 22 | stores a definition in the current dictionary 23 | 24 | `a: [A] ->` 25 | 26 | ``` 27 | f♭> sqr: [ dup * ] ; 28 | [ ] 29 | ``` 30 |
[src]
31 | 32 | ## `use` 33 | 34 | Move the contents of a map into scope 35 | - The map must be a map of keys to global symbols generated by `vocab` 36 | 37 | `{A} ->` 38 | 39 | ``` 40 | f♭> { ... } use 41 | [ ] 42 | ``` 43 |
[src]
44 | 45 | ## `vocab` 46 | 47 | Write the current local vocabulary to the stack 48 | - The returned value is a map of strings (keys) to global symbols 49 | 50 | `-> {A}` 51 | 52 | ``` 53 | f♭> vocab 54 | [ { ... } ] 55 | ``` 56 |
[src]
57 | 58 | ## `defined?` 59 | returns true if the word is defined in the current vocabulary 60 | 61 | `a: -> bool` 62 | 63 | ``` 64 | f♭> 'sqr' defined? 65 | [ true ] 66 | ``` 67 |
[src]
68 | 69 | ## `see` 70 | 71 | recalls the definition of a word as a string 72 | 73 | `a: -> str` 74 | 75 | ``` 76 | f♭> 'sqr' see 77 | [ '[ dup * ]' ] 78 | ``` 79 |
[src]
80 | 81 | ## `show` 82 | 83 | prints the definition of a word as a formatted string 84 | 85 | `a: ->` 86 | 87 | ``` 88 | f♭> "sqr" show 89 | [ dup * ] 90 | [ ] 91 | ``` 92 |
[src]
93 | 94 | ## `words` 95 | 96 | `-> [str*]` 97 | 98 | returns a list of defined words 99 |
[src]
100 | 101 | ## `locals` 102 | 103 | `-> [str*]` 104 | 105 | returns a list of locals words 106 |
[src]
107 | 108 | ## `scoped` 109 | 110 | `-> [str*]` 111 | 112 | returns a list of local scoped words 113 |
[src]
114 | 115 | ## `rewrite` 116 | 117 | `{A} [B] -> [C]` 118 | 119 | rewrites an expression using a set of rewrite rules 120 | 121 |
[src]
122 | -------------------------------------------------------------------------------- /docs/assets/ENV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hypercubed/f-flat_node/18dead2adbd2c20b5f32d766af3f9bd73dc13535/docs/assets/ENV.png -------------------------------------------------------------------------------- /docs/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import "{{ site.theme }}"; 5 | 6 | code { 7 | font-size: inherit; 8 | } 9 | 10 | ul { 11 | list-style-image: unset; 12 | } -------------------------------------------------------------------------------- /docs/assets/parent+child.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hypercubed/f-flat_node/18dead2adbd2c20b5f32d766af3f9bd73dc13535/docs/assets/parent+child.png -------------------------------------------------------------------------------- /docs/assets/stack+queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hypercubed/f-flat_node/18dead2adbd2c20b5f32d766af3f9bd73dc13535/docs/assets/stack+queue.png -------------------------------------------------------------------------------- /docs/code-structure.md: -------------------------------------------------------------------------------- 1 | # Code Structure 2 | 3 | ## Comments 4 | 5 | Comments are C-style. 6 | 7 | ``` 8 | // Single-line comment. 9 | 10 | /* 11 | Multi-line 12 | comment. 13 | */ 14 | ``` 15 | 16 | ## White space 17 | 18 | In F♭, spaces, tabs, newlines and commas (`,`) are all considered whitespace, and have no impact on code execution except as a separator between elements. Brackets (`[](){}`) are always considered to have whitespace padding. For example, the follow are considered eqivalent in F♭: `[1,2,3][4,5]` and `[ 1 2 3] [4 5]`. 19 | 20 | -------------------------------------------------------------------------------- /docs/compare.md: -------------------------------------------------------------------------------- 1 | # Hello world 2 | 3 | JavaScript | F-Flat 4 | ---|--- 5 | `console.log("Hello World!")` | `"Hello World!" println` 6 | 7 | # Literals 8 | 9 | JavaScript | F-Flat 10 | -----------------------|----- 11 | `3` | `3` 12 | `3.1415` | `3.1415` 13 | `0x0F` | `0x0F` 14 | *no complex numbers* | `i` 15 | `'Hello world!'` | `'Hello world!'` (doesn't support unicode escapes) 16 | `"\u0048\u0065\u006C\u006C\u006F\u0020\u0077\u006F\u0072\u006C\u0064"` | `"\u0048\u0065\u006C\u006C\u006F\u0020\u0077\u006F\u0072\u006C\u0064"` (supports unicode escapes) 17 | ``` `3 === ${1+2}` ``` | ``` `3 === $(1 2 +)` ``` 18 | `true` | `true` 19 | `[1,2,3]` | `[1 2 3]` (commas are options) 20 | 21 | # Arrays / Lists 22 | 23 | JavaScript | F-Flat 24 | -----------|--- 25 | `[3,4]` | `[ 3 4 ]` 26 | `[3,4][0]` | `[ 3 4 ] 0 @` 27 | 28 | # Objects / Records 29 | 30 | JavaScript | F-Flat 31 | ---------------------|--- 32 | `{ x: 3, y: 4 }` | `{ x: 3, y: 4 }` (commas are options) 33 | `({ x: 3, y: 4 }).x` | `{ x: 3, y: 4 } x: @` 34 | 35 | # Functions 36 | 37 | JavaScript | F-Flat 38 | ---------------------------------------|--- 39 | `1 + 2` | `1 2 +` 40 | `() => true` | `[ true ]` 41 | `(x,y) => x + y` | `[ + ]` 42 | `function add(x,y) { return x + y; }` | `add: [ + ] ;` 43 | `Math.max(3, 4)` | `3 4 max` 44 | `Math.min(1, Math.pow(2, 4))` | `1 2 4 ^ min` 45 | `[1,2,3].map(Math.sqrt)` | `[1 2 3] [ sqrt ] map` 46 | `[{ x: 3, y: 4 }, ... ].map(p => p.x)` | `[{ x: 3 y: 4 } ... ] [ @x ] map` 47 | 48 | # Control Flow 49 | 50 | JavaScript | F-Flat 51 | ----------------------|--- 52 | `3 > 2 ? '😺' : '🐶'` | `3 2 > '😺' '🐶' choose` 53 | 54 | # Strings 55 | 56 | JavaScript | F-Flat 57 | ----------------|--- 58 | `'abc' + '123'` | `'abc' '123' +` 59 | `'abc'.length` | `'abc' length` 60 | `'abc' + 123` | `'abc' 123 +` 61 | 62 | # Others 63 | 64 | JavaScript | F-Flat 65 | -------------|--- 66 | `var n = 42` | N/A 67 | -------------------------------------------------------------------------------- /docs/compared-to-json.md: -------------------------------------------------------------------------------- 1 | # F♭ is a superset of JSON 2 | 3 | By definition of the following words, F♭ is able to parse a superset of JSON. 4 | 5 | * `{` - start a quote 6 | * `}` - end a quote, convert to hash assuming a list of key value pairs `[ key: value key: value ]` 7 | * `[` - start a lazy quote 8 | * `]` - end a lazy quote 9 | * `:` - convert a string to an key 10 | * `{word}:` - key literal, same as `"{word}" :` 11 | 12 | The following 13 | 14 | ```json 15 | { 16 | "key": 10, 17 | "key2": "20" 18 | } 19 | ``` 20 | 21 | is interpreted by F♭ as: 22 | 23 | ``` 24 | { "key" : 10 "key2" : "20" } 25 | ``` 26 | 27 | in other words: 28 | 29 | 1. Start a quote 30 | 2. Push a string 31 | 3. Convert string to a key 32 | 4. Push a number 33 | 5. Push another string 34 | 6. Convert string to a key 35 | 7. push a string 36 | 8. end the quote and convert to an map assuming a list of key values pairs 37 | 38 | resulting in the equivalent of a JavaScript object: 39 | 40 | ```js 41 | { key: 10, key2: '20' } 42 | ``` 43 | 44 | However, because this is actually F♭ the syntax is relaxed. 45 | 46 | ## Both block and line comments are supported 47 | 48 | ``` 49 | /* This is JSON. 50 | JSON? 51 | This is F♭! */ 52 | { 53 | "key": 10, // This is a number 54 | "key2": "20" // This is a string 55 | } 56 | ``` 57 | 58 | ## Both double, single, and backtick quotes are supported. 59 | 60 | ``` 61 | { 62 | "key": 10, 63 | 'key2': `20` 64 | } 65 | ``` 66 | 67 | ## Commas are whitespace and optional. 68 | 69 | ``` 70 | { 71 | "key": 10 72 | 'key2': `20` 73 | } 74 | ``` 75 | 76 | ## Keys can be strings or words. 77 | 78 | Quotes around keys are optional if the key is an word \(no whitespace between the key and colon\) 79 | 80 | ``` 81 | { 82 | key: 10 83 | 'key2' : `20` 84 | } 85 | ``` 86 | 87 | Colon is optional between key and value if the key is a string. 88 | 89 | ``` 90 | { 91 | "key" 10 92 | 'key2' `20` 93 | } 94 | ``` 95 | 96 | ## Both keys and values can be computed: 97 | 98 | ``` 99 | { 100 | key: 5 2 * 101 | "key" 2 + : 10 10 + string 102 | } 103 | ``` 104 | 105 | Including template strings 106 | 107 | ``` 108 | { 109 | key: 5 2 * 110 | `key$(1 1 +)` : `$(10 10 +)` 111 | } 112 | ``` 113 | 114 | Including infinity, null, complex and date values: 115 | 116 | ``` 117 | { 118 | pi: 4 1 atan * 119 | complex: '2+4i' :complex 120 | date: '1/1/1990' :date 121 | nothing: null 122 | everything: 1 0 / 123 | } 124 | ``` 125 | 126 | results in: 127 | 128 | ``` 129 | { pi: 3.1415926535897932385 130 | complex: 2+4i 131 | date: Mon Jan 01 1990 00:00:00 GMT-0700 (Mountain Standard Time) 132 | nothing: Null 133 | everything: ComplexInfinity } 134 | ``` 135 | 136 | ## Square brackets are lazy, round brackets are not. 137 | 138 | ``` 139 | { 140 | a: [ 1 2 + ] 141 | b: ( 1 2 + ) 142 | } 143 | ``` 144 | 145 | results in the object: 146 | 147 | ``` 148 | { 149 | a: [ 1 2 + ] 150 | b: [ 3 ] 151 | } 152 | ``` 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /docs/concurrency.md: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/control-structures-conditionals-and-blocks.md: -------------------------------------------------------------------------------- 1 | # Control Structures, Conditionals and Blocks 2 | 3 | ## Choose 4 | 5 | The core control mechanism in f♭ is the conditional \(ternary\) operator `choose` word. 6 | 7 | ``` 8 | f♭> true 1 2 choose 9 | [ 1 ] 10 | 11 | f♭> clr false [ 3 ] [ 4 ] choose 12 | [ [ 4 ] ] 13 | ``` 14 | 15 | If conditional values are quotes they must be evaluated to execute. 16 | 17 | ``` 18 | f♭> true [ 1 2 + ] [ 1 2 * ] choose 19 | [ [ 1 2 + ] ] 20 | 21 | f♭> clr true [ 1 2 + ] [ 1 2 * ] choose eval 22 | [ 3 ] 23 | 24 | f♭> clr false [ 1 2 + ] [ 1 2 * ] choose eval 25 | [ 2 ] 26 | ``` 27 | 28 | Defining the word `branch: [ choose eval ] ;` allows simplified branching: 29 | 30 | ``` 31 | f♭> true [ 1 2 + ] [ 1 2 * ] branch 32 | [ 3 ] 33 | 34 | f♭> clr false [ 1 2 + ] [ 1 2 * ] branch 35 | [ 2 ] 36 | ``` 37 | 38 | ## When, Unless 39 | 40 | TBR 41 | 42 | ## Switch, Case 43 | 44 | TBR 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /docs/data-stuctures.md: -------------------------------------------------------------------------------- 1 | # Data Structures 2 | 3 | ## Lists 4 | 5 | Lists / Arrays are surrounded by square (`[]`) or round (`()`) brackets with optionally comma-separated elements (recall that commas are whitespace). The difference being that expressions within round brackets are executed while square brackets are lazy. 6 | 7 | ``` 8 | f♭> ( 1 2 ) 9 | [ [ 1 2 ] ] 10 | 11 | f♭> ( 3, 4 ) 12 | [ [ 1 2 ] [ 3 4 ] ] 13 | 14 | f♭> ( 5 6 * ) 15 | [ [ 1 2 ] [ 3 4 ] [ 30 ] ] 16 | 17 | f♭> [ 1 2 * ] 18 | [ [ 1 2 ] [ 3 4 ] [ 30 ] [ 1 2 * ] ] 19 | ``` 20 | 21 | ## Maps 22 | 23 | Maps (aka objects or records) are surrounded by curly brackets (`{}`) again with optionally comma-separated elements. The key in a key value pair is actually a key as described above. Expressions within curly brackets are executed immediately. 24 | 25 | ``` 26 | f♭> { x: 1 y: 2 } 27 | [ { x: 1 y: 2 } ] 28 | 29 | f♭> { z: 3, u: 4 5 * } 30 | [ { x: 1 y: 2 } { z: 3 u: 20 } ] 31 | 32 | f♭> { 'v' : 3 , 'w' : 4 } 33 | [ { x: 1 y: 2 } { z: 3 u: 20 } { v: 3 w: 4 } ] 34 | ``` 35 | 36 | -------------------------------------------------------------------------------- /docs/demo.md: -------------------------------------------------------------------------------- 1 | [![asciicast](https://asciinema.org/a/39282.png)](https://asciinema.org/a/39282) 2 | 3 | ```forth 4 | ** Welcome to f♭ ** 5 | 6 | f♭> 0.1 0.2 + 7 | [ 0.3 ] 8 | 9 | f♭> 0.3 = 10 | [ true ] 11 | 12 | f♭> clr 13 | [] 14 | 15 | f♭> -1 sqrt 16 | [ 0+1i ] 17 | 18 | f♭> 1 atan 4 * 19 | [ 0+1i 3.1415926535897932385 ] 20 | 21 | f♭> * exp 22 | [ -1-3.7356616720497115803e-20i ] 23 | 24 | f♭> abs 25 | [ 1 ] 26 | 27 | f♭> clr 28 | [] 29 | 30 | f♭> mersenne?: [ 2 swap ^ 1 - prime? ] ; 31 | [] 32 | 33 | f♭> 10 integers 34 | [ [ 1 2 3 4 5 6 7 8 9 10 ] ] 35 | 36 | f♭> [ mersenne? ] map 37 | [ 38 | [ false true true false true false true false false false ] 39 | ] 40 | 41 | f♭> clr 42 | [] 43 | 44 | f♭> 1000 ! 45 | [ 4.0238726007709377384e+2567 ] 46 | 47 | f♭> clr 48 | [] 49 | 50 | f♭> i ! 51 | [ 0.49801566811835599106-0.15494982830181068731i ] 52 | 53 | f♭> clr { first: "Manfred" } 54 | [ { first: 'Manfred' } ] 55 | 56 | f♭> { last: 'von Thun' } 57 | [ { first: 'Manfred' }, { last: 'von Thun' } ] 58 | 59 | f♭> + 60 | [ { first: 'Manfred', last: 'von Thun' } ] 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/dice-rolls-for-dungons-and-dragons-and-other-games.md: -------------------------------------------------------------------------------- 1 | # Dice Rolls for Dungeons & Dragons (and other games) 2 | 3 | (Adapted from https://github.com/crcx/parable/blob/master/examples/dice-roll.md) 4 | 5 | This simulates rolls of 4, 6, 8, 10, 12, and 20 sided dice. It has 6 | capacity to roll multiple dice and sum the results (including 7 | modifiers) quickly. 8 | 9 | We'll expose a few function: 10 | 11 | [ 'roll-dice' 'd4' 'd6' 'd8' 'd10' 'd12' 'd20' ] 12 | 13 | First up: a routine to roll a single die. Provide it with the number 14 | of sides; it'll return the result. This one isn't left visible at the 15 | end. 16 | 17 | roll-die: [ [ rand 100 * ] dip % floor 1 + ] ; 18 | 19 | Now provide handlers for each n-sided die. Each of these takes a 20 | number representing the number of rolls and returns the sum of the 21 | rolls. 22 | 23 | d4: [ [ [ 4 _roll-die ] swap times ] >> in ] ; 24 | d6: [ [ [ 6 _roll-die ] swap times ] >> in ] ; 25 | d8: [ [ [ 81 _roll-die ] swap times ] >> in ] ; 26 | d10: [ [ [ 10 _roll-die ] swap times ] >> in ] ; 27 | d12: [ [ [ 12 _roll-die ] swap times ] >> in ] ; 28 | d20: [ [ [ 20 _roll-die ] swap times ] >> in ] ; 29 | 30 | The final routine is a combinator that sums the results of the rolls. 31 | 32 | roll-dice: [ eval [ sum ] dip + ] ; 33 | 34 | Example: 35 | 36 | Assuming 2d6+5: 37 | 38 | [ 2 d6 5 ] roll-dice -------------------------------------------------------------------------------- /docs/dictionary-locals.md: -------------------------------------------------------------------------------- 1 | # Dictionary / Locals 2 | 3 | Unlike many other languages, f♭ does not have a concept of variables or lambdas. Like most stack based languages the primary location for value storage is the stack. f♭ has a write once dictionary for storage of word definitions. Values within a dictionary cannot be overwritten or deleted. However, using child stacks \(discussed later\) dictionary words can be shadowed. \(similar to JS scoping rules\). 4 | 5 | ``` 6 | f♭> x: [ 123 ] ; 7 | [ ] 8 | 9 | f♭> x 10 | [ 123 ] 11 | 12 | f♭> x: [ 456 ] ; 13 | Error: Cannot overwrite definition: x 14 | [ ] 15 | 16 | f♭> clr [ x: [ 456 ] ; x ] in x 17 | [ [ 456 ] 123 ] 18 | ``` 19 | 20 | ## Word requirements 21 | 22 | * Cannot contain a colon \(`:`\) \(special meaning\) 23 | * Cannot start with a period `.` 24 | * Cannot contain white space \(space, new lines, tabs, or comma `,`\). 25 | * Cannot contain brackets \( `(){}[]` \). 26 | * Cannot contain quotes \( '"\` \) 27 | * Cannot be numeric \(starting with a numeric value is ok\) 28 | * Cannot begin with a hash \(`#`\) 29 | * Cannot be a reserved word \(`null`, `true` or `false`\) 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Hello World 4 | 5 | Basic hello world: 6 | 7 | ``` 8 | 'Hello World!' println 9 | ``` 10 | 11 | REPL: 12 | 13 | ``` 14 | f♭> "Hello, world!" println 15 | Hello, world! 16 | [ ] 17 | 18 | f♭> ["Hello, world!" println] eval 19 | Hello, world! 20 | [ ] 21 | 22 | f♭> hi: ["Hello, world!" println] ; 23 | [ ] 24 | 25 | f♭> hi 26 | Hello, world! 27 | [ ] 28 | 29 | f♭> hi2: [ "Hello, " swap + "!" + println ] ; 30 | [ ] 31 | 32 | f♭> "world" hi2 33 | Hello, world! 34 | [ ] 35 | 36 | f♭> hi-all: [ hi2: * eval ] ; 37 | [ ] 38 | 39 | f♭> ["world" "everyone"] hi-all 40 | Hello, world! 41 | Hello, everyone! 42 | ``` 43 | 44 | ## Guessing game 45 | 46 | ``` 47 | f♭> guess-game: [ 48 | 'Guess the number!' println 49 | 'Please input your guess.' println 50 | prompt 51 | 'You guessed: ' swap + println 52 | ] ; 53 | f♭> guess-game 54 | ``` 55 | 56 | ## Average Some Values 57 | 58 | ``` 59 | [ 1 3 5 6 9 ] dup length [ 0 [ + ] reduce ] dip / 60 | ``` 61 | 62 | ## A tail of 'fizz' (3) Fizz buzzs 63 | 64 | ``` 65 | /** 66 | * FizzBuzz using branching 67 | */ 68 | 20 integers 69 | [ 70 | dup 15 divisor? 71 | [ drop 'fizzbuzz' println ] 72 | [ 73 | dup 3 divisor? 74 | [ drop 'fizz' println ] 75 | [ 76 | dup 5 divisor? 77 | [ drop 'buzz' println ] 78 | [ println ] 79 | branch 80 | ] 81 | branch 82 | ] 83 | branch 84 | ] each 85 | 86 | /** 87 | * FizzBuzz using switch-case 88 | */ 89 | 20 integers 90 | [ 91 | [ 92 | [ [15 divisor?] case [drop 'fizzbuzz' println]] 93 | [ [3 divisor?] case [drop 'fizz' println]] 94 | [ [dup 5 divisor?] case [drop 'buzz' println]] 95 | [ [true] case [println]] 96 | ] switch 97 | ] each 98 | 99 | /** 100 | * FizzBuzz using pattern matching 101 | */ 102 | 20 integers [ 103 | dup [ 5 divisor? ] [ 3 divisor? ] bi pair 104 | [ 105 | [dup [true true] =~ [ drop 'fizzbuzz' println ]] 106 | [dup [false true] =~ [ drop 'fizz' println ]] 107 | [dup [true false] =~ [ drop 'buzz' println ]] 108 | [dup [false false] =~ [ drop println ]] 109 | ] switch 110 | ] each 111 | 112 | /** 113 | * FizzBuzz using lambdas as switch 114 | */ 115 | 20 integers [ 116 | [ x: ] => [ 117 | [ 118 | [ .x 15 divisor? ['fizzbuzz' println]] 119 | [ .x 3 divisor? ['fizz' println]] 120 | [ .x 5 divisor? ['buzz' println]] 121 | [ true [.x println]] 122 | ] switch 123 | ] 124 | ] lambda each 125 | ``` 126 | 127 | ## The quadratic equation using lambdas 128 | 129 | ``` 130 | quad: [ 131 | [ a: b: c: ] => [ 132 | .b -1 * .b .b * 4 .a .c * * - sqrt -+ 133 | [ 2 .a * / ] bi@ 134 | ] 135 | ] lambda ; 136 | 137 | 1 2 3 quad 138 | ``` 139 | -------------------------------------------------------------------------------- /docs/examples/euler20.ff: -------------------------------------------------------------------------------- 1 | 200 set-precision 2 | 100 ! 3 | [ 0 [ swap 10 divrem ] 200 times ] >> in sum 4 | println -------------------------------------------------------------------------------- /docs/examples/fridge-game.ff: -------------------------------------------------------------------------------- 1 | fridge-game: 2 | [ 3 | cls 4 | [ 5 | 'You are in a fridge. What do you want to do?' 6 | '1. Try to get out.' 7 | '2. Eat.' 8 | '3. Die.' 9 | ] 10 | [println] each 11 | { 12 | 1: ['You try to get out. You fail. You die.'] 13 | 2: [ 14 | 'You eat. You eat some more.' 15 | 'Damn, this food is tasty!' 16 | 'You eat so much you die.' 17 | ] 18 | 3: ['You die.'] 19 | } 20 | prompt 21 | @ 22 | dup null? 23 | [ drop [ 'Did not understand.' ] ] 24 | when 25 | '' << 26 | 'Play again? write y if you do.' << 27 | [println] each 28 | prompt 29 | 'y' = 30 | [ fridge-game ] 31 | [ 'Thanks for playing.' println clr ] 32 | branch 33 | ] ; 34 | 35 | fridge-game -------------------------------------------------------------------------------- /docs/examples/site-war.ff: -------------------------------------------------------------------------------- 1 | site-size: [ read length ] ; 2 | 3 | site-war: [ dup [ [ site-size ] >> ] map all zip object ] ; 4 | 5 | [ 'http://google.com/' 'https://github.com/' ] site-war 6 | println -------------------------------------------------------------------------------- /docs/expressions-evaluation.md: -------------------------------------------------------------------------------- 1 | # Expressions / Evaluation 2 | 3 | Expressions use postfix operators, unlike many other languages. Expressions are evaluated immediately unless they follow an unpaired square brackets \(`[`\) or the word is has the colon suffix (in which case it is considered a key). In general all words should follow the Subject-(Object-)-Verb pattern. 4 | 5 | ``` 6 | f♭> "Hello World!" println 7 | Hello World! 8 | [ ] 9 | 10 | f♭> 1 2 + 11 | [ 3 ] 12 | 13 | f♭> [ 1 2 3 ^ min ] 14 | [ 3 [ 1 2 3 ^ min ] ] 15 | 16 | f♭> eval 17 | [ 3 1 ] 18 | 19 | f♭> (-1 -2 -3) [ sqrt ] map 20 | [ 21 | 3 22 | 1 23 | [ 0+1i 0+1.4142135623730950488i 0+1.7320508075688772935i ] 24 | ] 25 | ``` 26 | 27 | As mentioned above, lists are "lazy". Words entered following an unmatched square bracket are not immediately evaluated unless the word is prefixed with a colon \(`:`\): 28 | 29 | ``` 30 | f♭> [ 9 sqrt ] 31 | [ [ 9 sqrt ] ] 32 | 33 | f♭> clr 34 | [ ] 35 | 36 | f♭> [ 9 :sqrt ] 37 | [ [ 3 ] ] 38 | ``` 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/function-definitions-expanding-functions.md: -------------------------------------------------------------------------------- 1 | # Function Definitions / Expanding Functions 2 | 3 | ## Definition convention 4 | 5 | * Subject-(Object-)Verb pattern 6 | * Should be lower kebab-case 7 | * Inquisitive words \(typically boolean returning functions\) should end with question mark \(`?`\) 8 | * `string?`, `zero?`, `prime?` 9 | * Procedures should be verbs 10 | * `dup`, `dip`, `drop` 11 | * Types should be nouns 12 | * `string`, `number`, `complex` 13 | * By convention words begging with underscore `_` are considered private. 14 | 15 | ## Inlining Defintions 16 | 17 | TBR 18 | 19 | ## Defering Words 20 | 21 | TBR 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/history.md: -------------------------------------------------------------------------------- 1 | # Visual Basic, circa 2000 2 | 3 | # C++, circa 2012 4 | 5 | # JS, 2014 6 | 7 | # Node, 2015-2020 -------------------------------------------------------------------------------- /docs/immutability.md: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](intro.md) 4 | * [Why F♭?](why.md) 5 | * [Quick Demo](demo.md) 6 | * [Learn F♭ in minutes](learn-fflat-in-minutes.md) 7 | * [The Stack and the Environment](the-stack-and-the-repl.md) 8 | * [The REPL](the-repl.md) 9 | * [History of F♭](history.md) 10 | * The Language 11 | * [Code Structure](code-structure.md) 12 | * [Values](values.md) 13 | * [Type casting](type-casting.md) 14 | * [Immutability](immutability.md) 15 | * [Data Stuctures](data-stuctures.md) 16 | * [Expressions / Evaluation](expressions-evaluation.md) 17 | * [Dictionary / Locals](dictionary-locals.md) 18 | * [Operator Basis](operator-basis.md) 19 | * [Function Definitions / Expanding Functions](function-definitions-expanding-functions.md) 20 | * [Control Structures, Conditionals and Blocks](control-structures-conditionals-and-blocks.md) 21 | * [Compared to JavaScript](compare.md) 22 | * [Compared to JSON](compared-to-json.md) 23 | * [Module Loading](module-loading.md) 24 | * [JavaScript interop](javascript-interop.md) 25 | * [Concurrency](concurrency.md) 26 | * [Grammer](spec.md) 27 | * Internal Words 28 | * [Base](api/base.md) 29 | * [Core](api/core.md) 30 | * [Vocab](api/vocab.md) 31 | * [Math](api/math.md) 32 | * [Node](api/node.md) 33 | * [Objects](api/objects.md) 34 | * [Types](api/types.md) 35 | * [Experimental](api/experimental.md) 36 | * [Examples](examples.md) 37 | * [Site War](site-war.md) 38 | * [Project Euler Solutions](project-euler-solutions.md) 39 | * [Dice Rolls for Dungeons & Dragons \(and other games\)](dice-rolls-for-dungons-and-dragons-and-other-games.md) 40 | * [The Fridge Game](the-fridge-game.md) 41 | * [99-problems](99-problems-solved) 42 | 43 | -------------------------------------------------------------------------------- /docs/interesting.md: -------------------------------------------------------------------------------- 1 | Five interesting facts about F♭ (#fflat): 2 | 3 | 1. It's concatenative: 4 | 5 | http://evincarofautumn.blogspot.com/2012/02/why-concatenative-programming-matters.html 6 | 7 | 2. It's arbitrary precision: 8 | 9 | https://runkit.com/hypercubed/numerical-differentiation 10 | 11 | 3. All values are immutable, using structural sharing: 12 | 13 | Internally using https://github.com/aearly/icepick. 14 | 15 | 4. Actions can be undone: 16 | 17 | ![](https://pbs.twimg.com/media/DS4P_51UQAAT1XY.png) 18 | 19 | 5. F♭ is a super set of JSON: 20 | 21 | ![](https://pbs.twimg.com/media/DS4QG6EUMAAAdvK.png) 22 | 23 | Bonus. It's my model train. 24 | 25 | ![](https://pbs.twimg.com/tweet_video_thumb/DS4P4OPUQAACK7c.jpg) -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | F♭ (pronounced eff-flat) is a toy language. 4 | 5 | F♭ is a dynamically typed array-oriented concatenative language similar to Forth, Joy, Factor and others. F♭ is meant to be used interactively, for example in a CLI REPL, like R or the command shell, or in a stack based calculator. This constraint dictates many of the language features. 6 | 7 | ## Get F♭ 8 | 9 | F♭ code can be found at https://github.com/Hypercubed/f-flat_node. Currently, it can be only be used by cloning the github repo. 10 | 11 | ```bash 12 | git clone https://github.com/Hypercubed/f-flat_node.git 13 | cd f-flat_node 14 | npm install 15 | npm start 16 | ``` 17 | 18 | ## Guiding principles 19 | 20 | - interactive first 21 | - minimal hidden state 22 | - easy to type and read 23 | - reads left to right, top to bottom (like a book) 24 | - whitespace not significant syntax 25 | - interactive development. 26 | - flat concatenative language 27 | - name code not values 28 | - multiple return values 29 | - concatenation is composition/pipeline style 30 | - no unnecessary parentheses. 31 | - no surprises (Principle of "least astonishment") 32 | - immutable data 33 | - decimal and complex numbers 34 | - both double and single quotes 35 | - no order of operations (RPN) 36 | - Use common work-flow in Forth-like languages: 37 | - Write some code to perform a task 38 | - Identify some fragment that might be generally useful 39 | - Extract it and give it a name 40 | - Replace the relevant bits with the new word 41 | - \[Refactor\] when: [Read-Eval-Print-λove](https://leanpub.com/readevalprintlove003/read): 42 | - complexity tickles your conscious limits 43 | - you’re able to elucidate a name for something 44 | - at the point when you feel that you need a comment 45 | - the moment you start repeating yourself 46 | - when you need to hide detail 47 | - when your command set (API) grows too large 48 | - Don’t factor idioms 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/javascript-interop.md: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/manifesto.md: -------------------------------------------------------------------------------- 1 | # F♭ Manifesto 2 | 3 | 1. Interactive First 4 | 5 | The core target to F♭ is an interactive programming language. That is F♭ is designed to be used interactivly, like a calculator. Compilers, if they ever exists, are a secondary goal. Many of the remaining princibles flow out of this princibles. 6 | 7 | 2. No surprises 8 | 9 | 3. Flat concatenative work-flow like language 10 | 11 | 4. Function composition instead of function application 12 | 13 | 5. Reads like english (top to bootom, left to right). -------------------------------------------------------------------------------- /docs/module-loading.md: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/operator-basis.md: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/signatures.md: -------------------------------------------------------------------------------- 1 | Type signatures are written in parentheses, with the input and output types to the left and right, respectively, of an ASCII rightwards arrow ->. 2 | 3 | `x, y` - a numeric value 4 | 5 | `z` - a complex values 6 | 7 | `n` - an integer 8 | 9 | `a, b, c` - any primitive value 10 | 11 | `A, B, C` - any value including words to be evaluated (if `A` and `a` are used together `a` signifies the evaluatted version of `A`) 12 | 13 | `a:, b:, c:` - a key (often may be substitued for a string) 14 | 15 | `{A}`, `{ a: b }`, or `obj` - an object of key value pairs 16 | 17 | `bool` - a boolean value 18 | 19 | `str` - a string 20 | 21 | `seq` - a seqence (string or list) 22 | 23 | `#a, #b, #c` - a symbol 24 | 25 | `*` - Zero or more. (i.e. `a*` indicates zero or more of any value) 26 | `'` - Value after evaluation (i.e. if `x'` and `x'` are used together `x'` signifies the value of `x` after some operation) 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/site-war.md: -------------------------------------------------------------------------------- 1 | # Site War 2 | 3 | ```forth 4 | site-size: [ read ln ] ; 5 | 6 | site-war: [ dup [ [ site-size ] >> ] map all zip object ] ; 7 | 8 | [ 'http://google.com/' 'https://github.com/' ] site-war 9 | ``` -------------------------------------------------------------------------------- /docs/the-fridge-game.md: -------------------------------------------------------------------------------- 1 | # The Fridge Game 2 | 3 | ``` 4 | fridge-game: 5 | [ 6 | cls 7 | [ 8 | 'You are in a fridge. What do you want to do?' 9 | '1. Try to get out.' 10 | '2. Eat.' 11 | '3. Die.' 12 | ] 13 | [println] each 14 | { 15 | 1 ['You try to get out. You fail. You die.'] 16 | 2 [ 17 | 'You eat. You eat some more.' 18 | 'Damn, this food is tasty!' 19 | 'You eat so much you die.' 20 | ] 21 | 3 ['You die.'] 22 | } 23 | prompt 24 | @ 25 | dup null? 26 | [ drop [ 'Did not understand.' ] ] 27 | when 28 | '' << 29 | 'Play again? write y if you do.' << 30 | [println] each 31 | prompt 32 | 'y' = 33 | [ fridge-game ] 34 | [ 'Thanks for playing.' println clr ] 35 | branch 36 | ] ; 37 | 38 | fridge-game 39 | ``` 40 | 41 | Translated from the source at [Happy Learn Haskell Tutorial Vol 1](http://www.happylearnhaskelltutorial.com/1/fridge_the_game.html) -------------------------------------------------------------------------------- /docs/the-repl.md: -------------------------------------------------------------------------------- 1 | # Interactive F♭ 2 | 3 | If you are running F♭ from the github repo, you can start the REPL using `npm start`. You will then see the F♭ command prompt: 4 | 5 | ``` 6 | []] 7 | []] 8 | []]]]]]]] []] []]] F♭ Version 0.0.8 9 | []] []]] []] Copyright (c) 2000-2020 by Jayson Harshbarger 10 | []] []] []] 11 | []]]]]] []] []] Type '.exit' to exit the repl 12 | []] []][]] Type '.clear' to reset 13 | []] Type '.help' for more help 14 | 15 | f♭> 16 | ``` 17 | 18 | You can exit the REPL by hitting ctrl-C twice or by typing `.exit` 19 | 20 | ``` 21 | f♭> 22 | (To exit, press ^C again or type .exit) 23 | f♭> 24 | ``` 25 | 26 | Starting the REPL again you can enter values either as a single line or one value at at time: 27 | 28 | ``` 29 | f♭> 1 2 30 | [ 1 2 ] 31 | 32 | f♭> 'Hello' 33 | [ 1 2 'Hello' ] 34 | 35 | f♭> 36 | ``` 37 | 38 | The current stack will be printed after each value is entered. The current stack can be cleared using the `clr` command, the entire envoronment can be reset using the `.clear` REPL command. 39 | -------------------------------------------------------------------------------- /docs/the-stack-and-the-repl.md: -------------------------------------------------------------------------------- 1 | # Components 2 | 3 | ## The Stack and Queue 4 | 5 | ![](./assets/stack+queue.png) 6 | 7 | Most direct interaction in F♭ will be with the stack and the queue. In fact, since words are added to the queue before they are executed the user is primarily interacting with the queue. By convention the last item in the stack is called the bottom, while the first item in the queue is called the front. New actions are retrieved from the front of the queue \(FIFO\) and executed on the stack. Stack words \(i.e. `dup`, `swap`, `drop` \) interact with the bottom of the stack \(FILO\). 8 | 9 | ## The Environment 10 | 11 | ![](./assets/ENV.png) 12 | 13 | Words \(and corresponding actions\) in F♭ are stored in a two dictionaries. Words are stored in the "locals" dictionary. Due to prototype inheritance, words are recalled from both the locals dictionary and the "scope" \(as well as the parent as described below\). Words are added to the scope primarily through importing files.... more on that later. 14 | 15 | ## Parent / Child 16 | 17 | ![](./assets/parent+child.png) 18 | 19 | In F♭, a sub-environment may be invoked using the `in` word. When a stack is forked it receives its initial stack content from the bottom of parent's stack \(in the form of an array\) and creates a new dictionary \(scope + locals\) that inherits from the parent. In this way the child environment has access to all words defined in the parent. A child can send a value from it's stack to the parent using the `send` verb. 20 | 21 | -------------------------------------------------------------------------------- /docs/type-casting.md: -------------------------------------------------------------------------------- 1 | TBD -------------------------------------------------------------------------------- /docs/values.md: -------------------------------------------------------------------------------- 1 | # Values 2 | 3 | ## Numbers 4 | 5 | Numeric literals in F♭ are arbitary precision decimals. The values may be input as integer or floats in binary, hexadecimal or octal form. 6 | 7 | * Unsigned integer or floats \(`3`, `3.1415` ,`.14`\) 8 | * Signed integer or float \(`-3`, `+3`, `+3.1415`, `-3.1415`, `+.14`, `-.14`\) 9 | * Zero and negative zero \(`0`, `-0`\) 10 | * Exponential \(power-of-ten exponent\) notation \(`8e5`, `-123e-2`\) 11 | * Signed and unsigned binary, hexadecimal or octal integer \(`0xDEAD`, `-0xBEEF`\) 12 | * Signed and unsigned binary, hexadecimal or octal float \(`0xDEAD.BEEF`, `-0xDEAD.BEEF`\) 13 | * Signed and unsigned binary, hexadecimal or octal in power-of-two exponent notation \(`0x1.1p5`, `-0x1.1p-2`, `0x1.921FB4D12D84Ap+1`\) 14 | * Percentages \(`10%`\) 15 | * Underscores as separators \(`10_001`, `0xDEAD_BEEF`\) 16 | 17 | ## Complex numbers 18 | 19 | F♭ supports complex values. Complex values are not input directly but created through expressions: 20 | 21 | ``` 22 | f♭> 3 i 2 * + 23 | [ 3+2i ] 24 | 25 | f♭> -1 sqrt 26 | [ 3+2i 0+1i ] 27 | ``` 28 | 29 | The word `complex` can be used to convert a string or array to a complex value. 30 | 31 | ``` 32 | f♭> '3+2i' complex 33 | [ 3+2i ] 34 | 35 | f♭> [0,1] complex 36 | [ 3+2i 0+1i ] 37 | ``` 38 | 39 | Later we will learn that words prefixed with a colon \(`:`\) are immediate words. That is they work inside lazy arrays. 40 | 41 | ``` 42 | f♭> [ '3+2i' complex ] 43 | [ [ '3+2i' complex ] ] 44 | 45 | f♭> ln 46 | [ 2 ] 47 | 48 | f♭> clr 49 | [ ] 50 | 51 | f♭> [ '3+2i' :complex ] 52 | [ [ 3+2i ] ] 53 | 54 | f♭> ln 55 | [ 1 ] 56 | ``` 57 | 58 | ## Number-Like 59 | 60 | F♭ supports infinity and nan literals. In addtion to -infinity as the result of a calculation. 61 | 62 | ``` 63 | f♭> infinity 64 | [ Infinity ] 65 | 66 | f♭> -1 * 67 | [ -Infinity ] 68 | 69 | f♭> nan 70 | [ -Infinity NaN ] 71 | ``` 72 | 73 | ## Booleans and null 74 | 75 | F♭ supports `true`, `false` and `null` values. A null is considered an unknown. When comparing boolean values the values are considered sorted in this order: `false`, `null`, `true`. 76 | 77 | ## Strings 78 | 79 | String literals in F♭ use single \(`'`\), double \(`"`\), and backtick quotes \(\`\) where double quoted strings supports unicode escapes and backticks are string templates. 80 | 81 | f♭> 'Hello world!' 82 | [ 'Hello world!' ] 83 | 84 | f♭> "\u0048\u0065\u006C\u006C\u006F\u0020\u0077\u006F\u0072\u006C\u0064" 85 | [ 'Hello world!' 'Hello world' ] 86 | 87 | f♭> clr `3 === $(1 2 +)` 88 | [ '3 === 3' ] 89 | 90 | ## Dates 91 | 92 | Dates are input as strings and convered to date values usingthe`date` word. 93 | 94 | ``` 95 | f♭> '1/1/1990' date 96 | [ Mon Jan 01 1990 00:00:00 GMT-0700 (MST) ] 97 | ``` 98 | 99 | As with complex values, strings can be converted to dates using the immediate version `:date` 100 | 101 | ``` 102 | f♭> [ '1/1/1990' :date ] 103 | [ [ Mon Jan 01 1990 00:00:00 GMT-0700 (Mountain Standard Time) ] ] 104 | ``` 105 | 106 | ## Regex 107 | 108 | Regular expressions values are converted from strings: 109 | 110 | ``` 111 | f♭> '/\.*/g' :regexp 112 | [ /\.*/g ] 113 | ``` 114 | 115 | ## Keys 116 | 117 | A key can be added to the stack without execution by either post-fixing the word with a colon \(`:`\) or converting a string to a key using a colon \(`:`\). 118 | 119 | ``` 120 | f♭> x: 121 | [ x ] 122 | 123 | f♭> 'y' : 124 | [ x y ] 125 | ``` 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /docs/why.md: -------------------------------------------------------------------------------- 1 | # Why F♭ 2 | 3 | ## Why Concatenative Programming Matters 4 | 5 | For a great explaination please see Jon Purdy's [Why Concatenative Programming Matters](http://evincarofautumn.blogspot.com/2012/02/why-concatenative-programming-matters.html). 6 | 7 | In summary \(all derived from the above\): 8 | 9 | * function composition instead of function application 10 | * data flows in the order the functions are written in 11 | * return multiple values from a function, not just tuples 12 | * a parallel compiler is a plain old map-reduce \(like Unix pipes\) 13 | * It’s a stack 14 | * most language VMs are essentially concatenative 15 | 16 | ## Why stack-based 17 | 18 | The primary use case for F♭ at this time is in an interactive REPL. RPN \(and post-fix\) based calculators were \(are?\) popular among engineers and scientists because they are simpler to understand the effects of each action. If you are simply trying to calculate the value of of an equations \(for example `(-b + sqrt(b^2 - 4*a*c)) / 2a` this can be done on any modern calculator \(or computer\) without any understanding of the order of operations. However, I would argue, if you are doing complex calculations it is better to perform the calculation step by step with with feedback verifying your intent. 19 | 20 | ## Why F♭ is interesting 21 | 22 | ### Post-fix... really! 23 | 24 | One of the best things \(or worse things if you're so inclined\) about stack based languages is post fix notation over the mixed bag of infix, prefix, and postfix you see in may languages. For example, is this infix or postfix? 25 | 26 | ```js 27 | [1 2 3].map((x) => sin(2 * x * PI)); 28 | ``` 29 | 30 | I count all three! In a post-fix language we write: 31 | 32 | ```forth 33 | [1 2 3] [ 2 * pi * sin ] map 34 | ``` 35 | 36 | Not only is it shorter but it is all postfix... in fact it is consistently Subject-Verb \(or Subject-Object-Verb\). Also notice we didn't need to choose a name for the temperary variable `x`. 37 | 38 | But look at how definitions are defined in [Joy](https://hypercubed.github.io/joy/joy.html) (the grand father of concatenative languages): 39 | 40 | ``` 41 | DEFINE tps == 2 * PI * sin . 42 | ``` 43 | 44 | This deviates from the Subject\(s\)-Verb paradigm. In F♭ we could define words like: 45 | 46 | ``` 47 | tps: [ pi * sin ] ; 48 | ``` 49 | 50 | Again we are postfix following Subject-Object-Verb. 51 | 52 | ### Flat... 53 | 54 | F♭ is, well, flat. Except for possibly the various brackets \(for example \(`(` and `)`\) F♭ has no block structures. In fact, under the hood, the bracket themselves work like any other word. The `(` pushes a "quote" symbol onto the stack, then '\)' collects all elements up to the last "quote" and places the results onto the stack as a list. 55 | 56 | ### Hierarchical Write once, read many dictionary 57 | 58 | No concatinative language is complete without a dictionary of words. This dictionary, however, could be abused. Storing values inside the dictionary has the very real drawback of causing side effects. One way around this is that in F♭ the dictionary is write once. Definitions cannot be overwritten in the locals dictionary. This may seam to cause issues with name collisions, however, the F♭ dictionaries are hierarchical \(or scoped\) that allow for shadowing, much like scoping rules in JavaScript. 59 | 60 | ### Immutability 61 | 62 | All items on the stack or in the dictionary are immutable using [structural sharing](https://medium.com/@dtinth/immutable-js-persistent-data-structures-and-structural-sharing-6d163fbd73d2) at the object and array level. This avoids unwanted side effects, makes object stores memory efficient, and enables easy back tracings (calculations can be undone). 63 | 64 | ### Arbitrary-precision decimal and complex number types 65 | 66 | Internally, numeric values in are stored as arbitrary-precision integers, floats, and complex values. This allows for some interesting numerical capabilities. 67 | 68 | ### JSON format is a valid f-flat programs 69 | 70 | Using a few internal and defined words JSON file are valid f-flat programs. F♭ is able to parse a superset of JSON. The following is valid input to F♭: 71 | 72 | ``` 73 | { 74 | foo: 'bar', 75 | while: true, 76 | nothing: null, 77 | here: "is another", 78 | half: 0.5, 79 | to: Infinity, 80 | finally: 'a trailing comma', 81 | oh: [ 82 | "we shouldn't forget", 83 | 'arrays can have', 84 | 'trailing commas too', ], 85 | } 86 | ``` 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports.Stack = require('./dist/stack').createStack; 2 | 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@hypercubed/f-flat", 3 | "version": "0.0.8", 4 | "description": "F♭ (pronounced F-flat) is a toy language.", 5 | "main": "index.js", 6 | "bin": { 7 | "f-flat": "./bin/repl.js" 8 | }, 9 | "scripts": { 10 | "dist": "rimraf ./dist && tsc", 11 | "build": "npm run dist && npm run docs", 12 | "docs": "node ./scripts/generate-docs.js && node ./scripts/gen-spec.js", 13 | "prettier": "prettier \"{test,src,bin}/**/*.{js,ts,spec.ts}\" --write", 14 | "test": "npm run lint && jest", 15 | "watch": "jest --watch", 16 | "lint": "tslint \"{test,src,bin}/**/*.{js,ts,spec.ts}\"", 17 | "check": "npm run coverage && npm run lint", 18 | "coverage": "nyc -r lcov -r html -r text ava ./test/*.spec.ts", 19 | "start": "tsc && node ./bin/repl.js", 20 | "gui": "tsc && node ./bin/gui.js", 21 | "debugger": "tsc && node --inspect ./bin/repl.js", 22 | "np": "npm run dist && np" 23 | }, 24 | "author": "J. Harshbarger", 25 | "license": "MIT", 26 | "dependencies": { 27 | "@hypercubed/dynamo": "^1.0.2", 28 | "@hypercubed/milton": "^1.1.0", 29 | "@sindresorhus/is": "^1.2.0", 30 | "@types/icepick": "^2.3.0", 31 | "autobind-decorator": "^2.4.0", 32 | "blessed": "^0.1.81", 33 | "chalk": "^2.4.2", 34 | "clone-deep": "^2.0.1", 35 | "commander": "^2.11.0", 36 | "compose-regexp": "^0.4.1", 37 | "compute-erf": "^3.0.3", 38 | "decimal.js": "^10.2.0", 39 | "fantasy-helpers": "0.0.1", 40 | "fixed-width-string": "^1.1.0", 41 | "gradient-string": "^1.0.0", 42 | "icepick": "^2.4.0", 43 | "isomorphic-fetch": "^2.2.1", 44 | "memoizee": "^0.3.9", 45 | "mini-signals": "^1.1.1", 46 | "mongodb-extended-json": "^1.10.2", 47 | "myna-parser": "^2.5.1", 48 | "normalize-url": "^2.0.0", 49 | "progress": "^2.0.3", 50 | "smykowski": "^1.3.4", 51 | "ts-node": "^8.6.2", 52 | "tslib": "^1.8.0", 53 | "winston": "^2.2.0" 54 | }, 55 | "devDependencies": { 56 | "@types/jest": "^24.9.0", 57 | "@types/node": "^12.12.24", 58 | "fast-check": "^1.22.1", 59 | "grammkit": "^0.6.1", 60 | "jest": "^24.9.0", 61 | "nock": "^10.0.6", 62 | "np": "^5.2.1", 63 | "nyc": "^14.1.1", 64 | "parse-comments": "^0.4.3", 65 | "prettier": "^1.19.1", 66 | "rimraf": "^3.0.0", 67 | "ts-jest": "^24.3.0", 68 | "tslint": "^5.20.1", 69 | "typescript": "^3.7.4" 70 | }, 71 | "ava": { 72 | "compileEnhancements": false, 73 | "extensions": [ 74 | "ts" 75 | ], 76 | "require": [ 77 | "ts-node/register" 78 | ] 79 | }, 80 | "files": [ 81 | "dist", 82 | "src", 83 | "bin" 84 | ], 85 | "directories": { 86 | "doc": "docs", 87 | "test": "test" 88 | }, 89 | "repository": { 90 | "type": "git", 91 | "url": "git+https://github.com/Hypercubed/f-flat_node.git" 92 | }, 93 | "keywords": [ 94 | "javascript", 95 | "language", 96 | "flat", 97 | "concatenative", 98 | "stack-oriented" 99 | ], 100 | "bugs": { 101 | "url": "https://github.com/Hypercubed/f-flat_node/issues" 102 | }, 103 | "homepage": "https://github.com/Hypercubed/f-flat_node#readme" 104 | } 105 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true 3 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # F♭ 2 | 3 | F♭ (pronounced F-flat) is a toy language. 4 | 5 | F♭ is a dynamically typed array-oriented concatenative language like Forth, Joy, and [others](http://www.concatenative.org/). F♭ is meant to be used interactively, for example in a CLI REPL, like R or the command shell, or in a stack based calculator. This constraint dictates many of the language features. 6 | 7 | ## Features 8 | 9 | ### Flat functional [concatenative language](http://concatenative.org/wiki/view/Front%20Page) 10 | 11 | ### Stack based 12 | 13 | ### RPN 14 | 15 | ### Dynamic Types 16 | 17 | ### Arbitrary-precision decimal and complex number type. 18 | 19 | ### JSON format is a valid f-flat programs. 20 | 21 | ## Why? 22 | 23 | * Designing a programming language in another language is a great way to learn about programming languages in general and the host language in particular. 24 | 25 | * Concatenative languages, with inherent functional composition, are a great way to explore functional programming and mathematics. Higher order functions (including math functions) are composed of smaller functions. 26 | 27 | * Because 0.1 + 0.2 = 0.3 and sqrt of -1 is not "not a number". 28 | 29 | ## Example 30 | 31 | [![asciicast](https://asciinema.org/a/39282.png)](https://asciinema.org/a/39282) 32 | 33 | ```forth 34 | Welcome to F♭ REPL Interpreter 35 | F♭ Version 0.0.0 (C) 2000-2017 J. Harshbarger 36 | 37 | f♭> 0.1 0.2 + 38 | [ 0.3 ] 39 | 40 | f♭> 0.3 = 41 | [ true ] 42 | 43 | f♭> clr 44 | [] 45 | 46 | f♭> -1 sqrt 47 | [ 0+1i ] 48 | 49 | f♭> 1 atan 4 * 50 | [ 0+1i, 3.1415926535897932385 ] 51 | 52 | f♭> * exp 53 | [ -1-3.7356616720497115803e-20i ] 54 | 55 | f♭> abs 56 | [ 1 ] 57 | 58 | f♭> clr 59 | [] 60 | 61 | f♭> "mersenne?" [ 2 swap ^ 1 - prime? ] ; 62 | [] 63 | 64 | f♭> 10 integers 65 | [ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ] 66 | 67 | f♭> [ mersenne? ] map 68 | [ [ true, true, true, false, true, false, true, false, false, false ] ] 69 | 70 | f♭> clr 71 | [] 72 | 73 | f♭> 1000 ! 74 | [ 4.0238726007709377384e+2567 ] 75 | 76 | f♭> clr 77 | [] 78 | 79 | f♭> i ! 80 | [ 0.49801566811835599106-0.15494982830181068731i ] 81 | 82 | f♭> { first: "Manfred" } 83 | [ { first: 'Manfred' } ] 84 | 85 | f♭> { last: 'von Thun' } 86 | [ { first: 'Manfred' }, { last: 'von Thun' } ] 87 | 88 | f♭> + 89 | [ { first: 'Manfred', last: 'von Thun' } ] 90 | 91 | f♭> clr 92 | [ ] 93 | 94 | f♭> [ 1 2 3 ] 95 | [ [ 1 2 3 ] ] 96 | 97 | f♭> dup 98 | [ [ 1 2 3 ] [ 1 2 3 ] ] 99 | 100 | f♭> [ 2 * ] 101 | [ [ 1 2 3 ] [ 1 2 3 ] [ 2 * ] ] 102 | 103 | f♭> map 104 | [ [ 1 2 3 ] [ 2 4 6 ] ] 105 | 106 | f♭> + 107 | [ [ 1 2 3 2 4 6 ] ] 108 | 109 | f♭> + 110 | [ [ 1 2 3 2 4 6 ] ] 111 | 112 | f♭> clr 113 | [ ] 114 | 115 | f♭> dbl-sqr-cat: [ dup [ 2 * ] map + ] ; 116 | [ ] 117 | 118 | f♭> [ 1 2 3 ] 119 | [ [ 1 2 3 ] ] 120 | 121 | f♭> dbl-sqr-cat 122 | [ [ 1 2 3 2 4 6 ] ] 123 | 124 | f♭> 'dbl-sqr-cat' see 125 | [ '[ dup [ 2 * ] map + ]' ] 126 | 127 | f♭> drop 128 | [ ] 129 | 130 | f♭> dbl-sqr-cat: expand 131 | [ dup,2,*,*,Symbol((),swap,eval,dequote,+ ] 132 | 133 | ``` 134 | 135 | See other examples and guides [here](./docs/). 136 | 137 | ## Project Goals 138 | 139 | * conceptually simple 140 | * interactive first 141 | * minimal hidden state 142 | * easy to type and read 143 | * reads left to right, top to bottom 144 | * whitespace not significant syntax 145 | * no lambdas/parameters 146 | * interactive development 147 | * case insensitive 148 | * flat concatenative language 149 | * name code not values 150 | * multiple return values 151 | * concatenation is composition/pipeline style 152 | * no unnecessary parentheses 153 | * no surprises 154 | * immutable data 155 | * arbitrary-precision decimal and complex numbers 156 | * percent values 157 | * both double and single quotes 158 | * returns error objects (TBD) 159 | * pure functions 160 | * host language interface (TBD) 161 | * session saving (TBD) 162 | * undo/redo 163 | * state is serializable (TBD) 164 | * modules, namespaces, and "closures" 165 | 166 | ## Influences 167 | 168 | * [Many concatenative languages](http://concatenative.org/wiki/view/Front%20Page) (HP RPL, Forth, Joy, Factor, XY) 169 | * Haskell 170 | * JavaScript 171 | 172 | ## License 173 | 174 | MIT 175 | -------------------------------------------------------------------------------- /scripts/gen-spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const fs = require('fs'); 3 | const { join } = require('path'); 4 | const Myna = require('myna-parser'); 5 | 6 | const g = require('../dist/parser/tokenizer').fflatGrammar; 7 | 8 | const m = Myna.Myna; 9 | const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8')); 10 | const version = pkg.version; 11 | const schema = m.astSchemaToString('fflat'); 12 | const grammar = m.grammarToString('fflat').replace(/fflat\./g, ''); 13 | 14 | const grammkit = require('grammkit'); 15 | 16 | const map = new WeakMap(); 17 | 18 | function processRule(expr) { 19 | if (expr.name && map.has(expr)) 20 | return { 21 | type: 'rule_ref', 22 | name: expr.name || expr.toString() 23 | }; 24 | map.set(expr, true); 25 | 26 | switch (expr.type) { 27 | case 'rule': 28 | return { 29 | ...expr, 30 | expression: processRule(expr.expression) 31 | }; 32 | case 'optional': 33 | return { 34 | type: 'optional', 35 | expression: processRule(expr.rules[0]) 36 | }; 37 | case 'seq': 38 | case 'keywordAnyCase': 39 | case 'advanceUntilPast': 40 | return { 41 | type: 'sequence', 42 | elements: expr.rules.map(processRule) 43 | }; 44 | case 'advanceWhileNot': 45 | case 'repeatWhileNot': 46 | return { 47 | type: 'sequence', 48 | elements: [...expr.rules.map(processRule), { type: 'any' }] 49 | }; 50 | case 'choice': 51 | return { 52 | type: 'choice', 53 | alternatives: expr.rules.map(processRule) 54 | }; 55 | case 'text': 56 | case 'err': 57 | return { 58 | type: 'class', 59 | rawText: expr.toString() 60 | }; 61 | case 'zeroOrMore': 62 | return { 63 | type: 'zero_or_more', 64 | expression: processRule(expr.rules[0]) 65 | }; 66 | case 'oneOrMore': 67 | return { 68 | type: 'one_or_more', 69 | expression: processRule(expr.rules[0]) 70 | }; 71 | case 'delimitedList': 72 | case 'advanceIf': 73 | case 'unless': 74 | return processRule(expr.rules[0]); 75 | case 'not': 76 | return { 77 | type: 'simple_not', 78 | expression: processRule(expr.rules[0]) 79 | }; 80 | case 'advance': 81 | return { 82 | type: 'any' 83 | }; 84 | case 'charSet': 85 | case 'charRange': 86 | case 'anyCaseText': 87 | default: 88 | return { 89 | type: 'literal', 90 | value: expr.toString() 91 | }; 92 | } 93 | } 94 | 95 | const diagrams = []; 96 | 97 | Object.keys(g).forEach(k => { 98 | const expr = processRule({ 99 | type: 'rule', 100 | name: k, 101 | expression: g[k] 102 | }); 103 | 104 | diagrams.push('**' + k + '**\n'); 105 | diagrams.push(grammkit.diagram(expr)); 106 | }); 107 | 108 | const QUOTE = '```'; 109 | const fp = join(__dirname, '../docs/spec.md'); 110 | 111 | console.log('Writing', fp); 112 | 113 | fs.writeFileSync(fp,` 114 | 115 | # FFlat Specification ${version} 116 | 117 | ## PEG Grammar 118 | 119 | ${QUOTE} 120 | ${grammar} 121 | ${QUOTE} 122 | 123 | ## Syntax diagrams (WIP) 124 | 125 | ${diagrams.join('\n')} 126 | 127 | 153 | 154 | ` 155 | ); 156 | 157 | process.exit(); 158 | -------------------------------------------------------------------------------- /scripts/generate-docs.js: -------------------------------------------------------------------------------- 1 | const { readFileSync, writeFileSync } = require('fs'); 2 | const { join } = require('path'); 3 | const extract = require('parse-comments'); 4 | 5 | const SOURCE_PATH = '/../src/core/'; 6 | const TARGET_PATH = '/../docs/api/'; 7 | const LINK_PATH = 'https://github.com/Hypercubed/f-flat_node/blob/master/src/core/'; 8 | 9 | const files = [ 10 | 'core.ts', 11 | 'base.ts', 12 | 'vocab.ts', 13 | 'math.ts', 14 | 'node.ts', 15 | 'objects.ts', 16 | 'types.ts', 17 | 'experimental.ts', 18 | 'flags.ts' 19 | ]; 20 | 21 | files.map(file => { 22 | const str = read(file); 23 | const comments = extract(str); 24 | const md = process(file, comments); 25 | write(md, file.replace('.ts', '.md')); 26 | }).join('\n'); 27 | 28 | function process(file, comments) { 29 | const lp = join(LINK_PATH, file); 30 | return comments 31 | .map(c => { 32 | const link = `
[src]
`; 33 | return `${c.comment.content}${link}\n`; 34 | }) 35 | .join('\n'); 36 | } 37 | 38 | function read(fp) { 39 | fp = join(__dirname, SOURCE_PATH, fp); 40 | console.log('Reading', fp); 41 | return readFileSync(fp, 'utf8'); 42 | } 43 | 44 | function write(data, fp) { 45 | fp = join(__dirname, TARGET_PATH, fp); 46 | console.log('Writing', fp); 47 | return writeFileSync(fp, data, 'utf8'); 48 | } 49 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const MAXSTACK = 1e10; 2 | export const MAXRUN = 1e10; 3 | 4 | export const IDLE = 0; 5 | export const DISPATCHING = 1; 6 | export const YIELDING = 2; // aka suspended 7 | export const ERR = 3; 8 | // export const BLOCKED = 4; 9 | 10 | export const IIF = ':'; 11 | export const SEP = '.'; 12 | -------------------------------------------------------------------------------- /src/core/experimental.ts: -------------------------------------------------------------------------------- 1 | import memoize from 'memoizee'; 2 | import { writeFileSync } from 'fs'; 3 | import { signature } from '@hypercubed/dynamo'; 4 | 5 | import { stringifyStrict } from '../utils/json'; 6 | 7 | import { 8 | dynamo, 9 | Word, 10 | Sentence, 11 | Future, 12 | ReturnValues, 13 | StackValue, 14 | Decimal 15 | } from '../types'; 16 | import { StackEnv } from '../engine/env'; 17 | 18 | // class Apply { 19 | // @signature() 20 | // app(a: any[], b: Function) { 21 | // return Reflect.apply(b, null, a); 22 | // } 23 | // } 24 | 25 | /** 26 | * # Internal Experimental Words 27 | */ 28 | export const experimental = { 29 | /** 30 | * ## `stringify` 31 | * 32 | * `a -> str` 33 | */ 34 | stringify(a: any) { 35 | return stringifyStrict(a); 36 | }, 37 | 38 | /** 39 | * ## `spawn` 40 | * 41 | * evalues the quote in a child environment, returns a future 42 | * 43 | * `[A] -> future` 44 | */ 45 | spawn(this: StackEnv, a: Word | Sentence): Future { 46 | return new Future(a, this.createChildPromise(a)); 47 | }, 48 | 49 | /** 50 | * ## `await` 51 | * 52 | * evalues the quote in a child environment, waits for result 53 | * 54 | * `[A] -> [a]` 55 | */ 56 | ['await'](this: StackEnv, a: StackValue): Promise { 57 | // rollup complains on await 58 | if (a instanceof Future) { 59 | return a.promise; 60 | } 61 | return this.createChildPromise(a); 62 | }, 63 | 64 | /** 65 | * ## `suspend` 66 | * 67 | * stops execution, push queue to stack, loses other state 68 | * 69 | * `->` 70 | * 71 | * ``` 72 | * f♭> [ 1 2 * suspend 3 4 * ] in 73 | * [ [ 2 3 4 * ] ] 74 | * ``` 75 | */ 76 | suspend(this: StackEnv): ReturnValues { 77 | return new ReturnValues(this.queue.splice(0) as StackValue[]); // rename stop? 78 | }, 79 | 80 | /** 81 | * ## `all` 82 | * 83 | * executes each element in a child environment 84 | * 85 | * `[A] -> [a]` 86 | */ 87 | all(this: StackEnv, arr: StackValue[]): Promise { 88 | return Promise.all(arr.map(a => this.createChildPromise(a))); 89 | }, 90 | 91 | /** 92 | * ## `race` 93 | * 94 | * executes each element in a child environment, returns first to finish 95 | * 96 | * [A] -> [b]` 97 | */ 98 | race(this: StackEnv, arr: StackValue[]): Promise { 99 | return Promise.race(arr.map(a => this.createChildPromise(a))); 100 | }, 101 | 102 | /** 103 | * ## `js-raw` 104 | * 105 | * evalues a string as raw javascript 106 | * 107 | * `str -> a*` 108 | */ 109 | 'js-raw'(this: StackEnv, s: string): any { 110 | return new Function(`return ${s}`).call(this); 111 | }, 112 | 113 | 'create-object'(obj: any): any { 114 | return Object.create(obj); 115 | }, 116 | 117 | 'case-of?'(obj: any, proto: any): any { 118 | while (true) { 119 | if (obj === null) return false; 120 | if (proto === obj) return true; 121 | obj = Object.getPrototypeOf(obj); 122 | } 123 | }, 124 | 125 | put(obj: any, key: any, value: any): any { 126 | const proto = Object.getPrototypeOf(obj); 127 | const newObj = Object.create(proto); 128 | return Object.assign(newObj, { [key]: value }); 129 | }, 130 | 131 | digits(n: Decimal) { 132 | return (n as any).digits(); 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /src/core/flags.ts: -------------------------------------------------------------------------------- 1 | import { Decimal } from '../types'; 2 | import { log, FFlatError } from '../utils'; 3 | import { StackEnv } from '../engine/env'; 4 | 5 | /** 6 | * # Internal Flags 7 | */ 8 | export const flags = { 9 | /** 10 | * ## `set-system-property` 11 | * 12 | * sets a system level flag 13 | * - flags: `'auto-undo'`, `'log-level'`, `'decimal-precision'` 14 | * 15 | * `str a ->` 16 | * 17 | 18 | * 19 | * ``` 20 | * f♭> 'log-level' 'trace' set-system-property 21 | * [ ] 22 | * ``` 23 | */ 24 | 'set-system-property'(this: StackEnv, p: string, v: any): void { 25 | switch (p) { 26 | case 'auto-undo': 27 | this.autoundo = Boolean(v); 28 | break; 29 | case 'log-level': 30 | log.level = String(v); 31 | break; 32 | case 'decimal-precision': 33 | Decimal.config({ precision: +v }); 34 | break; 35 | default: 36 | throw new FFlatError(`'set-system-property' value is not a valid flag: "${p}"`, this); 37 | } 38 | }, 39 | 40 | /** 41 | * ## `get-system-property` 42 | * 43 | * gets a system level flag 44 | * - flags: `'auto-undo'`, `'log-level'`, `'decimal-precision'` 45 | * 46 | * `str -> a` 47 | * 48 | * ``` 49 | * f♭> 'log-level' get-system-property 50 | * [ 'warn' ] 51 | * ``` 52 | */ 53 | 'get-system-property'(this: StackEnv, p: string) { 54 | switch (p) { 55 | case 'auto-undo': 56 | return this.autoundo; 57 | case 'log-level': 58 | return (log.level = log.level); 59 | case 'decimal-precision': 60 | return Decimal.precision; 61 | default: 62 | throw new FFlatError(`'get-system-property' value is not a valid flag: "${p}"`, this); 63 | } 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/core/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vocab'; 2 | export * from './base'; 3 | export * from './core'; 4 | export * from './experimental'; 5 | export * from './math'; 6 | export * from './node'; 7 | export * from './objects'; 8 | export * from './types'; 9 | export * from './flags'; 10 | -------------------------------------------------------------------------------- /src/core/node.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes, createHash } from 'crypto'; 2 | import { readFileSync, existsSync } from 'fs'; 3 | import { arch } from 'os'; 4 | import { dirname, join } from 'path'; 5 | import * as fetch from 'isomorphic-fetch'; 6 | import * as normalizeUrl from 'normalize-url'; 7 | import { URL } from 'url'; 8 | import { signature } from '@hypercubed/dynamo'; 9 | 10 | import { bar } from '../utils'; 11 | import { dynamo } from '../types'; 12 | 13 | // const stdin = process.stdin; 14 | const stdout = process.stdout; 15 | 16 | function resolve(input: string, base = getURLStringForCwd()) { 17 | return new URL(input, base); 18 | } 19 | 20 | function getURLStringForCwd(): string | undefined { 21 | try { 22 | return getURLFromFilePath(`${process.cwd()}/`).href; 23 | } catch (e) { 24 | e.stack; 25 | // If the current working directory no longer exists. 26 | if (e.code === 'ENOENT') { 27 | return undefined; 28 | } 29 | throw e; 30 | } 31 | } 32 | 33 | // We percent-encode % character when converting from file path to URL, 34 | // as this is the only character that won't be percent encoded by 35 | // default URL percent encoding when pathname is set. 36 | const percentRegEx = /%/g; 37 | function getURLFromFilePath(filepath: string) { 38 | const tmp = new URL('file://'); 39 | if (filepath.includes('%')) filepath = filepath.replace(percentRegEx, '%25'); 40 | tmp.pathname = filepath; 41 | return tmp; 42 | } 43 | 44 | class Resolve { 45 | @signature() 46 | string(name: string): string { 47 | return normalizeUrl(resolve(name).href); 48 | } 49 | 50 | @signature() 51 | array([name, base]: string[]): string { 52 | return normalizeUrl(resolve(name, base).href); 53 | } 54 | } 55 | 56 | /** 57 | * # Internal Words for Node Environment 58 | */ 59 | export const node = { 60 | /** 61 | * ## `args` 62 | * 63 | * `-> [str*]` 64 | * 65 | * Returns an array containing of command line arguments passed when the process was launched 66 | */ 67 | args: () => process.argv, 68 | 69 | /** 70 | * ## `println` 71 | * 72 | * `a ->` 73 | * 74 | * Prints the value followed by (newline) 75 | * 76 | */ 77 | println(a: any) { 78 | bar.interrupt(String(a)); 79 | }, 80 | 81 | /** 82 | * ## `print` 83 | * 84 | * `a ->` 85 | * 86 | * Prints the value 87 | * 88 | */ 89 | print(a: any) { 90 | try { 91 | stdout.clearLine(); 92 | stdout.cursorTo(0); 93 | } catch (e) {} 94 | stdout.write(String(a)); 95 | }, 96 | 97 | table(a: any) { 98 | console.table(a); 99 | bar.interrupt(''); 100 | }, 101 | 102 | /** 103 | * ## `exit` 104 | * 105 | * `->` 106 | * 107 | * terminate the process synchronously with an a status code 108 | * 109 | */ 110 | exit(a: any) { 111 | process.exit(Number(a)); // exit: `process.exit` js-raw ; 112 | }, 113 | 114 | /** 115 | * ## `rand-u32` 116 | * 117 | * `-> x` 118 | * 119 | * Generates cryptographically strong pseudo-random with a givennumber of bytes to generate 120 | * 121 | */ 122 | 'rand-u32': () => randomBytes(4).readUInt32BE(0), 123 | 124 | /** 125 | * ## `dirname` 126 | * 127 | * `str₁ -> str₂` 128 | * 129 | * returns the directory name of a path, similar to the Unix dirname command. 130 | * See https://nodejs.org/api/path.html#path_path_dirname_path 131 | * 132 | */ 133 | dirname: (name: string) => dirname(name), 134 | 135 | /** 136 | * ## `path-join` 137 | * 138 | * `[ str* ] -> str` 139 | * 140 | * joins all given path segments together using the platform specific separator as a delimiter 141 | * See https://nodejs.org/api/path.html#path_path_join_paths 142 | * 143 | */ 144 | 'path-join': (args: string[]) => join(...args), 145 | 146 | /** 147 | * ## `resolve` 148 | * 149 | * `str₁ -> str₂` 150 | * 151 | * returns a URL href releative to the current base 152 | * 153 | */ 154 | resolve: dynamo.function(Resolve), 155 | 156 | /** 157 | * ## `exists` 158 | * 159 | * `str -> bool` 160 | * 161 | * Returns true if the file exists, false otherwise. 162 | * 163 | */ 164 | exists: (name: string) => existsSync(name), 165 | 166 | /** 167 | * ## `read` 168 | * 169 | * `str₁ -> str₂` 170 | * 171 | * Pushes the content of a file as a utf8 string 172 | * 173 | */ 174 | read(name: string): string { 175 | const url = resolve(name); 176 | if (url.protocol === 'file:') { 177 | return readFileSync(url, 'utf8'); 178 | } 179 | return fetch(url.href).then((res: any) => res.text()); 180 | }, 181 | 182 | /** 183 | * ## `cwd` 184 | * 185 | * `-> str` 186 | * 187 | * Pushes the current working directory 188 | * 189 | */ 190 | cwd: (): string => getURLStringForCwd(), 191 | 192 | md5(x: string) { 193 | return createHash('md5') 194 | .update(x) 195 | .digest('hex'); 196 | }, 197 | 198 | sha1(x: string) { 199 | return createHash('sha1') 200 | .update(x) 201 | .digest('base64'); 202 | }, 203 | 204 | /** 205 | * ## `get-env` 206 | * 207 | * `str₁ -> str₂` 208 | * 209 | * Gets a environment variable 210 | * 211 | */ 212 | 'get-env'(x: string) { 213 | if (x in process.env) { 214 | return process.env[x]; 215 | } 216 | return null; 217 | }, 218 | 219 | os: arch 220 | }; 221 | -------------------------------------------------------------------------------- /src/core/objects.ts: -------------------------------------------------------------------------------- 1 | import { signature, Any } from '@hypercubed/dynamo'; 2 | 3 | import { dynamo } from '../types'; 4 | import { toObject } from '../utils'; 5 | 6 | // TODO: use dynamo to get the guard 7 | class IsObject { 8 | @signature() 9 | map(a: object) { 10 | return true; 11 | } 12 | @signature() 13 | any(a: Any) { 14 | return false; 15 | } 16 | } 17 | 18 | /** 19 | * # Internal Object Words 20 | */ 21 | export const objects = { 22 | /** 23 | * ## `object` 24 | * 25 | * `[ a: b ... ] -> { a: b ... }` 26 | * 27 | * convert a quotation to an object 28 | * 29 | */ 30 | object: toObject, 31 | 32 | /** 33 | * ## `object?` 34 | * 35 | * `a -> bool` 36 | * 37 | * retruns true of the item is an object 38 | */ 39 | 'object?': dynamo.function(IsObject), 40 | 41 | /** 42 | * ## `has?` 43 | * 44 | * `{A} a: -> bool` 45 | * 46 | * returns true if an item contains a key 47 | * 48 | */ 49 | 'has?': (a: {}, b: any) => b in a, // object by keys, array by values (move to indexof?) 50 | 51 | /** 52 | * ## `keys` 53 | * 54 | * `{A} -> [str*]` 55 | * 56 | * returns an array of keys 57 | * 58 | */ 59 | keys: (o: {}) => Object.keys(o), 60 | 61 | /** 62 | * ## `vals` 63 | * 64 | * `{A} -> [b*]` 65 | * 66 | * returns an array of values 67 | * 68 | */ 69 | vals: (o: {}) => Object.values(o) 70 | }; 71 | -------------------------------------------------------------------------------- /src/ff-lib/boot.ff: -------------------------------------------------------------------------------- 1 | /* 2 | * # Bootstrap F-Flat 3 | */ 4 | 5 | 'loader.ff' 6 | resolve 7 | read eval 8 | 9 | 'core.ff' require 10 | 'shuffle.ff' require 11 | 'types.ff' require 12 | 13 | math: 'math.ff' import ; 14 | 15 | 'math.ff' require 16 | 'strings.ff' require 17 | 'datetime.ff' require 18 | 'lambdas.ff' require 19 | 'switch-patterns.ff' require 20 | 21 | math use 22 | 23 | 'NODE_ENV' get-env 'test' != 24 | [ 'usr.ff' if-exists-load ] when 25 | -------------------------------------------------------------------------------- /src/ff-lib/combinators.ff: -------------------------------------------------------------------------------- 1 | /** 2 | * # Classic combinators 3 | * (http://tunes.org/~iepos/joy.html) 4 | */ 5 | 6 | zap: [ drop ] ; // [A] zap == 7 | i: [ eval ] ; // [A] i == A 8 | unit: [ [] >> ] ; // [A] unit == [[A]] 9 | rep: [ dup << i i ] ; // [A] rep == A A 10 | m: [ dup eval ] ; // [A] m == [A] A 11 | run: [ dup << i ] ; // [A] run == A [A] 12 | // [A] dup == [A] [A] 13 | k: [ [zap] dip i ] ; // [B] [A] k == A 14 | z: [ zap i ] // [B] [A] z == B 15 | nip: [ swap zap ] ; // [B] [A] nip == [A] 16 | sap: [ dip i ] ; // [B] [A] sap == A B 17 | t: [ swap eval ] ; // [B] [A] t == [A] B 18 | dip: [ swap << i ] ; // [B] [A] dip == A [B] 19 | cat: [ + ] ; // [B] [A] cat == [B A] 20 | swat: [ swap + ] ; // [B] [A] swat == [A B] 21 | // [B] [A] swap == [A] [B] 22 | cons: [ >> ] ; // [B] [A] cons == [[B] A] 23 | take: [ swap << ] // [B] [A] take == [A [B]] 24 | tack: [ << ] ; // [B] [A] tack == [B [A]] 25 | sip: [ [dup] dip swap slip ] ; // [B] [A] sip == [B] A [B] 26 | w: [ [dup] dip i ] ; // [B] [A] w == [B] [B] A 27 | // [B] [A] peek == [B] [A] [B] 28 | // [B] [A] cake == [[B] A] [A [B]] 29 | // [C] [B] [A] poke == [A] [B] 30 | b: [ [ >> ] dip i ] ; // [C] [B] [A] b == [[C] B] A 31 | c: [ [swap] dip i ] ; // [C] [B] [A] c == [B] [C] A 32 | dig: [ [swap] dip swap ] ; // [C] [B] [A] dig == [B] [A] [C] 33 | bury: [ swap [swap] dip ] ; // [C] [B] [A] bury == [A] [C] [B] 34 | flip: [ [swap] dip bury ] ; // [C] [B] [A] flip == [A] [B] [C] 35 | s: [ [ over >> swap ] dip i ] ; // [C] [B] [A] s == [[C] B] [C] A 36 | // [D] [C] [B] [A] s' == [[D] C] A [D] B 37 | // [D] [C] [B] [A] j == [[C] [D] A] [B] A 38 | // [E] [D] [C] [B] [A] j' == [[D] A [E] B] [C] B 39 | 40 | x: [ dup eval ] ; 41 | ?: [ dup [ [ '_?' eval ] >> ] dip ] ; 42 | y: [ [ dup >> ] swap + dup >> eval ] ; 43 | id: [] ; 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/ff-lib/core.ff: -------------------------------------------------------------------------------- 1 | /** 2 | * # Core defined words 3 | */ 4 | 5 | // Comparisons 6 | >: [ <=> 1 = ] ; 7 | <: [ <=> -1 = ] ; 8 | >=: [ < ~ ] ; 9 | <=: [ > ~ ] ; 10 | !=: [ = ~ ] ; 11 | 12 | 'types.ff' require 13 | 'testing.ff' require 14 | 'shuffle.ff' require 15 | 16 | // keep/check 17 | keep: [ sip ] ; // B [A*] -> B a* B (aka sip) 18 | check: [ dupd eval ] ; // B [A*] -> B B a* (aka w) 19 | 20 | // 21 | stackd: [ q< stack q> ] ; 22 | get: [ q< @ dup null? swap q> swap choose ] ; 23 | zipwith: [ zipinto in ] ; 24 | return: [ stack send ] ; 25 | yield: [ return suspend ] ; 26 | delay: [ [ sleep ] >> slip eval ] ; 27 | 28 | // No matter how many parameters the quotation consumes from the stack when nullary executes it, they are all restored and the final value calculated by the execution of the quotation is pushed on top of that 29 | nullary: [ [ stack dup ] dip + in -1 @ [ unstack ] dip ] ; 30 | 31 | // control 32 | _do: [ dup dipd ] ; 33 | _check: [ q< q< truthy? q> q> ] ; 34 | branch: [ _check choose eval ] ; 35 | when: [ [ ] branch ] ; 36 | unless: [ [ ] swap branch ] ; 37 | ifte: [ slipd branch ] ; 38 | if: [ slip when ] ; 39 | loop: [ [ eval ] keep [ loop ] >> when ] ; 40 | while: [ swap _do + [ loop ] >> when ] ; 41 | runn: [ [ 1 - dup -1 > ] [ [ run ] dip ] while drop ] ; 42 | times: [ runn drop ] ; 43 | dupn: [ [ dup ] swap times ] ; 44 | 45 | // aggregates 46 | slice: [ [ % ] dip \ ] ; 47 | pop: [ -1 \ ] ; 48 | shift: [ 1 % ] ; 49 | car: [ 0 @ ] ; 50 | cdr: [ 1 % ] ; 51 | first: [ 0 @ ] ; 52 | last: [ -1 @ ] ; 53 | rest: [ 1 % ] ; 54 | head: [ \ ] ; 55 | tail: [ % ] ; 56 | startswith?: [ dup ln [ head ] >> dip = ] ; 57 | endswith?: [ dup ln [ tail ] >> dip = ] ; 58 | contains?: [ indexof 1 > ] ; 59 | 60 | [ [ 1 2 3 ] 3 contains? assert ] 'contains' test 61 | [ [ 1 2 3 ] 4 contains? assert_false ] 'contains' test 62 | 63 | // lists 64 | empty?: [ ln 0 = ] ; 65 | unit: [ [ ] >> ] ; 66 | cons: [ >> ] ; // aka unshift 67 | pair: [ unit >> ] ; 68 | uncons: [ [ first ] [ rest ] bi ] ; // aka shift 69 | 70 | // folds 71 | each: [ * eval ] ; 72 | reverse-each: [ / eval ] ; 73 | foldl: [ swapd each ] ; 74 | foldr: [ [ swap ] swap + swapd reverse-each ] ; 75 | foldl1: [ [ uncons ] dip each ] ; 76 | foldr1: [ [ uncons ] dip reverse-each ] ; 77 | fold: [ foldl1 ] ; 78 | guard: [ [ keep ] >> dip branch ] ; 79 | orelse: [ [ null? ~ ] guard ] ; 80 | keepif: [ over unit if ] ; 81 | until: [ [ [ ~ ] + ] dip while ] ; 82 | forever: [ [ eval ] keep [ forever ] >> eval ] ; 83 | either?: [ bi + ] ; 84 | 85 | appl: [ >> in ] ; 86 | y: [ [ dup >> ] swap + dup >> ] ; 87 | 88 | // functional 89 | map: [ * in ] ; 90 | 91 | [ [ 1 2 3 ] [ 2 * ] map [ 2 4 6 ] assert_equal ] 'map' test 92 | [ [ 1 2 3 4 ] [ 2 % 0 = ] map [ false true false true ] assert_equal ] 'map' test 93 | 94 | chain: [ over nothing? ~ swap when ] ; 95 | fmap: [ eval ] ; 96 | map-reduce: [ [ map ] dip fold ] ; 97 | reduce: [ foldl ] ; 98 | filter: [ [ dup empty swap ] dip [ keepif ] >> map swap * ] ; 99 | 100 | [ [ 1 2 3 4 ] [ 2 % 0 = ] filter [ 2 4 ] assert_equal ] 'filter' test 101 | [ [ 1 2 3 4 ] [ 2 > ] filter [ 3 4 ] assert_equal ] 'filter' test 102 | 103 | // aggregate reduction 104 | reverse: [ dup ln 1 > [ uncons [ reverse ] eval swap + ] when ] ; 105 | 106 | [ 'abcd' reverse 'dcba' assert_equal ] 'reverse' test 107 | 108 | /** 109 | * ## `nop` 110 | * no op 111 | * 112 | * ( -> ) 113 | */ 114 | nop: [] ; 115 | 116 | pluck: [ [ true ] * object swap * ] ; 117 | 118 | [ { x: 1, y: 2, z: 3 } [ x: z: ] pluck { x: 1, z: 3} assert_equal ] 'pluck' test 119 | -------------------------------------------------------------------------------- /src/ff-lib/datetime.ff: -------------------------------------------------------------------------------- 1 | 'shuffle.ff' require 2 | 3 | /** 4 | * # Dates and times lib 5 | */ 6 | 7 | // timing 8 | time: [ clock swap 100 times drop clock swap - 100 / ] ; 9 | _timefn: [ clock swap [ dup in drop ] 1000 times drop clock - ] ; 10 | timefn: [ _timefn () _timefn - -1000000 / inv ' ops/sec' + ] ; 11 | 12 | // dates 13 | zeller-congruence: [ 14 | [ 15 | dup 2 <= 16 | [ [ 1 - ] [ 12 + ] bi* ] when 17 | [ dup [ 4 \ + ] [ 100 \ - ] [ 400 \ + ] tri ] dip 18 | [ 1 + 3 * 5 \ + ] 19 | keep 2 * + 20 | ] 21 | dip 1 + + 7 % 22 | ] ; 23 | 24 | day: [ string 0 3 slice ] ; 25 | day-of-week: [ day [ 'Sun' 'Mon' 'Tue' 'Wed' 'Thu' 'Fri' 'Sat' ] swap indexof ] ; 26 | sunday?: [ day 'Sun' = ] ; 27 | monday?: [ day 'Mon' = ] ; 28 | tuesday?: [ day 'Tue' = ] ; 29 | wednesday?: [ day 'Wed' = ] ; 30 | thursday?: [ day 'Thu' = ] ; 31 | friday?: [ day 'Fri' = ] ; 32 | saturday?: [ day 'Sat' = ] ; -------------------------------------------------------------------------------- /src/ff-lib/lambdas.ff: -------------------------------------------------------------------------------- 1 | 'testing.ff' require 2 | 3 | _stackn: [ [ ] swap [ >> ] swap times ] ; 4 | 5 | =>: [ dup ln swap [ _stackn ] dip swap zip object ] ; 6 | lambda: [ [ swap rewrite eval ] + ] ; 7 | 8 | // f x y z = y2 + x2 − |y| (The Dark Side, http://evincarofautumn.blogspot.com/2012/02/why-concatenative-programming-matters.html) 9 | f: [ 10 | ( x: y: z: ) => [ .y 2 ^ .x 2 ^ + .y abs - ] 11 | ] lambda ; 12 | 13 | [ 14 | 1 2 3 f 3 assert_equal 15 | 5 8 13 f 81 assert_equal 16 | ] 'the dark side' test -------------------------------------------------------------------------------- /src/ff-lib/loader.ff: -------------------------------------------------------------------------------- 1 | /** 2 | * # Module loader 3 | */ 4 | 5 | /** 6 | * ## `include` 7 | * Reads a file onto the stack as a string and evaluates the content 8 | */ 9 | include: [ resolve read eval ] ; 10 | 11 | module: [ [vocab ] + in -1 @ ] ; 12 | 13 | __module_hash: [ hex-hash '%top._' swap + ] ; 14 | __load-module: [ [ dup [ ] >> '__filename' swap ; read eval vocab ] >> in -1 @ ] ; 15 | __load-and-sto-module: [ dup __load-module swap __module_hash swap ; ] ; 16 | 17 | imported?: [ resolve __module_hash defined? ] ; 18 | 19 | /** 20 | * ## `import` 21 | * Loads and creates a module from a file 22 | */ 23 | import: [ 24 | resolve 25 | dup __module_hash defined? 26 | [ __module_hash eval ] 27 | [ dup __load-and-sto-module __module_hash eval ] 28 | choose eval 29 | ] ; 30 | 31 | /** 32 | * ## `require` 33 | * Imports and uses a module from a file 34 | */ 35 | require: [ 36 | import 37 | use 38 | ] ; 39 | 40 | /** 41 | * ## `if-exists-load` 42 | * Loads a file if it exists 43 | */ 44 | if-exists-load: [ dup exists swap [ read eval ] >> [ ] choose eval ] ; 45 | -------------------------------------------------------------------------------- /src/ff-lib/math-sym.ff: -------------------------------------------------------------------------------- 1 | /** 2 | * # Mathematics symbols 3 | */ 4 | 5 | 'shuffle.ff' require 6 | 'math.ff' require 7 | 8 | ×: [*]; 9 | ∙: [*]; 10 | ÷: [/]; 11 | –: [/]; 12 | √: [sqrt]; 13 | ≠: [= ~]; 14 | ≤: [<=]; 15 | ≥: [>=]; 16 | // ≡: [is]; 17 | ⌊⌋: [floor]; 18 | ⌈⌉: [ceil]; 19 | ⌊⌉: [round]; 20 | ||: [abs]; 21 | ∆: [[ 2 ^ swap ] dip 4 * *]; 22 | ∑: [sum]; 23 | ∏: [product]; 24 | ∩: [*]; 25 | ∪: [+]; 26 | ∨: [+]; 27 | // |: [+]; 28 | ⊕: [-]; 29 | ⊻: [-]; 30 | ¬: [~]; 31 | ∞: (infinity); 32 | δ: [zero? [ ∞ ] [ 0 ] choose]; 33 | ±: [+-]; 34 | ∓: [-+]; 35 | ∅: (null); 36 | -------------------------------------------------------------------------------- /src/ff-lib/node.ff: -------------------------------------------------------------------------------- 1 | /** 2 | * # Node specific defined words 3 | */ 4 | 5 | /** 6 | * ## `env` 7 | * 8 | * returns an object containing the user environment 9 | * See https://nodejs.org/api/process.html#process_process_env 10 | * 11 | */ 12 | env: '() => process.env' js-raw ; 13 | 14 | /** 15 | * ## `os` 16 | * 17 | * returns a string identifying the operating system platform on which the Node.js process is running 18 | * see https://nodejs.org/api/process.html#process_process_platform 19 | */ 20 | os: '() => process.platform' js-raw ; 21 | -------------------------------------------------------------------------------- /src/ff-lib/shuffle.ff: -------------------------------------------------------------------------------- 1 | /** 2 | * # Mathematics lib 3 | * Many words derived from (Joy)[http://tunes.org/~iepos/joy.html] and Factor (http://factorcode.org/). 4 | */ 5 | 6 | 'testing.ff' require 7 | 8 | // core shuffle words 9 | slip: [ << eval ] ; // [B*] a -> b* a 10 | run: [ dup slip ] ; // [A*] -> a* [a*] 11 | dip: [ swap slip ] ; // b [A*] -> a* b 12 | 13 | [ [ 'b' ] 'A' slip stack [ 'b' 'A' ] assert_equal ] 'slip' test 14 | [ ['a'] run stack [ 'a' ['a'] ] assert_equal ] 'run' test 15 | [ 'B' ['a'] dip stack [ 'a' 'B' ] assert_equal ] 'dip' test 16 | 17 | // diped 18 | dupd: [ q< dup q> ] ; // b a -> b b a 19 | over: [ dupd swap ] ; // b a -> b a b (aka peek) 20 | sip: [ over slip ] ; // b [A*] -> b a* b (aka keep) 21 | dipd: [ q< dip q> ] ; // d c [B*] a -> d b* c a 22 | sipd: [ q< sip q> ] ; // c b [A*] -> c a* c b 23 | slipd: [ q< slip q> ] ; // [C*] b a -> c* b a 24 | swapd: [ q< swap q> ] ; // c b a -> b c a 25 | dive: [ swap dipd ] ; // [C*] [B*] [A*] -> a* [C*] [B*] (aka dip2) 26 | dup2: [ over over ] ; // b a -> b a b a 27 | sap: [ dip eval ] ; // [B*] [A*] -> a* b* 28 | tuck: [ swap over ] ; // b a -> a b a 29 | 30 | [ [ 'b' ] [ 'a' ] sap stack [ 'a' 'b' ] assert_equal ] 'sap' test 31 | [ [ dup 1 - ] (5) sap stack [ 5 4 ] assert_equal ] 'sap' test 32 | [ +:(4, 5) sap 9 assert_equal ] 'sap' test 33 | [ +:(*:(5,6) sap *:(5,2) sap) sap 40 assert_equal ] 'sap' test 34 | 35 | drop2: [ drop drop ] ; // b a -> (2drop in forth) 36 | 37 | m: [ dup eval ] ; // [A*] -> [A*] a* 38 | 39 | keep2: [ q< dup2 q> dive ] ; // [C*] [B*] [A*] -> [C*] [B*] a* [C*] [B*] (aka 2keep) 40 | 41 | nip: [ swap drop ] ; // b a -> a 42 | dropd: [ q< drop q> ] ; // b a -> a (aka nip) 43 | 44 | overd: [ q< over q> ] ; // c b a -> c b c a 45 | pick: [ overd swap ] ; // c b a -> c b a c 46 | dup3: [ pick pick pick ] ; // c b a -> c b a c b a 47 | dig: [ swapd swap ] ; // c b a -> b a c (rot in forth) 48 | rolldown: [ swapd swap ] ; // c b a -> b a c (aka dig) 49 | bury: [ swap swapd ] ; // c b a -> a c b 50 | flip: [ swapd bury ] ; // c b a -> a b c 51 | rot: [ bury swap ] ; // c b a -> a b c 52 | 53 | dip2: [ swap dipd ] ; // [C*] [B*] [A*] -> a* [C*] [B*] 54 | 55 | bi: [ sipd eval ] ; // c [B*] [A*] -> c b* c a* 56 | 57 | [ 5 [ 2 * ] [ 2 + ] bi stack [ 10 7 ] assert_equal ] 'bi' test 58 | 59 | bi2: [ q< keep2 q> eval ] ; // d c [B*] [A*] -> d c b* d c a* 60 | bi*: [ dipd eval ] ; // d c [B*] [A*] -> d b* c a* 61 | bi@: [ dup bi* ] ; // c b [A*] -> c a* b a* 62 | tri: [ q< sipd sip q> eval ] ; // d [C*] [B*] [A*] -> d c* d b* d a* 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/ff-lib/sort.ff: -------------------------------------------------------------------------------- 1 | 'testing.ff' require 2 | 'shuffle.ff' require 3 | 4 | /** 5 | * quicksort 6 | * Recursive quick sort implementation 7 | */ 8 | qsort: [ 9 | dup ln 1 > // when length > 1 10 | [ 11 | uncons // split into first [ ...rest ] 12 | over dup // shuffle 13 | [ [ <= ] >> filter qsort ] >> // filter values less than or equal to pivot 14 | swap [ [ > ] >> filter qsort ] >> // filter values greater than the pivot 15 | bi 16 | [ swap + ] dip + // merge results 17 | ] when 18 | ] ; 19 | 20 | [ 21 | [ 3 2 1 ] qsort [ 1 2 3 ] assert_equal 22 | [ -3 2 1 ] qsort [ -3 1 2 ] assert_equal 23 | [ -3 2 -1 ] qsort [ -3 -1 2 ] assert_equal 24 | [ 10 2 5 3 1 6 7 4 2 3 4 8 9 ] qsort [ 1 2 2 3 3 4 4 5 6 7 8 9 10 ] assert_equal 25 | "the quick brown fox jumps over the lazy dog" qsort ' abcdeeefghhijklmnoooopqrrsttuuvwxyz' assert_equal 26 | ] 'qsort' test -------------------------------------------------------------------------------- /src/ff-lib/strings.ff: -------------------------------------------------------------------------------- 1 | 'shuffle.ff' require 2 | 3 | // strings 4 | between?: [ swap dupd < bury < * ] ; 5 | lcase-char?: [ atoi [ 96 123 ] dip between? ] ; 6 | ucase-char?: [ atoi [ 64 91 ] dip between? ] ; 7 | lcase-char: [ dup ucase-char? [ atoi 32 + itoa ] when ] ; 8 | ucase-char: [ dup lcase-char? [ atoi 32 - itoa ] when ] ; 9 | char-map: [ map sum ] ; 10 | ucase: [ [ ucase-char ] char-map ] ; 11 | lcase: [ [ lcase-char ] char-map ] ; 12 | 13 | // rot12 14 | _rot13-char*: [ [ atoi ] dip [ - 13 + 26 % ] [ + ] bi itoa ] ; 15 | rot13-char: [ dup ucase-char? 64 97 choose _rot13-char* ] ; 16 | rot13: [ [ rot13-char ] char-map ] ; 17 | 18 | // inquisitive 19 | digit-char?: [ atoi [ 47 58 ] dip between? ] ; 20 | char?: [ [ lcase-char? ] [ ucase-char? ] either? ] ; 21 | alphanumeric?: [ [ char? ] [ digit-char? ] either? ] ; 22 | palindrome?: [ [ alphanumeric? ] filter lcase dup reverse = ] ; 23 | 24 | // regexp 25 | 26 | /** 27 | * ## `replace` 28 | * 29 | * {string}, {regexp | string}, {string} -> {string} 30 | */ 31 | replace: [ [ / ] dip * ] ; 32 | -------------------------------------------------------------------------------- /src/ff-lib/switch-patterns.ff: -------------------------------------------------------------------------------- 1 | 'testing.ff' require 2 | 'types.ff' require 3 | 4 | case: [ nullary truthy? ] ; 5 | p-case: [ [ =~ ] >> case ] ; 6 | 7 | ->: [ case ] ; 8 | ~>: [ p-case ] ; 9 | 10 | /** 11 | * Calls the quotation in the first quotation whose first values yields a truthy value. 12 | */ 13 | switch: [ 14 | dup empty? 15 | [ 16 | uncons slip // pop and evaluate first item in list 17 | [ switch ] >> // switch rest 18 | branch 19 | ] unless 20 | ] ; 21 | 22 | [ 23 | 0 24 | [ 25 | [ dup 0 = [drop 'no apples']] 26 | [ dup 1 = [drop 'one apple']] 27 | [ true [string ' apples' +]] 28 | ] switch 29 | 'no apples' assert_equal 30 | ] 'switch' test 31 | 32 | [ 33 | 1 34 | [ 35 | [ [0 = ] case [drop 'no apples']] 36 | [ [1 = ] case [drop 'one apple']] 37 | [ true [string ' apples' +]] 38 | ] switch 39 | 'one apple' assert_equal 40 | ] 'switch case' test 41 | 42 | [ 43 | 3 44 | [ 45 | [ 0 p-case [drop 'no apples']] 46 | [ 1 p-case [drop 'one apple']] 47 | [ _ p-case [string ' apples' +]] 48 | ] switch 49 | '3 apples' assert_equal 50 | ] 'switch p-case' test -------------------------------------------------------------------------------- /src/ff-lib/testing.ff: -------------------------------------------------------------------------------- 1 | throws?: [ [ false ] + [ true ] in-catch -1 @ ] ; 2 | 3 | dup2: [ q< dup q> swap q< dup q> swap ] ; 4 | slip: [ q< eval q> ] ; 5 | dip: [ swap slip ] ; 6 | unless: [ q< boolean q> [ ] swap choose eval ] ; 7 | <=: [ > ~ ] ; 8 | 9 | assert: [ 10 | dup boolean 11 | [ drop ] 12 | [ 13 | 'Assertion error: ' swap string << dup println 14 | throw 15 | ] 16 | choose eval 17 | ] ; 18 | 19 | assert_false: [ 20 | dup false = 21 | [ drop ] 22 | [ 23 | 'Assertion error: ' swap string << '!= false' << dup println 24 | throw 25 | ] 26 | choose eval 27 | ] ; 28 | 29 | assert_equal: [ 30 | dup2 = 31 | [ drop drop ] 32 | [ 33 | 'Assertion error: ' swap string << ' != ' << swap string << dup println 34 | throw 35 | ] 36 | choose eval 37 | ] ; 38 | 39 | assert_zero: [ 40 | dup 5e-20 <= 41 | [ drop ] 42 | [ 43 | 'Assertion error: ' swap string << ' !~ 0' << dup println 44 | throw 45 | ] 46 | choose eval 47 | ] ; 48 | 49 | assert_length: [ 50 | dup2 [ ln ] dip = 51 | [ drop drop ] 52 | [ 53 | swap 54 | 'Assertion error: length of ' swap string << ' != ' << swap string << dup println 55 | throw 56 | ] 57 | choose eval 58 | ] ; 59 | 60 | red: [ 61 | "\u001b[31m" 62 | swap 63 | "\u001b[0m" 64 | + + 65 | ] ; 66 | 67 | test: [ 68 | ' ✘ ' red swap + ' failed' + [ println ] >> 69 | [ throws? ] dip 70 | [ ] 71 | choose eval 72 | ] ; 73 | 74 | // [ false assert ] 'testing assert' test 75 | // [ 1 2 assert_equal ] 'assert_equal' test 76 | // [ 1 assert_zero ] 'assert_zero' test 77 | // ['abc' 4 assert_length ] 'assert_zero' test 78 | 79 | -------------------------------------------------------------------------------- /src/ff-lib/types.ff: -------------------------------------------------------------------------------- 1 | // types 2 | types: [ 3 | 'number' 4 | 'string' 5 | 'boolean' 6 | 'array' 7 | 'date' 8 | 'object' 9 | 'null' 10 | 'undefined' 11 | 'symbol' 12 | 'complex' 13 | 'action' 14 | 'future' ] ; 15 | 16 | 17 | null?: [ null = ] ; 18 | nan?: [ nan = ] ; 19 | action?: [ type 'action' = ] ; 20 | string?: [ type 'string' = ] ; 21 | boolean?: [ type 'boolean' = ] ; 22 | array?: [ type 'array' = ] ; 23 | number?: [ type 'number' = ] ; 24 | 25 | hex: [ 16 base ] ; 26 | oct: [ 8 base ] ; 27 | bin: [ 2 base ] ; 28 | 29 | // truthy/falsy 30 | truthy?: [ boolean ] ; 31 | falsy?: [ truthy? ~ ] ; 32 | -------------------------------------------------------------------------------- /src/ff-lib/usr.ff: -------------------------------------------------------------------------------- 1 | 'shuffle.ff' require 2 | 'testing.ff' require 3 | 'switch-patterns.ff' require 4 | 5 | !!: [ null? ~ ] ; 6 | 7 | // Misc 8 | compose: [ + ] ; 9 | prepose: [ swap + ] ; 10 | 11 | with: [ [ swap [ swap ] ] dip + >> ] ; 12 | eval2: [ slip eval ] ; 13 | replicate: [ [ unit ] dip * ] ; 14 | nth: [ -- @ ] ; 15 | 16 | // with-system-property: [ 17 | // over get-system-property 18 | // [ dupd set-system-property ] dip 19 | // swapd swap 20 | // in 21 | // [ set-system-property ] dip 22 | // ] ; 23 | 24 | // side effects 25 | traceoff: [ 'log-level' 'warn' set-system-property ] ; 26 | traceon: [ 'log-level' 'trace' set-system-property ] ; 27 | timing: [ 'log-level' 'timing' set-system-property ] ; 28 | // trace: [ traceon eval traceoff ] ; // doesn't work!! 29 | 30 | // quick check 31 | for-all: [ [ [ 100 times ] >> in ] dip [ ~ ] + filter ] ; 32 | check-for-all: [ for-all dup empty? [ '+++ OK, passed 100 tests.' println drop ] [ [ ' Failed!' + println ] map ] branch ] ; 33 | 34 | // generators 35 | next-fib*: [ tuck + ] ; 36 | fib*: [ ( 0 1 ) dip [ 0 1 [ tuck + ] yield ] [ in ] rolldown 2 - times drop ] ; 37 | count*: [ [ 0 [ ++ dup [ yield ] dip ] forever ] ] ; 38 | // cycle*: [ [ [ 1 splitat swap [ + ] keep eval send suspend ] forever ] >> ] ; 39 | 40 | // async 41 | spawn2: [ [ spawn ] dip spawn ] ; 42 | spawnsplit: [ spawn2 eval2 ] ; 43 | 44 | // fetch 45 | site-size: [ read ln ] ; 46 | site-war: [ dup [ [ site-size ] >> ] map all zip object ] ; 47 | 48 | // random 49 | rand-integer: [ rand-u32 ] ; 50 | rand-char: [ rand-integer 128 % itoa ] ; 51 | rand-seq: [ rand-integer 16 % ++ ++ ++ ++ [ times ] >> >> in ] ; 52 | rand-string: [ [ rand-char ] rand-seq sum ] ; 53 | rand-bool: [ rand-u32 even? ] ; 54 | 55 | // repl 56 | cls: [ "\u001B[2J\u001B[0;0f" println ] ; 57 | 58 | // short circuit logicals 59 | &&: [ slip when ] ; 60 | ||: [ slip [] swap branch ] ; 61 | 62 | p-reverse: [ 63 | dup ln 64 | [ 65 | [ 0 p-case [] ] 66 | [ 1 p-case [] ] 67 | [ _ p-case [ uncons p-reverse swap + ] ] 68 | ] 69 | switch 70 | ] ; 71 | 72 | /** 73 | * The quadratic equation using lambdas 74 | */ 75 | quad: [ 76 | [ a: b: c: ] => [ 77 | .b -1 * .b .b * 4 .a .c * * - sqrt -+ 78 | [ 2 .a * / ] bi@ 79 | ] 80 | ] lambda ; 81 | 82 | [ 1 -3 0 quad 3 assert_equal 0 assert_equal ] 'quadradic equation lambda' test 83 | [ 5 6 1 quad -0.2 assert_equal -1 assert_equal ] 'quadradic equation lambda' test 84 | [ 5 2 1 quad -0.2 0.4 i * + assert_equal -0.2 -0.4 i * + assert_equal ] 'quadradic equation lambda' test 85 | 86 | // s-test: [ 87 | // ` 88 | // Multi line string 89 | // Hello 90 | // World 91 | // ` 92 | // ] ; 93 | 94 | phi: ( 1 5 sqrt + 2 / ) ; // the golden ratio 95 | psi: ( phi inv ~ ) ; 96 | 97 | lucas: [ phi swap ^ floor ] ; 98 | binet: [ [ phi swap ^ ] [ psi swap ^ ] bi - 5 sqrt / ] ; 99 | 100 | fib: [ 101 | [ 102 | [ 0 p-case [ ]] 103 | [ 1 p-case [ ]] 104 | [ [ 10 > ] case [ binet round ]] 105 | [ [ 0 < ] case [ abs dup 1 + -1 swap ^ swap fib * ]] 106 | [ true [ [1 - fib] [2 - fib] bi + ]] 107 | ] switch 108 | ] ; 109 | 110 | -------------------------------------------------------------------------------- /src/parser/index.ts: -------------------------------------------------------------------------------- 1 | export * from './parser'; 2 | export * from './tokenizer'; 3 | -------------------------------------------------------------------------------- /src/parser/parser.ts: -------------------------------------------------------------------------------- 1 | import { tokenize } from './tokenizer'; 2 | 3 | import { Word, Key, Decimal, StackValue, I } from '../types'; 4 | import { unescapeString } from '../utils/stringConversion'; 5 | 6 | const templateAction = new Word(':template'); 7 | 8 | export function lexer(a: unknown) { 9 | if (Array.isArray(a)) { 10 | return a; 11 | } 12 | if (typeof a === 'string') { 13 | a = a.trim(); // fix this in parser 14 | const nodes = tokenize(a).map(processParserTokens); 15 | return Array.prototype.concat.apply([], nodes); // flatmap 16 | } 17 | return [a]; 18 | } 19 | 20 | function processParserTokens(node: any): StackValue | undefined { 21 | switch (node.name) { 22 | // todo: more literal: infinity, -infinity, regex, complex, percent 23 | case 'templateString': 24 | return templateString(node.allText); 25 | case 'singleQuotedString': 26 | return singleQuotedString(node.allText); 27 | case 'doubleQuotedString': 28 | return doubleQuotedString(node.allText); 29 | case 'symbol': 30 | return Symbol(node.allText.slice(1)); 31 | case 'key': { 32 | const id = node.allText 33 | .trim() 34 | .slice(0, -1); 35 | return new Key(id); 36 | } 37 | case 'number': { 38 | const n = processNumeric(node.allText); 39 | if (!isNaN(n)) { 40 | return n; 41 | } // else fall through 42 | } 43 | case 'word': { 44 | const id = node.allText.toLowerCase().trim(); 45 | if (id.length <= 0) return undefined; 46 | return new Word(id); 47 | } 48 | case 'bracket': 49 | return new Word(node.allText); 50 | case 'null': 51 | return null; 52 | case 'bool': 53 | return node.allText.toLowerCase() === 'true'; 54 | case 'nan': 55 | return NaN; 56 | case 'i': 57 | return I; 58 | default: 59 | throw new Error(`Unknown type in lexer: ${node.name} ${node.allText}`); 60 | } 61 | } 62 | 63 | function processNumeric(value: string): Decimal | number { 64 | if (typeof value !== 'string') { 65 | return NaN; 66 | } 67 | if (value[0] === '+' && value[1] !== '-') { 68 | value = value.slice(1, value.length); 69 | } 70 | value = value.replace(/_/g, ''); 71 | try { 72 | if (value === '-0') return new Decimal('-0'); 73 | return value.slice(-1) === '%' 74 | ? new Decimal(String(value.slice(0, -1))).div(100) 75 | : new Decimal(String(value)); 76 | } catch (e) { 77 | return NaN; 78 | } 79 | } 80 | 81 | function templateString(val: string) { 82 | return [val.slice(1, -1), templateAction]; 83 | } 84 | 85 | function doubleQuotedString(val: string): string { 86 | const v = val.slice(1, -1); 87 | return unescapeString(v); // todo-move to parser 88 | } 89 | 90 | function singleQuotedString(val: string): string { 91 | return val.slice(1, -1); 92 | } 93 | 94 | // function convertWord(value: string) { 95 | // return new Word(value); 96 | // } 97 | 98 | // function convertLiteral(value: string): StackValue | undefined { 99 | // // move these to parser 100 | // return new Word(value); 101 | // } 102 | -------------------------------------------------------------------------------- /src/parser/tokenizer.ts: -------------------------------------------------------------------------------- 1 | // Reference the Myna module 2 | import { Myna } from 'myna-parser'; 3 | 4 | // Construct a grammar object 5 | const g = Object.create(null); 6 | 7 | const DELIMITER = ' \t\n\r\f,'; 8 | const BRACKETS = '[]{}()'; 9 | const QUOTES = '\'"``'; 10 | const RESERVED = ','; 11 | const COLON = ':'; 12 | 13 | // words 14 | g.bracket = Myna.char(BRACKETS).ast; 15 | g.identifierFirst = Myna.notChar(DELIMITER + QUOTES + RESERVED + BRACKETS); 16 | g.identifierNext = Myna.notChar( 17 | DELIMITER + QUOTES + RESERVED + BRACKETS + COLON 18 | ); 19 | g.identifier = Myna.seq(g.identifierFirst, g.identifierNext.zeroOrMore); 20 | 21 | g.word = Myna.seq(g.identifier).ast; 22 | g.key = Myna.seq(g.identifier, Myna.char(COLON)).ast; 23 | 24 | // Numbers 25 | g.digit = Myna.choice(Myna.digit, Myna.char('_')); 26 | g.digits = g.digit.oneOrMore; 27 | 28 | // Decimal 29 | g.integer = Myna.seq(Myna.digit.oneOrMore, g.digit.zeroOrMore); 30 | g.fraction = Myna.seq('.', g.integer); 31 | g.plusOrMinus = Myna.char('+-'); 32 | g.exponent = Myna.seq(Myna.char('eE'), g.plusOrMinus.opt, g.digits); 33 | g.decimal = Myna.seq( 34 | g.plusOrMinus.opt, 35 | g.integer, 36 | g.fraction.opt, 37 | g.exponent.opt, 38 | Myna.char('%').opt 39 | ).thenNot(g.identifierNext.or(Myna.digit)); 40 | g.decimalFraction = Myna.seq( 41 | g.plusOrMinus.opt, 42 | g.integer.opt, 43 | g.fraction, 44 | g.exponent.opt, 45 | Myna.char('%').opt 46 | ).thenNot(g.identifierNext.or(Myna.digit)); 47 | 48 | // radix 49 | g.rawRadixDigit = Myna.char('0123456789abcdefABCDEF'); 50 | g.radixDigit = Myna.choice(g.rawRadixDigit, Myna.char('_')); 51 | g.radixInteger = Myna.seq(g.rawRadixDigit.oneOrMore, g.radixDigit.zeroOrMore); 52 | g.radixFraction = Myna.seq('.', g.radixInteger); 53 | g.radixExponent = Myna.seq( 54 | Myna.char('eEpP'), 55 | g.plusOrMinus.opt, 56 | g.radixDigit.oneOrMore 57 | ); 58 | g.radix = Myna.seq( 59 | g.plusOrMinus.opt, 60 | '0', 61 | Myna.char('oObBxF'), 62 | g.radixInteger, 63 | g.radixFraction.opt, 64 | g.radixExponent.opt 65 | ).thenNot(g.identifierNext.or(g.radixDigit)); 66 | 67 | g.number = Myna.choice(g.radix, g.decimal, g.decimalFraction, g.integer).ast; 68 | 69 | // Comments and whitespace 70 | g.untilEol = Myna.advanceWhileNot(Myna.newLine).then(Myna.newLine.opt); 71 | g.fullComment = Myna.seq('/*', Myna.advanceUntilPast('*/')); 72 | g.lineComment = Myna.seq('//', g.untilEol); 73 | g.comment = g.fullComment.or(g.lineComment); 74 | g.delimiter = Myna.char(DELIMITER).oneOrMore; 75 | g.ws = g.delimiter.or(Myna.atWs.then(Myna.advance)).zeroOrMore; 76 | 77 | // symbol 78 | g.symbol = Myna.seq(Myna.char('#'), g.identifier).ast; 79 | 80 | // literals 81 | g.bool = Myna.choice( 82 | Myna.keywordAnyCase('true'), 83 | Myna.keywordAnyCase('false') 84 | ).thenNot(g.identifierNext).ast; 85 | g.null = Myna.keywordAnyCase('null').thenNot(g.identifierNext).ast; 86 | g.nan = Myna.keywordAnyCase('nan').thenNot(g.identifierNext).ast; 87 | g.i = Myna.keywordAnyCase('i').thenNot(g.identifierNext).ast; 88 | 89 | g.literal = Myna.choice(g.bool, g.null, g.nan, g.i); 90 | 91 | // strings 92 | g.escapedChar = Myna.char('\\').then(Myna.advance); 93 | g.templateString = Myna.guardedSeq('`', Myna.notChar('`').zeroOrMore, '`').ast; 94 | g.singleQuotedString = Myna.guardedSeq( 95 | `'`, 96 | Myna.notChar(`'`).zeroOrMore, 97 | `'` 98 | ).ast; 99 | g.doubleQuotedString = Myna.guardedSeq( 100 | '"', 101 | Myna.notChar('"').zeroOrMore, 102 | '"' 103 | ).ast; 104 | g.string = Myna.choice( 105 | g.templateString, 106 | g.singleQuotedString, 107 | g.doubleQuotedString 108 | ); 109 | 110 | g.value = Myna.choice( 111 | g.comment, 112 | g.number, 113 | g.symbol, 114 | g.literal, 115 | g.key, 116 | g.word, 117 | g.string, 118 | g.bracket 119 | ); 120 | 121 | g.sequence = g.value.delimited(g.ws); 122 | 123 | Myna.registerGrammar('fflat', g, g.value); 124 | 125 | exports.fflatGrammar = Myna.grammars['fflat']; 126 | 127 | // Get the parser 128 | export const parser = function(text) { 129 | return Myna.parse(g.sequence, text); 130 | }; 131 | 132 | export const tokenize = function(text) { 133 | const result = Myna.parse(g.sequence, text); 134 | return result ? result.children : []; 135 | }; 136 | -------------------------------------------------------------------------------- /src/stack.ts: -------------------------------------------------------------------------------- 1 | /* global window, global, process */ 2 | 3 | import { StackEnv } from './engine/env'; 4 | import { join } from 'path'; 5 | 6 | import { 7 | dict, 8 | base, 9 | objects, 10 | core, 11 | math, 12 | types, 13 | experimental, 14 | node, 15 | flags 16 | } from './core'; 17 | 18 | let defaultRootStack: StackEnv; 19 | 20 | export function createRootEnv(): StackEnv { 21 | const env = new StackEnv({ 22 | // root 23 | silent: true 24 | }); 25 | 26 | // TODO: these should not be default 27 | // todo: move usr.ff loading out of boot 28 | 29 | const sysPath = join('file://', __dirname, '../src/ff-lib/'); 30 | const bootPath = join(sysPath, 'boot.ff'); 31 | 32 | const prelude = { 33 | __sys_path__: () => sysPath, // TODO: this should not be constant 34 | ...core, 35 | ...base, 36 | ...dict, 37 | ...objects, 38 | ...math, 39 | ...types, 40 | ...experimental, 41 | ...node, 42 | ...flags 43 | }; 44 | 45 | env.defineAction(prelude); 46 | 47 | // TODO: this should be optional 48 | return env.eval(`'${bootPath}' read eval`); 49 | } 50 | 51 | export function createStack(s = '', root?: StackEnv): StackEnv { 52 | return new StackEnv({ 53 | parent: root || defaultRootStack || (defaultRootStack = createRootEnv()), 54 | silent: false 55 | }).enqueue(s); 56 | } 57 | -------------------------------------------------------------------------------- /src/types/complex-infinity.ts: -------------------------------------------------------------------------------- 1 | import { guard } from '@hypercubed/dynamo'; 2 | 3 | import { Complex } from './complex'; 4 | 5 | export class AbstractValue { 6 | constructor(public type: string) {} 7 | 8 | toString(): string { 9 | return this.type; 10 | } 11 | 12 | inspect(): string { 13 | return this.toString(); 14 | } 15 | 16 | valueOf() { 17 | return Infinity; 18 | } 19 | } 20 | 21 | export class Indeterminate extends AbstractValue { 22 | static indeterminate = new Indeterminate(); 23 | 24 | @guard() 25 | static isIndeterminate(a: any): a is ComplexInfinity { 26 | return a === Indeterminate.indeterminate; 27 | } 28 | 29 | constructor() { 30 | super('Indeterminate'); 31 | } 32 | } 33 | 34 | export class ComplexInfinity extends AbstractValue { 35 | static complexInfinity = new ComplexInfinity(); 36 | 37 | static get symbol() { 38 | return '∞̅'; 39 | } 40 | 41 | static times(rhs: Complex | ComplexInfinity) { 42 | if (ComplexInfinity.isComplexInfinity(rhs)) { 43 | return ComplexInfinity.complexInfinity; 44 | } 45 | 46 | if (rhs.cmp(0) !== 0) { 47 | return ComplexInfinity.complexInfinity; 48 | } 49 | 50 | return indeterminate; 51 | } 52 | 53 | static div(rhs: Complex | ComplexInfinity) { 54 | if (ComplexInfinity.isComplexInfinity(rhs)) { 55 | return indeterminate; 56 | } 57 | return ComplexInfinity.complexInfinity; 58 | } 59 | 60 | static idiv(lhs: Complex | ComplexInfinity) { 61 | return 0; 62 | } 63 | 64 | @guard() 65 | static isComplexInfinity(a: any): a is ComplexInfinity { 66 | return a === ComplexInfinity.complexInfinity; 67 | } 68 | 69 | constructor() { 70 | super('ComplexInfinity'); 71 | } 72 | } 73 | 74 | export const { complexInfinity } = ComplexInfinity; 75 | export const { indeterminate } = Indeterminate; 76 | -------------------------------------------------------------------------------- /src/types/decimal.ts: -------------------------------------------------------------------------------- 1 | import { guard, conversion } from '@hypercubed/dynamo'; 2 | import { Decimal } from 'decimal.js'; 3 | 4 | import { g, c } from '../utils/gamma'; 5 | 6 | export { Decimal }; 7 | 8 | export class DecimalDef extends Decimal { 9 | @guard(Decimal) 10 | static isDecimal(value: unknown): value is Decimal { 11 | return value instanceof Decimal; 12 | } 13 | 14 | @conversion() 15 | static fromNumber(value: number): Decimal { 16 | return new Decimal(value); 17 | } 18 | } 19 | 20 | Decimal.config({ 21 | precision: 20, 22 | rounding: 4, 23 | modulo: 1, 24 | toExpNeg: -7, 25 | toExpPos: 21, 26 | minE: -9e15, 27 | maxE: 9e15, 28 | crypto: undefined 29 | }); 30 | 31 | // const $PI = '3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632789'; 32 | // const $E = '2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466391932003059921817413596629043572900334295260595630738132328627943490763233829880753195251019011573834187930702154089149934884167509244761460668082264800168477411853742345442437107539077744992069551702761838606261331384583000752044933826560297606737113200709328709127443747047230696977209310141692836819025515108657463772111252389784425056953696770785449969967946864454905987931636889230098793127736178215424999229576351'; 33 | 34 | export const pi = new Decimal(-1).acos(); // Decimal.acos(-1); 35 | export const twoPiSqrt = pi.times(2).sqrt(); 36 | 37 | export const zero = new Decimal(0); 38 | export const one = new Decimal(1); 39 | export const e = Decimal.exp(1); 40 | 41 | const P = Decimal.prototype; 42 | 43 | // P.empty = () => zero; 44 | 45 | P.toString = P.valueOf; 46 | 47 | P.valueOf = function() { 48 | return Number(this.toString()); 49 | }; 50 | 51 | P.toJSON = function() { 52 | return { $numberDecimal: this.toString() }; 53 | }; 54 | 55 | P.inspect = function() { 56 | return this.toString(); 57 | }; 58 | 59 | P.fromJSON = function(json: { $numberDecimal: string }) { 60 | return new Decimal(json.$numberDecimal); 61 | }; 62 | 63 | P.digits = function() { 64 | return this.toString().replace(/[-+.eE]/g, ''); 65 | }; 66 | 67 | // TODO: gamma(0) === ComplexInfinity 68 | 69 | export function gammaDecimal(a: Decimal) { 70 | // more tests 71 | // The Lanczos approximation 72 | // https://en.wikipedia.org/wiki/Lanczos_approximation 73 | // G(z+1) = sqrt(2*pi)*(z+g+1/2)^(z+1/2)*exp(-(z+g+1/2))*Ag(z) 74 | // Ag(z) = c0 + sum(k=1..N, ck/(z+k)) 75 | // todo: Memoization? 76 | 77 | if (a.lt(0.5)) { 78 | return pi.div( 79 | a 80 | .times(pi) 81 | .sin() 82 | .times(gammaDecimal(one.minus(a))) 83 | ); 84 | } 85 | 86 | const z = a.minus(1); 87 | let agz: Decimal | number = c[0]; 88 | 89 | for (let k = 1; k < c.length; ++k) { 90 | const den = z.plus(k); 91 | if (den.isZero()) { 92 | agz = c[k] < 0 ? -Infinity : Infinity; 93 | } else { 94 | agz = new Decimal(c[k]).div(den).plus(agz); // Ag += c(k)/(z+k) 95 | } 96 | } 97 | 98 | const t = z.plus(g + 0.5); // z+g+1/2 99 | 100 | return twoPiSqrt // sqrt(2*PI) 101 | .times(t.pow(z.plus(0.5))) // *(z+g+1/2)^(z+0.5) 102 | .times(t.neg().exp()) // *exp(-(z+g+1/2)) 103 | .times(agz); // *Ag(z) 104 | } 105 | -------------------------------------------------------------------------------- /src/types/dynamo.ts: -------------------------------------------------------------------------------- 1 | import { Dynamo, guard } from '@hypercubed/dynamo'; 2 | 3 | import { Future } from './future'; 4 | import { DecimalDef } from './decimal'; 5 | import { Complex } from './complex'; 6 | import { Word, Key, Sentence } from './words'; 7 | import { ComplexInfinity, Indeterminate } from './complex-infinity'; 8 | 9 | export const dynamo = new Dynamo(); 10 | 11 | class IsSymbol { 12 | @guard(Symbol) 13 | static isSymbol(value: unknown) { 14 | return typeof value === 'symbol'; 15 | } 16 | } 17 | 18 | dynamo.add( 19 | Future, 20 | Word, 21 | Key, 22 | Sentence, 23 | DecimalDef, 24 | Complex, 25 | ComplexInfinity, 26 | Indeterminate, 27 | IsSymbol 28 | ); 29 | -------------------------------------------------------------------------------- /src/types/future.ts: -------------------------------------------------------------------------------- 1 | import { guard } from '@hypercubed/dynamo'; 2 | 3 | import { Sentence, Word } from './words'; 4 | import { StackValue } from './stack-values'; 5 | 6 | export class Future { 7 | @guard() 8 | static isFuture(x: unknown): x is Future { 9 | return x instanceof Future; 10 | } 11 | 12 | value: StackValue; 13 | 14 | constructor( 15 | public action: Word | Sentence | undefined, 16 | public promise: Promise 17 | ) { 18 | if (typeof promise !== 'undefined') { 19 | promise.then(data => this.resolve(data)); 20 | } 21 | } 22 | 23 | isResolved(): boolean { 24 | return this.value !== undefined; 25 | } 26 | 27 | state(): string { 28 | return this.isResolved() ? 'resolved' : 'pending'; 29 | } 30 | 31 | resolve(p: any): any { 32 | if (this.isResolved()) { 33 | return; 34 | } 35 | this.action = undefined; 36 | this.value = p; 37 | 38 | return this.value; 39 | } 40 | 41 | toString(): string { 42 | return this.inspect(); 43 | } 44 | 45 | near(): any { 46 | return this.isResolved() ? this.value : this.action; 47 | } 48 | 49 | inspect(): string { 50 | const state = this.state(); 51 | let near = this.near(); 52 | near = near.inspect ? near.inspect() : String(near); 53 | return `[Future:${state} [${near}]]`; 54 | } 55 | 56 | toJSON(): any { 57 | let value: any = this.value || { $undefined: true }; 58 | return { 59 | '@@Future': value.toJSON ? value.toJSON() : value 60 | }; 61 | } 62 | 63 | extract(): any { 64 | if (!this.isResolved()) { 65 | // error? 66 | return undefined; 67 | } 68 | return this.value; 69 | } 70 | 71 | map(fn: any): Future { 72 | return new Future(this.action, this.promise.then(fn)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dynamo'; 2 | export * from './decimal'; 3 | export * from './complex'; 4 | export * from './words'; 5 | export * from './vocabulary-values'; 6 | export * from './stack-values'; 7 | export * from './return-values'; 8 | export * from './future'; 9 | export * from './complex-infinity'; 10 | -------------------------------------------------------------------------------- /src/types/return-values.ts: -------------------------------------------------------------------------------- 1 | import { StackValue } from './stack-values'; 2 | 3 | export class ReturnValues { 4 | constructor(public value: StackValue[]) {} 5 | } 6 | -------------------------------------------------------------------------------- /src/types/stack-values.ts: -------------------------------------------------------------------------------- 1 | import { Word, Key, Alias } from './words'; 2 | import { Future } from './future'; 3 | import { Complex } from './complex'; 4 | import { Decimal } from './decimal'; 5 | import { ScopeModule } from './vocabulary-values'; 6 | 7 | export type StackValue = 8 | | number // TODO: remove this, should only be Decimal? 9 | | string 10 | | boolean 11 | | Symbol 12 | | Key 13 | | Future 14 | | undefined 15 | | null 16 | | Complex 17 | | Decimal 18 | | ScopeModule 19 | | { [s: string]: StackValue } 20 | | StackValue[]; 21 | 22 | export type QueueValue = 23 | | StackValue 24 | | Alias 25 | | Word; 26 | -------------------------------------------------------------------------------- /src/types/vocabulary-values.ts: -------------------------------------------------------------------------------- 1 | import { Word, Sentence } from './words'; 2 | 3 | export type ScopeModule = { [k in string]: symbol }; 4 | 5 | // TODO: only Sentence and ScopeModule 6 | export type VocabValue = 7 | | Word 8 | | Sentence 9 | | ScopeModule 10 | | { [key: string]: VocabValue } 11 | | Function 12 | | symbol; 13 | -------------------------------------------------------------------------------- /src/types/words.ts: -------------------------------------------------------------------------------- 1 | import { guard } from '@hypercubed/dynamo'; 2 | import { encode } from '../utils/json'; 3 | import { StackValue } from './stack-values'; 4 | 5 | function toString(x: any) { 6 | if (Array.isArray(x)) { 7 | return `[ ${x.map(x => toString(x)).join(' ')} ]`; 8 | } 9 | return String(x); 10 | } 11 | 12 | abstract class Action { 13 | constructor(public value: any, public displayString?: string) { 14 | if (!displayString) { 15 | this.displayString = toString(value); 16 | } 17 | } 18 | 19 | inspect(): string { 20 | if (typeof this.value === 'string') { 21 | return this.value; 22 | } 23 | if (typeof this.value === 'undefined') { 24 | return 'undefined'; 25 | } 26 | return this.value.inspect ? this.value.inspect() : String(this.value); 27 | } 28 | 29 | toJSON(): any { 30 | return { 31 | '@@Action': encode(this.value) 32 | }; 33 | } 34 | 35 | equals(b: any): boolean { 36 | return typeof this.value.equals === 'function' 37 | ? this.value.equals(b.value) 38 | : this.value === b.value; 39 | } 40 | 41 | extract(): any { 42 | return this.value; 43 | } 44 | 45 | toString(): string { 46 | return toString(this.displayString || this.value); 47 | } 48 | 49 | [Symbol.toPrimitive](hint?: string) { 50 | if (hint === 'string') { 51 | return toString(this.value); 52 | } 53 | if (hint === 'number') { 54 | return Number(this.value); 55 | } 56 | return this.value; 57 | } 58 | } 59 | 60 | export class Word extends Action { 61 | @guard() 62 | static isWord(x: unknown): x is Word { 63 | return x instanceof Word; 64 | } 65 | 66 | constructor(public value: string, public displayString?: string) { 67 | // value s/b PropertyKey? 68 | super(value, displayString); 69 | } 70 | } 71 | 72 | export class Key extends Action { 73 | @guard() 74 | static isKey(x: unknown): x is Key { 75 | return x instanceof Key; 76 | } 77 | 78 | constructor(public value: string, public displayString?: string) { 79 | super(value, displayString); 80 | if (!displayString) { 81 | this.displayString = toString(value) + ':'; 82 | } 83 | } 84 | } 85 | 86 | export class Alias extends Action { // global word 87 | @guard() 88 | static isAlias(x: unknown): x is Alias { 89 | return x instanceof Alias; 90 | } 91 | 92 | constructor(public value: symbol, public displayString?: string) { 93 | super(value, displayString); 94 | } 95 | } 96 | 97 | export class Sentence extends Action { 98 | @guard() 99 | static isSentence(x: unknown): x is Sentence { 100 | return x instanceof Sentence; 101 | } 102 | 103 | constructor(value: StackValue[], displayString?: string) { 104 | super(value, displayString); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/utils/fflat-error.ts: -------------------------------------------------------------------------------- 1 | import { ffPrettyPrint } from './pprint'; 2 | import { StackEnv } from '../engine/env'; 3 | 4 | export class FFlatError extends Error { 5 | constructor(message = 'FFlatError', state?: StackEnv) { 6 | super(message); 7 | 8 | // extending Error is weird and does not propagate `message` 9 | Reflect.defineProperty(this, 'message', { 10 | enumerable: false, 11 | value: message 12 | }); 13 | 14 | Reflect.defineProperty(this, 'name', { 15 | enumerable: false, 16 | value: 'FFlatError' 17 | }); 18 | 19 | let stackValue: string[]; 20 | if (state) { 21 | stackValue = [ 22 | `${this.name}: ${this.message}`, 23 | `stack/queue: ${state.stack.length} / ${state.queue.length}` 24 | ]; 25 | 26 | stackValue.push(`stack trace:`); 27 | 28 | state.trace.forEach(s => { 29 | stackValue.push(` ${ffPrettyPrint.formatTrace(s as any)}`); 30 | }); 31 | } else { 32 | stackValue = ['']; 33 | } 34 | 35 | Reflect.defineProperty(this, 'stack', { 36 | enumerable: false, 37 | value: stackValue.join('\n') 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/gamma.ts: -------------------------------------------------------------------------------- 1 | // http://www-m3.ma.tum.de/bornemann/Numerikstreifzug/Chapter5/Matlab/gamma.m 2 | // http://mrob.com/pub/ries/lanczos-gamma.html 3 | // http://www.boost.org/doc/libs/1_60_0/libs/math/doc/html/math_toolkit/lanczos.html 4 | // http://www.vttoth.com/CMS/projects/41 5 | 6 | // Lanczos approximation for the complex plane 7 | // calculated using vpa digits(256) 8 | // the best set of coeffs was selected from 9 | // a solution space of g=0 to 32 with 1 to 32 terms 10 | // these coeffs really give superb performance 11 | // of 15 sig. digits for 0<=real(z)<=171 12 | // coeffs should sum to about g*g/2+23/24 13 | 14 | // g=4.7421875, n=15 15 | // Sources: Preda1,Godfrey3,Godfrey6 16 | 17 | export const g = 607 / 128; // 4.7421875; 18 | 19 | export const c = [ 20 | 0.99999999999999709182, 21 | 57.156235665862923517, 22 | -59.597960355475491248, 23 | 14.136097974741747174, 24 | -0.49191381609762019978, 25 | 0.33994649984811888699e-4, 26 | 0.46523628927048575665e-4, 27 | -0.98374475304879564677e-4, 28 | 0.15808870322491248884e-3, 29 | -0.21026444172410488319e-3, 30 | 0.2174396181152126432e-3, 31 | -0.16431810653676389022e-3, 32 | 0.84418223983852743293e-4, 33 | -0.2619083840158140867e-4, 34 | 0.36899182659531622704e-5 35 | ]; 36 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './fflat-error'; 2 | export * from './logger'; 3 | export * from './pprint'; 4 | export * from './stringConversion'; 5 | export * from './utils'; 6 | export * from './kleene-logic'; 7 | export * from './json'; 8 | export * from './types'; 9 | export * from './rewrite'; 10 | -------------------------------------------------------------------------------- /src/utils/json.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Smykowski, 3 | toJSON, 4 | encodeSpecialNumbers, 5 | encodeUndefined, 6 | encodeDates, 7 | encodeRegexps, 8 | encodeSymbols, 9 | encodeSet, 10 | encodeMap 11 | } from 'smykowski'; 12 | 13 | const smykowski = new Smykowski() 14 | .addEncoder(encodeSpecialNumbers) 15 | .addEncoder(encodeDates) 16 | .addEncoder(encodeRegexps) 17 | .addEncoder(encodeSymbols) 18 | .addEncoder(encodeSet) 19 | .addEncoder(encodeMap) 20 | .addEncoder(encodeUndefined) 21 | .addEncoder(toJSON); 22 | 23 | export const encode = smykowski.encode.bind(smykowski); 24 | export const stringifyStrict = smykowski.stringify.bind(smykowski); 25 | -------------------------------------------------------------------------------- /src/utils/kleene-logic.ts: -------------------------------------------------------------------------------- 1 | export type bool = boolean | null; 2 | export type cmpValue = -1 | 0 | 1; 3 | 4 | export function not(a: bool) { 5 | if (a === true) return false; 6 | if (a === false) return true; 7 | return null; 8 | } 9 | 10 | export function and(lhs: bool, rhs: bool): bool { 11 | if (lhs === true) return rhs; 12 | if (rhs === true) return lhs; 13 | if (lhs === false || rhs === false) return false; 14 | return null; 15 | } 16 | 17 | export function nand(lhs: bool, rhs: bool): bool { 18 | return not(and(lhs, rhs)); 19 | } 20 | 21 | export function or(lhs: bool, rhs: bool): bool { 22 | return nand(not(lhs), not(rhs)); 23 | } 24 | 25 | export function xor(lhs: bool, rhs: bool): bool { 26 | return and(or(lhs, rhs), nand(lhs, rhs)); 27 | } 28 | 29 | export function nor(lhs: bool, rhs: bool): bool { 30 | return not(or(lhs, rhs)); 31 | } 32 | 33 | export function mimpl(lhs: bool, rhs: bool): bool { 34 | return or(not(lhs), rhs); 35 | } 36 | 37 | export function cimpl(lhs: bool, rhs: bool): bool { 38 | return or(lhs, not(rhs)); 39 | } 40 | 41 | export function mnonimpl(lhs: bool, rhs: bool): bool { 42 | return and(lhs, not(rhs)); 43 | } 44 | 45 | export function cnonimpl(lhs: bool, rhs: bool): bool { 46 | return and(not(lhs), rhs); 47 | } 48 | 49 | export function cmp(lhs: bool, rhs: bool): cmpValue { 50 | const slhs = sort_order(lhs); 51 | const srhs = sort_order(rhs); 52 | if (slhs === srhs) return 0; 53 | return slhs > srhs ? 1 : -1; 54 | } 55 | 56 | function sort_order(a: bool): cmpValue { 57 | if (a === null) return 0; 58 | return a ? 1 : -1; 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import { transports, Logger } from 'winston'; 2 | import * as ProgressBar from 'progress'; 3 | 4 | export const log = new Logger({ 5 | levels: { 6 | error: 0, 7 | warn: 1, 8 | info: 2, 9 | timing: 3, 10 | trace: 4, 11 | debug: 5 12 | }, 13 | transports: [new transports.Console()] 14 | }); 15 | 16 | // log.cli(); 17 | log.level = (process.env as any).NODE_ENV || 'warn'; 18 | 19 | export const bar = new ProgressBar( 20 | ':elapsed s [:bar] S::stack Q::queue :: :lastAction', 21 | { 22 | complete: '=', 23 | incomplete: ' ', 24 | total: 100, 25 | width: 50, 26 | clear: true, 27 | renderThrottle: 3200, 28 | stream: process.stderr 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /src/utils/pattern.ts: -------------------------------------------------------------------------------- 1 | import { signature, Any } from '@hypercubed/dynamo'; 2 | 3 | import { dynamo, StackValue, Word, Key } from '../types'; 4 | import { deepEquals } from './utils'; 5 | 6 | class PatternMatch { 7 | @signature(Any, Symbol) 8 | 'any, Symbol'(a: any, b: Symbol): boolean { 9 | if (b === Symbol.for('_')) return true; 10 | return typeof a === 'symbol' ? a === b : false; 11 | } 12 | 13 | @signature(Any, Word) 14 | 'any, Word'(a: any, b: Word): boolean { 15 | if (b.value === '_') return true; 16 | return a instanceof Word && a.value === b.value; 17 | } 18 | 19 | @signature(Any, Key) 20 | 'any, Key'(a: any, b: Word): boolean { 21 | if (b.value === '_') return true; 22 | return a instanceof Key && a.value === b.value; 23 | } 24 | 25 | @signature(Array, Array) 26 | 'Array, Array'(a: StackValue[], b: StackValue[]): boolean { 27 | if (a.length < b.length) { 28 | // todo: handle "rest" pattern '...' 29 | return false; 30 | } 31 | for (let i = 0; i < a.length; i++) { 32 | const bb = b[i]; 33 | if (bb instanceof Word && bb.value === '...') return true; 34 | if (!patternMatch(a[i], bb)) { 35 | return false; 36 | } 37 | } 38 | return true; 39 | } 40 | 41 | @signature(Object, Object) 42 | 'map, map'(a: {}, b: {}): boolean { 43 | const ak = Object.keys(a); 44 | const bk = Object.keys(b); 45 | if (ak.length < bk.length) { 46 | return false; 47 | } 48 | if (bk.length === 0) { 49 | return true; 50 | } 51 | for (let i = 0; i < bk.length; i++) { 52 | // rest? 53 | const key = ak[i]; 54 | if (!patternMatch(a[key], b[key])) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | @signature(Any, RegExp) 62 | 'any, RegExp'(lhs: string, rhs: RegExp) { 63 | rhs.lastIndex = 0; 64 | return rhs.test(lhs); 65 | } 66 | 67 | @signature(Any, Any) 68 | 'any, any'(a: any, b: any): boolean { 69 | return deepEquals(a, b); 70 | } 71 | } 72 | 73 | export const patternMatch = dynamo.function(PatternMatch); 74 | -------------------------------------------------------------------------------- /src/utils/pprint.ts: -------------------------------------------------------------------------------- 1 | import is from '@sindresorhus/is'; 2 | import stripAnsi from 'strip-ansi'; 3 | import * as fixedWidthString from 'fixed-width-string'; 4 | 5 | import { Sentence, Complex, Decimal } from '../types'; 6 | 7 | import { 8 | Milton, 9 | trimStrings, 10 | maxDepth, 11 | ansiColors, 12 | arrayDecender, 13 | objectDecender, 14 | jsValues, 15 | jsonValues, 16 | indent, 17 | COLORIZE_OPTIONS 18 | } from '@hypercubed/milton'; 19 | 20 | function getDefaultWidth() { 21 | if (process && process.stdout && process.env.NODE_ENV !== 'test') { 22 | return process.stdout.columns || 70; 23 | } 24 | return 70; 25 | } 26 | 27 | export const decimal = () => (s: any, _p: any, v: any) => { 28 | if (v instanceof Decimal) { 29 | return v.toString(); 30 | } 31 | return s; 32 | }; 33 | 34 | export const objectToString = () => (s: any, _p: any, v: any) => { 35 | const t = toString.call(s); 36 | if (t === '[object Object]' && s.toString) { 37 | const vt = s.toString(); 38 | if (vt !== t) return vt; 39 | } 40 | return s; 41 | }; 42 | 43 | export const sentences = (_o: never, _r: never, get: any) => ( 44 | s: any, 45 | path: any, 46 | v: any 47 | ) => (v instanceof Sentence ? get(v.value, path) : s); 48 | 49 | export const symbols = () => (s: any) => { 50 | if (typeof s === 'symbol') { 51 | const esc = s.toString().replace(/Symbol\(([^)]*)\).*/g, '$1'); 52 | return `#${esc}`; 53 | } 54 | return s; 55 | }; 56 | 57 | export const breakLength = (options: any) => { 58 | return (s: any) => { 59 | if (typeof s === 'string') { 60 | const oneline = s.replace(/\n\s*/g, options.compact ? '' : ' '); 61 | return stripAnsi(oneline).length < options.breakLength ? oneline : s; 62 | } 63 | return s; 64 | }; 65 | }; 66 | 67 | const literals = () => { 68 | return (s: any, _p: any, v: any) => { 69 | if (typeof v === 'object') { 70 | if (v instanceof Complex) { 71 | return `"${s}":complex`; 72 | } 73 | if (v instanceof RegExp) { 74 | return `"${s}":regexp`; 75 | } 76 | if (v instanceof Date) { 77 | return `"${s}":date`; 78 | } 79 | } 80 | return s; 81 | }; 82 | }; 83 | 84 | const functions = () => { 85 | return (s: any, _p: any, v: any) => { 86 | if (typeof v === 'function') { 87 | return `[function${v.name && ` ${v.name}`}]`; 88 | } 89 | return s; 90 | }; 91 | }; 92 | 93 | export const oneline = () => (s: any) => 94 | typeof s === 'string' ? s.replace(/\n\s*/g, ' ') : s; 95 | 96 | function base(_: Milton) { 97 | _.add(jsValues); 98 | _.add(jsonValues, { quote: `'` }); 99 | _.add(functions); 100 | _.add(decimal); 101 | 102 | _.add(symbols); 103 | _.add(sentences); 104 | _.add(objectToString); 105 | 106 | _.add(arrayDecender, { comma: false, maxLength: 20 }); 107 | _.add(objectDecender, { quoteKeys: false, compact: false }); 108 | 109 | return _; 110 | } 111 | 112 | const COLORS = { 113 | ...COLORIZE_OPTIONS, 114 | Word: 'green', 115 | Key: 'bold.yellow', 116 | Decimal: COLORIZE_OPTIONS.number, 117 | Complex: COLORIZE_OPTIONS.number, 118 | ComplexInfinity: COLORIZE_OPTIONS.number, 119 | Indeterminate: COLORIZE_OPTIONS.number 120 | }; 121 | 122 | const stringify = new Milton(); 123 | stringify.use(base); 124 | stringify.add(oneline); 125 | 126 | const pretty = new Milton(); 127 | pretty.use(base); 128 | pretty.add(trimStrings); 129 | pretty.add(maxDepth, { max: 5 }); 130 | pretty.add(indent); 131 | pretty.add(breakLength, { compact: false, get breakLength() { return getDefaultWidth(); }}); 132 | 133 | const color = new Milton(); 134 | color.use(base); 135 | color.add(trimStrings); 136 | color.add(maxDepth, { max: 5 }); 137 | color.add(indent); 138 | color.add(breakLength, { compact: false, get breakLength() { return getDefaultWidth(); }}); 139 | color.add(ansiColors, COLORS); 140 | 141 | const trace = new Milton(); 142 | trace.use(base); 143 | trace.add(oneline); 144 | trace.add(ansiColors, COLORS); 145 | 146 | const literal = new Milton(); 147 | literal.use(base); 148 | literal.add(literals); 149 | literal.add(trimStrings); 150 | literal.add(maxDepth, { max: 5 }); 151 | literal.add(indent); 152 | literal.add(breakLength, { compact: false, get breakLength() { return getDefaultWidth(); }}); 153 | literal.add(ansiColors, COLORS); 154 | 155 | export const ffPrettyPrint = { 156 | get consoleWidth() { 157 | return process?.stdout?.columns || 80; // todo: this should be an option input 158 | }, 159 | 160 | color: color.stringify.bind(color), 161 | 162 | stringify: stringify.stringify.bind(stringify), 163 | 164 | trace: trace.stringify.bind(trace), 165 | 166 | literal: literal.stringify.bind(literal), 167 | 168 | formatTrace( 169 | { stack, queue, currentAction }, 170 | max = ffPrettyPrint.consoleWidth 171 | ): string { 172 | const maxOutputWidth = ffPrettyPrint.consoleWidth; 173 | max = max < 0 ? maxOutputWidth / 2 + max : max / 2; 174 | max -= 16; 175 | 176 | const stackString = trace.stringify(stack); 177 | const lastActionString = is.undefined(currentAction) 178 | ? '' 179 | : trace.stringify(currentAction); 180 | const queueString = trace.stringify(queue); 181 | return `${lpad(stackString, max)}> ${fixedWidthString( 182 | lastActionString, 183 | 16 184 | )} <${rtrim(queueString, max)}`; 185 | } 186 | }; 187 | 188 | function lpad(str: string, n = 40): string { 189 | str = str.replace(/[\s\n]+/gm, ' '); 190 | return fixedWidthString(str, n, { align: 'right' }); 191 | } 192 | 193 | function rtrim(str: string, n = 40): string { 194 | str = str.replace(/[\s\n]+/gm, ' '); 195 | return fixedWidthString(str, n); 196 | } 197 | -------------------------------------------------------------------------------- /src/utils/regex-logic.ts: -------------------------------------------------------------------------------- 1 | import { 2 | sequence, 3 | flags, 4 | lookAhead, 5 | avoid, 6 | either, 7 | suffix 8 | } from 'compose-regexp'; 9 | 10 | export function rAnd(lhs: RegExp, rhs: RegExp) { 11 | const slhs = lookAhead(lhs); 12 | const srhs = lookAhead(rhs); 13 | return flags(lhs.flags, sequence(slhs, srhs)); 14 | } 15 | 16 | export function rNot(lhs: RegExp) { 17 | return flags(lhs.flags, avoid(lhs)); 18 | } 19 | 20 | export function rNand(lhs: RegExp, rhs: RegExp) { 21 | return rNot(rAnd(lhs, rhs)); 22 | } 23 | 24 | export function rOr(lhs: RegExp, rhs: RegExp) { 25 | return flags(lhs.flags, either(lhs, rhs)); 26 | } 27 | 28 | export function rNor(lhs: RegExp, rhs: RegExp) { 29 | return rNot(rOr(lhs, rhs)); 30 | } 31 | 32 | export function rXor(lhs: RegExp, rhs: RegExp) { 33 | return rAnd(rOr(lhs, rhs), rNot(rAnd(lhs, rhs))); 34 | } 35 | 36 | export function rLsh(lhs: RegExp, rhs: RegExp) { 37 | return flags(lhs.flags, sequence(lhs, rhs)); 38 | } 39 | 40 | export function rRsh(lhs: RegExp, rhs: RegExp) { 41 | return flags(rhs.flags, sequence(lhs, rhs)); 42 | } 43 | 44 | export function rRepeat(lhs: RegExp, rhs: number) { 45 | rhs = +rhs; 46 | const max = rhs === Infinity ? '' : rhs | 0; 47 | const min = rhs === Infinity ? 1 : rhs | 0; 48 | const s = max ? `{${min}}` : `{${min},${max}}`; 49 | return flags(lhs.flags, suffix(s, lhs)); 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/rewrite.ts: -------------------------------------------------------------------------------- 1 | import { signature, Any } from '@hypercubed/dynamo'; 2 | import is from '@sindresorhus/is'; 3 | import { getIn } from 'icepick'; 4 | 5 | import { Vocabulary } from '../engine/vocabulary'; 6 | 7 | import { 8 | dynamo, 9 | StackValue, 10 | Sentence, 11 | Word, 12 | ReturnValues, 13 | } from '../types'; 14 | 15 | import { IIF } from '../constants'; 16 | 17 | function create(dictObject: Object | undefined) { 18 | const wordPaths: string[] = []; 19 | 20 | class Rewrite { 21 | @signature() 22 | array(arr: any[]) { 23 | return arr.reduce((p, i) => { 24 | const n = _rewrite(i); 25 | n instanceof ReturnValues ? p.push(...n.value) : p.push(n); 26 | return p; 27 | }, []); 28 | } 29 | @signature() 30 | Sentence(action: Sentence) { 31 | const expandedValue = _rewrite(action.value); 32 | return new ReturnValues(expandedValue); 33 | } 34 | @signature() 35 | Word(action: Word) { 36 | if (wordPaths.includes(action.value as string)) { 37 | return action; 38 | } 39 | const path = Vocabulary.makePath(action.value as string); 40 | const value: StackValue = getIn(dictObject, path as string[]); 41 | 42 | if ( 43 | is.undefined(value) && 44 | (typeof action.value !== 'string' || 45 | !(action.value as string).endsWith(IIF)) 46 | ) 47 | return action; 48 | if (is.function_(value)) return new ReturnValues([action]); 49 | 50 | wordPaths.push(action.value as string); 51 | const ret = _rewrite(value); 52 | wordPaths.pop(); 53 | return ret; 54 | } 55 | @signature() 56 | plainObject(obj: Object) { 57 | return Object.keys(obj).reduce((p, key) => { 58 | const n = _rewrite(obj[key]); // todo: think about this, do we ever want to work on anything other than {string: Array}? 59 | n instanceof ReturnValues 60 | ? (p[key] = n.value.length === 1 ? n.value[0] : n.value) 61 | : (p[key] = n); 62 | return p; 63 | }, {}); 64 | } 65 | @signature(Any) 66 | any(y: any) { 67 | return y; 68 | } 69 | } 70 | 71 | const _rewrite = dynamo.function(Rewrite); 72 | return _rewrite; 73 | } 74 | 75 | export function rewrite(x: Object, y: any) { 76 | const _rewrite = create(x); 77 | try { 78 | return _rewrite(y); 79 | } catch (e) { 80 | throw e; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/utils/stringConversion.ts: -------------------------------------------------------------------------------- 1 | import * as punycode from 'punycode'; 2 | 3 | import { StackEnv } from '../engine/env'; 4 | 5 | const cap = '${expression-hole}'; 6 | const reExpression = /\$\([^\)]*\)/g; 7 | 8 | function parts(str: string) { 9 | const queues: any = []; 10 | 11 | const raw = str 12 | .replace(reExpression, x => { 13 | queues.push(x.slice(2, -1)); 14 | return cap; 15 | }) 16 | .split(cap); 17 | return { raw, queues }; 18 | } 19 | 20 | export function templateParts(stack: StackEnv, str: string, sen?: any): any { 21 | const { raw, queues } = parts(str); 22 | 23 | const strings = raw.map(s => unicodeEscape(s)); 24 | const stacks = queues.map(q => { 25 | const c = stack.createChild().eval(q); 26 | if (sen) c.eval(sen); 27 | return c.stack; 28 | }); 29 | return { raw, strings, stacks }; 30 | } 31 | 32 | export function templateSubstitute(str: string, values: any[]) { 33 | return str.replace(reExpression, x => values.shift()); 34 | } 35 | 36 | export function template(stack: StackEnv, template: string, sen?: any): string { 37 | let { strings, stacks } = templateParts(stack, template, sen); 38 | const values = stacks.map(s => 39 | unicodeEscape(s.map(x => String(x)).join(' ')) 40 | ); 41 | 42 | return strings 43 | .reduce((acc, s) => { 44 | acc.push(s); 45 | const val = values.shift(); 46 | if (val) acc.push(val); 47 | return acc; 48 | }, []) 49 | .join(''); 50 | } 51 | 52 | export function unescapeString(x: string): string { 53 | return unicodeEscape(convertjEsc2Char(String(x), true)); 54 | } 55 | 56 | // following code from https://mathiasbynens.be/notes/javascript-encoding#comment-8 57 | function unicodeEscape(x: string): string { 58 | // note: this will match `u{123}` (with leading `\`) as well 59 | return x.replace(/\\u\{([0-9a-fA-F]{1,8})\}/g, ($0, $1) => { 60 | return punycode.ucs2.encode([parseInt($1, 16)]); 61 | }); 62 | } 63 | 64 | /* 65 | following code from http://www.rishida.net/tools/conversion/conversionfunctions.js 66 | 67 | Copyright (C) 2007 Richard Ishida ishida@w3.org 68 | This program is free software; you can redistribute it and/or modify it under the terms 69 | of the GNU General Public License as published by the Free Software Foundation; either 70 | version 2 of the License, or (at your option) any later version as long as you point to 71 | http://rishida.net/ in your code. 72 | */ 73 | 74 | function dec2hex(x: number): string { 75 | return (x + 0).toString(16).toUpperCase(); 76 | } 77 | 78 | function hex2char(hex: string): string { 79 | // converts a single hex number to a character 80 | // note that no checking is performed to ensure that this is just a hex number, eg. no spaces etc 81 | // hex: string, the hex codepoint to be converted 82 | 83 | let result = ''; 84 | let n = parseInt(hex, 16); 85 | if (n <= 0xffff) { 86 | result += String.fromCharCode(n); 87 | } else if (n <= 0x10ffff) { 88 | n -= 0x10000; 89 | result += 90 | String.fromCharCode(0xd800 | (n >> 10)) + 91 | String.fromCharCode(0xdc00 | (n & 0x3ff)); 92 | } else { 93 | result += `hex2Char error: Code point out of range: ${dec2hex(n)}`; 94 | } 95 | return result; 96 | } 97 | 98 | function convertjEsc2Char(str: string, shortEscapes: boolean): string { 99 | // converts a string containing JavaScript or Java escapes to a string of characters 100 | // str: string, the input 101 | // shortEscapes: boolean, if true the function will convert \b etc to characters 102 | 103 | // convert \U and 6 digit escapes to characters 104 | str = str.replace(/\\U([A-Fa-f0-9]{8})/g, (matchstr, parens) => 105 | hex2char(parens) 106 | ); 107 | 108 | // convert \u and 6 digit escapes to characters 109 | str = str.replace(/\\u([A-Fa-f0-9]{4})/g, (matchstr, parens) => 110 | hex2char(parens) 111 | ); 112 | 113 | // convert \b etc to characters, if flag set 114 | if (shortEscapes) { 115 | // str = str.replace(/\\0/g, '\0'); 116 | str = str.replace(/\\b/g, '\b'); 117 | str = str.replace(/\\t/g, '\t'); 118 | str = str.replace(/\\n/g, '\n'); 119 | str = str.replace(/\\v/g, '\v'); 120 | str = str.replace(/\\f/g, '\f'); 121 | str = str.replace(/\\r/g, '\r'); 122 | str = str.replace(/\\'/g, `'`); 123 | str = str.replace(/\\"/g, '"'); 124 | str = str.replace(/\\\\/g, '\\'); 125 | } 126 | return str; 127 | } 128 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { signature, Any } from '@hypercubed/dynamo'; 2 | 3 | import { dynamo, Key, Word, Sentence, Decimal, Complex } from '../types/index'; 4 | 5 | class GetType { 6 | @signature([Word, Sentence, Key]) 7 | words(x: Word | Sentence | Key) { 8 | return 'action'; 9 | } 10 | @signature(Array) 11 | array(x: any[]) { 12 | return 'array'; 13 | } 14 | @signature([Number, Decimal]) 15 | Decimal(x: Decimal) { 16 | return 'number'; 17 | } 18 | @signature() 19 | Complex(x: Complex) { 20 | return 'complex'; 21 | } 22 | @signature() 23 | Date(x: Date) { 24 | return 'date'; 25 | } 26 | @signature() 27 | RegExp(x: RegExp) { 28 | return 'regexp'; 29 | } 30 | @signature(Any) 31 | any(x: unknown) { 32 | return typeof x; 33 | } 34 | } 35 | 36 | // TODO: get type name from dynamo? 37 | export const type = dynamo.function(GetType); 38 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { signature, Any } from '@hypercubed/dynamo'; 2 | import { dynamo, Word, Sentence, Decimal, Complex } from '../types/index'; 3 | 4 | export const arrayRepeat = (a: any[], len: any) => { 5 | len = Number(len) | 0; 6 | let acc: any[] = []; 7 | if (len < 1) { 8 | return acc; 9 | } 10 | let i = -1; 11 | while (++i < len) { 12 | acc = acc.concat(a); 13 | } 14 | return acc; 15 | }; 16 | 17 | export const arrayMul = (lhs: any[], rhs: any) => { 18 | const len = lhs.length; 19 | let acc: any[] = []; 20 | if (len < 1) { 21 | return acc; 22 | } 23 | let i = -1; 24 | while (++i < len) { 25 | acc = acc.concat([lhs[i]], rhs); 26 | } 27 | return acc; 28 | }; 29 | 30 | export const arrayInvMul = (lhs: any[], rhs: any) => { 31 | const len = lhs.length; 32 | let acc: any[] = []; 33 | if (len < 1) { 34 | return acc; 35 | } 36 | let i = len; 37 | while (--i > -1) { 38 | acc = acc.concat([lhs[i]], rhs); 39 | } 40 | return acc; 41 | }; 42 | 43 | function objEquiv(a: {}, b: {}): boolean { 44 | if (typeof a === 'undefined' || typeof b === 'undefined') return a === b; 45 | if (a === null || b === null) return a === b; 46 | 47 | const ka = Object.keys(a); 48 | const kb = Object.keys(b); 49 | 50 | if (ka.length !== kb.length) return false; 51 | 52 | ka.sort(); 53 | kb.sort(); 54 | 55 | // key test 56 | for (let i = ka.length - 1; i >= 0; i--) { 57 | if (ka[i] != kb[i]) return false; // tslint:disable-line 58 | } 59 | 60 | // object test 61 | for (let i = ka.length - 1; i >= 0; i--) { 62 | const key = ka[i]; 63 | if (!deepEquals(a[key], b[key])) return false; 64 | } 65 | return typeof a === typeof b; 66 | } 67 | 68 | class Equal { 69 | @signature() 70 | 'Array, Array'(a: any[], b: any[]): boolean { 71 | if (a.length !== b.length) { 72 | return false; 73 | } 74 | for (let i = 0; i < a.length; i++) { 75 | if (!deepEquals(a[i], b[i])) { 76 | return false; 77 | } 78 | } 79 | return true; 80 | } 81 | 82 | @signature([Word, Sentence], [Word, Sentence]) 83 | 'Word, Word'(a: Word, b: Word): boolean { 84 | return a.value === b.value; 85 | } 86 | 87 | @signature() 88 | 'number, number'(a: number, b: number): boolean { 89 | if (Object.is(a, -0)) return Object.is(b, -0); 90 | if (Number.isNaN(a)) return Number.isNaN(b); 91 | return a === b; 92 | } 93 | 94 | @signature() 95 | 'Decimal, Decimal'(a: Decimal, b: Decimal): boolean { 96 | // if (a.isZero() && b.isZero()) return a.isPos() === b.isPos(); 97 | if (a.isNaN()) return b.isNaN(); 98 | return a.equals(b); 99 | } 100 | 101 | @signature() 102 | 'Complex, Complex'(a: Complex, b: Complex): boolean { 103 | // if (a.isZero() && b.isZero()) return a.isPos() === b.isPos(); 104 | if (a.isNaN()) return b.isNaN(); 105 | return a.equals(b); 106 | } 107 | 108 | @signature(Any, Date) 109 | @signature(Date, Any) 110 | 'Date, any'(a: any, b: any): boolean { 111 | return +a === +b; 112 | } 113 | 114 | @signature() 115 | 'RegExp, RegExp'(a: RegExp, b: RegExp): boolean { 116 | return a.toString() === b.toString(); 117 | } 118 | 119 | @signature(Object, Object) 120 | 'Object, Object' = objEquiv; 121 | 122 | @signature(Any, Any) 123 | 'any, any'(a: any, b: any): boolean { 124 | return false; 125 | } 126 | } 127 | 128 | const __eql = dynamo.function(Equal); 129 | 130 | export function deepEquals(a: any, b: any): boolean { 131 | return a === b ? true : __eql(a, b); 132 | } 133 | 134 | class ToObject { 135 | @signature() 136 | array(a: any[]) { 137 | // hash-map 138 | const r = {}; 139 | const l = a.length; 140 | for (let i = 0; l - i > 1; i++) { 141 | Object.assign(r, { [a[i++]]: a[i] }); 142 | } 143 | return r; 144 | } 145 | 146 | @signature(Any) 147 | any = Object; 148 | } 149 | 150 | export const toObject = dynamo.function(ToObject); 151 | -------------------------------------------------------------------------------- /test/__snapshots__/json.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`deep 1`] = `"{\\"string\\":\\"a_string\\",\\"number\\":42,\\"decimal\\":{\\"$numberDecimal\\":\\"42\\"},\\"array\\":[\\"a_string\\",42,{\\"$numberDecimal\\":\\"42\\"}],\\"object\\":{\\"also a string\\":\\"string\\",\\"a number\\":42}}"`; 4 | -------------------------------------------------------------------------------- /test/core-ff.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ, τ } from './helpers/setup'; 2 | 3 | test('stackd', async () => { 4 | expect(await ƒ('1 2 3 4 stackd')).toBe(`[ [ 1 2 3 ] 4 ]`); 5 | }); 6 | 7 | test('nullary', async () => { 8 | expect(await ƒ('1 2 3 [ 3 * ] nullary')).toBe(`[ 1 2 3 9 ]`); 9 | }); 10 | 11 | test('should branch on truthy and falsy', async () => { 12 | expect(await ƒ('5 false [ 2 + ] [ 2 * ] branch')).toEqual(τ([10])); 13 | expect(await ƒ('5 true [ 2 + ] [ 2 * ] branch')).toEqual(τ([7])); 14 | // expect(await ƒ('5 null [ 2 + ] [ 2 * ] branch')).toEqual(τ([10])); 15 | expect(await ƒ('5 "this is truthy" [ 2 + ] [ 2 * ] branch')).toEqual(τ([7])); 16 | }); 17 | 18 | test('should slice', async () => { 19 | expect(await ƒ('["a" "b" "c" "d"] 0 1 slice')).toEqual(τ([['a']])); 20 | expect(await ƒ('["a" "b" "c" "d"] 0 -1 slice')).toEqual(τ([['a', 'b', 'c']])); 21 | expect(await ƒ('["a" "b" "c" "d"] 1 -1 slice')).toEqual(τ([['b', 'c']])); 22 | }); 23 | 24 | test('map', async () => { 25 | expect(await ƒ('[ 3 2 1 ] [ 2 * ] map')).toEqual(τ([[6, 4, 2]])); 26 | expect(await ƒ('[ -3 -2 -1 ] [ abs ] map')).toEqual(τ([[3, 2, 1]])); 27 | }); 28 | 29 | test('filter and reduce', async () => { 30 | expect(await ƒ('[ 10 2 5 3 1 6 7 4 2 3 4 8 9 ] [ even? ] filter')).toEqual( 31 | `[ [ 10 2 6 4 2 4 8 ] ]` 32 | ); 33 | expect(await ƒ('10 integers [ even? ] filter')).toEqual(`[ [ 2 4 6 8 10 ] ]`); 34 | expect(await ƒ('10 integers 0 [ + ] reduce')).toEqual(`[ 55 ]`); 35 | expect(await ƒ('10 integers 1 [ * ] reduce')).toEqual(`[ 3628800 ]`); 36 | expect(await ƒ('10 integers [ + ] fold')).toEqual(`[ 55 ]`); 37 | expect(await ƒ('10 integers [ * ] fold')).toEqual(`[ 3628800 ]`); 38 | }); 39 | 40 | test('zipwith', async () => { 41 | expect(await ƒ('[ 1 2 3 ] [ 4 5 6 ] [ + ] zipwith in')).toEqual( 42 | `[ [ 5 7 9 ] ]` 43 | ); 44 | }); 45 | 46 | test('dot', async () => { 47 | expect(await ƒ('[ 1 2 3 ] [ 4 5 6 ] dot')).toEqual(`[ 32 ]`); 48 | expect(await ƒ('[ 1 2 3 4 ] [ 4 5 6 ] dot')).toEqual(`[ 32 ]`); 49 | }); 50 | 51 | test('!=', async () => { 52 | expect(await ƒ('1 2 =')).toEqual(`[ false ]`); 53 | expect(await ƒ('1 i * 2 i * =')).toEqual(`[ false ]`); 54 | expect(await ƒ('null 2 =')).toEqual(`[ false ]`); 55 | expect(await ƒ('nan 2 =')).toEqual(`[ false ]`); 56 | expect(await ƒ('{ x: 1 } { x: 2 } =')).toEqual(`[ false ]`); 57 | expect(await ƒ('"1/1/1990" date "1/2/1990" date =')).toEqual(`[ false ]`); 58 | }); 59 | 60 | test('should foldl and foldr', async () => { 61 | expect(await ƒ('10 integers 0 [+] foldl')).toEqual(`[ 55 ]`); 62 | expect(await ƒ('10 integers 0 [+] foldr')).toEqual(`[ 55 ]`); 63 | expect(await ƒ('10 integers 0 [-] foldl')).toEqual(`[ -55 ]`); 64 | expect(await ƒ('10 integers 0 [-] foldr')).toEqual(`[ -5 ]`); 65 | }); 66 | 67 | test('should uncons', async () => { 68 | expect(await ƒ('(5 4 3) uncons')).toEqual(`[ 5 [ 4 3 ] ]`); 69 | }); 70 | 71 | test('nop', async () => { // core-ff 72 | expect(await ƒ('"abc" nop')).toEqual(`[ 'abc' ]`); 73 | // t.deepEqual(await fJSON('"abc" id'), ['abc']); 74 | }); 75 | -------------------------------------------------------------------------------- /test/core.spec.ts: -------------------------------------------------------------------------------- 1 | import { F, Decimal, ƒ, τ } from './helpers/setup'; 2 | 3 | test('choose', async () => { 4 | expect(await ƒ('true 3 4 choose')).toEqual(τ([3])); 5 | expect(await ƒ('false 3 4 choose')).toEqual(τ([4])); 6 | }); 7 | 8 | describe('@', () => { 9 | test('pick', async () => { 10 | expect(await ƒ('{a: 1} a: @')).toEqual(`[ 1 ]`); 11 | expect(await ƒ('{a: 2} "a" @')).toEqual(`[ 2 ]`); 12 | expect(await ƒ('{a: 3} b: @')).toEqual(`[ null ]`); // s/b undefined? 13 | expect(await ƒ('{a: {b: 5}} "a.b" @')).toEqual(`[ 5 ]`); 14 | expect(await ƒ('{a: {b: 5}} a.b: @')).toEqual(`[ 5 ]`); 15 | expect(await ƒ('{a: 7} "A" lcase @')).toEqual(`[ 7 ]`); 16 | }); 17 | 18 | test('pick into object', async () => { 19 | expect(await ƒ('{ a: { a: 1 } a: @ }')).toEqual(`[ { a: 1 } ]`); 20 | expect(await ƒ('{ a: 2 } q< { a: q> over @ }')).toEqual(`[ { a: 2 } ]`); 21 | expect(await ƒ('{ a: 3 } q< { b: q> a: @ }')).toEqual(`[ { b: 3 } ]`); 22 | }); 23 | 24 | test('pick into object with default', async () => { 25 | expect(await ƒ('{ a: { a: 1 } b: @ 2 orelse }')).toEqual(`[ { a: 2 } ]`); 26 | expect(await ƒ('{ a: 3 } q< { b: q> over @ 5 orelse }')).toEqual( 27 | `[ { b: 5 } ]` 28 | ); 29 | expect(await ƒ('{ a: 7 } q< { c: q> b: @ 11 orelse }')).toEqual( 30 | `[ { c: 11 } ]` 31 | ); 32 | }); 33 | 34 | test('pick into array', async () => { 35 | expect(await ƒ('( { a: 1 } a: @ )')).toEqual(`[ [ 1 ] ]`); 36 | }); 37 | 38 | test('pick from array', async () => { 39 | expect(await ƒ('[1 2] 0 @')).toEqual(`[ 1 ]`); 40 | expect(await ƒ('[3 5] 1 @')).toEqual(`[ 5 ]`); 41 | expect(await ƒ('([7 11] 0 @)')).toEqual(`[ [ 7 ] ]`); 42 | }); 43 | 44 | test('pick from null', async () => { 45 | expect(await ƒ('null 0 @')).toEqual(`[ null ]`); 46 | expect(await ƒ('[ 1 2 3 ] null @')).toEqual(`[ null ]`); 47 | expect(await ƒ('null null @')).toEqual(`[ null ]`); 48 | }); 49 | }); 50 | 51 | test('q< q>', async () => { 52 | expect(await ƒ('1 2 q< 3 q>')).toEqual(`[ 1 3 2 ]`); 53 | }); 54 | 55 | test('stack unstack', async () => { 56 | expect(await ƒ('1 2 3 stack')).toEqual(`[ [ 1 2 3 ] ]`); 57 | expect(await ƒ('[ 1 2 3 ] unstack')).toEqual(`[ 1 2 3 ]`); 58 | expect(await ƒ('1 2 3 [ 4 5 6 ] unstack')).toEqual(`[ 1 2 3 4 5 6 ]`); 59 | expect(await ƒ('[2 1 +] unstack')).toEqual(`[ 2 1 + ]`); // check this... action on the stack!! 60 | }); 61 | 62 | test('clr', async () => { 63 | expect(await ƒ('1 2 clr 3')).toEqual(τ([3])); 64 | }); 65 | 66 | test('depth', async () => { 67 | expect(await ƒ('"abc" depth')).toEqual(`[ 'abc' 1 ]`); // todo: return a decimal 68 | expect(await ƒ('"abc" 123 depth')).toEqual(`[ 'abc' 123 2 ]`); 69 | }); 70 | 71 | // eval 72 | 73 | describe('in', () => { 74 | test('in', async () => { 75 | expect(await ƒ('[ 2 1 + ] in')).toEqual(`[ [ 3 ] ]`); 76 | }); 77 | 78 | test('clr in', async () => { 79 | expect(await ƒ('1 2 [ 2 1 clr 3 ] in')).toEqual(`[ 1 2 [ 3 ] ]`); 80 | }); 81 | }); 82 | 83 | test('in-catch', async () => { 84 | expect(await ƒ(`[ 1 2 + ] [ 'err' ] in-catch`)).toEqual(`[ [ 3 ] ]`); 85 | expect(await ƒ(`[ 1 '2' + ] [ 'err' ] in-catch`)).toEqual(`[ [ 'err' ] ]`); 86 | }); 87 | 88 | test('throw', () => { 89 | expect(ƒ(`'Err' throw`)).rejects.toThrow('Err'); 90 | }); 91 | 92 | test('send', async () => { 93 | expect(await ƒ('[ 1 2 3 send 4 ] in')).toEqual(`[ 3 [ 1 2 4 ] ]`); 94 | }); 95 | 96 | test('drop', async () => { 97 | expect(await ƒ('1 2 drop 3')).toEqual(τ([1, 3])); 98 | }); 99 | 100 | test('swap', async () => { 101 | expect(await ƒ('1 2 swap 3')).toEqual(τ([2, 1, 3])); 102 | expect(await ƒ('"abc" "def" swap')).toEqual(`[ 'def' 'abc' ]`); 103 | expect(await ƒ('abc: def: swap')).toEqual(`[ def: abc: ]`); 104 | }); 105 | 106 | describe('dup', () => { 107 | test('dup', async () => { 108 | expect(await ƒ('1 2 dup 3')).toEqual(τ([1, 2, 2, 3])); 109 | expect(await ƒ('[ 1 2 + ] dup swap drop eval')).toEqual(τ([3])); 110 | expect(await ƒ('abc: dup')).toEqual(`[ abc: abc: ]`); 111 | }); 112 | 113 | test('dup arrays', async () => { 114 | const f = F().eval('[ 1 2 3 ] dup'); 115 | expect(f.stack).toEqual([ 116 | [1, 2, 3].map(x => new Decimal(x)), 117 | [1, 2, 3].map(x => new Decimal(x)) 118 | ]); 119 | expect(f.stack[0]).toBe(f.stack[1]); 120 | }); 121 | }); 122 | 123 | test('indexof', async () => { 124 | expect(await ƒ('"abc" "b" indexof')).toEqual(`[ 1 ]`); 125 | expect(await ƒ(`[ 'a' 'b' 'c' ] "c" indexof`)).toEqual(`[ 2 ]`); 126 | expect(await ƒ(`[ 1 2 3 ] 2 indexof`)).toEqual(`[ 1 ]`); 127 | expect(await ƒ(`{ x: 1, y: 2 } 1 indexof`)).toEqual(`[ 'x' ]`); 128 | expect(await ƒ(`{ x: 1, y: 2 } 2 indexof`)).toEqual(`[ 'y' ]`); 129 | }); 130 | 131 | test('zip', async () => { 132 | expect(await ƒ('[ 1 2 3 ] [ 4 5 6 ] zip')).toEqual(`[ [ 1 4 2 5 3 6 ] ]`); 133 | }); 134 | 135 | test('zipinto', async () => { 136 | expect(await ƒ('[ 1 2 3 ] [ 4 5 6 ] [ 7 8 9 ] zipinto')).toEqual(`[ [ 1 4 7 8 9 2 5 7 8 9 3 6 7 8 9 ] ]`); 137 | }); 138 | 139 | // template 140 | // sleep 141 | 142 | 143 | -------------------------------------------------------------------------------- /test/dates.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ, τ } from './helpers/setup'; 2 | 3 | const d = new Date('1990-01-01T07:00:00.000Z'); 4 | 5 | test('should create dates', async () => { 6 | expect(await ƒ('"1/1/1990" date')).toBe(τ`[ ${d} ]`); 7 | expect(await ƒ('"1990-01-01T07:00:00.000Z" date')).toBe(τ`[ ${d} ]`); 8 | }); 9 | 10 | test('should convert to numbers', async () => { 11 | expect(await ƒ('"1/1/1990" date number')).toBe(`[ 631177200000 ]`); 12 | }); 13 | 14 | test('should check dates type', async () => { 15 | expect(await ƒ('"1/1/1990" date type')).toBe(`[ 'date' ]`); 16 | }); 17 | 18 | test('should generate current date', async () => { 19 | expect(await ƒ('now type')).toBe(`[ 'date' ]`); 20 | }); 21 | 22 | test('should perform basic arithmetic', async () => { 23 | expect(await ƒ('"1/1/1990" date 1000 60 * 60 * 24 * +')).toBe( 24 | τ`[ ${new Date('1990-01-02T07:00:00.000Z')} ]` 25 | ); 26 | expect(await ƒ('"1/1/1990" date 1000 60 * 60 * 24 * -')).toBe( 27 | τ`[ ${new Date('1989-12-31T07:00:00.000Z')} ]` 28 | ); 29 | }); 30 | 31 | test('should test equality', async () => { 32 | expect(await ƒ('"1/1/1990" date "1/1/1990" date =')).toBe(`[ true ]`); 33 | expect(await ƒ('"1/1/1990" date "1/2/1990" date =')).toBe(`[ false ]`); 34 | }); 35 | 36 | test('should compare', async () => { 37 | expect(await ƒ('"1/1/1990" date "1/1/1990" date <=>')).toBe(`[ 0 ]`); 38 | expect(await ƒ('"1/1/1990" date "1/1/1970" date <=>')).toBe(`[ 1 ]`); 39 | expect(await ƒ('"1/1/1990" date "1/1/2000" date <=>')).toBe(`[ -1 ]`); 40 | }); 41 | 42 | test('should test inequality', async () => { 43 | expect(await ƒ('"1/1/1990" date "1/1/1990" date <')).toBe(`[ false ]`); 44 | expect(await ƒ('"1/1/1990" date "1/1/1990" date >')).toBe(`[ false ]`); 45 | expect(await ƒ('"1/1/1960" date "1/1/1990" date <')).toBe(`[ true ]`); 46 | expect(await ƒ('"1/1/1960" date "1/1/1990" date >')).toBe(`[ false ]`); 47 | expect(await ƒ('"1/1/1990" date "1/1/1960" date <')).toBe(`[ false ]`); 48 | expect(await ƒ('"1/1/1990" date "1/1/1960" date >')).toBe(`[ true ]`); 49 | }); 50 | 51 | test('should get day', async () => { 52 | expect(await ƒ('"1/1/1990" date day')).toBe(`[ 'Mon' ]`); 53 | expect(await ƒ('"1/1/1990" date 1000 60 * 60 * 24 * - day')).toBe( 54 | `[ 'Sun' ]` 55 | ); 56 | }); 57 | 58 | test('date works as a "macro"', async () => { 59 | expect(await ƒ('"1/1/1990":date')).toBe(τ`[ ${d} ]`); 60 | expect(await ƒ('[ "1/1/1990":date ]')).toBe(τ`[ [ ${d} ] ]`); 61 | }); 62 | -------------------------------------------------------------------------------- /test/flags.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ } from './helpers/setup'; 2 | 3 | test('get-system-property', async () => { 4 | expect(await ƒ(`'log-level' get-system-property`)).toEqual(`[ 'test' ]`); 5 | expect(await ƒ(`'decimal-precision' get-system-property`)).toEqual(`[ 20 ]`); 6 | expect(await ƒ(`'auto-undo' get-system-property`)).toEqual(`[ true ]`); 7 | 8 | expect(ƒ(`'unknown-prop' get-system-property`)).rejects.toThrow( 9 | `'get-system-property' value is not a valid flag: "unknown-prop"` 10 | ); 11 | }); 12 | 13 | test('set-system-property', async () => { 14 | expect(await ƒ(`'decimal-precision' 20 set-system-property`)).toEqual(`[ ]`); 15 | expect(ƒ(`'unknown-prop' true set-system-property`)).rejects.toThrow(`'set-system-property' value is not a valid flag: "unknown-prop"`); 16 | }); -------------------------------------------------------------------------------- /test/helpers/setup.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import stripAnsi from 'strip-ansi'; 3 | 4 | import { createStack } from '../../src/stack'; 5 | import { log } from '../../src/utils/logger'; 6 | import { ffPrettyPrint } from '../../src/utils/pprint'; 7 | 8 | const { trace, stringify } = ffPrettyPrint; 9 | 10 | export * from '../../src/types'; 11 | 12 | // TODO: create test user directory fixtures 13 | process.chdir(join(__filename, '../../../src/ff-lib/')); 14 | 15 | log.level = process.env.NODE_ENV || 'error'; 16 | 17 | export { createStack as F }; 18 | 19 | /** 20 | * Evaluates the input async 21 | * returns the stack as a JSON object 22 | */ 23 | export async function μ(strings: any, ...values: any[]): Promise { 24 | if (arguments.length > 1) { 25 | strings = τ(strings, ...values); 26 | } 27 | const f = await createStack().promise(strings); 28 | return f.toJSON(); 29 | } 30 | 31 | /** 32 | * Evaluates the input async 33 | * returns the stack as a string 34 | */ 35 | export async function ƒ(strings: any, ...values: any[]): Promise { 36 | if (arguments.length > 1) { 37 | strings = τ(strings, ...values); 38 | } 39 | const f = await createStack().promise(strings); 40 | return stripAnsi(trace(f.stack)); 41 | } 42 | 43 | /** 44 | * stringifies the input 45 | */ 46 | export function τ(strings: any, ...values: any[]) { 47 | if (arguments.length === 1) { 48 | return stringify(strings); 49 | } 50 | let result = [strings[0]]; 51 | values.forEach((value, i) => { 52 | result.push(stringify(value), strings[i + 1]); 53 | }); 54 | return result.join(''); 55 | } 56 | -------------------------------------------------------------------------------- /test/lists.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ, τ } from './helpers/setup'; 2 | 3 | test('should push lists', async () => { 4 | expect(await ƒ('( 1 ) ( 2 )')).toEqual(`[ [ 1 ] [ 2 ] ]`); 5 | expect(await ƒ('(1) (2)')).toEqual(`[ [ 1 ] [ 2 ] ]`); 6 | expect(await ƒ('(1) (2 3 +)')).toEqual(`[ [ 1 ] [ 5 ] ]`); 7 | }); 8 | 9 | test('should evaluate quoted lists', async () => { 10 | expect(await ƒ('[ ( 1 2 + ) ] in')).toEqual(`[ [ [ 3 ] ] ]`); 11 | }); 12 | 13 | test('should get length', async () => { 14 | expect(await ƒ('( 1 2 ) ln')).toEqual(`[ 2 ]`); 15 | }); 16 | 17 | test('should add', async () => { 18 | expect(await ƒ('(1) (2) +')).toEqual(`[ [ 1 2 ] ]`); 19 | expect(await ƒ('(1 2 3) dup (4 5 6) +')).toEqual( 20 | τ([ 21 | [1, 2, 3], 22 | [1, 2, 3, 4, 5, 6] 23 | ]) 24 | ); 25 | }); 26 | 27 | test('should multiply', async () => { 28 | expect(await ƒ('(1) 2 *')).toEqual(`[ [ 1 1 ] ]`); 29 | expect(await ƒ('(1) 3 *')).toEqual(`[ [ 1 1 1 ] ]`); 30 | expect(await ƒ('(1 2) 2 *')).toEqual(`[ [ 1 2 1 2 ] ]`); 31 | expect(await ƒ('(1 2 +) 2 *')).toEqual(`[ [ 3 3 ] ]`); 32 | expect(await ƒ('(1 2 +) 0 *')).toEqual(`[ [ ] ]`); 33 | }); 34 | 35 | test('mul identities', async () => { 36 | expect(await ƒ('(1) 3 * sum')).toEqual(`[ 3 ]`); 37 | expect(await ƒ('(2) 3 * sum')).toEqual(`[ 6 ]`); 38 | expect(await ƒ('(1) 3 * 3 / + sum')).toEqual(`[ 3 ]`); 39 | expect(await ƒ('(2) 3 * 3 / + sum')).toEqual(`[ 6 ]`); 40 | }); 41 | 42 | test('div identities', async () => { 43 | expect(await ƒ('(1) 1 /')).toEqual(`[ [ 1 ] [ ] ]`); 44 | expect(await ƒ('(1 2) 1 /')).toEqual(`[ [ 1 ] [ 2 ] ]`); 45 | expect(await ƒ('(1 2 3 4) 2 /')).toEqual(`[ [ 1 2 ] [ 3 4 ] ]`); 46 | }); 47 | 48 | test('add/sub identities', async () => { // math-ff 49 | expect(await ƒ('(1) 2 + sum')).toEqual(`[ 3 ]`); 50 | }); 51 | 52 | test('pow identities', async () => { // math-ff 53 | // right associative 54 | expect(await ƒ('(1) 2 pow')).toEqual(`[ [ 1 1 ] ]`); // should work with ^ 55 | expect(await ƒ('(2) 3 pow')).toEqual(`[ [ 2 2 2 ] ]`); 56 | expect(await ƒ('(1 1) 3 pow')).toEqual(`[ [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ] ]`); 57 | expect(await ƒ('(1 2) 3 pow')).toEqual(`[ [ 1 1 1 2 2 1 2 2 1 1 2 2 1 2 ] ]`); 58 | // t.deepEqual(await ƒ('(1) 2 + 2 - sum', [1]); // ??? 59 | }); 60 | 61 | test('should test equality', async () => { 62 | expect(await ƒ('(1 2) (1 2) =')).toEqual(`[ true ]`); 63 | expect(await ƒ('(1) (2) =')).toEqual(`[ false ]`); 64 | expect(await ƒ('(1 2) (1) =')).toEqual(`[ false ]`); 65 | expect(await ƒ('(1 2) (1 1) =')).toEqual(`[ false ]`); 66 | }); 67 | 68 | test('should eval lists', async () => { 69 | expect(await ƒ('(1 2) eval')).toEqual(`[ 1 2 ]`); 70 | }); 71 | 72 | test('should zip lists', async () => { 73 | expect(await ƒ('( 1 2 3 ) ( 4 ) *')).toEqual(`[ [ 1 4 2 4 3 4 ] ]`); 74 | }); 75 | 76 | test('should join', async () => { 77 | expect(await ƒ('( 1 2 3 ) "-" *')).toEqual(`[ '1-2-3' ]`); 78 | }); 79 | 80 | test('should <<', async () => { 81 | expect(await ƒ('( 1 2 3 ) 4 <<')).toEqual(`[ [ 1 2 3 4 ] ]`); 82 | expect(await ƒ('( 1 2 3 ) dup 4 <<')).toEqual(`[ [ 1 2 3 ] [ 1 2 3 4 ] ]`); 83 | }); 84 | 85 | test('should >>', async () => { 86 | expect(await ƒ('4 ( 1 2 3 ) >>')).toEqual(`[ [ 4 1 2 3 ] ]`); 87 | expect(await ƒ('4 ( 1 2 3 ) tuck >>')).toEqual(`[ [ 1 2 3 ] [ 4 1 2 3 ] ]`); 88 | }); 89 | 90 | test('should @', async () => { 91 | expect(await ƒ('( 4 5 6 ) 0 @')).toEqual(`[ 4 ]`); 92 | expect(await ƒ('( 4 5 6 ) 1 @')).toEqual(`[ 5 ]`); 93 | expect(await ƒ('( 4 5 6 ) 2 @')).toEqual(`[ 6 ]`); 94 | }); 95 | 96 | test('should @ from end', async () => { 97 | expect(await ƒ('( 4 5 6 ) -1 @')).toEqual(`[ 6 ]`); 98 | expect(await ƒ('( 4 5 6 ) -2 @')).toEqual(`[ 5 ]`); 99 | expect(await ƒ('( 4 5 6 ) -3 @')).toEqual(`[ 4 ]`); 100 | }); 101 | 102 | test('should @ out of range', async () => { 103 | expect(await ƒ('( 4 5 6 ) 10 @')).toEqual(`[ null ]`); // undefined? 104 | expect(await ƒ('( 4 5 6 ) -10 @')).toEqual(`[ null ]`); 105 | }); 106 | 107 | test('should pop and shift without mutation', async () => { 108 | expect(await ƒ('( 1 2 3 ) dup pop')).toEqual(`[ [ 1 2 3 ] [ 1 2 ] ]`); 109 | expect(await ƒ('( 1 2 3 ) dup shift')).toEqual(`[ [ 1 2 3 ] [ 2 3 ] ]`); 110 | }); 111 | 112 | test('should fine maximum and minimum', async () => { // math-ff 113 | expect(await ƒ('( 2 3 1 6 3 ) maximum')).toEqual(`[ 6 ]`); 114 | expect(await ƒ('( 2 3 1 6 3 ) minimum')).toEqual(`[ 1 ]`); 115 | }); 116 | 117 | test('should split at', async () => { // base 118 | expect(await ƒ('["a" "b" "c" "d"] 1 /')).toEqual(τ([['a'], ['b', 'c', 'd']])); 119 | expect(await ƒ('["a" "b" "c" "d"] -1 /')).toEqual( 120 | τ([['a', 'b', 'c'], ['d']]) 121 | ); 122 | }); 123 | -------------------------------------------------------------------------------- /test/objects.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ } from './helpers/setup'; 2 | 3 | test('should create objects object', async () => { 4 | expect(await ƒ('[ "first" "Manfred" "last" "von Thun" ] object')).toEqual( 5 | `[ { first: 'Manfred', last: 'von Thun' } ]` 6 | ); 7 | 8 | expect(await ƒ('{ first: "Manfred" last: "von Thun" }')).toEqual( 9 | `[ { first: 'Manfred', last: 'von Thun' } ]` 10 | ); 11 | 12 | expect(await ƒ('{ name: { first: "Manfred" last: "von Thun" } }')).toEqual( 13 | `[ { name: { first: 'Manfred', last: 'von Thun' } } ]` 14 | ); 15 | 16 | expect( 17 | await ƒ('{ name: [ { first: "Manfred" } { last: "von Thun" } ] }') 18 | ).toEqual(`[ { name: [ { first: 'Manfred' } { last: 'von Thun' } ] } ]`); 19 | }); 20 | 21 | test('should create objects object, cont', async () => { 22 | // t.deepEqual(await ƒ('{ name: [ { first: "Manfred" } { last: "von" " Thun" + } ] }'), [{ name: [{first: 'Manfred'}, {last: 'von Thun'}] }]); 23 | expect(await ƒ('{ first: "Manfred", last: "von Thun" }')).toEqual( 24 | `[ { first: 'Manfred', last: 'von Thun' } ]` 25 | ); 26 | expect(await ƒ('{ first: "Manfred", last: [ "von" "Thun" ] " " * }')).toEqual( 27 | `[ { first: 'Manfred', last: 'von Thun' } ]` 28 | ); 29 | }); 30 | 31 | test('should test is object', async () => { 32 | expect(await ƒ('{ first: "Manfred" last: "von Thun" } object?')).toEqual( 33 | `[ true ]` 34 | ); 35 | expect(await ƒ('[ first: "Manfred" last: "von Thun" ] object?')).toEqual( 36 | `[ false ]` 37 | ); 38 | }); 39 | 40 | test('should create objects object, cont2', async () => { 41 | expect(await ƒ('{ "first": "Manfred", "last": "von Thun" }')).toEqual( 42 | `[ { first: 'Manfred', last: 'von Thun' } ]` 43 | ); 44 | 45 | expect(await ƒ('[ { first: "Manfred", last: "von Thun" } ]')).toEqual( 46 | `[ [ { first: 'Manfred', last: 'von Thun' } ] ]` 47 | ); 48 | 49 | expect(await ƒ('[ { "first": "Manfred", "last": "von Thun" } ]')).toEqual( 50 | `[ [ { first: 'Manfred', last: 'von Thun' } ] ]` 51 | ); 52 | }); 53 | 54 | test('should test is object', async () => { 55 | expect(await ƒ('{ first: "Manfred" last: "von Thun" } object?')).toEqual( 56 | `[ true ]` 57 | ); 58 | expect(await ƒ('[ first: "Manfred" last: "von Thun" ] object?')).toEqual( 59 | `[ false ]` 60 | ); 61 | }); 62 | 63 | test('should get keys and values', async () => { 64 | expect(await ƒ('{ first: "Manfred" last: "von Thun" } keys')).toEqual( 65 | `[ [ 'first' 'last' ] ]` 66 | ); 67 | expect(await ƒ('{ "first" "Manfred" "last" "von Thun" } vals')).toEqual( 68 | `[ [ 'Manfred' 'von Thun' ] ]` 69 | ); 70 | }); 71 | 72 | test('should get single values usint @', async () => { 73 | expect(await ƒ('{ first: "Manfred" last: "von Thun" } first: @')).toEqual( 74 | `[ 'Manfred' ]` 75 | ); 76 | expect(await ƒ('{ first: "Manfred" last: "von Thun" } last: @')).toEqual( 77 | `[ 'von Thun' ]` 78 | ); 79 | }); 80 | 81 | test('should join objects', async () => { 82 | expect(await ƒ('{ first: "Manfred" } { last: "von Thun" } +')).toEqual( 83 | `[ { first: 'Manfred', last: 'von Thun' } ]` 84 | ); 85 | expect( 86 | await ƒ('{ first: "Manfred" } { first: "John" last: "von Thun" } <<') 87 | ).toEqual(`[ { first: 'John', last: 'von Thun' } ]`); 88 | expect( 89 | await ƒ('{ first: "Manfred" } { first: "John" last: "von Thun" } >>') 90 | ).toEqual(`[ { last: 'von Thun', first: 'Manfred' } ]`); 91 | }); 92 | 93 | test('should join objects without mutations', async () => { 94 | expect(await ƒ('{ first: "Manfred" } dup { last: "von Thun" } +')).toEqual( 95 | `[ { first: 'Manfred' } { first: 'Manfred', last: 'von Thun' } ]` 96 | ); 97 | expect(await ƒ('{ first: "Manfred" } dup { last: "von Thun" } >>')).toEqual( 98 | `[ { first: 'Manfred' } { last: 'von Thun', first: 'Manfred' } ]` 99 | ); 100 | expect(await ƒ('{ first: "Manfred" } dup { last: "von Thun" } <<')).toEqual( 101 | `[ { first: 'Manfred' } { first: 'Manfred', last: 'von Thun' } ]` 102 | ); 103 | }); 104 | 105 | test('objects', async () => { 106 | expect(await ƒ('{ name: [ "Manfred" "von Thun" ] }')).toEqual( 107 | `[ { name: [ 'Manfred' 'von Thun' ] } ]` 108 | ); 109 | expect(await ƒ('{ name: "Manfred" " von Thun" + }')).toEqual( 110 | `[ { name: 'Manfred von Thun' } ]` 111 | ); 112 | expect(await ƒ('{ name: ( "Manfred" " von Thun" + ) }')).toEqual( 113 | `[ { name: [ 'Manfred von Thun' ] } ]` 114 | ); 115 | expect(await ƒ('{ first: "Manfred" last: "von Thun" } ln')).toEqual(`[ 2 ]`); 116 | }); 117 | 118 | test('should <=> objects by key length', async () => { 119 | expect(await ƒ('{ x: 123 } { y: 456 } <=>')).toEqual(`[ 0 ]`); 120 | expect(await ƒ('{ x: 123, z: 789 } { y: 456 } <=>')).toEqual(`[ 1 ]`); 121 | expect(await ƒ('{ x: 123 } { y: 456, z: 789 } <=>')).toEqual(`[ -1 ]`); 122 | }); 123 | 124 | test('objects in defintions', async () => { 125 | expect(await ƒ('x: [ { x: "123" } ] ; x')).toEqual(`[ { x: '123' } ]`); 126 | expect(await ƒ('x: [ { "x" : "123" } ] ; x')).toEqual(`[ { x: '123' } ]`); 127 | expect(await ƒ('x: [ { 1: "123" } ] ; x')).toEqual(`[ { "1": '123' } ]`); // todo: stringify should single quote strings 128 | }); 129 | -------------------------------------------------------------------------------- /test/quotes.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ, τ, F, Decimal, Word } from './helpers/setup'; 2 | 3 | test('should push quotes', async () => { 4 | expect(await ƒ('[ 1 ] [ 2 ]')).toEqual(`[ [ 1 ] [ 2 ] ]`); 5 | expect(await ƒ('[1] [2]')).toEqual(`[ [ 1 ] [ 2 ] ]`); 6 | }); 7 | 8 | test('should not eval within quote', async () => { 9 | const f = F().eval('[ 1 ] [ 1 2 + ]'); 10 | expect(f.stack.length).toBe(2); 11 | expect(f.stack[0]).toEqual([new Decimal(1)]); 12 | expect(f.stack[1].toString()).toBe('1,2,+'); 13 | expect(f.stack[1][2] instanceof Object).toBeTruthy(); 14 | }); 15 | 16 | test('should add quotes', async () => { 17 | expect(await ƒ('[1] [2] +')).toEqual(`[ [ 1 2 ] ]`); 18 | }); 19 | 20 | test('should mul quotes', async () => { 21 | const plus = new Word('+'); 22 | expect(await ƒ('[ 1 2 + ] 2 *')).toEqual(τ([[1, 2, plus, 1, 2, plus]])); 23 | }); 24 | 25 | test('should test equality', async () => { 26 | expect(await ƒ('[ 1 2 + ] [ 1 2 ] =')).toEqual(`[ false ]`); 27 | expect(await ƒ('[ 1 2 + ] [ 1 2 + ] =')).toEqual(`[ true ]`); 28 | }); 29 | 30 | test('should eval quotes', async () => { 31 | expect(await ƒ('[1 2 +] eval')).toEqual(`[ 3 ]`); 32 | }); 33 | 34 | test('should zip quotes', async () => { 35 | expect(await ƒ('[ 1 2 + ] [ 4 ] *')).toEqual(`[ [ 1 4 2 4 + 4 ] ]`); 36 | }); 37 | 38 | test('should join lists', async () => { 39 | expect(await ƒ('[ 1 2 + ] "," *')).toEqual(`[ '1,2,+' ]`); 40 | }); 41 | 42 | test('should <=> arrays by length', async () => { 43 | expect(await ƒ('[1 2 3] [4 5 6] <=>')).toEqual(`[ 0 ]`); 44 | expect(await ƒ('[1 2 3 4] [4 5 6] <=>')).toEqual(`[ 1 ]`); 45 | expect(await ƒ('[1 2 3] [4 5 6 7] <=>')).toEqual(`[ -1 ]`); 46 | }); 47 | -------------------------------------------------------------------------------- /test/regexp.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ } from './helpers/setup'; 2 | 3 | test('should convert a string to a regexp', async () => { 4 | expect(await ƒ('"[;:]" regexp type')).toEqual(`[ 'regexp' ]`); 5 | }); 6 | 7 | test('should split string using regexp', async () => { 8 | // todo: better comparisons with NaN 9 | expect(await ƒ('"a;b:c" "[;:]" regexp /')).toEqual(`[ [ 'a' 'b' 'c' ] ]`); 10 | }); 11 | 12 | test('should replace string using regexp', async () => { 13 | // todo: better comparisons with NaN 14 | expect(await ƒ('"a;b:c" "[;:]" regexp "-->" replace')).toEqual( 15 | `[ 'a-->b-->c' ]` 16 | ); 17 | }); 18 | 19 | test('regular expressions, replace', async () => { 20 | expect(await ƒ('"abc" "/a./" regexp "X" replace')).toEqual(`[ 'Xc' ]`); 21 | expect(await ƒ('"abc" "/a.$/" regexp "X" replace')).toEqual(`[ 'abc' ]`); 22 | expect(await ƒ('"abc" "/a.*$/" regexp "X" replace')).toEqual(`[ 'X' ]`); 23 | expect(await ƒ('"bcd" "/a./" regexp "X" replace')).toEqual(`[ 'bcd' ]`); 24 | }); 25 | 26 | test('regular expressions, match', async () => { 27 | expect(await ƒ('"abc" "/a./" regexp match')).toEqual(`[ [ 'ab' ] ]`); 28 | expect(await ƒ('"abc" "/a.$/" regexp match')).toEqual(`[ [ ] ]`); 29 | expect(await ƒ('"abc" "/a.*$/" regexp match')).toEqual(`[ [ 'abc' ] ]`); 30 | expect(await ƒ('"bcd" "/a./" regexp match')).toEqual(`[ [ ] ]`); 31 | expect(await ƒ('"bcd" "/a./" regexp match')).toEqual(`[ [ ] ]`); 32 | }); 33 | 34 | test('regular expressions, match?', async () => { 35 | expect(await ƒ('"abc" "/a./" regexp =~')).toEqual(`[ true ]`); 36 | expect(await ƒ('"abc" "/A./" regexp =~')).toEqual(`[ false ]`); 37 | expect(await ƒ('"abc" "/A./i" regexp =~')).toEqual(`[ true ]`); 38 | expect(await ƒ('"abc" "/a.$/" regexp =~')).toEqual(`[ false ]`); 39 | expect(await ƒ('"abc" "/a.*$/" regexp =~')).toEqual(`[ true ]`); 40 | expect(await ƒ('"bcd" "/a./" regexp =~')).toEqual(`[ false ]`); 41 | }); 42 | 43 | test('regular expressions, match @', async () => { 44 | expect(await ƒ('"aaaa1aaaa2aaaa3" "/[0-9]/g" regexp match 0 @')).toEqual( 45 | `[ '1' ]` 46 | ); 47 | expect(await ƒ('"aaaa1aaaa2aaaa3" "/[0-9]/g" regexp match 1 @')).toEqual( 48 | `[ '2' ]` 49 | ); 50 | expect(await ƒ('"aaaa1aaaa2aaaa3" "/[0-9]/g" regexp match 2 @')).toEqual( 51 | `[ '3' ]` 52 | ); 53 | }); 54 | 55 | test('can add (or) regexp', async () => { 56 | expect(await ƒ('"abc" regexp "def" regexp + type')).toEqual(`[ 'regexp' ]`); 57 | expect(await ƒ('":" regexp ";" regexp +')).toEqual(`[ /:|;/ ]`); 58 | expect(await ƒ('"abc" regexp "def" regexp +')).toEqual(`[ /abc|def/ ]`); 59 | expect(await ƒ('"/abc/i" regexp "/def/" regexp +')).toEqual(`[ /abc|def/i ]`); 60 | expect(await ƒ('"a;b:c" ";" regexp ":" regexp + /')).toEqual( 61 | `[ [ 'a' 'b' 'c' ] ]` 62 | ); 63 | }); 64 | 65 | test('can mul (and) regexp', async () => { 66 | expect(await ƒ('"abc" regexp "def" regexp * type')).toEqual(`[ 'regexp' ]`); 67 | expect(await ƒ('":" regexp ";" regexp *')).toEqual(`[ /(?=:)(?=;)/ ]`); 68 | expect(await ƒ('"abc" regexp "def" regexp *')).toEqual( 69 | `[ /(?=abc)(?=def)/ ]` 70 | ); 71 | }); 72 | 73 | // todo: nor, xor, etc. 74 | 75 | test('can mul (repeat) regexp', async () => { 76 | expect(await ƒ('"abc" regexp 2 * type')).toEqual(`[ 'regexp' ]`); 77 | expect(await ƒ('":" regexp 2 *')).toEqual(`[ /:{2}/ ]`); 78 | expect(await ƒ('"abc" regexp 2 *')).toEqual(`[ /(?:abc){2}/ ]`); 79 | expect(await ƒ('"/abc/i" regexp 2 *')).toEqual(`[ /(?:abc){2}/i ]`); 80 | expect(await ƒ('"abc" regexp infinity *')).toEqual(`[ /(?:abc){1,}/ ]`); 81 | expect(await ƒ('"a;;b;c" ";" regexp 2 * /')).toEqual(`[ [ 'a' 'b;c' ] ]`); 82 | }); 83 | 84 | test('can ~ (not) regexp', async () => { 85 | expect(await ƒ('"abc" regexp ~ type')).toEqual(`[ 'regexp' ]`); 86 | expect(await ƒ('":" regexp ~')).toEqual(`[ /(?!:)/ ]`); 87 | expect(await ƒ('"abc" regexp ~')).toEqual(`[ /(?!abc)/ ]`); 88 | expect(await ƒ('"/abc/i" regexp ~')).toEqual(`[ /(?!abc)/i ]`); 89 | }); 90 | 91 | test('can test equality of regexp', async () => { 92 | expect(await ƒ('";" regexp dup =')).toEqual(`[ true ]`); 93 | expect(await ƒ('";" regexp ";" regexp =')).toEqual(`[ true ]`); 94 | expect(await ƒ('";" regexp ":" regexp =')).toEqual(`[ false ]`); 95 | expect(await ƒ('";|:" regexp ";" regexp ":" regexp + =')).toEqual(`[ true ]`); 96 | }); 97 | 98 | test('can left and right "shift"', async () => { 99 | expect(await ƒ('"/abc/i" regexp "/def/" regexp <<')).toEqual(`[ /abcdef/i ]`); 100 | expect(await ƒ('"/abc/i" regexp "/def/" regexp >>')).toEqual(`[ /abcdef/ ]`); 101 | }); 102 | 103 | test('regexp works as a macro "macro"', async () => { 104 | expect(await ƒ('"/abc/i":regexp')).toEqual(`[ /abc/i ]`); 105 | expect(await ƒ('[ "/abc/i":regexp ]')).toEqual(`[ [ /abc/i ] ]`); 106 | }); 107 | 108 | test('regexp are not statefull', async () => { 109 | expect(await ƒ(`'abc' 'abc' '/abc/g' regexp dup [ =~ swap ] dip =~`)).toEqual( 110 | `[ true true ]` 111 | ); 112 | }); 113 | -------------------------------------------------------------------------------- /test/scoping.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ } from './helpers/setup'; 2 | 3 | test('in', async () => { 4 | expect(await ƒ(`a: [ 'before' ] ; [ a ] in`)).toEqual(`[ [ 'before' ] ]`); 5 | expect(await ƒ(`a: [ "outer" ] ; [ a: [ "inner" ] ; a ] in a`)).toEqual( 6 | `[ [ 'inner' ] 'outer' ]` 7 | ); 8 | expect(await ƒ(`a: [ 'outer' ] ; [ b: [ 'inner' ] ; a ] in b: defined?`)).toEqual( 9 | `[ [ 'outer' ] false ]` 10 | ); 11 | }); 12 | 13 | describe('module `use` scoping', () => { 14 | test('scoped words', async () => { 15 | expect( 16 | await ƒ(` 17 | [ 18 | δx: [ 1 2 + ] ; 19 | δy: [ δx 2 * ] ; 20 | ] :module use 21 | δx δy 22 | `) 23 | ).toEqual(`[ 3 6 ]`); 24 | }); 25 | 26 | test('local words over scoped words', async () => { 27 | expect( 28 | await ƒ(` 29 | [ 30 | δx: [ 1 2 + ] ; 31 | δy: [ δx 2 * ] ; 32 | ] :module use 33 | δx: [ 8 ] ; 34 | δx δy 35 | `) 36 | ).toEqual(`[ 8 6 ]`); 37 | 38 | expect( 39 | await ƒ(` 40 | δx: [ 8 ] ; 41 | [ 42 | δx: [ 1 2 + ] ; 43 | δy: [ δx 2 * ] ; 44 | ] module use 45 | δx δy 46 | `) 47 | ).toEqual(`[ 8 6 ]`); 48 | }); 49 | }); 50 | 51 | test('module def scoping', async () => { 52 | expect( 53 | await ƒ(` 54 | s: [ 55 | x: [ 1 2 + ] ; 56 | y: [ x 3 * ] ; 57 | ] module ; 58 | x: [ 5 ] ; 59 | y: [ 7 ] ; 60 | x y s.x s.y 61 | `) 62 | ).toEqual(`[ 5 7 3 9 ]`); 63 | 64 | expect( 65 | await ƒ(` 66 | x: [ 5 ] ; 67 | y: [ 7 ] ; 68 | s: [ 69 | x: [ 1 2 + ] ; 70 | y: [ x 3 * ] ; 71 | ] module ; 72 | x y s.x s.y 73 | `) 74 | ).toEqual(`[ 5 7 3 9 ]`); 75 | 76 | expect( 77 | await ƒ(` 78 | x: [ 5 ] ; 79 | y: [ 7 ] ; 80 | s: [ 81 | y: [ x 3 * ] ; 82 | ] module ; 83 | x y s.y 84 | `) 85 | ).toEqual(`[ 5 7 15 ]`); 86 | }); 87 | 88 | test('calling within child', async () => { 89 | expect( 90 | await ƒ(` 91 | x: [ 5 ] ; 92 | y: [ 7 ] ; 93 | s: [ 94 | x: [ 1 2 + ] ; 95 | y: [ x 3 * ] ; 96 | ] module ; 97 | [ 98 | x y s.x s.y 99 | ] in 100 | `) 101 | ).toEqual(`[ [ 5 7 3 9 ] ]`); 102 | }); 103 | 104 | test('deep calling within child', async () => { 105 | expect( 106 | await ƒ(` 107 | x: [ 5 ] ; 108 | y: [ 7 ] ; 109 | s: [ 110 | s: [ 111 | x: [ 1 2 + ] ; 112 | y: [ x 3 * ] ; 113 | ] module ; 114 | vocab 115 | ] module ; 116 | [ 117 | x y s.s.x s.s.y 118 | ] in 119 | `) 120 | ).toEqual(`[ [ 5 7 3 9 ] ]`); 121 | }); 122 | 123 | test(`locals don't collide with scoped definitions`, async () => { 124 | expect( 125 | await ƒ(` 126 | x: [ 128 ] ; 127 | y: [ x x + ] ; 128 | [ 129 | x: [ 256 ] ; 130 | y 131 | ] in 132 | `) 133 | ).toEqual(`[ [ 256 ] ]`); 134 | }); 135 | 136 | test(`defined?`, async () => { 137 | expect(await ƒ(`'swap' defined?`)).toEqual(`[ true ]`); 138 | expect(await ƒ(`'slip' defined?`)).toEqual(`[ true ]`); 139 | expect(await ƒ(`'junk' defined?`)).toEqual(`[ false ]`); 140 | expect(ƒ(`'%top' defined?`)).rejects.toThrow(`'defined?' invalid key: "%top"`); // ???? 141 | }); 142 | 143 | test('hides private', async () => { 144 | expect( 145 | await ƒ(` 146 | [ 147 | _x: [ 1 2 + ] ; 148 | y: [ _x 3 * ] ; 149 | ] module use 150 | y 151 | '_x' defined? 152 | `) 153 | ).toEqual(`[ 9 false ]`); 154 | 155 | expect( 156 | await ƒ(` 157 | s: [ 158 | _x: [ 1 2 + ] ; 159 | y: [ _x 3 * ] ; 160 | ] module ; 161 | s.y 162 | 's._x' defined? 163 | `) 164 | ).toEqual(`[ 9 false ]`); 165 | }); 166 | 167 | test('module `include` scoping', async () => { 168 | expect( 169 | await ƒ(` 170 | drop2: [ dup ] ; 171 | [ 172 | 'shuffle.ff' include 173 | x: [ 1 2 3 drop2 ] ; 174 | ] module use 175 | x 176 | `) 177 | ).toEqual(`[ 1 ]`); 178 | }); 179 | 180 | test('module `use` scoping', async () => { 181 | expect( 182 | await ƒ(` 183 | drop2: [ dup ] ; 184 | [ 185 | 'shuffle.ff' import use 186 | x: [ 1 2 3 drop2 ] ; 187 | ] module use 188 | x 189 | `) 190 | ).toEqual(`[ 1 ]`); 191 | }); 192 | 193 | test('only `use` modules', async () => { 194 | expect(ƒ(`{ x: [ 1 2 + ] } use`)).rejects.toThrow(`'use' invalid vocabulary. Vocabulary should be a map of global symbols`); 195 | expect(ƒ(`{ x: #y } use`)).rejects.toThrow(`'use' invalid vocabulary. Symbol is undefined: y`); 196 | }); 197 | 198 | test('explicit locals are not bound', async () => { 199 | expect( 200 | await ƒ(` 201 | s: [ 202 | x: [ 1 2 + ] ; 203 | y: [ .x 3 * ] ; 204 | ] module ; 205 | x: [ 5 ] ; 206 | y: [ 7 ] ; 207 | x y s.x s.y 208 | `) 209 | ).toEqual(`[ 5 7 3 15 ]`); 210 | }); 211 | 212 | test('core words are bound', async () => { 213 | expect( 214 | await ƒ(` 215 | x: [ 25 sqrt ] ; 216 | sqrt: [ 4 ] ; 217 | x 218 | `) 219 | ).toEqual(`[ 5 ]`); 220 | }); 221 | 222 | describe('inline at defintion', () => { 223 | test(`; does inline`, async () => { 224 | expect( 225 | await ƒ(` 226 | x: [ 5 ! ] ; 227 | !: [ drop 4 ] ; 228 | x 229 | `) 230 | ).toEqual(`[ 120 ]`); 231 | }); 232 | 233 | test(`explicit non-inline`, async () => { 234 | expect( 235 | await ƒ(` 236 | x: [ 5 .! ] ; 237 | !: [ drop 4 ] ; 238 | x 239 | `) 240 | ).toEqual(`[ 4 ]`); 241 | }); 242 | }); 243 | 244 | test('binding vocab', async () => { 245 | expect( 246 | await ƒ(` 247 | [ 248 | δy: [ 249 | δz: [ 8 ] ; 250 | ] module ; 251 | δx: [ δy.δz ] ; 252 | ] module use 253 | δx 254 | 'δx' defined? 255 | 'δy' defined? 256 | 'δz' defined? 257 | `) 258 | ).toEqual(`[ 8 true true false ]`); 259 | 260 | expect( 261 | await ƒ(` 262 | [ 263 | [ 264 | δy: [ 265 | δz: [ 13 ] ; 266 | ] module ; 267 | ] module use 268 | δx: [ δy.δz ] ; 269 | ] module use 270 | δx 271 | 'δx' defined? 272 | 'δy' defined? 273 | 'δz' defined? 274 | `) 275 | ).toEqual(`[ 13 true false false ]`); 276 | }); 277 | -------------------------------------------------------------------------------- /test/shuffle-ff.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ, τ } from './helpers/setup'; 2 | 3 | test('should slip', async () => { 4 | expect(await ƒ('[ 1 2 + ] 4 slip')).toEqual(`[ 3 4 ]`); 5 | }); -------------------------------------------------------------------------------- /test/symbols.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ } from './helpers/setup'; 2 | 3 | test('symbols type', async () => { 4 | expect(await ƒ('#test type')).toEqual(`[ 'symbol' ]`); 5 | expect(await ƒ('"test" # type')).toEqual(`[ 'symbol' ]`); 6 | }); 7 | 8 | test('symbols equality', async () => { 9 | expect(await ƒ('#test dup =')).toEqual(`[ true ]`); 10 | expect(await ƒ('#test #test =')).toEqual(`[ false ]`); 11 | expect(await ƒ('"test" # dup =')).toEqual(`[ true ]`); 12 | expect(await ƒ('"test" # "test" # =')).toEqual(`[ false ]`); 13 | }); 14 | -------------------------------------------------------------------------------- /test/template-strings.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ, τ } from './helpers/setup'; 2 | 3 | test('should split string templates into parts', async () => { 4 | expect(await ƒ(`\"ar$('one')ou$('two')nd\" template-parts`)).toEqual( 5 | τ([ 6 | { 7 | raw: ['ar', 'ou', 'nd'], 8 | strings: ['ar', 'ou', 'nd'], 9 | stacks: [['one'], ['two']] 10 | } 11 | ]) 12 | ); 13 | expect(await ƒ('"ar$(1)ou$(1 1 +)nd" template-parts')).toEqual( 14 | τ([ 15 | { 16 | raw: ['ar', 'ou', 'nd'], 17 | strings: ['ar', 'ou', 'nd'], 18 | stacks: [[1], [2]] 19 | } 20 | ]) 21 | ); 22 | expect(await ƒ(`'\\u{41}$( 42 )b' template-parts`)).toEqual( 23 | τ([ 24 | { 25 | raw: ['\\u{41}', 'b'], 26 | strings: ['A', 'b'], 27 | stacks: [[42]] 28 | } 29 | ]) 30 | ); 31 | }); 32 | 33 | test('should process templates', async () => { 34 | expect(await ƒ('"-1 sqrt = $( -1 sqrt )" template')).toEqual( 35 | `[ '-1 sqrt = 0+1i' ]` 36 | ); 37 | expect(await ƒ('"0.1 0.2 + = $( 0.1 0.2 + )" template')).toEqual( 38 | `[ '0.1 0.2 + = 0.3' ]` 39 | ); 40 | expect(await ƒ('"$0.1 (0.2) + = $( 0.1 0.2 + )" template')).toEqual( 41 | `[ '$0.1 (0.2) + = 0.3' ]` 42 | ); 43 | expect(await ƒ('"$(0.1 dup dup +) + = $( 0.1 0.2 + )" template')).toEqual( 44 | `[ '0.1 0.2 + = 0.3' ]` 45 | ); 46 | expect(await ƒ('"true AND null = $( true null * )" template')).toEqual( 47 | `[ 'true AND null = null' ]` 48 | ); 49 | }); 50 | 51 | test('should process string templates literal', async () => { 52 | expect(await ƒ('`-1 sqrt = $( -1 sqrt )`')).toEqual(`[ '-1 sqrt = 0+1i' ]`); 53 | expect(await ƒ('`0.1 0.2 + = $( 0.1 0.2 + )`')).toEqual( 54 | `[ '0.1 0.2 + = 0.3' ]` 55 | ); 56 | expect(await ƒ('`$0.1 (0.2) + = $( 0.1 0.2 + )`')).toEqual( 57 | `[ '$0.1 (0.2) + = 0.3' ]` 58 | ); 59 | expect(await ƒ('`$(0.1 dup dup +) + = $( 0.1 0.2 + )`')).toEqual( 60 | `[ '0.1 0.2 + = 0.3' ]` 61 | ); 62 | expect(await ƒ('`true AND null = $( true null * )`')).toEqual( 63 | `[ 'true AND null = null' ]` 64 | ); 65 | }); 66 | 67 | test('should decode templates literal', async () => { 68 | expect( 69 | await ƒ( 70 | '`\\u{48}\\u{65}\\u{6c}\\u{6c}\\u{6f}\\u{20}\\u{77}\\u{6f}\\u{72}\\u{6c}\\u{64}`' 71 | ) 72 | ).toEqual(`[ 'Hello world' ]`); 73 | }); 74 | 75 | test('should process string template with', async () => { 76 | expect(await ƒ('"-1 sqrt = $( -1 )" [ sqrt ] template-with')).toEqual( 77 | `[ '-1 sqrt = 0+1i' ]` 78 | ); 79 | expect(await ƒ('"abc ucase = $( \'abc\' )" [ ucase ] template-with')).toEqual( 80 | `[ 'abc ucase = ABC' ]` 81 | ); 82 | }); 83 | -------------------------------------------------------------------------------- /test/types.spec.ts: -------------------------------------------------------------------------------- 1 | import { ƒ } from './helpers/setup'; 2 | 3 | test('type', async () => { 4 | expect(await ƒ('"abc" type')).toEqual(`[ 'string' ]`); 5 | expect(await ƒ('123 type')).toEqual(`[ 'number' ]`); 6 | }); 7 | 8 | test('number', async () => { 9 | expect(await ƒ('123 number')).toEqual(`[ 123 ]`); 10 | expect(await ƒ('"123" number')).toEqual(`[ 123 ]`); 11 | expect(ƒ('"abc" number')).rejects.toThrow(`'number' [DecimalError] Invalid argument: abc`); // nan? 12 | }); 13 | 14 | test('string', async () => { 15 | expect(await ƒ(`123 string`)).toEqual(`[ '123' ]`); 16 | expect(await ƒ(`'123' string`)).toEqual(`[ '123' ]`); 17 | }); 18 | 19 | test('itoa', async () => { 20 | expect(await ƒ(`65 itoa`)).toEqual(`[ 'A' ]`); 21 | }); 22 | 23 | test('atoi', async () => { 24 | expect(await ƒ(`'A' atoi`)).toEqual(`[ 65 ]`); 25 | }); 26 | 27 | test('atob', async () => { 28 | expect(await ƒ(`'SGVsbG8=' atob`)).toEqual(`[ 'Hello' ]`); 29 | }); 30 | 31 | test('btoa', async () => { 32 | expect(await ƒ(`'Hello' btoa`)).toEqual(`[ 'SGVsbG8=' ]`); 33 | }); 34 | 35 | test('hash', async () => { 36 | expect(await ƒ(`'Hello' hash`)).toEqual(`[ 69609650 ]`); 37 | }); 38 | 39 | test('hex-hash', async () => { 40 | expect(await ƒ(`'Hello' hex-hash`)).toEqual(`[ '42628b2' ]`); 41 | }); 42 | 43 | describe('base', () => { 44 | test('base, pos integers', async () => { 45 | expect(await ƒ('5 16 base')).toEqual(`[ '0x5' ]`); 46 | expect(await ƒ('5 2 base')).toEqual(`[ '0b101' ]`); 47 | 48 | expect(await ƒ('3735928559 16 base')).toEqual(`[ '0xDEADBEEF' ]`); 49 | expect(await ƒ('3735928559 2 base')).toEqual( 50 | `[ '0b11011110101011011011111011101111' ]` 51 | ); 52 | 53 | expect(await ƒ('18446744073709551615 16 base')).toEqual( 54 | `[ '0xFFFFFFFFFFFFFFFF' ]` 55 | ); 56 | expect(await ƒ('18446744073709551615 10 base')).toEqual( 57 | `[ '18446744073709551615' ]` 58 | ); 59 | expect(await ƒ('18446744073709551615 8 base')).toEqual( 60 | `[ '0o1777777777777777777777' ]` 61 | ); 62 | expect(await ƒ('18446744073709551615 4 base')).toEqual( 63 | `[ '33333333333333333333333333333333' ]` 64 | ); 65 | expect(await ƒ('18446744073709551615 2 base')).toEqual( 66 | `[ '0b1111111111111111111111111111111111111111111111111111111111111111' ]` 67 | ); 68 | }); 69 | 70 | test('base, neg integers', async () => { 71 | expect(await ƒ('-3735928559 16 base')).toEqual(`[ '-0xDEADBEEF' ]`); 72 | expect(await ƒ('-18446744073709551615 16 base')).toEqual( 73 | `[ '-0xFFFFFFFFFFFFFFFF' ]` 74 | ); 75 | expect(await ƒ('-18446744073709551615 10 base')).toEqual( 76 | `[ '-18446744073709551615' ]` 77 | ); 78 | expect(await ƒ('-18446744073709551615 8 base')).toEqual( 79 | `[ '-0o1777777777777777777777' ]` 80 | ); 81 | expect(await ƒ('-18446744073709551615 2 base')).toEqual( 82 | `[ '-0b1111111111111111111111111111111111111111111111111111111111111111' ]` 83 | ); 84 | }); 85 | 86 | test('base, pos floats', async () => { 87 | expect(await ƒ('0.125 16 base')).toEqual(`[ '0x0.2' ]`); 88 | expect(await ƒ('0.125 10 base')).toEqual(`[ '0.125' ]`); 89 | expect(await ƒ('0.125 8 base')).toEqual(`[ '0o0.1' ]`); 90 | // t.deepEqual(await ƒ('0.125 4 base'), `[ '0.02' ]`); 91 | expect(await ƒ('0.125 2 base')).toEqual(`[ '0b0.001' ]`); 92 | 93 | // t.deepEqual(await fJSON('123456789.87654321 2 base'), ['0b111010110111100110100010101.1110000001100101001000101100010001111011']); 94 | }); 95 | 96 | test('base, neg floats', async () => { 97 | expect(await ƒ('-0.125 16 base')).toEqual(`[ '-0x0.2' ]`); 98 | expect(await ƒ('-0.125 10 base')).toEqual(`[ '-0.125' ]`); 99 | expect(await ƒ('-0.125 8 base')).toEqual(`[ '-0o0.1' ]`); 100 | expect(await ƒ('-0.125 2 base')).toEqual(`[ '-0b0.001' ]`); 101 | }); 102 | 103 | test('base with inf and nan', async () => { 104 | expect(await ƒ('nan 16 base')).toEqual(`[ 'NaN' ]`); 105 | expect(await ƒ('Infinity 16 base')).toEqual(`[ 'Infinity' ]`); 106 | expect(await ƒ('-Infinity 16 base')).toEqual(`[ '-Infinity' ]`); 107 | }); 108 | }); 109 | 110 | test('boolean', async () => { 111 | expect(await ƒ(`123 boolean`)).toEqual(`[ true ]`); 112 | expect(await ƒ(`0 boolean`)).toEqual(`[ false ]`); 113 | expect(await ƒ(`'123' boolean`)).toEqual(`[ true ]`); 114 | expect(await ƒ(`'' boolean`)).toEqual(`[ false ]`); 115 | }); 116 | 117 | test(':', async () => { 118 | expect(await ƒ(`'eval' :`)).toEqual(`[ eval: ]`); 119 | expect(await ƒ(`eval: :`)).toEqual(`[ eval: ]`); 120 | }); 121 | 122 | test('of', async () => { 123 | expect(await ƒ('"abc" 123 of')).toEqual(`[ '123' ]`); 124 | expect(await ƒ('123 "456" of')).toEqual(`[ 456 ]`); 125 | }); 126 | 127 | test('is?', async () => { 128 | expect(await ƒ('"abc" "abc" is?')).toEqual(`[ true ]`); 129 | expect(await ƒ('["abc"] ["abc"] is?')).toEqual(`[ false ]`); 130 | expect(await ƒ('["abc"] dup is?')).toEqual(`[ true ]`); 131 | }); 132 | 133 | test('nothing?', async () => { 134 | expect(await ƒ('"abc" nothing?')).toEqual(`[ false ]`); 135 | expect(await ƒ('null nothing?')).toEqual(`[ true ]`); 136 | }); 137 | 138 | 139 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "emitDecoratorMetadata": true, 5 | "strictNullChecks": false, 6 | "target": "es6", 7 | "outDir": "dist", 8 | "moduleResolution": "node", 9 | "module": "commonjs", 10 | "importHelpers": true, 11 | "listFiles": false, 12 | "traceResolution": false, 13 | "pretty": true, 14 | "lib": ["es2018", "dom"], 15 | "types": ["node", "jest"], 16 | "allowJs": true, 17 | "sourceMap": true 18 | }, 19 | "include": ["src/**/*.ts"], 20 | "exclude": [ 21 | "node_modules" 22 | ], 23 | "compileOnSave": false 24 | } 25 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "jsRules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | true, 6 | "check-space" 7 | ], 8 | "indent": [ 9 | true, 10 | "spaces" 11 | ], 12 | "no-duplicate-variable": true, 13 | "no-eval": true, 14 | "no-trailing-whitespace": true, 15 | "no-unsafe-finally": true, 16 | "one-line": [ 17 | true, 18 | "check-open-brace", 19 | "check-whitespace" 20 | ], 21 | "quotemark": [ 22 | true, 23 | "single" 24 | ], 25 | "semicolon": [ 26 | true, 27 | "always" 28 | ], 29 | "triple-equals": [ 30 | true, 31 | "allow-null-check" 32 | ], 33 | "variable-name": [ 34 | true, 35 | "ban-keywords" 36 | ], 37 | "whitespace": [ 38 | true, 39 | "check-branch", 40 | "check-decl", 41 | "check-operator", 42 | "check-separator", 43 | "check-type" 44 | ] 45 | }, 46 | "rules": { 47 | "class-name": true, 48 | "comment-format": [ 49 | true, 50 | "check-space" 51 | ], 52 | "indent": [ 53 | true, 54 | "spaces" 55 | ], 56 | "no-eval": true, 57 | "no-internal-module": true, 58 | "no-trailing-whitespace": true, 59 | "no-unsafe-finally": true, 60 | "no-var-keyword": true, 61 | "one-line": [ 62 | true, 63 | "check-open-brace", 64 | "check-whitespace" 65 | ], 66 | "quotemark": [ 67 | true, 68 | "single" 69 | ], 70 | "semicolon": [ 71 | true, 72 | "always" 73 | ], 74 | "triple-equals": [ 75 | true, 76 | "allow-null-check" 77 | ], 78 | "typedef-whitespace": [ 79 | true, 80 | { 81 | "call-signature": "nospace", 82 | "index-signature": "nospace", 83 | "parameter": "nospace", 84 | "property-declaration": "nospace", 85 | "variable-declaration": "nospace" 86 | } 87 | ], 88 | "variable-name": [ 89 | true, 90 | "ban-keywords" 91 | ], 92 | "whitespace": [ 93 | true, 94 | "check-branch", 95 | "check-decl", 96 | "check-operator", 97 | "check-separator", 98 | "check-type" 99 | ] 100 | } 101 | } --------------------------------------------------------------------------------