subscript 6.2.0 → 6.3.0
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 +7 -2
- package/justin.js +171 -11
- package/justin.min.js +1 -1
- package/package.json +10 -8
- package/{index.js → src/index.js} +0 -0
- package/src/justin.js +74 -0
- package/src/subscript.js +91 -0
- package/subscript.js +84 -13
- package/subscript.min.js +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ fn.args // ['a', 'c', 'd']
|
|
|
19
19
|
|
|
20
20
|
_Subscript_ is designed to be useful for:
|
|
21
21
|
|
|
22
|
-
* templates (perfect match with [template parts](https://github.com/github/template-parts))
|
|
22
|
+
* templates (perfect match with [template parts](https://github.com/github/template-parts), [templize](https://github.com/spectjs/templize))
|
|
23
23
|
* expressions evaluators, calculators
|
|
24
24
|
* configurable subsets of languages (eg. [justin](#justin)) <!-- see sonr, mineural -->
|
|
25
25
|
* pluggable/mock language features (eg. pipe operator)
|
|
@@ -317,16 +317,21 @@ math-parser: -
|
|
|
317
317
|
## Alternatives
|
|
318
318
|
|
|
319
319
|
* [jexpr](https://github.com/justinfagnani/jexpr)
|
|
320
|
+
* [jsep](https://github.com/EricSmekens/jsep)
|
|
320
321
|
* [jexl](https://github.com/TomFrost/Jexl)
|
|
321
322
|
* [mozjexl](https://github.com/mozilla/mozjexl)
|
|
322
323
|
* [expr-eval](https://github.com/silentmatt/expr-eval)
|
|
323
324
|
* [expression-eval](https://github.com/donmccurdy/expression-eval)
|
|
324
|
-
* [jsep](https://github.com/EricSmekens/jsep)
|
|
325
325
|
* [string-math](https://github.com/devrafalko/string-math)
|
|
326
326
|
* [nerdamer](https://github.com/jiggzson/nerdamer)
|
|
327
327
|
* [math-codegen](https://github.com/mauriciopoppe/math-codegen)
|
|
328
328
|
* [math-parser](https://www.npmjs.com/package/math-parser)
|
|
329
329
|
* [math.js](https://mathjs.org/docs/expressions/parsing.html)
|
|
330
|
+
|
|
331
|
+
## Next door
|
|
332
|
+
|
|
333
|
+
* [engine262](https://github.com/engine262/engine262)
|
|
330
334
|
* [Jessie](https://github.com/endojs/Jessie)
|
|
335
|
+
* [xst](https://github.com/Moddable-OpenSource/moddable-xst)
|
|
331
336
|
|
|
332
337
|
<p align=center>🕉</p>
|
package/justin.js
CHANGED
|
@@ -1,22 +1,180 @@
|
|
|
1
|
+
const SPACE=32;
|
|
2
|
+
|
|
3
|
+
// current string, index and collected ids
|
|
4
|
+
let idx, cur, args,
|
|
5
|
+
|
|
6
|
+
// no handling tagged literals since easily done on user side with cache, if needed
|
|
7
|
+
parse = (s, fn= !(cur=s, idx=0, args=[], s=expr()) || cur[idx] ? err() : ctx=>s(ctx||{})) => (fn.args = args, fn),
|
|
8
|
+
|
|
9
|
+
isId = c =>
|
|
10
|
+
(c >= 48 && c <= 57) || // 0..9
|
|
11
|
+
(c >= 65 && c <= 90) || // A...Z
|
|
12
|
+
(c >= 97 && c <= 122) || // a...z
|
|
13
|
+
c == 36 || c == 95 || // $, _,
|
|
14
|
+
(c >= 192 && c != 215 && c != 247), // any non-ASCII
|
|
15
|
+
|
|
16
|
+
err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
|
|
17
|
+
|
|
18
|
+
skip = (is=1, from=idx, l) => {
|
|
19
|
+
if (typeof is == 'number') idx += is;
|
|
20
|
+
else while (is(cur.charCodeAt(idx))) idx++;
|
|
21
|
+
return cur.slice(from, idx)
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// a + b - c
|
|
25
|
+
expr = (prec=0, end, cc, token, newNode, fn) => {
|
|
26
|
+
// chunk/token parser
|
|
27
|
+
while (
|
|
28
|
+
( cc=space() ) && // till not end
|
|
29
|
+
// FIXME: extra work is happening here, when lookup bails out due to lower precedence -
|
|
30
|
+
// it makes extra `space` call for parent exprs on the same character to check precedence again
|
|
31
|
+
( newNode =
|
|
32
|
+
(fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
|
|
33
|
+
(!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
|
|
34
|
+
)
|
|
35
|
+
) token = newNode;
|
|
36
|
+
|
|
37
|
+
// check end character
|
|
38
|
+
// FIXME: can't show "Unclose paren", because can be unknown operator within group as well
|
|
39
|
+
if (end) cc==end?idx++:err();
|
|
40
|
+
|
|
41
|
+
return token
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// skip space chars, return first non-space character
|
|
45
|
+
space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
|
|
46
|
+
|
|
47
|
+
// variable identifier
|
|
48
|
+
id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
|
|
49
|
+
|
|
50
|
+
// operator/token lookup table
|
|
51
|
+
lookup = [],
|
|
52
|
+
|
|
53
|
+
// create operator checker/mapper (see examples)
|
|
54
|
+
set = parse.set = (
|
|
55
|
+
op,
|
|
56
|
+
opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
|
|
57
|
+
c=op.charCodeAt(0),
|
|
58
|
+
l=op.length,
|
|
59
|
+
prev=lookup[c],
|
|
60
|
+
arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
|
|
61
|
+
word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
|
|
62
|
+
map=
|
|
63
|
+
// binary
|
|
64
|
+
arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
|
|
65
|
+
!a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
|
|
66
|
+
ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
|
|
67
|
+
) :
|
|
68
|
+
// unary prefix (0 args)
|
|
69
|
+
arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
|
|
70
|
+
fn // custom parser
|
|
71
|
+
) =>
|
|
72
|
+
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));
|
|
73
|
+
|
|
74
|
+
const PERIOD=46, CPAREN=41, CBRACK$1=93, DQUOTE$1=34, _0=48, _9=57,
|
|
75
|
+
PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR$1=6, PREC_XOR=7, PREC_AND=8,
|
|
76
|
+
PREC_EQ$1=9, PREC_COMP$1=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY$1=15, PREC_CALL=18;
|
|
77
|
+
|
|
78
|
+
let list$1, op$1, prec$1, fn$1,
|
|
79
|
+
isNum = c => c>=_0 && c<=_9,
|
|
80
|
+
// 1.2e+3, .5
|
|
81
|
+
num = n => (
|
|
82
|
+
n&&err('Unexpected number'),
|
|
83
|
+
n = skip(c=>c == PERIOD || isNum(c)),
|
|
84
|
+
(cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
|
|
85
|
+
n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
|
|
86
|
+
),
|
|
87
|
+
|
|
88
|
+
inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
|
|
89
|
+
|
|
90
|
+
// numbers
|
|
91
|
+
for (op$1=_0;op$1<=_9;) lookup[op$1++] = num;
|
|
92
|
+
|
|
93
|
+
// operators
|
|
94
|
+
for (list$1=[
|
|
95
|
+
// "a"
|
|
96
|
+
'"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE$1), skip()||err('Bad string'), ()=>a),,
|
|
97
|
+
|
|
98
|
+
// a.b
|
|
99
|
+
'.', (a,id) => (space(), id=skip(isId)||err(), fn$1=ctx=>a(ctx)[id], fn$1.id=()=>id, fn$1.of=a, fn$1), PREC_CALL,
|
|
100
|
+
|
|
101
|
+
// .2
|
|
102
|
+
// FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
|
|
103
|
+
'.', a => !a && num(skip(-1)),,
|
|
104
|
+
|
|
105
|
+
// a[b]
|
|
106
|
+
'[', (a,b,fn) => a && (b=expr(0,CBRACK$1)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
|
|
107
|
+
|
|
108
|
+
// a(), a(b), (a,b), (a+b)
|
|
109
|
+
'(', (a,b,fn) => (
|
|
110
|
+
b=expr(0,CPAREN),
|
|
111
|
+
// a(), a(b), a(b,c,d)
|
|
112
|
+
a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
|
|
113
|
+
// (a+b)
|
|
114
|
+
b || err()
|
|
115
|
+
), PREC_CALL,
|
|
116
|
+
|
|
117
|
+
// [a,b,c] or (a,b,c)
|
|
118
|
+
',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
|
|
119
|
+
b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
|
|
120
|
+
fn
|
|
121
|
+
), PREC_SEQ,
|
|
122
|
+
|
|
123
|
+
'|', PREC_OR$1, (a,b)=>a|b,
|
|
124
|
+
'||', PREC_SOME, (a,b)=>a||b,
|
|
125
|
+
|
|
126
|
+
'&', PREC_AND, (a,b)=>a&b,
|
|
127
|
+
'&&', PREC_EVERY, (a,b)=>a&&b,
|
|
128
|
+
|
|
129
|
+
'^', PREC_XOR, (a,b)=>a^b,
|
|
130
|
+
|
|
131
|
+
// ==, !=
|
|
132
|
+
'==', PREC_EQ$1, (a,b)=>a==b,
|
|
133
|
+
'!=', PREC_EQ$1, (a,b)=>a!=b,
|
|
134
|
+
|
|
135
|
+
// > >= >> >>>, < <= <<
|
|
136
|
+
'>', PREC_COMP$1, (a,b)=>a>b,
|
|
137
|
+
'>=', PREC_COMP$1, (a,b)=>a>=b,
|
|
138
|
+
'>>', PREC_SHIFT, (a,b)=>a>>b,
|
|
139
|
+
'>>>', PREC_SHIFT, (a,b)=>a>>>b,
|
|
140
|
+
'<', PREC_COMP$1, (a,b)=>a<b,
|
|
141
|
+
'<=', PREC_COMP$1, (a,b)=>a<=b,
|
|
142
|
+
'<<', PREC_SHIFT, (a,b)=>a<<b,
|
|
143
|
+
|
|
144
|
+
// + ++ - --
|
|
145
|
+
'+', PREC_SUM, (a,b)=>a+b,
|
|
146
|
+
'+', PREC_UNARY$1, (a)=>+a,
|
|
147
|
+
'++', a => inc(a||expr(PREC_UNARY$1-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY$1,
|
|
148
|
+
|
|
149
|
+
'-', PREC_SUM, (a,b)=>a-b,
|
|
150
|
+
'-', PREC_UNARY$1, (a)=>-a,
|
|
151
|
+
'--', a => inc(a||expr(PREC_UNARY$1-1), a ? (a,b)=>a[b]-- : (a,b)=>--a[b]), PREC_UNARY$1,
|
|
152
|
+
|
|
153
|
+
// ! ~
|
|
154
|
+
'!', PREC_UNARY$1, (a)=>!a,
|
|
155
|
+
|
|
156
|
+
// * / %
|
|
157
|
+
'*', PREC_MULT, (a,b)=>a*b,
|
|
158
|
+
'/', PREC_MULT, (a,b)=>a/b,
|
|
159
|
+
'%', PREC_MULT, (a,b)=>a%b
|
|
160
|
+
]; [op$1,prec$1,fn$1,...list$1]=list$1, op$1;) set(op$1,prec$1,fn$1);
|
|
161
|
+
|
|
1
162
|
// justin lang https://github.com/endojs/Jessie/issues/66
|
|
2
|
-
import {set, lookup, skip, cur, idx, err, expr, isId, space, args} from './index.js'
|
|
3
|
-
export { default } from './subscript.js'
|
|
4
163
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_EXP=14, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
|
|
164
|
+
const CBRACK=93, DQUOTE=34, QUOTE=39, BSLASH=92,
|
|
165
|
+
PREC_OR=6, PREC_EQ=9, PREC_COMP=10, PREC_EXP=14, PREC_UNARY=15;
|
|
8
166
|
|
|
9
167
|
|
|
10
|
-
let
|
|
168
|
+
let list, op, prec, fn,
|
|
11
169
|
escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
|
|
12
170
|
string = q => (qc, c, str='') => {
|
|
13
|
-
qc&&err('Unexpected string') // must not follow another token
|
|
171
|
+
qc&&err('Unexpected string'); // must not follow another token
|
|
14
172
|
while (c=cur.charCodeAt(idx), c-q) {
|
|
15
|
-
if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
|
|
16
|
-
else str += skip()
|
|
173
|
+
if (c === BSLASH) skip(), c=skip(), str += escape[c] || c;
|
|
174
|
+
else str += skip();
|
|
17
175
|
}
|
|
18
176
|
return skip()||err('Bad string'), () => str
|
|
19
|
-
}
|
|
177
|
+
};
|
|
20
178
|
|
|
21
179
|
// operators
|
|
22
180
|
for (list=[
|
|
@@ -71,4 +229,6 @@ for (list=[
|
|
|
71
229
|
// for JSON case we should not collect arg (different evaluator than ternary)
|
|
72
230
|
':', (a, prec, b) => (b=expr(1.1)||err(), a.id&&args.pop(), ctx => [(a.id||a)(ctx), b(ctx)]), 1.1
|
|
73
231
|
|
|
74
|
-
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
|
|
232
|
+
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
|
|
233
|
+
|
|
234
|
+
export { parse as default };
|
package/justin.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e,t,r,l,a,n,d,o=(l,a=(t=l,e=0,r=[],!(l=
|
|
1
|
+
let e,t,r,l,a,n,d,o=(l,a=(t=l,e=0,r=[],!(l=c())||t[e]?f():e=>l(e||{})))=>(a.args=r,a),i=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,f=(r="Bad syntax",l=t[e])=>{throw SyntaxError(r+" `"+l+"` at "+e)},p=(r=1,l=e,a)=>{if("number"==typeof r)e+=r;else for(;r(t.charCodeAt(e));)e++;return t.slice(l,e)},c=(t=0,r,l,a,n,d)=>{for(;(l=s())&&(n=(d=h[l])&&d(a,t)||!a&&u());)a=n;return r&&(l==r?e++:f()),a},s=r=>{for(;(r=t.charCodeAt(e))<=32;)e++;return r},u=(e=p(i),t)=>e?(t=t=>t[e],r.push(e),t.id=()=>e,t):0,h=[],g=o.set=(r,l,a=32,n=r.charCodeAt(0),d=r.length,o=h[n],f=a.length||([a,l]=[l,a],0),p=r.toUpperCase()!==r,s=(f>1?(e,t)=>e&&(t=c(l))&&(e.length||t.length?r=>a(e(r),t(r),e.id?.(r),t.id?.(r)):(e=a(e(),t()),()=>e)):f?e=>!e&&(e=c(l-1))&&(t=>a(e(t))):a))=>h[n]=(a,n,f=e)=>n<l&&(d<2||t.substr(e,d)==r)&&(!p||!i(t.charCodeAt(e+d)))&&(e+=d,s(a,n))||(e=f,o&&o(a,n)),x=e=>e>=48&&e<=57,C=r=>(r&&f("Unexpected number"),r=p((e=>46==e||x(e))),(69==t.charCodeAt(e)||101==t.charCodeAt(e))&&(r+=p(2)+p(x)),(r=+r)!=r?f("Bad number"):()=>r),A=(e,t)=>r=>t(e.of?e.of(r):r,e.id(r));for(a=48;a<=57;)h[a++]=C;for(l=['"',e=>(e=e?f("Unexpected string"):p((e=>e-34)),p()||f("Bad string"),()=>e),,".",(e,t)=>(s(),t=p(i)||f(),d=r=>e(r)[t],d.id=()=>t,d.of=e,d),18,".",e=>!e&&C(p(-1)),,"[",(e,t,r)=>e&&(t=c(0,93)||f(),(r=r=>e(r)[t(r)]).id=t,r.of=e,r),18,"(",(e,t,r)=>(t=c(0,41),e?r=>e(r).apply(e.of?.(r),t?t.all?t.all(r):[t(r)]:[]):t||f()),18,",",(e,t,r=c(1),l=(t=>(e(t),r(t))))=>(r?l.all=e.all?t=>[...e.all(t),r(t)]:t=>[e(t),r(t)]:f("Skipped argument"),l),1,"|",6,(e,t)=>e|t,"||",4,(e,t)=>e||t,"&",8,(e,t)=>e&t,"&&",5,(e,t)=>e&&t,"^",7,(e,t)=>e^t,"==",9,(e,t)=>e==t,"!=",9,(e,t)=>e!=t,">",10,(e,t)=>e>t,">=",10,(e,t)=>e>=t,">>",11,(e,t)=>e>>t,">>>",11,(e,t)=>e>>>t,"<",10,(e,t)=>e<t,"<=",10,(e,t)=>e<=t,"<<",11,(e,t)=>e<<t,"+",12,(e,t)=>e+t,"+",15,e=>+e,"++",e=>A(e||c(14),e?(e,t)=>e[t]++:(e,t)=>++e[t]),15,"-",12,(e,t)=>e-t,"-",15,e=>-e,"--",e=>A(e||c(14),e?(e,t)=>e[t]--:(e,t)=>--e[t]),15,"!",15,e=>!e,"*",13,(e,t)=>e*t,"/",13,(e,t)=>e/t,"%",13,(e,t)=>e%t];[a,n,d,...l]=l,a;)g(a,n,d);let U,b,m,y,B={n:"\n",r:"\r",t:"\t",b:"\b",f:"\f",v:"\v"},v=r=>(l,a,n="")=>{for(l&&f("Unexpected string");(a=t.charCodeAt(e))-r;)92===a?(p(),a=p(),n+=B[a]||a):n+=p();return p()||f("Bad string"),()=>n};for((U=['"',v(34),,"'",v(39),,"/*",(r,l)=>(p((r=>42!==r&&47!==t.charCodeAt(e+1))),p(2),r||c(l)),,"//",(e,t)=>(p((e=>e>=32)),e||c(t)),,"null",e=>e?f("Unexpected literal"):()=>null,,"true",e=>e?f("Unexpected literal"):()=>!0,,"false",e=>e?f("Unexpected literal"):()=>!1,,"undefined",e=>e?f("Unexpected literal"):()=>{},,";",e=>c()||(()=>{}),,"===",9,(e,t)=>e===t,"!==",9,(e,t)=>e!==t,"~",15,e=>~e,"**",(e,t,r=c(13))=>t=>e(t)**r(t),14,":",3.1,(e,t)=>[e,t],"?",3,(e,t)=>e?t[0]:t[1],"??",6,(e,t)=>e??t,"?.",e=>e&&(t=>e(t)||(()=>{})),,"?.",(e,t)=>(s(),(t=p(i))&&(r=>e(r)?.[t])),,"in",10,(e,t)=>e in t,"[",e=>!e&&((e=c(0,93))?e.all?t=>e.all(t):t=>[e(t)]:e=>[]),,"{",(e,t)=>!e&&((e=c(0,125))?r=>(t=(e.all||e)(r),Object.fromEntries(e.all?t:[t])):e=>({})),,":",(e,t,l)=>(l=c(1.1)||f(),e.id&&r.pop(),t=>[(e.id||e)(t),l(t)]),1.1]);[b,m,y,...U]=U,b;)g(b,m,y);export{o as default};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subscript",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Fast and tiny expression evaluator with common syntax microlanguage.",
|
|
5
|
-
"main": "subscript.js",
|
|
6
|
-
"module": "subscript.js",
|
|
5
|
+
"main": "src/subscript.js",
|
|
6
|
+
"module": "src/subscript.js",
|
|
7
|
+
"browser": "subscript.js",
|
|
7
8
|
"type": "module",
|
|
8
9
|
"files": [
|
|
9
|
-
"
|
|
10
|
+
"src",
|
|
10
11
|
"subscript.js",
|
|
11
12
|
"subscript.min.js",
|
|
12
13
|
"justin.js",
|
|
@@ -18,10 +19,11 @@
|
|
|
18
19
|
"test": "test"
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
21
|
-
"build": "
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
22
|
+
"build": "npm run build-subscript && npm run minify-subscript && npm run build-justin && npm run minify-justin",
|
|
23
|
+
"build-subscript": "rollup src/subscript.js --file subscript.js --format esm --name \"Subscript\"",
|
|
24
|
+
"minify-subscript": "terser subscript.js -o subscript.min.js --module -c passes=3 -m",
|
|
25
|
+
"build-justin": "rollup src/justin.js --file justin.js --format esm --name \"Justin\"",
|
|
26
|
+
"minify-justin": "terser justin.js -o justin.min.js --module -c passes=3 -m",
|
|
25
27
|
"test": "node test"
|
|
26
28
|
},
|
|
27
29
|
"repository": {
|
|
File without changes
|
package/src/justin.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// justin lang https://github.com/endojs/Jessie/issues/66
|
|
2
|
+
import {set, lookup, skip, cur, idx, err, expr, isId, space, args} from './index.js'
|
|
3
|
+
export { default } from './subscript.js'
|
|
4
|
+
|
|
5
|
+
const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, QUOTE=39, _0=48, _9=57, BSLASH=92,
|
|
6
|
+
PREC_SEQ=1, PREC_COND=3, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
|
|
7
|
+
PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_EXP=14, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
let u, list, op, prec, fn,
|
|
11
|
+
escape = {n:'\n', r:'\r', t:'\t', b:'\b', f:'\f', v:'\v'},
|
|
12
|
+
string = q => (qc, c, str='') => {
|
|
13
|
+
qc&&err('Unexpected string') // must not follow another token
|
|
14
|
+
while (c=cur.charCodeAt(idx), c-q) {
|
|
15
|
+
if (c === BSLASH) skip(), c=skip(), str += escape[c] || c
|
|
16
|
+
else str += skip()
|
|
17
|
+
}
|
|
18
|
+
return skip()||err('Bad string'), () => str
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// operators
|
|
22
|
+
for (list=[
|
|
23
|
+
// "' with /
|
|
24
|
+
'"', string(DQUOTE),,
|
|
25
|
+
"'", string(QUOTE),,
|
|
26
|
+
|
|
27
|
+
// /**/, //
|
|
28
|
+
'/*', (a, prec) => (skip(c => c !== 42 && cur.charCodeAt(idx+1) !== 47), skip(2), a||expr(prec)),,
|
|
29
|
+
'//', (a, prec) => (skip(c => c >= 32), a||expr(prec)),,
|
|
30
|
+
|
|
31
|
+
// literals
|
|
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
|
+
|
|
37
|
+
';', a => expr()||(()=>{}),,
|
|
38
|
+
|
|
39
|
+
// operators
|
|
40
|
+
'===', PREC_EQ, (a,b) => a===b,
|
|
41
|
+
'!==', PREC_EQ, (a,b) => a!==b,
|
|
42
|
+
'~', PREC_UNARY, (a) => ~a,
|
|
43
|
+
|
|
44
|
+
// right order
|
|
45
|
+
'**', (a,prec,b=expr(PREC_EXP-1)) => ctx=>a(ctx)**b(ctx), PREC_EXP,
|
|
46
|
+
|
|
47
|
+
// ?:
|
|
48
|
+
':', 3.1, (a,b) => [a,b],
|
|
49
|
+
'?', 3, (a,b) => a ? b[0] : b[1],
|
|
50
|
+
|
|
51
|
+
'??', PREC_OR, (a,b) => a??b,
|
|
52
|
+
|
|
53
|
+
// a?.[, a?.( - postfix operator
|
|
54
|
+
'?.', a => a && (ctx => a(ctx)||(()=>{})),,//(a) => a||(()=>{}),
|
|
55
|
+
// a?.b - optional chain operator
|
|
56
|
+
'?.', (a,id) => (space(), id=skip(isId)) && (ctx => a(ctx)?.[id]),,
|
|
57
|
+
|
|
58
|
+
'in', PREC_COMP, (a,b) => a in b,
|
|
59
|
+
|
|
60
|
+
// [a,b,c]
|
|
61
|
+
'[', (a) => !a && (
|
|
62
|
+
a=expr(0,CBRACK),
|
|
63
|
+
!a ? ctx => [] : a.all ? ctx => a.all(ctx) : ctx => [a(ctx)]
|
|
64
|
+
),,
|
|
65
|
+
|
|
66
|
+
// {a:1, b:2, c:3}
|
|
67
|
+
'{', (a, entries) => !a && (
|
|
68
|
+
a=expr(0,125),
|
|
69
|
+
!a ? ctx => ({}) : ctx => (entries=(a.all||a)(ctx), Object.fromEntries(a.all?entries:[entries]))
|
|
70
|
+
),,
|
|
71
|
+
// for JSON case we should not collect arg (different evaluator than ternary)
|
|
72
|
+
':', (a, prec, b) => (b=expr(1.1)||err(), a.id&&args.pop(), ctx => [(a.id||a)(ctx), b(ctx)]), 1.1
|
|
73
|
+
|
|
74
|
+
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
|
package/src/subscript.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {parse, set, lookup, skip, cur, idx, err, expr, isId, args, space} from './index.js'
|
|
2
|
+
|
|
3
|
+
const PERIOD=46, OPAREN=40, CPAREN=41, OBRACK=91, CBRACK=93, SPACE=32, DQUOTE=34, _0=48, _9=57,
|
|
4
|
+
PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
|
|
5
|
+
PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_POSTFIX=16, PREC_CALL=18, PREC_GROUP=19
|
|
6
|
+
|
|
7
|
+
let u, list, op, prec, fn,
|
|
8
|
+
isNum = c => c>=_0 && c<=_9,
|
|
9
|
+
// 1.2e+3, .5
|
|
10
|
+
num = n => (
|
|
11
|
+
n&&err('Unexpected number'),
|
|
12
|
+
n = skip(c=>c == PERIOD || isNum(c)),
|
|
13
|
+
(cur.charCodeAt(idx) == 69 || cur.charCodeAt(idx) == 101) && (n += skip(2) + skip(isNum)),
|
|
14
|
+
n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
|
|
15
|
+
),
|
|
16
|
+
|
|
17
|
+
inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx))
|
|
18
|
+
|
|
19
|
+
// numbers
|
|
20
|
+
for (op=_0;op<=_9;) lookup[op++] = num
|
|
21
|
+
|
|
22
|
+
// operators
|
|
23
|
+
for (list=[
|
|
24
|
+
// "a"
|
|
25
|
+
'"', a => (a=a?err('Unexpected string'):skip(c => c-DQUOTE), skip()||err('Bad string'), ()=>a),,
|
|
26
|
+
|
|
27
|
+
// a.b
|
|
28
|
+
'.', (a,id) => (space(), id=skip(isId)||err(), fn=ctx=>a(ctx)[id], fn.id=()=>id, fn.of=a, fn), PREC_CALL,
|
|
29
|
+
|
|
30
|
+
// .2
|
|
31
|
+
// FIXME: .123 is not operator, so we skip back, but mb reorganizing num would be better
|
|
32
|
+
'.', a => !a && num(skip(-1)),,
|
|
33
|
+
|
|
34
|
+
// a[b]
|
|
35
|
+
'[', (a,b,fn) => a && (b=expr(0,CBRACK)||err(), fn=ctx=>a(ctx)[b(ctx)], fn.id=b, fn.of=a, fn), PREC_CALL,
|
|
36
|
+
|
|
37
|
+
// a(), a(b), (a,b), (a+b)
|
|
38
|
+
'(', (a,b,fn) => (
|
|
39
|
+
b=expr(0,CPAREN),
|
|
40
|
+
// a(), a(b), a(b,c,d)
|
|
41
|
+
a ? ctx => a(ctx).apply(a.of?.(ctx), b ? b.all ? b.all(ctx) : [b(ctx)] : []) :
|
|
42
|
+
// (a+b)
|
|
43
|
+
b || err()
|
|
44
|
+
), PREC_CALL,
|
|
45
|
+
|
|
46
|
+
// [a,b,c] or (a,b,c)
|
|
47
|
+
',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
|
|
48
|
+
b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
|
|
49
|
+
fn
|
|
50
|
+
), PREC_SEQ,
|
|
51
|
+
|
|
52
|
+
'|', PREC_OR, (a,b)=>a|b,
|
|
53
|
+
'||', PREC_SOME, (a,b)=>a||b,
|
|
54
|
+
|
|
55
|
+
'&', PREC_AND, (a,b)=>a&b,
|
|
56
|
+
'&&', PREC_EVERY, (a,b)=>a&&b,
|
|
57
|
+
|
|
58
|
+
'^', PREC_XOR, (a,b)=>a^b,
|
|
59
|
+
|
|
60
|
+
// ==, !=
|
|
61
|
+
'==', PREC_EQ, (a,b)=>a==b,
|
|
62
|
+
'!=', PREC_EQ, (a,b)=>a!=b,
|
|
63
|
+
|
|
64
|
+
// > >= >> >>>, < <= <<
|
|
65
|
+
'>', PREC_COMP, (a,b)=>a>b,
|
|
66
|
+
'>=', PREC_COMP, (a,b)=>a>=b,
|
|
67
|
+
'>>', PREC_SHIFT, (a,b)=>a>>b,
|
|
68
|
+
'>>>', PREC_SHIFT, (a,b)=>a>>>b,
|
|
69
|
+
'<', PREC_COMP, (a,b)=>a<b,
|
|
70
|
+
'<=', PREC_COMP, (a,b)=>a<=b,
|
|
71
|
+
'<<', PREC_SHIFT, (a,b)=>a<<b,
|
|
72
|
+
|
|
73
|
+
// + ++ - --
|
|
74
|
+
'+', PREC_SUM, (a,b)=>a+b,
|
|
75
|
+
'+', PREC_UNARY, (a)=>+a,
|
|
76
|
+
'++', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]++ : (a,b)=>++a[b]), PREC_UNARY,
|
|
77
|
+
|
|
78
|
+
'-', PREC_SUM, (a,b)=>a-b,
|
|
79
|
+
'-', PREC_UNARY, (a)=>-a,
|
|
80
|
+
'--', a => inc(a||expr(PREC_UNARY-1), a ? (a,b)=>a[b]-- : (a,b)=>--a[b]), PREC_UNARY,
|
|
81
|
+
|
|
82
|
+
// ! ~
|
|
83
|
+
'!', PREC_UNARY, (a)=>!a,
|
|
84
|
+
|
|
85
|
+
// * / %
|
|
86
|
+
'*', PREC_MULT, (a,b)=>a*b,
|
|
87
|
+
'/', PREC_MULT, (a,b)=>a/b,
|
|
88
|
+
'%', PREC_MULT, (a,b)=>a%b
|
|
89
|
+
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
|
|
90
|
+
|
|
91
|
+
export default parse
|
package/subscript.js
CHANGED
|
@@ -1,10 +1,81 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const SPACE=32;
|
|
2
|
+
|
|
3
|
+
// current string, index and collected ids
|
|
4
|
+
let idx, cur, args,
|
|
5
|
+
|
|
6
|
+
// no handling tagged literals since easily done on user side with cache, if needed
|
|
7
|
+
parse = (s, fn= !(cur=s, idx=0, args=[], s=expr()) || cur[idx] ? err() : ctx=>s(ctx||{})) => (fn.args = args, fn),
|
|
8
|
+
|
|
9
|
+
isId = c =>
|
|
10
|
+
(c >= 48 && c <= 57) || // 0..9
|
|
11
|
+
(c >= 65 && c <= 90) || // A...Z
|
|
12
|
+
(c >= 97 && c <= 122) || // a...z
|
|
13
|
+
c == 36 || c == 95 || // $, _,
|
|
14
|
+
(c >= 192 && c != 215 && c != 247), // any non-ASCII
|
|
15
|
+
|
|
16
|
+
err = (msg='Bad syntax',c=cur[idx]) => { throw SyntaxError(msg + ' `' + c + '` at ' + idx) },
|
|
17
|
+
|
|
18
|
+
skip = (is=1, from=idx, l) => {
|
|
19
|
+
if (typeof is == 'number') idx += is;
|
|
20
|
+
else while (is(cur.charCodeAt(idx))) idx++;
|
|
21
|
+
return cur.slice(from, idx)
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// a + b - c
|
|
25
|
+
expr = (prec=0, end, cc, token, newNode, fn) => {
|
|
26
|
+
// chunk/token parser
|
|
27
|
+
while (
|
|
28
|
+
( cc=space() ) && // till not end
|
|
29
|
+
// FIXME: extra work is happening here, when lookup bails out due to lower precedence -
|
|
30
|
+
// it makes extra `space` call for parent exprs on the same character to check precedence again
|
|
31
|
+
( newNode =
|
|
32
|
+
(fn=lookup[cc]) && fn(token, prec) || // if operator with higher precedence isn't found
|
|
33
|
+
(!token && id()) // parse literal or quit. token seqs are forbidden: `a b`, `a "b"`, `1.32 a`
|
|
34
|
+
)
|
|
35
|
+
) token = newNode;
|
|
36
|
+
|
|
37
|
+
// check end character
|
|
38
|
+
// FIXME: can't show "Unclose paren", because can be unknown operator within group as well
|
|
39
|
+
if (end) cc==end?idx++:err();
|
|
40
|
+
|
|
41
|
+
return token
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// skip space chars, return first non-space character
|
|
45
|
+
space = cc => { while ((cc = cur.charCodeAt(idx)) <= SPACE) idx++; return cc },
|
|
46
|
+
|
|
47
|
+
// variable identifier
|
|
48
|
+
id = (name=skip(isId), fn) => name ? (fn=ctx => ctx[name], args.push(name), fn.id=()=>name, fn) : 0,
|
|
49
|
+
|
|
50
|
+
// operator/token lookup table
|
|
51
|
+
lookup = [],
|
|
52
|
+
|
|
53
|
+
// create operator checker/mapper (see examples)
|
|
54
|
+
set = parse.set = (
|
|
55
|
+
op,
|
|
56
|
+
opPrec, fn=SPACE, // if opPrec & fn come in reverse order - consider them raw parse fn case, still precedence possible
|
|
57
|
+
c=op.charCodeAt(0),
|
|
58
|
+
l=op.length,
|
|
59
|
+
prev=lookup[c],
|
|
60
|
+
arity=fn.length || ([fn,opPrec]=[opPrec,fn], 0),
|
|
61
|
+
word=op.toUpperCase()!==op, // make sure word boundary comes after word operator
|
|
62
|
+
map=
|
|
63
|
+
// binary
|
|
64
|
+
arity>1 ? (a,b) => a && (b=expr(opPrec)) && (
|
|
65
|
+
!a.length && !b.length ? (a=fn(a(),b()), ()=>a) : // static pre-eval like `"a"+"b"`
|
|
66
|
+
ctx => fn(a(ctx),b(ctx),a.id?.(ctx),b.id?.(ctx))
|
|
67
|
+
) :
|
|
68
|
+
// unary prefix (0 args)
|
|
69
|
+
arity ? a => !a && (a=expr(opPrec-1)) && (ctx => fn(a(ctx))) :
|
|
70
|
+
fn // custom parser
|
|
71
|
+
) =>
|
|
72
|
+
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));
|
|
73
|
+
|
|
74
|
+
const PERIOD=46, CPAREN=41, CBRACK=93, DQUOTE=34, _0=48, _9=57,
|
|
4
75
|
PREC_SEQ=1, PREC_SOME=4, PREC_EVERY=5, PREC_OR=6, PREC_XOR=7, PREC_AND=8,
|
|
5
|
-
PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15,
|
|
76
|
+
PREC_EQ=9, PREC_COMP=10, PREC_SHIFT=11, PREC_SUM=12, PREC_MULT=13, PREC_UNARY=15, PREC_CALL=18;
|
|
6
77
|
|
|
7
|
-
let
|
|
78
|
+
let list, op, prec, fn,
|
|
8
79
|
isNum = c => c>=_0 && c<=_9,
|
|
9
80
|
// 1.2e+3, .5
|
|
10
81
|
num = n => (
|
|
@@ -14,10 +85,10 @@ let u, list, op, prec, fn,
|
|
|
14
85
|
n=+n, n!=n ? err('Bad number') : () => n // 0 args means token is static
|
|
15
86
|
),
|
|
16
87
|
|
|
17
|
-
inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx))
|
|
88
|
+
inc = (a,fn) => ctx => fn(a.of?a.of(ctx):ctx, a.id(ctx));
|
|
18
89
|
|
|
19
90
|
// numbers
|
|
20
|
-
for (op=_0;op<=_9;) lookup[op++] = num
|
|
91
|
+
for (op=_0;op<=_9;) lookup[op++] = num;
|
|
21
92
|
|
|
22
93
|
// operators
|
|
23
94
|
for (list=[
|
|
@@ -44,9 +115,9 @@ for (list=[
|
|
|
44
115
|
), PREC_CALL,
|
|
45
116
|
|
|
46
117
|
// [a,b,c] or (a,b,c)
|
|
47
|
-
',', (a,prec,b=expr(PREC_SEQ)) => (
|
|
48
|
-
b.all = a.all ? ctx => [...a.all(ctx),
|
|
49
|
-
|
|
118
|
+
',', (a,prec,b=expr(PREC_SEQ),fn=ctx => (a(ctx), b(ctx))) => (
|
|
119
|
+
b ? (fn.all = a.all ? ctx => [...a.all(ctx),b(ctx)] : ctx => [a(ctx),b(ctx)]) : err('Skipped argument',),
|
|
120
|
+
fn
|
|
50
121
|
), PREC_SEQ,
|
|
51
122
|
|
|
52
123
|
'|', PREC_OR, (a,b)=>a|b,
|
|
@@ -86,6 +157,6 @@ for (list=[
|
|
|
86
157
|
'*', PREC_MULT, (a,b)=>a*b,
|
|
87
158
|
'/', PREC_MULT, (a,b)=>a/b,
|
|
88
159
|
'%', PREC_MULT, (a,b)=>a%b
|
|
89
|
-
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn)
|
|
90
|
-
|
|
91
|
-
export default
|
|
160
|
+
]; [op,prec,fn,...list]=list, op;) set(op,prec,fn);
|
|
161
|
+
|
|
162
|
+
export { parse as default };
|
package/subscript.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e,r,t,a,o,l,
|
|
1
|
+
let e,r,t,a,o,d,l,n=(a,o=(r=a,e=0,t=[],!(a=i())||r[e]?h():e=>a(e||{})))=>(o.args=t,o),f=e=>e>=48&&e<=57||e>=65&&e<=90||e>=97&&e<=122||36==e||95==e||e>=192&&215!=e&&247!=e,h=(t="Bad syntax",a=r[e])=>{throw SyntaxError(t+" `"+a+"` at "+e)},s=(t=1,a=e,o)=>{if("number"==typeof t)e+=t;else for(;t(r.charCodeAt(e));)e++;return r.slice(a,e)},i=(r=0,t,a,o,d,l)=>{for(;(a=p())&&(d=(l=c[a])&&l(o,r)||!o&&u());)o=d;return t&&(a==t?e++:h()),o},p=t=>{for(;(t=r.charCodeAt(e))<=32;)e++;return t},u=(e=s(f),r)=>e?(r=r=>r[e],t.push(e),r.id=()=>e,r):0,c=[],g=n.set=(t,a,o=32,d=t.charCodeAt(0),l=t.length,n=c[d],h=o.length||([o,a]=[a,o],0),s=t.toUpperCase()!==t,p=(h>1?(e,r)=>e&&(r=i(a))&&(e.length||r.length?t=>o(e(t),r(t),e.id?.(t),r.id?.(t)):(e=o(e(),r()),()=>e)):h?e=>!e&&(e=i(a-1))&&(r=>o(e(r))):o))=>c[d]=(o,d,h=e)=>d<a&&(l<2||r.substr(e,l)==t)&&(!s||!f(r.charCodeAt(e+l)))&&(e+=l,p(o,d))||(e=h,n&&n(o,d)),C=e=>e>=48&&e<=57,A=t=>(t&&h("Unexpected number"),t=s((e=>46==e||C(e))),(69==r.charCodeAt(e)||101==r.charCodeAt(e))&&(t+=s(2)+s(C)),(t=+t)!=t?h("Bad number"):()=>t),x=(e,r)=>t=>r(e.of?e.of(t):t,e.id(t));for(o=48;o<=57;)c[o++]=A;for(a=['"',e=>(e=e?h("Unexpected string"):s((e=>e-34)),s()||h("Bad string"),()=>e),,".",(e,r)=>(p(),r=s(f)||h(),l=t=>e(t)[r],l.id=()=>r,l.of=e,l),18,".",e=>!e&&A(s(-1)),,"[",(e,r,t)=>e&&(r=i(0,93)||h(),(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||h()),18,",",(e,r,t=i(1),a=(r=>(e(r),t(r))))=>(t?a.all=e.all?r=>[...e.all(r),t(r)]:r=>[e(r),t(r)]:h("Skipped argument"),a),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,d,l,...a]=a,o;)g(o,d,l);export{n as default};
|