├── .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 |
3 |
4 | ## `stringify`
5 |
6 | `a -> str`
7 |
8 |
9 | ## `spawn`
10 |
11 | evalues the quote in a child environment, returns a future
12 |
13 | `[A] -> future`
14 |
15 |
16 | ## `await`
17 |
18 | evalues the quote in a child environment, waits for result
19 |
20 | `[A] -> [a]`
21 |
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 |
34 |
35 | ## `all`
36 |
37 | executes each element in a child environment
38 |
39 | `[A] -> [a]`
40 |
41 |
42 | ## `race`
43 |
44 | executes each element in a child environment, returns first to finish
45 |
46 | [A] -> [b]`
47 |
48 |
49 | ## `js-raw`
50 |
51 | evalues a string as raw javascript
52 |
53 | `str -> a*`
54 |
55 |
--------------------------------------------------------------------------------
/docs/api/flags.md:
--------------------------------------------------------------------------------
1 | # Internal Flags
2 |
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 |
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 |
30 |
--------------------------------------------------------------------------------
/docs/api/math.md:
--------------------------------------------------------------------------------
1 | # Internal Math Words
2 |
3 |
4 | ## `re`
5 |
6 | `z -> x`
7 |
8 | Real part of a value
9 |
10 |
11 |
12 | ## `im`
13 |
14 | `z -> y`
15 |
16 | Imaginary part of a value
17 |
18 |
19 |
20 | ## `arg`
21 |
22 | `z -> a`
23 |
24 | Argument (polar angle) of a complex number
25 |
26 |
27 |
28 | ## `abs`
29 |
30 | `x -> x'`
31 |
32 | Absolute value and complex magnitude
33 |
34 |
35 |
36 | ## `cos`
37 |
38 | `x -> x'`
39 |
40 | Cosine of argument in radians
41 |
42 |
43 |
44 | ## `sin`
45 |
46 | `x -> x'`
47 |
48 | Sine of argument in radians
49 |
50 |
51 |
52 | ## `tan`
53 |
54 | `x -> x'`
55 |
56 | Tangent of argument in radians
57 |
58 |
59 |
60 | ## `asin`
61 |
62 | `x -> x'`
63 |
64 | Inverse sine in radians
65 |
66 |
67 |
68 | ## `atan`
69 |
70 | `x -> x'`
71 |
72 | Inverse tangent in radians
73 |
74 |
75 |
76 | ## `round`
77 |
78 | `x -> n`
79 |
80 | Round to nearest decimal or integer
81 |
82 |
83 |
84 | ## `floor`
85 |
86 | `x -> n`
87 |
88 | Round toward negative infinity
89 |
90 |
91 |
92 | ## `ceil`
93 |
94 | `x -> n`
95 |
96 | Round toward positive infinity
97 |
98 |
99 |
100 | ## `sqrt`
101 |
102 | `x -> x'`
103 |
104 | Square root
105 |
106 |
107 | ## `conj`
108 |
109 | `z -> z'`
110 |
111 | Complex conjugate
112 |
113 |
114 |
115 | ## `exp`
116 |
117 | `x -> x'`
118 |
119 | Exponential
120 |
121 |
122 |
123 | ## `gamma`
124 |
125 | `x -> x'`
126 |
127 | Gamma function
128 |
129 |
130 |
131 | ## `atan2`
132 |
133 | `x₁ x₂ -> x₃`
134 |
135 | Four-quadrant inverse tangent
136 |
137 |
138 |
139 | ## `erf`
140 |
141 | `x -> x'`
142 |
143 | Error function
144 |
145 |
146 |
147 | ## `bit-and`
148 |
149 | `x₁ x₂ -> x₃`
150 |
151 | bitwise and
152 |
153 |
154 |
155 | ## `bit-or`
156 |
157 | `x₁ x₂ -> x₃`
158 |
159 | bitwise or
160 |
161 |
162 |
163 | ## `bit-xor`
164 |
165 | `x₁ x₂ -> x₃`
166 |
167 | bitwise xor
168 |
169 |
170 |
171 | ## `bit-not`
172 |
173 | `x -> x'`
174 |
175 | bitwise not
176 |
177 |
178 |
179 | ## `rand`
180 |
181 | `-> x`
182 |
183 | pseudo-random number in the range [0, 1)
184 |
185 |
186 |
187 | ## `infinity`
188 |
189 | `-> -Infinity`
190 |
191 | pushes the value Infinity
192 |
193 |
194 | ## `-infinity`
195 |
196 | `-> -Infinity`
197 |
198 | pushes the value -Infinity
199 |
200 |
201 | ## `complexinfinity`
202 |
203 | `-> ComplexInfinity`
204 |
205 | pushes the value complexinfinity
206 |
207 |
--------------------------------------------------------------------------------
/docs/api/node.md:
--------------------------------------------------------------------------------
1 | # Internal Words for Node Environment
2 |
3 |
4 | ## `args`
5 |
6 | `-> [str*]`
7 |
8 | Returns an array containing of command line arguments passed when the process was launched
9 |
10 |
11 | ## `println`
12 |
13 | `a ->`
14 |
15 | Prints the value followed by (newline)
16 |
17 |
18 |
19 | ## `print`
20 |
21 | `a ->`
22 |
23 | Prints the value
24 |
25 |
26 |
27 | ## `exit`
28 |
29 | `->`
30 |
31 | terminate the process synchronously with an a status code
32 |
33 |
34 |
35 | ## `rand-u32`
36 |
37 | `-> x`
38 |
39 | Generates cryptographically strong pseudo-random with a givennumber of bytes to generate
40 |
41 |
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 |
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 |
60 |
61 | ## `resolve`
62 |
63 | `str₁ -> str₂`
64 |
65 | returns a URL href releative to the current base
66 |
67 |
68 |
69 | ## `exists`
70 |
71 | `str -> bool`
72 |
73 | Returns true if the file exists, false otherwise.
74 |
75 |
76 |
77 | ## `read`
78 |
79 | `str₁ -> str₂`
80 |
81 | Pushes the content of a file as a utf8 string
82 |
83 |
84 |
85 | ## `cwd`
86 |
87 | `-> str`
88 |
89 | Pushes the current working directory
90 |
91 |
92 |
93 | ## `get-env`
94 |
95 | `str₁ -> str₂`
96 |
97 | Gets a environment variable
98 |
99 |
100 |
--------------------------------------------------------------------------------
/docs/api/objects.md:
--------------------------------------------------------------------------------
1 | # Internal Object Words
2 |
3 |
4 | ## `object`
5 |
6 | `[ a: b ... ] -> { a: b ... }`
7 |
8 | convert a quotation to an object
9 |
10 |
11 |
12 | ## `object?`
13 |
14 | `a -> bool`
15 |
16 | retruns true of the item is an object
17 |
18 |
19 | ## `has?`
20 |
21 | `{A} a: -> bool`
22 |
23 | returns true if an item contains a key
24 |
25 |
26 |
27 | ## `keys`
28 |
29 | `{A} -> [str*]`
30 |
31 | returns an array of keys
32 |
33 |
34 |
35 | ## `vals`
36 |
37 | `{A} -> [b*]`
38 |
39 | returns an array of values
40 |
41 |
42 |
--------------------------------------------------------------------------------
/docs/api/types.md:
--------------------------------------------------------------------------------
1 | # Internal Type Words
2 |
3 |
4 | ## `type`
5 |
6 | `a -> str`
7 |
8 | retruns the type of an item
9 |
10 |
11 |
12 | ## `number`
13 |
14 | `a -> x`
15 |
16 | converts to a number
17 |
18 |
19 |
20 | ## `complex`
21 |
22 | `a -> z`
23 |
24 | converts to a complex number
25 |
26 |
27 |
28 | ## `string`
29 |
30 | converts to a string
31 |
32 |
33 |
34 | ## `itoa`
35 |
36 | `x -> str`
37 |
38 | returns a string created from UTF-16 character code
39 |
40 |
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 |
49 |
50 | ## `atob`
51 |
52 | `str -> str`
53 |
54 | decodes a string of data which has been encoded using base-64 encoding
55 |
56 |
57 |
58 | ## `btoa`
59 |
60 | `str -> str`
61 |
62 | creates a base-64 encoded ASCII string from a String
63 |
64 |
65 |
66 | ## `hash`
67 |
68 | `a -> x`
69 |
70 | creates a numeric hash from a String
71 |
72 |
73 |
74 | ## `hex-hash`
75 |
76 | `a -> str`
77 |
78 | creates a hexidecimal hash from a String
79 |
80 |
81 |
82 | ## `base`
83 |
84 | `x -> str`
85 |
86 | Convert an integer to a string in the given base
87 |
88 |
89 |
90 | ## `boolean`
91 |
92 | `a -> bool`
93 |
94 | converts a value to a boolean
95 |
96 |
97 |
98 | ## `:` (key)
99 |
100 | `a -> a:`
101 |
102 | converts a string to a key
103 |
104 |
105 |
106 | ## `#` (symbol)
107 |
108 | `a -> #a`
109 |
110 | converts a string to a unique symbol
111 |
112 |
113 |
114 | ## `array`
115 |
116 | `a -> [A]`
117 |
118 | converts a value to an array
119 |
120 |
121 |
122 | ## `of`
123 |
124 | `a b -> c`
125 |
126 | converts the rhs value to the type of the lhs
127 |
128 |
129 |
130 | ## `is?`
131 |
132 | `a b -> bool`
133 |
134 | returns true if to values are the same value
135 |
136 |
137 |
138 | ## `nothing?`
139 |
140 | `a -> bool`
141 |
142 | returns true if the value is null or undefined
143 |
144 |
145 |
146 | ## `date`
147 |
148 | `a -> date`
149 |
150 | convert a value to a date/time
151 |
152 |
153 |
154 | ## `now`
155 |
156 | `-> date`
157 |
158 | returns the current date/time
159 |
160 |
161 |
162 | ## `clock`
163 |
164 | `-> x`
165 |
166 | returns a high resoltion time elapsed
167 |
168 |
169 |
170 | ## `regexp`
171 |
172 | `a -> regexp`
173 |
174 | convert string to regular expresion
175 |
176 |
177 |
--------------------------------------------------------------------------------
/docs/api/vocab.md:
--------------------------------------------------------------------------------
1 | Converts a stack item to a "function" definition
2 |
3 |
4 | # Internal Vocabulary Words
5 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
93 |
94 | ## `words`
95 |
96 | `-> [str*]`
97 |
98 | returns a list of defined words
99 |
100 |
101 | ## `locals`
102 |
103 | `-> [str*]`
104 |
105 | returns a list of locals words
106 |
107 |
108 | ## `scoped`
109 |
110 | `-> [str*]`
111 |
112 | returns a list of local scoped words
113 |
114 |
115 | ## `rewrite`
116 |
117 | `{A} [B] -> [C]`
118 |
119 | rewrites an expression using a set of rewrite rules
120 |
121 |
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 | [](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 | 
18 |
19 | 5. F♭ is a super set of JSON:
20 |
21 | 
22 |
23 | Bonus. It's my model train.
24 |
25 | 
--------------------------------------------------------------------------------
/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 | 
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 | 
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 | 
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 | [](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 = ``;
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 | }
--------------------------------------------------------------------------------