@creationix/rex 0.3.1 → 0.4.1
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/package.json +1 -1
- package/rex-cli.js +615 -269
- package/rex-cli.ts +32 -11
- package/rex-repl.js +582 -256
- package/rex-repl.ts +4 -3
- package/rex.js +310 -180
- package/rex.ohm +50 -22
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +26 -8
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +342 -201
- package/rexc-interpreter.ts +262 -85
package/rexc-interpreter.ts
CHANGED
|
@@ -4,48 +4,50 @@ const DIGITS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_
|
|
|
4
4
|
const digitMap = new Map<string, number>(Array.from(DIGITS).map((char, index) => [char, index]));
|
|
5
5
|
|
|
6
6
|
const OPCODES = {
|
|
7
|
-
do:
|
|
8
|
-
add:
|
|
9
|
-
sub:
|
|
10
|
-
mul:
|
|
11
|
-
div:
|
|
12
|
-
eq:
|
|
13
|
-
neq:
|
|
14
|
-
lt:
|
|
15
|
-
lte:
|
|
16
|
-
gt:
|
|
17
|
-
gte:
|
|
18
|
-
and:
|
|
19
|
-
or:
|
|
20
|
-
xor:
|
|
21
|
-
not:
|
|
22
|
-
boolean:
|
|
23
|
-
number:
|
|
24
|
-
string:
|
|
25
|
-
array:
|
|
26
|
-
object:
|
|
27
|
-
mod:
|
|
28
|
-
neg:
|
|
7
|
+
do: "",
|
|
8
|
+
add: "ad",
|
|
9
|
+
sub: "sb",
|
|
10
|
+
mul: "ml",
|
|
11
|
+
div: "dv",
|
|
12
|
+
eq: "eq",
|
|
13
|
+
neq: "nq",
|
|
14
|
+
lt: "lt",
|
|
15
|
+
lte: "le",
|
|
16
|
+
gt: "gt",
|
|
17
|
+
gte: "ge",
|
|
18
|
+
and: "an",
|
|
19
|
+
or: "or",
|
|
20
|
+
xor: "xr",
|
|
21
|
+
not: "nt",
|
|
22
|
+
boolean: "bt",
|
|
23
|
+
number: "nm",
|
|
24
|
+
string: "st",
|
|
25
|
+
array: "ar",
|
|
26
|
+
object: "ob",
|
|
27
|
+
mod: "md",
|
|
28
|
+
neg: "ng",
|
|
29
|
+
range: "rn",
|
|
30
|
+
size: "sz",
|
|
29
31
|
} as const;
|
|
30
32
|
|
|
31
33
|
export type RexcContext = {
|
|
32
34
|
vars?: Record<string, unknown>;
|
|
33
|
-
refs?:
|
|
35
|
+
refs?: Record<string, unknown>;
|
|
34
36
|
self?: unknown;
|
|
35
37
|
selfStack?: unknown[];
|
|
36
|
-
opcodes?:
|
|
38
|
+
opcodes?: Record<string, (args: unknown[], state: RexcRuntimeState) => unknown>;
|
|
37
39
|
/** Maximum loop iterations before the interpreter throws. */
|
|
38
40
|
gasLimit?: number;
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
export type RexcRuntimeState = {
|
|
42
44
|
vars: Record<string, unknown>;
|
|
43
|
-
refs: Record<
|
|
45
|
+
refs: Record<string, unknown>;
|
|
44
46
|
};
|
|
45
47
|
|
|
46
48
|
type LoopControl = { kind: "break" | "continue"; depth: number };
|
|
47
49
|
|
|
48
|
-
type OpcodeMarker = { __opcode:
|
|
50
|
+
type OpcodeMarker = { __opcode: string };
|
|
49
51
|
|
|
50
52
|
function decodePrefix(text: string, start: number, end: number): number {
|
|
51
53
|
let value = 0;
|
|
@@ -64,7 +66,7 @@ function decodeZigzag(value: number): number {
|
|
|
64
66
|
function isValueStart(char: string | undefined): boolean {
|
|
65
67
|
if (!char) return false;
|
|
66
68
|
if (digitMap.has(char)) return true;
|
|
67
|
-
return "+*:%$@'
|
|
69
|
+
return "+*:%$@'^~=/([{,?!|&><;".includes(char);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
function isDefined(value: unknown): boolean {
|
|
@@ -76,7 +78,6 @@ class CursorInterpreter {
|
|
|
76
78
|
private pos = 0;
|
|
77
79
|
private readonly state: RexcRuntimeState;
|
|
78
80
|
private readonly selfStack: unknown[];
|
|
79
|
-
private readonly opcodeMarkers: OpcodeMarker[];
|
|
80
81
|
private readonly pointerCache = new Map<number, unknown>();
|
|
81
82
|
private readonly gasLimit: number;
|
|
82
83
|
private gas = 0;
|
|
@@ -96,31 +97,26 @@ class CursorInterpreter {
|
|
|
96
97
|
this.state = {
|
|
97
98
|
vars: ctx.vars ?? {},
|
|
98
99
|
refs: {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
tr: true,
|
|
101
|
+
fl: false,
|
|
102
|
+
nl: null,
|
|
103
|
+
un: undefined,
|
|
104
|
+
nan: NaN,
|
|
105
|
+
inf: Infinity,
|
|
106
|
+
nif: -Infinity,
|
|
107
|
+
...ctx.refs,
|
|
107
108
|
},
|
|
108
109
|
};
|
|
109
110
|
this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
|
|
110
111
|
this.gasLimit = ctx.gasLimit ?? 0;
|
|
111
|
-
this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
|
|
112
|
-
for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
|
|
113
|
-
const id = Number(idText);
|
|
114
|
-
if (Number.isInteger(id)) this.state.refs[id] = value;
|
|
115
|
-
}
|
|
116
112
|
if (ctx.opcodes) {
|
|
117
|
-
for (const [
|
|
118
|
-
if (op) this.customOpcodes.set(
|
|
113
|
+
for (const [key, op] of Object.entries(ctx.opcodes)) {
|
|
114
|
+
if (op) this.customOpcodes.set(key, op);
|
|
119
115
|
}
|
|
120
116
|
}
|
|
121
117
|
}
|
|
122
118
|
|
|
123
|
-
private readonly customOpcodes = new Map<
|
|
119
|
+
private readonly customOpcodes = new Map<string, (args: unknown[], state: RexcRuntimeState) => unknown>();
|
|
124
120
|
|
|
125
121
|
private readSelf(depthPrefix: number): unknown {
|
|
126
122
|
const depth = depthPrefix + 1;
|
|
@@ -176,6 +172,20 @@ class CursorInterpreter {
|
|
|
176
172
|
return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
|
|
177
173
|
}
|
|
178
174
|
|
|
175
|
+
private advanceByBytes(start: number, byteCount: number): number {
|
|
176
|
+
if (byteCount <= 0) return start;
|
|
177
|
+
let bytes = 0;
|
|
178
|
+
let index = start;
|
|
179
|
+
for (const char of this.text.slice(start)) {
|
|
180
|
+
const charBytes = Buffer.byteLength(char);
|
|
181
|
+
if (bytes + charBytes > byteCount) break;
|
|
182
|
+
bytes += charBytes;
|
|
183
|
+
index += char.length;
|
|
184
|
+
if (bytes === byteCount) return index;
|
|
185
|
+
}
|
|
186
|
+
throw new Error("String container overflows input");
|
|
187
|
+
}
|
|
188
|
+
|
|
179
189
|
private ensure(char: string) {
|
|
180
190
|
if (this.text[this.pos] !== char) throw new Error(`Expected '${char}' at ${this.pos}`);
|
|
181
191
|
this.pos += 1;
|
|
@@ -217,35 +227,34 @@ class CursorInterpreter {
|
|
|
217
227
|
const power = decodeZigzag(prefix.value);
|
|
218
228
|
const significand = this.evalValue();
|
|
219
229
|
if (typeof significand !== "number") throw new Error("Decimal significand must be numeric");
|
|
220
|
-
return significand
|
|
230
|
+
return parseFloat(`${significand}e${power}`);
|
|
221
231
|
}
|
|
222
232
|
case ":":
|
|
223
233
|
this.pos += 1;
|
|
224
234
|
return prefix.raw;
|
|
225
235
|
case "%":
|
|
226
236
|
this.pos += 1;
|
|
227
|
-
return
|
|
237
|
+
return { __opcode: prefix.raw } satisfies OpcodeMarker;
|
|
228
238
|
case "@":
|
|
229
239
|
this.pos += 1;
|
|
230
240
|
return this.readSelf(prefix.value);
|
|
231
241
|
case "'":
|
|
232
242
|
this.pos += 1;
|
|
233
|
-
return this.state.refs[prefix.
|
|
243
|
+
return this.state.refs[prefix.raw];
|
|
234
244
|
case "$":
|
|
235
245
|
this.pos += 1;
|
|
236
246
|
return this.state.vars[prefix.raw];
|
|
237
247
|
case ",": {
|
|
238
248
|
this.pos += 1;
|
|
239
249
|
const start = this.pos;
|
|
240
|
-
const end = start
|
|
241
|
-
if (end > this.text.length) throw new Error("String container overflows input");
|
|
250
|
+
const end = this.advanceByBytes(start, prefix.value);
|
|
242
251
|
const value = this.text.slice(start, end);
|
|
243
252
|
this.pos = end;
|
|
244
253
|
return value;
|
|
245
254
|
}
|
|
246
255
|
case "^": {
|
|
247
256
|
this.pos += 1;
|
|
248
|
-
const target = this.pos
|
|
257
|
+
const target = this.advanceByBytes(this.pos, prefix.value);
|
|
249
258
|
if (this.pointerCache.has(target)) return this.pointerCache.get(target);
|
|
250
259
|
const save = this.pos;
|
|
251
260
|
this.pos = target;
|
|
@@ -261,6 +270,14 @@ class CursorInterpreter {
|
|
|
261
270
|
this.writePlace(place, value);
|
|
262
271
|
return value;
|
|
263
272
|
}
|
|
273
|
+
case "/": {
|
|
274
|
+
this.pos += 1;
|
|
275
|
+
const place = this.readPlace();
|
|
276
|
+
const oldValue = this.readPlaceValue(place);
|
|
277
|
+
const newValue = this.evalValue();
|
|
278
|
+
this.writePlace(place, newValue);
|
|
279
|
+
return oldValue;
|
|
280
|
+
}
|
|
264
281
|
case "~": {
|
|
265
282
|
this.pos += 1;
|
|
266
283
|
const place = this.readPlace();
|
|
@@ -288,7 +305,7 @@ class CursorInterpreter {
|
|
|
288
305
|
case "<":
|
|
289
306
|
return this.evalLoopLike(tag);
|
|
290
307
|
case "#":
|
|
291
|
-
return this.
|
|
308
|
+
return this.evalWhileLike();
|
|
292
309
|
default:
|
|
293
310
|
throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
|
|
294
311
|
}
|
|
@@ -311,7 +328,7 @@ class CursorInterpreter {
|
|
|
311
328
|
this.ensure(")");
|
|
312
329
|
|
|
313
330
|
if (typeof callee === "object" && callee && "__opcode" in callee) {
|
|
314
|
-
return this.applyOpcode((callee as OpcodeMarker).__opcode, args);
|
|
331
|
+
return this.applyOpcode((callee as OpcodeMarker).__opcode as string, args);
|
|
315
332
|
}
|
|
316
333
|
return this.navigate(callee, args);
|
|
317
334
|
}
|
|
@@ -498,13 +515,10 @@ class CursorInterpreter {
|
|
|
498
515
|
if (keysOnly) return entries.map(([key]) => ({ key, value: key }));
|
|
499
516
|
return entries.map(([key, value]) => ({ key, value }));
|
|
500
517
|
}
|
|
501
|
-
if (typeof iterable === "
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
else out.push({ key: index, value: index + 1 });
|
|
506
|
-
}
|
|
507
|
-
return out;
|
|
518
|
+
if (typeof iterable === "string") {
|
|
519
|
+
const entries = Array.from(iterable);
|
|
520
|
+
if (keysOnly) return entries.map((_value, index) => ({ key: index, value: index }));
|
|
521
|
+
return entries.map((value, index) => ({ key: index, value }));
|
|
508
522
|
}
|
|
509
523
|
return [];
|
|
510
524
|
}
|
|
@@ -551,9 +565,12 @@ class CursorInterpreter {
|
|
|
551
565
|
return last;
|
|
552
566
|
}
|
|
553
567
|
|
|
554
|
-
private
|
|
568
|
+
private evalWhileLike(): unknown {
|
|
555
569
|
this.pos += 1; // skip '#'
|
|
556
|
-
this.
|
|
570
|
+
const open = this.text[this.pos];
|
|
571
|
+
if (!open || !"([{".includes(open)) throw new Error(`Expected opener after '#' at ${this.pos}`);
|
|
572
|
+
const close = open === "(" ? ")" : open === "[" ? "]" : "}";
|
|
573
|
+
this.pos += 1;
|
|
557
574
|
|
|
558
575
|
const condStart = this.pos;
|
|
559
576
|
|
|
@@ -561,18 +578,23 @@ class CursorInterpreter {
|
|
|
561
578
|
const condValue = this.evalValue();
|
|
562
579
|
const bodyStart = this.pos;
|
|
563
580
|
|
|
564
|
-
// Skip past
|
|
565
|
-
const bodyValueCount = 1;
|
|
581
|
+
// Skip past body values to find the closing bracket
|
|
582
|
+
const bodyValueCount = open === "{" ? 2 : 1;
|
|
566
583
|
let cursor = bodyStart;
|
|
567
584
|
for (let index = 0; index < bodyValueCount; index += 1) {
|
|
568
585
|
cursor = this.skipValueFrom(cursor);
|
|
569
586
|
}
|
|
570
587
|
const bodyEnd = cursor;
|
|
571
588
|
this.pos = bodyEnd;
|
|
572
|
-
this.ensure(
|
|
589
|
+
this.ensure(close);
|
|
573
590
|
const afterClose = this.pos;
|
|
574
591
|
|
|
575
|
-
|
|
592
|
+
if (open === "[") return this.evalWhileArrayComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
593
|
+
if (open === "{") return this.evalWhileObjectComprehension(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
594
|
+
return this.evalWhileLoop(condStart, bodyStart, bodyEnd, afterClose, condValue);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
private evalWhileLoop(condStart: number, bodyStart: number, bodyEnd: number, afterClose: number, condValue: unknown): unknown {
|
|
576
598
|
let last: unknown = undefined;
|
|
577
599
|
let currentCond = condValue;
|
|
578
600
|
while (isDefined(currentCond)) {
|
|
@@ -588,7 +610,6 @@ class CursorInterpreter {
|
|
|
588
610
|
last = undefined;
|
|
589
611
|
}
|
|
590
612
|
|
|
591
|
-
// Re-evaluate condition
|
|
592
613
|
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
593
614
|
}
|
|
594
615
|
|
|
@@ -596,6 +617,60 @@ class CursorInterpreter {
|
|
|
596
617
|
return last;
|
|
597
618
|
}
|
|
598
619
|
|
|
620
|
+
private evalWhileArrayComprehension(condStart: number, bodyStart: number, bodyEnd: number, afterClose: number, condValue: unknown): unknown[] | LoopControl {
|
|
621
|
+
const out: unknown[] = [];
|
|
622
|
+
let currentCond = condValue;
|
|
623
|
+
while (isDefined(currentCond)) {
|
|
624
|
+
this.tick();
|
|
625
|
+
this.selfStack.push(currentCond);
|
|
626
|
+
const value = this.evalBodySlice(bodyStart, bodyEnd);
|
|
627
|
+
this.selfStack.pop();
|
|
628
|
+
|
|
629
|
+
const control = this.handleLoopControl(value);
|
|
630
|
+
if (control) {
|
|
631
|
+
if (control.depth > 1) return { kind: control.kind, depth: control.depth - 1 } satisfies LoopControl;
|
|
632
|
+
if (control.kind === "break") break;
|
|
633
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (isDefined(value)) out.push(value);
|
|
637
|
+
|
|
638
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
this.pos = afterClose;
|
|
642
|
+
return out;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
private evalWhileObjectComprehension(condStart: number, bodyStart: number, bodyEnd: number, afterClose: number, condValue: unknown): Record<string, unknown> | LoopControl {
|
|
646
|
+
const result: Record<string, unknown> = {};
|
|
647
|
+
let currentCond = condValue;
|
|
648
|
+
while (isDefined(currentCond)) {
|
|
649
|
+
this.tick();
|
|
650
|
+
this.selfStack.push(currentCond);
|
|
651
|
+
const save = this.pos;
|
|
652
|
+
this.pos = bodyStart;
|
|
653
|
+
const key = this.evalValue();
|
|
654
|
+
const value = this.evalValue();
|
|
655
|
+
this.pos = save;
|
|
656
|
+
this.selfStack.pop();
|
|
657
|
+
|
|
658
|
+
const control = this.handleLoopControl(value);
|
|
659
|
+
if (control) {
|
|
660
|
+
if (control.depth > 1) return { kind: control.kind, depth: control.depth - 1 } satisfies LoopControl;
|
|
661
|
+
if (control.kind === "break") break;
|
|
662
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (isDefined(value)) result[String(key)] = value;
|
|
666
|
+
|
|
667
|
+
currentCond = this.evalBodySlice(condStart, bodyStart);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
this.pos = afterClose;
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
673
|
+
|
|
599
674
|
private evalArrayComprehension(iterable: unknown, varA: string | undefined, varB: string | undefined, bodyStart: number, bodyEnd: number, keysOnly: boolean): unknown[] | LoopControl {
|
|
600
675
|
const items = this.iterate(iterable, keysOnly);
|
|
601
676
|
const out: unknown[] = [];
|
|
@@ -654,7 +729,7 @@ class CursorInterpreter {
|
|
|
654
729
|
return out;
|
|
655
730
|
}
|
|
656
731
|
|
|
657
|
-
private applyOpcode(id:
|
|
732
|
+
private applyOpcode(id: string, args: unknown[]): unknown {
|
|
658
733
|
const custom = this.customOpcodes.get(id);
|
|
659
734
|
if (custom) return custom(args, this.state);
|
|
660
735
|
switch (id) {
|
|
@@ -724,6 +799,22 @@ class CursorInterpreter {
|
|
|
724
799
|
return Array.isArray(args[0]) ? args[0] : undefined;
|
|
725
800
|
case OPCODES.object:
|
|
726
801
|
return args[0] && typeof args[0] === "object" && !Array.isArray(args[0]) ? args[0] : undefined;
|
|
802
|
+
case OPCODES.range: {
|
|
803
|
+
const from = Number(args[0]);
|
|
804
|
+
const to = Number(args[1]);
|
|
805
|
+
const step = to >= from ? 1 : -1;
|
|
806
|
+
const out: number[] = [];
|
|
807
|
+
for (let v = from; step > 0 ? v <= to : v >= to; v += step)
|
|
808
|
+
out.push(v);
|
|
809
|
+
return out;
|
|
810
|
+
}
|
|
811
|
+
case OPCODES.size: {
|
|
812
|
+
const target = args[0];
|
|
813
|
+
if (Array.isArray(target)) return target.length;
|
|
814
|
+
if (typeof target === "string") return Array.from(target).length;
|
|
815
|
+
if (target && typeof target === "object") return Object.keys(target as Record<string, unknown>).length;
|
|
816
|
+
return undefined;
|
|
817
|
+
}
|
|
727
818
|
default:
|
|
728
819
|
throw new Error(`Unknown opcode ${id}`);
|
|
729
820
|
}
|
|
@@ -733,12 +824,55 @@ class CursorInterpreter {
|
|
|
733
824
|
let current = base;
|
|
734
825
|
for (const key of keys) {
|
|
735
826
|
if (current === undefined || current === null) return undefined;
|
|
736
|
-
current = (current
|
|
827
|
+
current = this.readProperty(current, key);
|
|
828
|
+
if (current === undefined) return undefined;
|
|
737
829
|
}
|
|
738
830
|
return current;
|
|
739
831
|
}
|
|
740
832
|
|
|
741
|
-
private
|
|
833
|
+
private readProperty(target: unknown, key: unknown): unknown {
|
|
834
|
+
const index = this.parseIndexKey(key);
|
|
835
|
+
if (Array.isArray(target)) {
|
|
836
|
+
if (index === undefined) return undefined;
|
|
837
|
+
return target[index];
|
|
838
|
+
}
|
|
839
|
+
if (typeof target === "string") {
|
|
840
|
+
if (index === undefined) return undefined;
|
|
841
|
+
return Array.from(target)[index];
|
|
842
|
+
}
|
|
843
|
+
if (this.isPlainObject(target)) {
|
|
844
|
+
const prop = String(key);
|
|
845
|
+
if (!Object.prototype.hasOwnProperty.call(target as Record<string, unknown>, prop)) return undefined;
|
|
846
|
+
return (target as Record<string, unknown>)[prop];
|
|
847
|
+
}
|
|
848
|
+
return undefined;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
private canWriteProperty(target: unknown, key: unknown): { kind: "array"; index: number } | { kind: "object" } | undefined {
|
|
852
|
+
const index = this.parseIndexKey(key);
|
|
853
|
+
if (Array.isArray(target)) {
|
|
854
|
+
if (index === undefined) return undefined;
|
|
855
|
+
return { kind: "array", index };
|
|
856
|
+
}
|
|
857
|
+
if (this.isPlainObject(target)) return { kind: "object" };
|
|
858
|
+
return undefined;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
private parseIndexKey(key: unknown): number | undefined {
|
|
862
|
+
if (typeof key === "number" && Number.isInteger(key) && key >= 0) return key;
|
|
863
|
+
if (typeof key !== "string" || key.length === 0) return undefined;
|
|
864
|
+
if (!/^(0|[1-9]\d*)$/.test(key)) return undefined;
|
|
865
|
+
const index = Number(key);
|
|
866
|
+
return Number.isSafeInteger(index) ? index : undefined;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
private isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
870
|
+
if (!value || typeof value !== "object") return false;
|
|
871
|
+
const proto = Object.getPrototypeOf(value);
|
|
872
|
+
return proto === Object.prototype || proto === null;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
private readPlace(): { root: string; keys: unknown[]; isRef: boolean } {
|
|
742
876
|
this.skipNonCode();
|
|
743
877
|
const direct = this.readRootVarOrRefIfPresent();
|
|
744
878
|
if (direct) {
|
|
@@ -784,7 +918,7 @@ class CursorInterpreter {
|
|
|
784
918
|
throw new Error(`Invalid place at ${this.pos}`);
|
|
785
919
|
}
|
|
786
920
|
|
|
787
|
-
private readRootVarOrRefIfPresent(): { root: string
|
|
921
|
+
private readRootVarOrRefIfPresent(): { root: string; isRef: boolean } | undefined {
|
|
788
922
|
const save = this.pos;
|
|
789
923
|
const prefix = this.readPrefix();
|
|
790
924
|
const tag = this.text[this.pos];
|
|
@@ -794,14 +928,14 @@ class CursorInterpreter {
|
|
|
794
928
|
}
|
|
795
929
|
this.pos += 1;
|
|
796
930
|
return {
|
|
797
|
-
root:
|
|
931
|
+
root: prefix.raw,
|
|
798
932
|
isRef: tag === "'",
|
|
799
933
|
};
|
|
800
934
|
}
|
|
801
935
|
|
|
802
|
-
private writePlace(place: { root: string
|
|
936
|
+
private writePlace(place: { root: string; keys: unknown[]; isRef: boolean }, value: unknown) {
|
|
803
937
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
804
|
-
const rootKey =
|
|
938
|
+
const rootKey = place.root;
|
|
805
939
|
if (place.keys.length === 0) {
|
|
806
940
|
rootTable[rootKey] = value;
|
|
807
941
|
return;
|
|
@@ -812,17 +946,44 @@ class CursorInterpreter {
|
|
|
812
946
|
rootTable[rootKey] = target;
|
|
813
947
|
}
|
|
814
948
|
for (let index = 0; index < place.keys.length - 1; index += 1) {
|
|
815
|
-
const key =
|
|
816
|
-
const
|
|
817
|
-
if (!
|
|
818
|
-
|
|
949
|
+
const key = place.keys[index];
|
|
950
|
+
const access = this.canWriteProperty(target, key);
|
|
951
|
+
if (!access) return;
|
|
952
|
+
if (access.kind === "array") {
|
|
953
|
+
const next = (target as unknown[])[access.index];
|
|
954
|
+
if (!next || typeof next !== "object") (target as unknown[])[access.index] = {};
|
|
955
|
+
target = (target as unknown[])[access.index] as Record<string, unknown>;
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
const prop = String(key);
|
|
959
|
+
const next = (target as Record<string, unknown>)[prop];
|
|
960
|
+
if (!next || typeof next !== "object") (target as Record<string, unknown>)[prop] = {};
|
|
961
|
+
target = (target as Record<string, unknown>)[prop];
|
|
819
962
|
}
|
|
820
|
-
|
|
963
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
964
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
965
|
+
if (!access) return;
|
|
966
|
+
if (access.kind === "array") {
|
|
967
|
+
(target as unknown[])[access.index] = value;
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
(target as Record<string, unknown>)[String(lastKey)] = value;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
private readPlaceValue(place: { root: string; keys: unknown[]; isRef: boolean }): unknown {
|
|
974
|
+
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
975
|
+
let current: unknown = rootTable[place.root];
|
|
976
|
+
for (const key of place.keys) {
|
|
977
|
+
if (current === undefined || current === null) return undefined;
|
|
978
|
+
current = this.readProperty(current, key);
|
|
979
|
+
if (current === undefined) return undefined;
|
|
980
|
+
}
|
|
981
|
+
return current;
|
|
821
982
|
}
|
|
822
983
|
|
|
823
|
-
private deletePlace(place: { root: string
|
|
984
|
+
private deletePlace(place: { root: string; keys: unknown[]; isRef: boolean }) {
|
|
824
985
|
const rootTable = place.isRef ? this.state.refs : this.state.vars;
|
|
825
|
-
const rootKey =
|
|
986
|
+
const rootKey = place.root;
|
|
826
987
|
if (place.keys.length === 0) {
|
|
827
988
|
delete rootTable[rootKey];
|
|
828
989
|
return;
|
|
@@ -830,10 +991,25 @@ class CursorInterpreter {
|
|
|
830
991
|
let target = rootTable[rootKey];
|
|
831
992
|
if (!target || typeof target !== "object") return;
|
|
832
993
|
for (let index = 0; index < place.keys.length - 1; index += 1) {
|
|
833
|
-
|
|
994
|
+
const key = place.keys[index];
|
|
995
|
+
const access = this.canWriteProperty(target, key);
|
|
996
|
+
if (!access) return;
|
|
997
|
+
if (access.kind === "array") {
|
|
998
|
+
target = (target as unknown[])[access.index];
|
|
999
|
+
if (!target || typeof target !== "object") return;
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
target = (target as Record<string, unknown>)[String(key)];
|
|
834
1003
|
if (!target || typeof target !== "object") return;
|
|
835
1004
|
}
|
|
836
|
-
|
|
1005
|
+
const lastKey = place.keys[place.keys.length - 1];
|
|
1006
|
+
const access = this.canWriteProperty(target, lastKey);
|
|
1007
|
+
if (!access) return;
|
|
1008
|
+
if (access.kind === "array") {
|
|
1009
|
+
delete (target as unknown[])[access.index];
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
delete (target as Record<string, unknown>)[String(lastKey)];
|
|
837
1013
|
}
|
|
838
1014
|
|
|
839
1015
|
private skipValue() {
|
|
@@ -852,12 +1028,12 @@ class CursorInterpreter {
|
|
|
852
1028
|
}
|
|
853
1029
|
|
|
854
1030
|
if (tag === ",") {
|
|
855
|
-
this.pos
|
|
1031
|
+
this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
856
1032
|
const end = this.pos;
|
|
857
1033
|
this.pos = save;
|
|
858
1034
|
return end;
|
|
859
1035
|
}
|
|
860
|
-
if (tag === "=") {
|
|
1036
|
+
if (tag === "=" || tag === "/") {
|
|
861
1037
|
this.pos += 1;
|
|
862
1038
|
this.skipValue();
|
|
863
1039
|
this.skipValue();
|
|
@@ -892,7 +1068,8 @@ class CursorInterpreter {
|
|
|
892
1068
|
if (opener && "([{".includes(opener)) {
|
|
893
1069
|
const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
|
|
894
1070
|
if (prefix.value > 0) {
|
|
895
|
-
this.pos
|
|
1071
|
+
const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
|
|
1072
|
+
this.pos = bodyEnd + 1;
|
|
896
1073
|
const end = this.pos;
|
|
897
1074
|
this.pos = save;
|
|
898
1075
|
return end;
|