subscript 6.0.1 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -7
- package/index.js +71 -0
- package/justin.js +5 -5
- package/justin.min.js +1 -1
- package/package.json +2 -3
- package/subscript.js +3 -3
- package/subscript.min.js +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ _Subscript_ is micro-language with common syntax subset of C++, JS, Java, Python
|
|
|
8
8
|
* Standard conventional syntax
|
|
9
9
|
* Any fragment can be copy-pasted to any target language
|
|
10
10
|
* Tiny size <sub><a href="https://bundlephobia.com/package/subscript@6.0.0"><img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/subscript/latest?color=brightgreen&label=gzip"/></a></sub>
|
|
11
|
-
* :rocket:
|
|
11
|
+
* :rocket: Fast [performance](#performance)
|
|
12
12
|
* Configurable & extensible
|
|
13
13
|
* Trivial to use
|
|
14
14
|
|
|
@@ -34,7 +34,7 @@ _Subscript_ has [2kb](https://npmfs.com/package/subscript/6.0.0/subscript.min.js
|
|
|
34
34
|
|
|
35
35
|
## Design
|
|
36
36
|
|
|
37
|
-
Default operators (same as JS precedence order):
|
|
37
|
+
Default operators are (same as JS precedence order):
|
|
38
38
|
|
|
39
39
|
* `( a, b, c )`
|
|
40
40
|
* `a . b`, `a [ b ]`, `a ( b, c )`
|
|
@@ -56,7 +56,23 @@ Default literals:
|
|
|
56
56
|
* `"abc"` strings
|
|
57
57
|
* `1.2e+3` numbers
|
|
58
58
|
|
|
59
|
-
Everything else can be extended via `parse.set(
|
|
59
|
+
Everything else can be extended via `parse.set(token, precedence, operator)` for unary or binary operators (detected by number of arguments in `operator`), or via `parse.set(token, parse, precedence)` for custom tokens.
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
import script from 'subscript.js'
|
|
63
|
+
|
|
64
|
+
// add ~ unary operator with precedence 15
|
|
65
|
+
script.set('~', 15, a => ~a)
|
|
66
|
+
|
|
67
|
+
// add === binary operator
|
|
68
|
+
script.set('===', 9, (a, b) => a===b)
|
|
69
|
+
|
|
70
|
+
// add literals
|
|
71
|
+
script.set('true', a => ()=>true)
|
|
72
|
+
script.set('false', a => ()=>false)
|
|
73
|
+
|
|
74
|
+
script`true === false`() // false
|
|
75
|
+
```
|
|
60
76
|
|
|
61
77
|
See [subscript.js](subscript.js) or [justin.js](./justin.js) for examples.
|
|
62
78
|
|
|
@@ -273,7 +289,7 @@ Parse 30k times:
|
|
|
273
289
|
```
|
|
274
290
|
subscript: ~170 ms
|
|
275
291
|
justin: ~183 ms
|
|
276
|
-
jsep: ~
|
|
292
|
+
jsep: ~270 ms
|
|
277
293
|
mr-parser: ~420 ms
|
|
278
294
|
expr-eval: ~480 ms
|
|
279
295
|
math-parser: ~570 ms
|
|
@@ -284,15 +300,15 @@ new Function: ~1154 ms
|
|
|
284
300
|
|
|
285
301
|
Eval 30k times:
|
|
286
302
|
```
|
|
287
|
-
subscript: ~
|
|
288
|
-
justin: ~
|
|
303
|
+
subscript: ~17 ms
|
|
304
|
+
justin: ~17 ms
|
|
289
305
|
jsep (expression-eval): ~30 ms
|
|
290
306
|
mr-parser: -
|
|
291
307
|
expr-eval: ~72 ms
|
|
292
308
|
math-parser: -
|
|
293
309
|
jexl: ~110 ms
|
|
294
310
|
mathjs: ~119 ms
|
|
295
|
-
new Function: ~
|
|
311
|
+
new Function: ~7 ms
|
|
296
312
|
```
|
|
297
313
|
|
|
298
314
|
## Alternatives
|
package/index.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const SPACE=32
|
|
2
|
+
|
|
3
|
+
// current string & index
|
|
4
|
+
export let idx, cur,
|
|
5
|
+
|
|
6
|
+
parse = (s, ...fields) => !(cur=s.raw ? String.raw(s,...fields) : s, idx=0, s=expr()) || cur[idx] ? err('Unexpected end') : ctx=>s(ctx||{}),
|
|
7
|
+
|
|
8
|
+
isId = c =>
|
|
9
|
+
(c >= 48 && c <= 57) || // 0..9
|
|
10
|
+
(c >= 65 && c <= 90) || // A...Z
|
|
11
|
+
(c >= 97 && c <= 122) || // a...z
|
|
12
|
+
c == 36 || c == 95 || // $, _,
|
|
13
|
+
(c >= 192 && c != 215 && c != 247), // any non-ASCII
|
|
14
|
+
|
|
15
|
+
err = (msg='Unexpected token',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
|
|
16
|
+
|
|
17
|
+
skip = (is=1, from=idx, l) => {
|
|
18
|
+
if (typeof is == 'number') idx += is
|
|
19
|
+
else while (is(cur.charCodeAt(idx))) idx++
|
|
20
|
+
return cur.slice(from, idx)
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// a + b - c
|
|
24
|
+
expr = (prec=0, end, cc, token, newNode, fn) => {
|
|
25
|
+
// chunk/token parser
|
|
26
|
+
while (
|
|
27
|
+
( cc=space() ) && // till not end
|
|
28
|
+
// FIXME: extra work is happening here, when lookup bails out due to lower precedence -
|
|
29
|
+
// it makes extra `space` call for parent exprs on the same character to check precedence again
|
|
30
|
+
( newNode =
|
|
31
|
+
(fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
|
|
32
|
+
(!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
|
|
33
|
+
)
|
|
34
|
+
) token = newNode;
|
|
35
|
+
|
|
36
|
+
// check end character
|
|
37
|
+
if (end) cc==end?idx++:err('Missing', String.fromCharCode(end))
|
|
38
|
+
|
|
39
|
+
return token
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
// skip space chars, return first non-space character
|
|
43
|
+
space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
|
|
44
|
+
|
|
45
|
+
// variable identifier
|
|
46
|
+
id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], fn.id=()=>name, fn) : 0,
|
|
47
|
+
|
|
48
|
+
// operator/token lookup table
|
|
49
|
+
lookup = [],
|
|
50
|
+
|
|
51
|
+
// create operator checker/mapper (see examples)
|
|
52
|
+
set = parse.set = (
|
|
53
|
+
op,
|
|
54
|
+
opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
|
|
55
|
+
c=op.charCodeAt(0),
|
|
56
|
+
l=op.length,
|
|
57
|
+
prev=lookup[c],
|
|
58
|
+
arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
|
|
59
|
+
word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
|
|
60
|
+
map=
|
|
61
|
+
// binary
|
|
62
|
+
arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
|
|
63
|
+
!a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
|
|
64
|
+
ctx => fn(a(ctx),b(ctx))
|
|
65
|
+
) :
|
|
66
|
+
// unary prefix (0 args)
|
|
67
|
+
arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
|
|
68
|
+
fn // custom parser
|
|
69
|
+
) =>
|
|
70
|
+
// FIXME: find out if that's possible to globalize precision and instead of passing it to map, just provide global
|
|
71
|
+
lookup[c] = (a, curPrec, from=idx) => curPrec<opPrec && (l<2||cur.substr(idx,l)==op) && (!word||!isId(cur.charCodeAt(idx+l))) && (idx+=l, map(a, curPrec)) || (idx=from, prev&&prev(a, curPrec))
|
package/justin.js
CHANGED
|
@@ -10,7 +10,7 @@ PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_EXP=14,
|
|
|
10
10
|
let u, list, op, prec, fn,
|
|
11
11
|
escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
|
|
12
12
|
string = q => (qc, c, str='') => {
|
|
13
|
-
qc&&err() // must not follow another token
|
|
13
|
+
qc&&err('Unexpected string') // must not follow another token
|
|
14
14
|
while (c=cur.charCodeAt(idx), c-q) {
|
|
15
15
|
if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
|
|
16
16
|
else str += skip()
|
|
@@ -29,10 +29,10 @@ for (list=[
|
|
|
29
29
|
'//', (a, prec) => (skip(c => c >= 32), a||expr(prec)),,
|
|
30
30
|
|
|
31
31
|
// literals
|
|
32
|
-
'null', a => a ? err() : ()=>null,,
|
|
33
|
-
'true', a => a ? err() : ()=>true,,
|
|
34
|
-
'false', a => a ? err() : ()=>false,,
|
|
35
|
-
'undefined', a => a ? err() : ()=>undefined,,
|
|
32
|
+
'null', a => a ? err('Unexpected literal') : ()=>null,,
|
|
33
|
+
'true', a => a ? err('Unexpected literal') : ()=>true,,
|
|
34
|
+
'false', a => a ? err('Unexpected literal') : ()=>false,,
|
|
35
|
+
'undefined', a => a ? err('Unexpected literal') : ()=>undefined,,
|
|
36
36
|
|
|
37
37
|
';', a => expr()||(()=>{}),,
|
|
38
38
|
|
package/justin.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let r,
|
|
1
|
+
let e,r,t,l,n,a,d=(t,...l)=>(r=t.raw?String.raw(t,...l):t,e=0,!(t=p())||r[e]?i("Unexpected end"):e=>t(e||{})),o=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,i=(t="Unexpected token",l=r[e])=>{throw SyntaxError(t+" `"+l+"` at "+e)},f=(t=1,l=e,n)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(l,e)},p=(r=0,t,l,n,a,d)=>{for(;(l=c())&&(a=(d=u[l])&&d(n,r)||!n&&s());)n=a;return t&&(l==t?e++:i("Missing",String.fromCharCode(t))),n},c=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},s=(e=f(o),r)=>e?((r=r=>r[e]).id=()=>e,r):0,u=[],h=d.set=(t,l,n=32,a=t.charCodeAt(0),d=t.length,i=u[a],f=n.length||([n,l]=[l,n],0),c=t.toUpperCase()!==t,s=(f>1?(e,r)=>e&&(r=p(l))&&(e.length||r.length?t=>n(e(t),r(t)):(e=n(e(),r()),()=>e)):f?e=>!e&&(e=p(l-1))&&(r=>n(e(r))):n))=>u[a]=(n,a,f=e)=>a<l&&(d<2||r.substr(e,d)==t)&&(!c||!o(r.charCodeAt(e+d)))&&(e+=d,s(n,a))||(e=f,i&&i(n,a)),g=e=>e>=48&&e<=57,x=t=>(t&&i("Unexpected number"),t=f((e=>46==e||g(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=f(2)+f(g)),(t=+t)!=t?i("Bad number"):()=>t),C=(e,r,t=e.of)=>l=>r(t?t(l):l,e.id());for(l=48;l<=57;)u[l++]=x;for(t=['"',e=>(e=e?i("Unexpected string"):f((e=>e-34)),f()||i("Bad string"),()=>e),,".",(e,r,t)=>e?(c(),r=f(o)||i(),(t=t=>e(t)[r]).id=()=>r,t.of=e,t):x(f(-1)),18,"[",(e,r,t)=>e&&(r=p(0,93)||i("Empty group"),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=p(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||i("Empty group")),18,",",(e,r,t=p(1))=>(t.all=e.all?(r,l,n=e.all(r))=>n.push(t(r))&&n:r=>[e(r),t(r)],t),1,"|",6,(e,r)=>e|r,"||",4,(e,r)=>e||r,"&",8,(e,r)=>e&r,"&&",5,(e,r)=>e&&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,"<<",11,(e,r)=>e<<r,"+",12,(e,r)=>e+r,"+",15,e=>+e,"++",e=>C(e||p(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>C(e||p(14),e?(e,r)=>e[r]--:(e,r)=>--e[r]),15,"!",15,e=>!e,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r];[l,n,a,...t]=t,l;)h(l,n,a);let U,A,b,m,y={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},E=t=>(l,n,a="")=>{for(l&&i("Unexpected string");(n=r.charCodeAt(e))-t;)92===n?(f(),n=f(),a+=y[n]||n):a+=f();return f()||i("Bad string"),()=>a};for((U=['"',E(34),,"'",E(39),,"/*",(t,l)=>(f((t=>42!==t&&47!==r.charCodeAt(e+1))),f(2),t||p(l)),,"//",(e,r)=>(f((e=>e>=32)),e||p(r)),,"null",e=>e?i("Unexpected literal"):()=>null,,"true",e=>e?i("Unexpected literal"):()=>!0,,"false",e=>e?i("Unexpected literal"):()=>!1,,"undefined",e=>e?i("Unexpected literal"):()=>{},,";",e=>p()||(()=>{}),,"===",9,(e,r)=>e===r,"!==",9,(e,r)=>e!==r,"~",15,e=>~e,"**",(e,r,t=p(13))=>r=>e(r)**t(r),14,":",3.1,(e,r)=>[e,r],"?",3,(e,r)=>e?r[2]:r[1],"?.",e=>e&&(r=>e(r)||(()=>{})),,"?.",(e,r)=>(c(),(r=f(o))&&(t=>e(t)?.[r])),,"in",10,(e,r)=>e in r,"[",(e,r)=>!e&&((e=p(0,93))?e.all?r=>e.all(r):r=>[e(r)]:e=>[]),,"{",(e,r)=>!e&&((e=p(0,125))?t=>(r=(e.all||e)(t),Object.fromEntries(e.all?r:[r])):e=>({})),,":",(e,r,t)=>(t=p(3.1)||i(),r=>[(e.id||e)(r),t(r),e(r)]),3.1]);[A,b,m,...U]=U,A;)h(A,b,m);export{d as default};
|
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subscript",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.2",
|
|
4
4
|
"description": "Microlanguage with common syntax for JS/C++/Python/Rust",
|
|
5
5
|
"main": "subscript.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
8
|
-
"
|
|
9
|
-
"evaluate.js",
|
|
8
|
+
"index.js",
|
|
10
9
|
"subscript.js",
|
|
11
10
|
"subscript.min.js",
|
|
12
11
|
"justin.js",
|
package/subscript.js
CHANGED
|
@@ -8,7 +8,7 @@ let u, list, op, prec, fn,
|
|
|
8
8
|
isNum = c => c>=_0 && c<=_9,
|
|
9
9
|
// 1.2e+3, .5
|
|
10
10
|
num = n => (
|
|
11
|
-
n&&err(),
|
|
11
|
+
n&&err('Unexpected number'),
|
|
12
12
|
n = skip(c=>c==PERIOD || isNum(c)),
|
|
13
13
|
(cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
|
|
14
14
|
n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
|
|
@@ -22,7 +22,7 @@ for (op=_0;op<=_9;) lookup[op++] = num
|
|
|
22
22
|
// operators
|
|
23
23
|
for (list=[
|
|
24
24
|
// "a"
|
|
25
|
-
'"', a => (a=a?err():skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
|
|
25
|
+
'"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
|
|
26
26
|
|
|
27
27
|
// a.b, .2, 1.2 parser in one
|
|
28
28
|
'.', (a,id,fn) => !a ? num(skip(-1)) : // FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
|
|
@@ -42,7 +42,7 @@ for (list=[
|
|
|
42
42
|
|
|
43
43
|
// [a,b,c] or (a,b,c)
|
|
44
44
|
',', (a,prec,b=expr(PREC_SEQ)) => (
|
|
45
|
-
b.all = a.all ?
|
|
45
|
+
b.all = a.all ? ctx => [...a.all(ctx), b(ctx)] : ctx => [a(ctx),b(ctx)],
|
|
46
46
|
b
|
|
47
47
|
), PREC_SEQ,
|
|
48
48
|
|
package/subscript.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let r,
|
|
1
|
+
let e,r,t,o,a,n,d=(t,...o)=>(r=t.raw?String.raw(t,...o):t,e=0,!(t=i())||r[e]?f("Unexpected end"):e=>t(e||{})),l=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f=(t="Unexpected token",o=r[e])=>{throw SyntaxError(t+" `"+o+"` at "+e)},h=(t=1,o=e,a)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(o,e)},i=(r=0,t,o,a,n,d)=>{for(;(o=c())&&(n=(d=p[o])&&d(a,r)||!a&&s());)a=n;return t&&(o==t?e++:f("Missing",String.fromCharCode(t))),a},c=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},s=(e=h(l),r)=>e?((r=r=>r[e]).id=()=>e,r):0,p=[],g=d.set=(t,o,a=32,n=t.charCodeAt(0),d=t.length,f=p[n],h=a.length||([a,o]=[o,a],0),c=t.toUpperCase()!==t,s=(h>1?(e,r)=>e&&(r=i(o))&&(e.length||r.length?t=>a(e(t),r(t)):(e=a(e(),r()),()=>e)):h?e=>!e&&(e=i(o-1))&&(r=>a(e(r))):a))=>p[n]=(a,n,h=e)=>n<o&&(d<2||r.substr(e,d)==t)&&(!c||!l(r.charCodeAt(e+d)))&&(e+=d,s(a,n))||(e=h,f&&f(a,n)),C=e=>e>=48&&e<=57,u=t=>(t&&f("Unexpected number"),t=h((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=h(2)+h(C)),(t=+t)!=t?f("Bad number"):()=>t),x=(e,r,t=e.of)=>o=>r(t?t(o):o,e.id());for(o=48;o<=57;)p[o++]=u;for(t=['"',e=>(e=e?f("Unexpected string"):h((e=>e-34)),h()||f("Bad string"),()=>e),,".",(e,r,t)=>e?(c(),r=h(l)||f(),(t=t=>e(t)[r]).id=()=>r,t.of=e,t):u(h(-1)),18,"[",(e,r,t)=>e&&(r=i(0,93)||f(),(t=t=>e(t)[r(t)]).id=r,t.of=e,t),18,"(",(e,r,t)=>(r=i(0,41),e?t=>e(t).apply(e.of?.(t),r?r.all?r.all(t):[r(t)]:[]):r||f()),18,",",(e,r,t=i(1))=>(t.all=e.all?r=>[...e.all(r),t(r)]:r=>[e(r),t(r)],t),1,"|",6,(e,r)=>e|r,"||",4,(e,r)=>e||r,"&",8,(e,r)=>e&r,"&&",5,(e,r)=>e&&r,"^",7,(e,r)=>e^r,"==",9,(e,r)=>e==r,"!=",9,(e,r)=>e!=r,">",10,(e,r)=>e>r,">=",10,(e,r)=>e>=r,">>",11,(e,r)=>e>>r,">>>",11,(e,r)=>e>>>r,"<",10,(e,r)=>e<r,"<=",10,(e,r)=>e<=r,"<<",11,(e,r)=>e<<r,"+",12,(e,r)=>e+r,"+",15,e=>+e,"++",e=>x(e||i(14),e?(e,r)=>e[r]++:(e,r)=>++e[r]),15,"-",12,(e,r)=>e-r,"-",15,e=>-e,"--",e=>x(e||i(14),e?(e,r)=>e[r]--:(e,r)=>--e[r]),15,"!",15,e=>!e,"*",13,(e,r)=>e*r,"/",13,(e,r)=>e/r,"%",13,(e,r)=>e%r];[o,a,n,...t]=t,o;)g(o,a,n);export{d as default};
|