Skip to content

Commit ac7e62c

Browse files
committed
Optimizations
1 parent 8038786 commit ac7e62c

12 files changed

Lines changed: 125 additions & 67 deletions

File tree

feature/op/assign-logical.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Logical/nullish assignment operators + destructuring
3+
*
4+
* ||= &&= ??= + destructuring support for let/const/var
5+
*/
6+
import { binary, operator, compile } from '../../parse.js';
7+
import { destructure } from '../destruct.js';
8+
import { isLval, prop } from '../member.js';
9+
10+
const ASSIGN = 20;
11+
const err = msg => { throw Error(msg) };
12+
13+
binary('||=', ASSIGN, true);
14+
binary('&&=', ASSIGN, true);
15+
binary('??=', ASSIGN, true);
16+
17+
// Override = to support destructuring
18+
operator('=', (a, b) => {
19+
// Handle let/const/var declarations: ['=', ['let', pattern], value]
20+
if (Array.isArray(a) && (a[0] === 'let' || a[0] === 'const' || a[0] === 'var')) {
21+
const pattern = a[1];
22+
b = compile(b);
23+
if (typeof pattern === 'string') return ctx => { ctx[pattern] = b(ctx); };
24+
return ctx => destructure(pattern, b(ctx), ctx);
25+
}
26+
isLval(a) || err('Invalid assignment target');
27+
return (b = compile(b), prop(a, (obj, path, ctx) => obj[path] = b(ctx)));
28+
});
29+
30+
// Compile
31+
operator('||=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] ||= b(ctx))));
32+
operator('&&=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] &&= b(ctx))));
33+
operator('??=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] ??= b(ctx))));

feature/op/assignment.js

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
/**
2-
* Assignment operators
2+
* Assignment operators (C-family)
33
*
4-
* = += -= *= /= %= |= &= ^= >>= <<= >>>= ||= &&= ??=
5-
* Note: **= is in pow.js (must be with ** for correct parsing)
4+
* = += -= *= /= %= |= &= ^= >>= <<=
5+
* Note: **= is in pow.js, >>>= ||= &&= ??= are JS-specific (in assignment-js.js)
66
*/
77
import { binary, operator, compile } from '../../parse.js';
8-
import { destructure } from '../destruct.js';
9-
import { isLval, prop } from '../member.js';
108

119
const ASSIGN = 20;
12-
const err = msg => { throw Error(msg) };
1310

1411
// Base assignment
1512
binary('=', ASSIGN, true);
@@ -20,44 +17,31 @@ binary('-=', ASSIGN, true);
2017
binary('*=', ASSIGN, true);
2118
binary('/=', ASSIGN, true);
2219
binary('%=', ASSIGN, true);
23-
// **= is in pow.js
2420

2521
// Compound bitwise
2622
binary('|=', ASSIGN, true);
2723
binary('&=', ASSIGN, true);
2824
binary('^=', ASSIGN, true);
2925
binary('>>=', ASSIGN, true);
3026
binary('<<=', ASSIGN, true);
31-
binary('>>>=', ASSIGN, true);
3227

33-
// Compound logical
34-
binary('||=', ASSIGN, true);
35-
binary('&&=', ASSIGN, true);
36-
binary('??=', ASSIGN, true);
28+
// Simple assign helper for x, a.b, a[b], (x)
29+
const assign = (a, fn, obj, key) =>
30+
typeof a === 'string' ? ctx => fn(ctx, a, ctx) :
31+
a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key, ctx)) :
32+
a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx), ctx)) :
33+
a[0] === '()' && a.length === 2 ? assign(a[1], fn) : // unwrap parens: (x) = 1
34+
(() => { throw Error('Invalid assignment target') })();
3735

