Skip to content

Commit c93bc7b

Browse files
committed
Update docs/readme
1 parent 915f7b0 commit c93bc7b

2 files changed

Lines changed: 114 additions & 113 deletions

File tree

README.md

Lines changed: 81 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,94 @@
1-
# sub<sub><sup> ✦ </sup></sub>script [![build](https://github.com/dy/subscript/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/subscript/actions/workflows/node.js.yml) [![npm](https://img.shields.io/npm/v/subscript)](http://npmjs.org/subscript) [![demo](https://img.shields.io/badge/demo-%F0%9F%9A%80-white)](https://dy.github.io/subscript/repl) [![](https://img.shields.io/badge/MIT-%E0%A5%90-white)](https://krishnized.github.io/license)
1+
# sub<sub><sup> ✦ </sup></sub>script [![build](https://github.com/dy/subscript/actions/workflows/node.js.yml/badge.svg)](https://github.com/dy/subscript/actions/workflows/node.js.yml) [![npm](https://img.shields.io/npm/v/subscript)](http://npmjs.org/subscript) [![size](https://img.shields.io/bundlephobia/minzip/subscript?label=size)](https://bundlephobia.com/package/subscript)
22

3-
> Safe expression evaluator & language tool.
3+
> Tiny expression compiler.
44
55
```js
66
import subscript from 'subscript'
77

8-
subscript`a + b * 2`({ a: 1, b: 3 }) // 7
8+
let fn = subscript('a + b * 2')
9+
fn({ a: 1, b: 3 }) // 7
910
```
1011

