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
66import 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
3143import 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
4153import 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
7894import { 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
139162import { bundle } from ' subscript/util/bundle.js'
140163
141- // Bundle specific features into single file
142164const 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
0 commit comments