3836
// Compile
39-
operator('=', (a, b) => {
40-
// Handle let/const/var declarations: ['=', ['let', pattern], value]
41-
if (Array.isArray(a) && (a[0] === 'let' || a[0] === 'const' || a[0] === 'var')) {
42-
const pattern = a[1];
43-
b = compile(b);
44-
if (typeof pattern === 'string') return ctx => { ctx[pattern] = b(ctx); };
45-
return ctx => destructure(pattern, b(ctx), ctx);
46-
}
47-
isLval(a) || err('Invalid assignment target');
48-
return (b = compile(b), prop(a, (obj, path, ctx) => obj[path] = b(ctx)));
49-
});
50-
operator('+=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] += b(ctx))));
51-
operator('-=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] -= b(ctx))));
52-
operator('*=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] *= b(ctx))));
53-
operator('/=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] /= b(ctx))));
54-
operator('%=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] %= b(ctx))));
55-
operator('|=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] |= b(ctx))));
56-
operator('&=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] &= b(ctx))));
57-
operator('^=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] ^= b(ctx))));
58-
operator('>>=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] >>= b(ctx))));
59-
operator('<<=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] <<= b(ctx))));
60-
operator('>>>=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] >>>= b(ctx))));
61-
operator('||=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] ||= b(ctx))));
62-
operator('&&=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] &&= b(ctx))));
63-
operator('??=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (obj, path, ctx) => obj[path] ??= b(ctx))));
37+
operator('=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] = b(ctx))));
38+
operator('+=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] += b(ctx))));
39+
operator('-=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] -= b(ctx))));
40+
operator('*=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] *= b(ctx))));
41+
operator('/=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] /= b(ctx))));
42+
operator('%=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] %= b(ctx))));
43+
operator('|=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] |= b(ctx))));
44+
operator('&=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] &= b(ctx))));
45+
operator('^=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] ^= b(ctx))));
46+
operator('>>=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] >>= b(ctx))));
47+
operator('<<=', (a, b) => (b = compile(b), assign(a, (o, k, ctx) => o[k] <<= b(ctx))));

feature/op/bitwise-unsigned.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Unsigned right shift operators
3+
*
4+
* >>> >>>=
5+
*/
6+
import { binary, operator, compile } from '../../parse.js';
7+
import { isLval, prop } from '../member.js';
8+
9+
const ASSIGN = 20, SHIFT = 100;
10+
const err = msg => { throw Error(msg) };
11+
12+
binary('>>>', SHIFT);
13+
binary('>>>=', ASSIGN, true);
14+
15+
// Compile
16+
operator('>>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >>> b(ctx)));
17+
operator('>>>=', (a, b) => (isLval(a) || err('Invalid assignment target'), b = compile(b), prop(a, (o, k, ctx) => o[k] >>>= b(ctx))));

feature/op/bitwise.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
2-
* Bitwise operators
2+
* Bitwise operators (C-family)
33
*
4-
* | & ^ ~ >> << >>>
4+
* | & ^ ~ >> <<
5+
* Note: >>> is JS-specific (in bitwise-js.js)
56
*/
67
import { binary, unary, operator, compile } from '../../parse.js';
78

@@ -15,7 +16,6 @@ binary('^', XOR);
1516
// Shifts (after < >)
1617
binary('>>', SHIFT);
1718
binary('<<', SHIFT);
18-
binary('>>>', SHIFT);
1919

2020
// Unary
2121
unary('~', PREFIX);
@@ -27,4 +27,3 @@ operator('&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) & b(ctx))
2727
operator('^', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) ^ b(ctx)));
2828
operator('>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >> b(ctx)));
2929
operator('<<', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) << b(ctx)));
30-
operator('>>>', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) >>> b(ctx)));
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Strict equality operators (JS-specific)
2+
* Identity operators
33
*
44
* === !==
55
*/

feature/op/increment.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@
33
*
44
* ++ -- (prefix and postfix)
55
*/
6-
import { token, expr, operator } from '../../parse.js';
7-
import { prop, isLval } from '../member.js';
6+
import { token, expr, operator, compile } from '../../parse.js';
87

98
const POSTFIX = 150;
109

1110
token('++', POSTFIX, a => a ? ['++', a, null] : ['++', expr(POSTFIX - 1)]);
1211
token('--', POSTFIX, a => a ? ['--', a, null] : ['--', expr(POSTFIX - 1)]);
1312

1413
// Compile (b=null means postfix, b=undefined means prefix)
15-
const err = msg => { throw Error(msg) };
16-
operator('++', (a, b) => (isLval(a) || err('Invalid increment target'), prop(a, b === null ? (obj, path) => obj[path]++ : (obj, path) => ++obj[path])));
17-
operator('--', (a, b) => (isLval(a) || err('Invalid decrement target'), prop(a, b === null ? (obj, path) => obj[path]-- : (obj, path) => --obj[path])));
14+
// Simple prop helper for increment - handles x, a.b, a[b], (x)
15+
const inc = (a, fn, obj, key) =>
16+
typeof a === 'string' ? ctx => fn(ctx, a) :
17+
a[0] === '.' ? (obj = compile(a[1]), key = a[2], ctx => fn(obj(ctx), key)) :
18+
a[0] === '[]' && a.length === 3 ? (obj = compile(a[1]), key = compile(a[2]), ctx => fn(obj(ctx), key(ctx))) :
19+
a[0] === '()' && a.length === 2 ? inc(a[1], fn) : // unwrap parens: (x)++
20+
(() => { throw Error('Invalid increment target') })();
21+
22+
operator('++', (a, b) => inc(a, b === null ? (o, k) => o[k]++ : (o, k) => ++o[k]));
23+
operator('--', (a, b) => inc(a, b === null ? (o, k) => o[k]-- : (o, k) => --o[k]));

feature/seq.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Sequence operators (C-family)
3+
*
4+
* , ; — returns last evaluated value
5+
*/
6+
import { nary, operator, compile } from '../parse.js';
7+
8+
const STATEMENT = 5, SEQ = 10;
9+
10+
// Sequences
11+
nary(',', SEQ);
12+
nary(';', STATEMENT, true); // right-assoc to allow same-prec statements
13+
14+
// Compile - returns last evaluated value
15+
const seq = (...args) => (args = args.map(compile), ctx => {
16+
let r;
17+
for (const arg of args) r = arg(ctx);
18+
return r;
19+
});
20+
operator(',', seq);
21+
operator(';', seq);

justin.js

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
22
* justin: JSON superset expression language
33
*
4-
* Builds on subscript with: C-family operators, comments, collections,
5-
* plus JS-specific: optional chaining, arrow functions, spread, templates.
4+
* Builds on subscript with JS-specific features:
5+
* optional chaining, arrow functions, spread, templates.
66
*/
77
import './subscript.js';
88
import { parse } from './parse.js';
@@ -15,20 +15,14 @@ parse.number = { '0x': 16, '0b': 2, '0o': 8 };
1515

1616
import './feature/comment.js';
1717

18-
// Extended operators (JS-specific)
19-
// Note: assignment (=) must be imported BEFORE equality-strict (===)
20-
// so that === is checked first in the token chain
21-
import './feature/op/assignment.js'; // = += -= *= /= etc
22-
import './feature/op/equality-strict.js'; // === !==
18+
// Extended operators
19+
// Note: assignment (=) is in subscript, must come BEFORE identity (===)
20+
import './feature/op/identity.js'; // === !==
2321
import './feature/op/nullish.js'; // ??
24-
import './feature/op/bitwise.js'; // | & ^ ~ >> << >>>
2522
import './feature/op/pow.js'; // ** **=
26-
import './feature/op/increment.js'; // ++ --
2723
import './feature/op/membership.js'; // in (instanceof is in jessie/class.js)
28-
29-
// Extended features
30-
import './feature/member.js'; // Array literals, unsafe check
31-
import './feature/group.js'; // Sequences , ;
24+
import './feature/op/bitwise-unsigned.js'; // >>> >>>=
25+
import './feature/op/assign-logical.js'; // ||= &&= ??= + destructuring
3226

3327
// JS-specific operators (ternary, arrow, spread, optional chaining, typeof/void/delete/new)
3428
import './feature/literal.js';

parse.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export let idx, cur,
102102
(matched = curOp,
103103
(curOp ?
104104
op == curOp :
105-
(l < 2 || cur.substr(idx, l) == op) && (!word || !parse.id(cur.charCodeAt(idx + l))) && (matched = curOp = op)
105+
(l < 2 || (op.charCodeAt(1) === cur.charCodeAt(idx + 1) && (l < 3 || cur.substr(idx, l) == op))) && (!word || !parse.id(cur.charCodeAt(idx + l))) && (matched = curOp = op)
106106
) &&
107107
curPrec < prec &&
108108
(idx += l, (r = map(a)) ? loc(r, from) : (idx = from, matched = 0, word && r !== false && (parse.reserved = 1), !word && !prev && err()), r)

subscript.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
import './feature/number.js'; // Decimal numbers: 123, 1.5, 1e3
1616
import './feature/string.js'; // Double-quoted strings with escapes
1717

18-
// Operators (minimal set)
18+
// Operators (C-family common set) - order matters for token chain performance
19+
import './feature/op/assignment.js'; // = += -= *= /= %= |= &= ^= >>= <<=
1920
import './feature/op/logical.js'; // ! && ||
21+
import './feature/op/bitwise.js'; // ~ | & ^ >> <<
2022
import './feature/op/comparison.js'; // < > <= >=
2123
import './feature/op/equality.js'; // == !=
2224
import './feature/op/arithmetic.js'; // + - * / %
25+
import './feature/op/increment.js'; // ++ --
2326

24-
import './feature/prop.js'; // Property access: a.b, a[b], f(), ()
27+
import './feature/group.js'; // Grouping: (a), sequences: a, b; a; b
28+
import './feature/member.js'; // Property access: a.b, a[b], f(), [a,b]
2529

2630
import { parse, compile } from './parse.js';
2731
export * from './parse.js';

0 commit comments

Comments
 (0)