12+
1113
* **Minimal** – common expressions < JSON + expressions < JS subset
12-
* **Safe** — sandboxed, no global access
13-
* **Fast** — Pratt parser engine, strong in class
14+
* **Safe** — sandboxed, blocks `__proto__`, `constructor`, no global access
15+
* **Fast** — Pratt parser engine, see [benchmarks](#performance)
1416
* **Portable** — universal expression format, any compile target
15-
* **Self-hosting** — compiles own source (js in js)
16-
* **Language tool** — modular syntax extensions for custom DSL
17+
* **Metacircular**[jessie](#jessie) can parse and compile itself
18+
* **Extensible** — pluggable syntax for building custom DSL
19+
20+
21+
[**Playground →**](https://dy.github.io/subscript/)
22+
23+
24+
## Install
25+
26+
```
27+
npm install subscript
28+
```
1729

1830

1931
## Presets
2032

21-
**subscript** — common expressions
33+
**subscript** — common expressions (~4kb gzip)
2234
```js
23-
import expr from 'subscript'
35+
import subscript from 'subscript'
2436

25-
expr`a.b + c * 2`({ a: { b: 1 }, c: 3 }) // 7
26-
expr`x > 0 && y != z`({ x: 1, y: 2, z: 3 }) // true
37+
subscript('a.b + c * 2')({ a: { b: 1 }, c: 3 }) // 7
38+
subscript('x > 0 && y != z')({ x: 1, y: 2, z: 3 }) // true
2739
```
2840

29-
**justin** — JSON + expressions (JSON superset)
41+
**justin** — JSON + expressions (~6kb gzip)
3042
```js
3143
import justin from 'subscript/justin.js'
3244

33-
justin`{ x: a?.b ?? 0, y: [1, ...rest] }`({ a: null, rest: [2, 3] })
45+
justin('{ x: a?.b ?? 0, y: [1, ...rest] }')({ a: null, rest: [2, 3] })
3446
// { x: 0, y: [1, 2, 3] }
3547

36-
justin`items.filter(x => x > 1)`({ items: [1, 2, 3] }) // [2, 3]
48+
justin('items.filter(x => x > 1)')({ items: [1, 2, 3] }) // [2, 3]
3749
```
3850

39-
**jessie**JSON + expressions + statements (JS subset)
51+
**jessie**JS subset (~8kb gzip)
4052
```js
4153
import jessie from 'subscript/jessie.js'
4254

43-
jessie`
55+
let fn = jessie(`
4456
function factorial(n) {
4557
if (n <= 1) return 1
4658
return n * factorial(n - 1)
4759
}
4860
factorial(5)
49-
`({}) // 120
61+
`)
62+
fn({}) // 120
5063
```
5164

65+
Jessie can parse and compile its own source code.
66+
5267
## Extension
5368

69+
Add a set intersection operator:
70+
5471
```js
55-
import subscript, { binary, operator, compile, token } from 'subscript/justin.js'
72+
import { binary, operator, compile } from 'subscript/justin.js'
5673

57-
// add intersection operator
58-
binary('', 80)
59-
operator('', (a, b) => (
74+
binary('', 80) // register parser
75+
operator('', (a, b) => ( // register compiler
6076
a = compile(a), b = compile(b),
6177
ctx => a(ctx).filter(x => b(ctx).includes(x))
6278
))
79+
```
6380

64-
subscript`[1,2,3] ∩ [2,3,4]`({}) // [2, 3]
65-
66-
// add units
67-
token('px', 200, n => n && [, n[1] + 'px']) // 5px → "5px"
81+
```js
82+
import justin from 'subscript/justin.js'
83+
justin('[1,2,3] ∩ [2,3,4]')({}) // [2, 3]
6884
```
6985

70-
See [docs.md](./docs.md) for full API.
86+
See [docs.md](./docs.md) for full API: `binary`, `unary`, `nary`, `group`, `access`, `literal`, `token`.
7187

7288

73-
## Expressions format
89+
## Tree Format
7490

75-
Subscript uses simplified syntax tree format:
91+
Expressions parse to a minimal JSON-compatible AST:
7692

7793
```js
7894
import { parse } from 'subscript'
@@ -85,11 +101,11 @@ Three forms:
85101

86102
```js
87103
'x' // identifier — resolve from context
88-
[, value] // literal — return as-is
104+
[, value] // literal — return as-is (empty slot = data)
89105
[op, ...args] // operation — apply operator
90106
```
91107

92-
See [spec.md](./spec.md) for full specification.
108+
Portable to any language. See [spec.md](./spec.md).
93109

94110

95111
## Safety
@@ -100,51 +116,56 @@ Blocked by default:
100116
- Global access (only context is visible)
101117

102118
```js
103-
subscript`constructor.constructor("alert(1)")()`({})
119+
subscript('constructor.constructor("alert(1)")()')({})
104120
// undefined (blocked)
105121
```
106122

107123
## Performance
108124

109-
Parse 30k times:
110-
```
111-
subscript: ~150 ms 🥇
112-
justin: ~183 ms
113-
jsep: ~270 ms 🥈
114-
jexpr: ~297 ms 🥉
115-
mr-parser: ~420 ms
116-
expr-eval: ~480 ms
117-
math-parser: ~570 ms
118-
math-expression-evaluator: ~900ms
119-
jexl: ~1056 ms
120-
mathjs: ~1200 ms
121-
new Function: ~1154 ms
122-
123-
// Evaluate 30k times:
124-
new Function ~7ms 🥇
125-
subscript ~15ms 🥈
126-
justin: ~17 ms
127-
jsep ~30ms 🥉
128-
math-expression-evaluator: ~50ms
129-
expr-eval: ~72 ms
130-
jexl: ~110 ms
131-
mathjs: ~119 ms
125+
Parse 30k expressions:
126+
127+
| Parser | Time |
128+
|--------|------|
129+
| subscript | ~150ms |
130+
| justin | ~183ms |
131+
| jsep | ~270ms |
132+
| expr-eval | ~480ms |
133+
| jexl | ~1056ms |
134+
135+
Evaluate 30k times:
136+
137+
| Evaluator | Time |
138+
|-----------|------|
139+
| new Function | ~7ms |
140+
| subscript | ~15ms |
141+
| jsep+eval | ~30ms |
142+
| expr-eval | ~72ms |
143+
144+
145+
## Template Tag
146+
147+
For repeated evaluation, use template syntax for automatic caching:
148+
149+
```js
150+
subscript`a + b`({ a: 1, b: 2 }) // cached compilation
151+
152+
// interpolate values
153+
const limit = 100
154+
subscript`x < ${limit}`({ x: 50 }) // true
132155
```
133156

157+
## Bundle
134158

135-
## Utils
159+
Create custom dialect as single file:
136160

137-
**Bundle** — create custom dialect bundle:
138161
```js
139162
import { bundle } from 'subscript/util/bundle.js'
140163

141-
// Bundle specific features into single file
142164
const code = await bundle('subscript/jessie.js')
143-
// → self-contained ES module with parse, compile exports
165+
// → self-contained ES module
144166
```
145167

146-
**Playground** — interactive dialect builder with live bundling:
147-
[**Try it →**](https://dy.github.io/subscript/)
168+
[**Playground →**](https://dy.github.io/subscript/) — interactive dialect builder
148169

149170

150171
## Used by

docs.md

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,55 @@
55
```js
66
import subscript from 'subscript'
77

8-
// evaluate expression
9-
subscript`x + 1`({ x: 2 }) // 3
8+
// compile and evaluate
9+
let fn = subscript('x + 1')
10+
fn({ x: 2 }) // 3
1011

11-
// with interpolation
12-
const max = 100
13-
subscript`x < ${max}`({ x: 50 }) // true
12+
// or one-liner
13+
subscript('a * b')({ a: 3, b: 4 }) // 12
1414
```
1515

16-
---
16+
1717

1818
## Evaluate Code
1919

20-
### Template Tag *(recommended)*
20+
### Function Call
21+
22+
```js
23+
subscript('a + b')({ a: 1, b: 2 }) // 3
24+
```
25+
26+
Parses and compiles on each call. Use for dynamic expressions.
27+
28+
### Template Tag *(cached)*
2129

2230
```js
2331
subscript`a + b`({ a: 1, b: 2 }) // 3
2432

25-
// caching - same template reuses compiled function
33+
// same template reuses compiled function
2634
const check = () => subscript`x > 0`
2735
check() === check() // true (cached)
2836

2937
// embed values
3038
const limit = 10
3139
subscript`x > ${limit}`({ x: 15 }) // true
40+
```
3241

33-
// embed functions
42+
### Interpolation
43+
44+
```js
3445
const double = x => x * 2
3546
subscript`${double}(x)`({ x: 5 }) // 10
3647

3748
// embed objects
3849
const math = { pi: 3.14 }
3950
subscript`${math}.pi * r * r`({ r: 2 }) // 12.56
4051

41-
// embed AST
52+
// embed AST nodes
4253
subscript`${['+', 'a', 'b']} * 2`({ a: 1, b: 2 }) // 6
4354
```
4455

45-
#### Detection Rules
56+
**Detection rules:**
4657

4758
| Value | Detected As | Result |
4859
|-------|-------------|--------|
@@ -55,40 +66,19 @@ subscript`${['+', 'a', 'b']} * 2`({ a: 1, b: 2 }) // 6
5566
| `[, 100]` | Array (undefined first) | AST literal |
5667

5768

58-
### String Input
59-
60-
```js
61-
subscript('a + b')({ a: 1, b: 2 }) // 3
62-
```
63-
64-
No caching. Use for dynamic code.
65-
66-
---
6769

68-
## Swap Parser / Compiler
69-
70-
### `subscript.parse`
71-
72-
Default: expr (minimal). Upgrade for more features:
70+
## Parse / Compile Separately
7371

7472
```js
75-
import { parse } from 'subscript/justin.js'
76-
subscript.parse = parse // + arrows, templates, JSON
77-
```
78-
73+
import { parse, compile } from 'subscript'
7974

80-
### `subscript.compile`
75+
let ast = parse('a + b')
76+
// ['+', 'a', 'b']
8177

82-
Default: JS evaluator.
83-
84-
```js
85-
import { codegen } from 'subscript/compile/js-emit.js'
86-
subscript.compile = ast => codegen(ast) // emit source instead
78+
let fn = compile(ast)
79+
fn({ a: 1, b: 2 }) // 3
8780
```
8881

89-
---
90-
91-
9282

9383
## Presets
9484

@@ -252,21 +242,11 @@ token('?', 25, left => {
252242
})
253243
```
254244

255-
### Import Order
245+
> [!NOTE]
246+
> When extending operators that share a prefix (like `=`, `==`, `===`), **import shorter operators first** so longer ones are checked first in the token chain.
247+
> This applies to: `=`/`==`/`===`, `!`/`!=`/`!==`, `|`/`||`, `&`/`&&`, etc.
256248
257-
When extending operators that share a prefix (like `=`, `==`, `===`), **import shorter operators first** so longer ones are checked first in the token chain:
258249

259-
```js
260-
// Correct order - = before ===
261-
import './feature/op/assignment.js' // =
262-
import './feature/op/equality-strict.js' // ===
263-
264-
// Wrong order would make === parse as = followed by ==
265-
```
266-
267-
This applies to: `=`/`==`/`===`, `!`/`!=`/`!==`, `|`/`||`, `&`/`&&`, etc.
268-
269-
---
270250

271251
## Extend Compiler
272252

@@ -303,7 +283,7 @@ import { prop, compile } from 'subscript'
303283
operator('delete', a => prop(a, (obj, key) => delete obj[key]))
304284
```
305285

306-
---
286+
307287

308288
## Named Exports
309289

@@ -328,7 +308,7 @@ import {
328308
} from 'subscript'
329309
```
330310

331-
---
311+
332312

333313
## Syntax Tree
334314

0 commit comments

Comments
 (0)