Skip to content

Commit

Permalink
docs(pointfree-lang): update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Apr 3, 2018
1 parent 3310ec3 commit ca5dfd3
Showing 1 changed file with 183 additions and 56 deletions.
239 changes: 183 additions & 56 deletions packages/pointfree-lang/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ This project is part of the [@thi.ng/umbrella](https://github.com/thi-ng/umbrell
- [Identifiers](#identifiers)
- [Word definitions](#word-definitions)
- [Hyperstatic words](#hyperstatic-words)
- [Local variables](#local-variables)
- [Boolean](#boolean)
- [Numbers](#numbers)
- [Strings](#strings)
- [Quotations (Arrays)](#quotations-arrays)
- [Literal quotes](#literal-quotes)
- [Variables](#variables)
- [Dynamic scoping](#dynamic-scoping)
- [Objects](#objects)
- [Sets](#sets)
- [Ideas / Todos](#ideas--todos)
- [Authors](#authors)
- [License](#license)
Expand All @@ -42,9 +43,10 @@ an ES6 embedded DSL for concatenative programming:
& parser
- untyped, interpreted, but with AOT compilation of user defined words
- hyperstatic word definitions
- support for custom / externally defined vocabularies (word sets / JS functions)
- scoped variables (stored in environment object)
- nested quotations (code as data)
- support for custom / externally defined words (JS functions)
- dynamically scoped variables (stored in environment object)
- syntax sugar for declaring & autobinding local vars w/ stack values
- nested quotations (code as data, vanilla JS arrays)
- array & object literals (optionally w/ computed properties)
- all other features of @thi.ng/pointfree (combinators, array/vector ops etc.)

Expand Down Expand Up @@ -158,21 +160,24 @@ pf.runWord("hairx", env, [100, 200, 640, 480]);
As noted previously, the syntax is closely based on Forth (and other
concatenative languages), however since this implementation is targetted
to ES6 environments, the semantics and actual implementation differ
drastically. In @thi.ng/pointfree (and therefore also in this DSL layer):
drastically. In this DSL (and most aspects also in @thi.ng/pointfree):

- words and programs are implemented as functional compositions of
vanilla JS functions, i.e. `1 2 +` => `add(push(1)(push(2)(ctx)))`
- therefore no user controlled context switch between immediate &
compile modes, as in Forth
- parsing of word definitions and quotations triggers compile mode
automatically
- both stacks (D & R stacks) can store any valid JS data type
- parsing of word definitions triggers compile mode automatically
- variables and both stacks (D & R stacks) can store any valid JS data
type
- no linear memory as in Forth, instead variables and the dictionary of
(custom / FFI or user defined) words is stored in a separate
environment object, which is passed to each word/function
- the DSL has syntax sugar for variable value lookups & assignments
- the DSL allows nested quotations & object literals, optionally with
lazily resolved computed properties and/or values
- within word definitions the DSL supports binding stack values to local
vars at the beginning of the word
- the DSL supports nested quotations (array) & object literals, incl.
support for computed properties and/or values (lazily resolved within
words)
- all symbols are separated by whitespace (like in Clojure, commas are
considered whitespace too)

Expand All @@ -183,8 +188,8 @@ includes the `--` string, it's marked as a [stack effect
comment](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree#about-stack-effects)
in preparation for future tooling additions.

Comments can span multiple lines. There's no special syntax for single
line comments:
Comments current cannot contain `(` or `)`, but can span multiple lines.
There's no special syntax for single line comments:

```
( multiline:
Expand All @@ -203,8 +208,8 @@ ______ ____ |__| _____/ |__/ ____\______ ____ ____
### Identifiers

Word identifiers can contain any alhpanumeric character and these
additional ones: `*?$%&/|~<>=._+-`. Digits are not allowed as first
char.
additional ones: `*?$%&/|~<>=._+-`. However, digits are not allowed as
first char.

All 100+ built-in words defined by
[@thi.ng/pointfree](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree)
Expand All @@ -213,29 +218,39 @@ aren't valid names in the ES6 context):

| Alias | Original name |
| --- | --- |
| ?drop | dropif |
| ?dup | dupif |
| -rot | invrot |
| >r | movdr |
| >r2 | movdr2 |
| r> | movrd |
| r2> | movrd2 |
| if | condq |
| switch | casesq |
| while | loopq |
| + | add |
| - | sub |
| * | mul |
| / | div |
| 1+ | inc |
| 1- | dec |
| v+ | vadd |
| v- | vsub |
| v* | vmul |
| v/ | vdiv |
| . | print |
| .s | printds |
| .r | printrs |
| `?drop` | `dropif` |
| `?dup` | `dupif` |
| `-rot` | `invrot` |
| `>r` | `movdr` |
| `>r2` | `movdr2` |
| `r>` | `movrd` |
| `r2>` | `movrd2` |
| `if` | `condq` |
| `switch` | `casesq` |
| `while` | `loopq` |
| `+` | `add` |
| `-` | `sub` |
| `*` | `mul` |
| `/` | `div` |
| `v+` | `vadd` |
| `v-` | `vsub` |
| `v*` | `vmul` |
| `v/` | `vdiv` |
| `=` | `eq` |
| `not=` | `neq` |
| `<=` | `lteq` |
| `>=` | `gteq` |
| `<` | `lt` |
| `>` | `gt` |
| `pos?` | `ispos` |
| `neg?` | `isneg` |
| `nil?` | `isnil` |
| `zero?` | `iszero` |
| `pi` | `Math.PI` |
| `tau` | `2 * Math.PI` |
| `.` | `print` |
| `.s` | `printds` |
| `.r` | `printrs` |

The ID resolution priority is:

Expand All @@ -255,17 +270,29 @@ As in Forth, new words can be defined using the `: name ... ;` form.

Will result in `100`.

There're no formatting rules enforced (yet, but under consideration).
However, it's strongly encouraged to include [stack effect
comments](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree#about-stack-effects)
as shown in the examples above.

**Word definitions MUST be terminated with `;`.**

#### Hyperstatic words

Unlike [variables](#variables), words are defined in a
Unlike [variables](#variables), which are [dynamically
scoped](https://en.wikipedia.org/wiki/Scope_(computer_science)#Dynamic_scoping),
words are defined in a
[hyper-static](http://wiki.c2.com/?HyperStaticGlobalEnvironment)
environment, meaning new versions of existing words can be defined,
however any other word (incl. the new version of same word) which uses
the earlier version will continue to do so. By implication, this too
means that attempting to use undefined words inside a word definition
will fail, even if they'd be defined later on.
the earlier version will continue to use that older version.
Consequently, this too means that attempting to use undefined words
inside a word definition will fail, even if they'd be defined later on.
In these cases, use of variables and/or quotations is encouraged to
implement dynamic programming techniques.

```ts
// hyperstaticness by example
pf.run(`
: foo "foo1" ;
: bar foo "bar" + ;
Expand All @@ -279,12 +306,35 @@ foo bar
// [ 'foo1foo2', 'foo1bar' ]
```

There're no formatting rules enforced (yet, but under consideration).
However, it's strongly encouraged to include [stack effect
comments](https://github.com/thi-ng/umbrella/tree/master/packages/pointfree#about-stack-effects)
as shown in the examples above.
#### Local variables

**Word definitions MUST be terminated with `;`.**
A word definition can include an optional declaration of local
variables, which are automatically bound to stack values each time the
word is invoked. The declarations are given via the form:

```
: wordname ^{ name1 name2 ... } ... ;
```

If used, the declaration MUST be given as first element of the word,
even before the optional stack comment:

```ts
// word with 2 local vars binding: a & b
// when the word is used, first pops 2 values from stack
// and stores them in local vars (in right to left order)
pf.run(`
: add ^{ a b } ( a b -- a+b )
"a=" @a + .
"b=" @b + . ;
1 2 add
`);
// a=1
// b=2
```

See [section about variables](#variables) for further details...

### Boolean

Expand Down Expand Up @@ -318,16 +368,16 @@ A single element quotation can be formed by prefixing a term with `'`. Nestable.
### Variables

Variables can be looked up & resolved via the currently active
environment by prefixing their name with `@`. Attempting to resolve an
unknown var will result in an error.
environment and scope by prefixing their name with `@`. Attempting to
resolve an unknown var will result in an error.

```ts
pf.runU(`@a @b +`, {a: 10, b: 20});
// 30
```

Storing a stack value in a variable (in the the current environment) is
done via the `!` suffix:
Assigning a value to a variable (in the the current scope) is done via
the `!` suffix:

```ts
pf.runE(`1 2 + a!`)
Expand All @@ -338,7 +388,87 @@ Furthermore, readonly variables can be defined via words. In this case
no prefix must be used and these kind of variables are
[hyperstatic](#hyperstatic-words).

TODO add info about scoping and resolution in words / quotations...
```ts
pf.run(`: pi 3.1415 ; "π=" pi + .`);
// π=3.1415
```

#### Dynamic scoping

Each variable is resolved via its own stack of binding scopes, and
therefore technically results in [dynamic
scoping](https://en.wikipedia.org/wiki/Scope_(computer_science)#Dynamic_scoping).
However, in this DSL a new scope is only introduced when a word defines
a local var with an already existing name, so in practice the effect is
more like lexical scoping.

Var assignment always only impacts the current scope of a var.

```ts
// predefined global scope (via env binding)
pf.runU(`@a`, {a: 1});
// 1

// dynamically created global var, then used in quotation
pf.runU(`1 a! [@a @a]`);
// [1, 1]

// var lookup inside word
pf.runU(`: foo @a ; foo`, {a: 1});
// 1

// global & word local vars
// local var (value obtained from stack) takes precendence inside word
pf.runU(`: foo ^{ a } @a ; 2 foo, 3 foo, @a vec3`, {a: 1});
// [2, 3, 1]

// nested local var scopes
// both `foo` & `bar` define a local var `a`
pf.run(`
: foo ^{ a }
"foo a=" @a + .
( since 'a' is declared as local var )
( assignment is only to local scope )
100 a! ;
: bar ^{ a }
"bar1 a=" @a + .
@a inc foo ( call 'foo' w/ new value )
"bar2 a=" @a + . ; ( @a still has same value here )
1 bar
"global a=" @a + . ( global @a never modified )
`, { a: 0 });
// bar1 a=1
// foo a=2
// bar2 a=1
// global a=0

// since `b` is NOT declared as local var inside `foo`
// assigning a value to `b` (even inside `foo`) will be treated as global
pf.runE(`: foo @a b! ; foo`, {a: 1})
// { a: 1, b: 1, __words: { foo: [Function] } }

// here `foo` doesn't declare any locals
// so assignment to `a` will impact parent scope:
// - when `foo` is called from `bar`, bar's `a` var is modified
// - when `foo` is called from root level, global var `a` is created/modified
pf.runE(`
: foo 10 a! ;
: bar ^{ a }
"before foo a=" @a + .
foo
"after foo a=" @a + . ;
1 bar
foo`
);
// before foo a=1
// after foo a=10
{ a: 10 ... }
```

### Objects

Expand All @@ -347,10 +477,10 @@ Plain objects literals can be created similarly as in JS, i.e.
`{key1: value, key2: val2 ...}` (again commas are optional)

Keys can be given with or without doublequotes (string literals). Quotes
are only needed if:
for keys are only needed if:

- the key contains spaces, has `@` prefix or `!` suffix
- is binary/hex number
- is a binary / hex number
- a number in scientific notation

Furthermore, variables can be used both as keys and/or values:
Expand All @@ -367,9 +497,6 @@ pf.runU(src, {bingo: 42}, [43]);
// nope
```

### Sets

TODO

## Ideas / Todos

Expand Down

0 comments on commit ca5dfd3

Please sign in to comment.