@creationix/rex 0.3.0 → 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.
@@ -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: 0,
8
- add: 1,
9
- sub: 2,
10
- mul: 3,
11
- div: 4,
12
- eq: 5,
13
- neq: 6,
14
- lt: 7,
15
- lte: 8,
16
- gt: 9,
17
- gte: 10,
18
- and: 11,
19
- or: 12,
20
- xor: 13,
21
- not: 14,
22
- boolean: 15,
23
- number: 16,
24
- string: 17,
25
- array: 18,
26
- object: 19,
27
- mod: 20,
28
- neg: 21,
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?: Partial<Record<number, unknown>>;
35
+ refs?: Record<string, unknown>;
34
36
  self?: unknown;
35
37
  selfStack?: unknown[];
36
- opcodes?: Partial<Record<number, (args: unknown[], state: RexcRuntimeState) => unknown>>;
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<number, unknown>;
45
+ refs: Record<string, unknown>;
44
46
  };
45
47
 
46
48
  type LoopControl = { kind: "break" | "continue"; depth: number };
47
49
 
48
- type OpcodeMarker = { __opcode: number };
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 "+*:%$@'^~=([{,?!|&><;".includes(char);
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,28 +97,26 @@ class CursorInterpreter {
96
97
  this.state = {
97
98
  vars: ctx.vars ?? {},
98
99
  refs: {
99
- 0: ctx.refs?.[0],
100
- 1: ctx.refs?.[1] ?? true,
101
- 2: ctx.refs?.[2] ?? false,
102
- 3: ctx.refs?.[3] ?? null,
103
- 4: ctx.refs?.[4] ?? undefined,
100
+ tr: true,
101
+ fl: false,
102
+ nl: null,
103
+ un: undefined,
104
+ nan: NaN,
105
+ inf: Infinity,
106
+ nif: -Infinity,
107
+ ...ctx.refs,
104
108
  },
105
109
  };
106
110
  this.selfStack = ctx.selfStack && ctx.selfStack.length > 0 ? [...ctx.selfStack] : [initialSelf];
107
111
  this.gasLimit = ctx.gasLimit ?? 0;
108
- this.opcodeMarkers = Array.from({ length: 256 }, (_, id) => ({ __opcode: id }));
109
- for (const [idText, value] of Object.entries(ctx.refs ?? {})) {
110
- const id = Number(idText);
111
- if (Number.isInteger(id)) this.state.refs[id] = value;
112
- }
113
112
  if (ctx.opcodes) {
114
- for (const [idText, op] of Object.entries(ctx.opcodes)) {
115
- if (op) this.customOpcodes.set(Number(idText), op);
113
+ for (const [key, op] of Object.entries(ctx.opcodes)) {
114
+ if (op) this.customOpcodes.set(key, op);
116
115
  }
117
116
  }
118
117
  }
119
118
 
120
- private readonly customOpcodes = new Map<number, (args: unknown[], state: RexcRuntimeState) => unknown>();
119
+ private readonly customOpcodes = new Map<string, (args: unknown[], state: RexcRuntimeState) => unknown>();
121
120
 
122
121
  private readSelf(depthPrefix: number): unknown {
123
122
  const depth = depthPrefix + 1;
@@ -173,6 +172,20 @@ class CursorInterpreter {
173
172
  return { start, end, value: decodePrefix(this.text, start, end), raw: this.text.slice(start, end) };
174
173
  }
175
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
+
176
189
  private ensure(char: string) {
177
190
  if (this.text[this.pos] !== char) throw new Error(`Expected '${char}' at ${this.pos}`);
178
191
  this.pos += 1;
@@ -214,35 +227,34 @@ class CursorInterpreter {
214
227
  const power = decodeZigzag(prefix.value);
215
228
  const significand = this.evalValue();
216
229
  if (typeof significand !== "number") throw new Error("Decimal significand must be numeric");
217
- return significand * 10 ** power;
230
+ return parseFloat(`${significand}e${power}`);
218
231
  }
219
232
  case ":":
220
233
  this.pos += 1;
221
234
  return prefix.raw;
222
235
  case "%":
223
236
  this.pos += 1;
224
- return this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
237
+ return { __opcode: prefix.raw } satisfies OpcodeMarker;
225
238
  case "@":
226
239
  this.pos += 1;
227
240
  return this.readSelf(prefix.value);
228
241
  case "'":
229
242
  this.pos += 1;
230
- return this.state.refs[prefix.value];
243
+ return this.state.refs[prefix.raw];
231
244
  case "$":
232
245
  this.pos += 1;
233
246
  return this.state.vars[prefix.raw];
234
247
  case ",": {
235
248
  this.pos += 1;
236
249
  const start = this.pos;
237
- const end = start + prefix.value;
238
- if (end > this.text.length) throw new Error("String container overflows input");
250
+ const end = this.advanceByBytes(start, prefix.value);
239
251
  const value = this.text.slice(start, end);
240
252
  this.pos = end;
241
253
  return value;
242
254
  }
243
255
  case "^": {
244
256
  this.pos += 1;
245
- const target = this.pos + prefix.value;
257
+ const target = this.advanceByBytes(this.pos, prefix.value);
246
258
  if (this.pointerCache.has(target)) return this.pointerCache.get(target);
247
259
  const save = this.pos;
248
260
  this.pos = target;
@@ -258,6 +270,14 @@ class CursorInterpreter {
258
270
  this.writePlace(place, value);
259
271
  return value;
260
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
+ }
261
281
  case "~": {
262
282
  this.pos += 1;
263
283
  const place = this.readPlace();
@@ -285,7 +305,7 @@ class CursorInterpreter {
285
305
  case "<":
286
306
  return this.evalLoopLike(tag);
287
307
  case "#":
288
- return this.evalWhileLoop();
308
+ return this.evalWhileLike();
289
309
  default:
290
310
  throw new Error(`Unexpected tag '${tag}' at ${this.pos}`);
291
311
  }
@@ -308,7 +328,7 @@ class CursorInterpreter {
308
328
  this.ensure(")");
309
329
 
310
330
  if (typeof callee === "object" && callee && "__opcode" in callee) {
311
- return this.applyOpcode((callee as OpcodeMarker).__opcode, args);
331
+ return this.applyOpcode((callee as OpcodeMarker).__opcode as string, args);
312
332
  }
313
333
  return this.navigate(callee, args);
314
334
  }
@@ -495,13 +515,10 @@ class CursorInterpreter {
495
515
  if (keysOnly) return entries.map(([key]) => ({ key, value: key }));
496
516
  return entries.map(([key, value]) => ({ key, value }));
497
517
  }
498
- if (typeof iterable === "number" && Number.isFinite(iterable) && iterable > 0) {
499
- const out: Array<{ key: unknown; value: unknown }> = [];
500
- for (let index = 0; index < Math.floor(iterable); index += 1) {
501
- if (keysOnly) out.push({ key: index, value: index });
502
- else out.push({ key: index, value: index + 1 });
503
- }
504
- 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 }));
505
522
  }
506
523
  return [];
507
524
  }
@@ -548,9 +565,12 @@ class CursorInterpreter {
548
565
  return last;
549
566
  }
550
567
 
551
- private evalWhileLoop(): unknown {
568
+ private evalWhileLike(): unknown {
552
569
  this.pos += 1; // skip '#'
553
- this.ensure("(");
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;
554
574
 
555
575
  const condStart = this.pos;
556
576
 
@@ -558,18 +578,23 @@ class CursorInterpreter {
558
578
  const condValue = this.evalValue();
559
579
  const bodyStart = this.pos;
560
580
 
561
- // Skip past the body to find the closing paren
562
- const bodyValueCount = 1;
581
+ // Skip past body values to find the closing bracket
582
+ const bodyValueCount = open === "{" ? 2 : 1;
563
583
  let cursor = bodyStart;
564
584
  for (let index = 0; index < bodyValueCount; index += 1) {
565
585
  cursor = this.skipValueFrom(cursor);
566
586
  }
567
587
  const bodyEnd = cursor;
568
588
  this.pos = bodyEnd;
569
- this.ensure(")");
589
+ this.ensure(close);
570
590
  const afterClose = this.pos;
571
591
 
572
- // Now iterate
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 {
573
598
  let last: unknown = undefined;
574
599
  let currentCond = condValue;
575
600
  while (isDefined(currentCond)) {
@@ -585,7 +610,6 @@ class CursorInterpreter {
585
610
  last = undefined;
586
611
  }
587
612
 
588
- // Re-evaluate condition
589
613
  currentCond = this.evalBodySlice(condStart, bodyStart);
590
614
  }
591
615
 
@@ -593,6 +617,60 @@ class CursorInterpreter {
593
617
  return last;
594
618
  }
595
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
+
596
674
  private evalArrayComprehension(iterable: unknown, varA: string | undefined, varB: string | undefined, bodyStart: number, bodyEnd: number, keysOnly: boolean): unknown[] | LoopControl {
597
675
  const items = this.iterate(iterable, keysOnly);
598
676
  const out: unknown[] = [];
@@ -651,31 +729,38 @@ class CursorInterpreter {
651
729
  return out;
652
730
  }
653
731
 
654
- private applyOpcode(id: number, args: unknown[]): unknown {
732
+ private applyOpcode(id: string, args: unknown[]): unknown {
655
733
  const custom = this.customOpcodes.get(id);
656
734
  if (custom) return custom(args, this.state);
657
735
  switch (id) {
658
736
  case OPCODES.do:
659
737
  return args.length ? args[args.length - 1] : undefined;
660
738
  case OPCODES.add:
739
+ if (args[0] === undefined || args[1] === undefined) return undefined;
661
740
  if (typeof args[0] === "string" || typeof args[1] === "string") {
662
- return String(args[0] ?? "") + String(args[1] ?? "");
741
+ return String(args[0]) + String(args[1]);
663
742
  }
664
- return Number(args[0] ?? 0) + Number(args[1] ?? 0);
743
+ return Number(args[0]) + Number(args[1]);
665
744
  case OPCODES.sub:
666
- return Number(args[0] ?? 0) - Number(args[1] ?? 0);
745
+ if (args[0] === undefined || args[1] === undefined) return undefined;
746
+ return Number(args[0]) - Number(args[1]);
667
747
  case OPCODES.mul:
668
- return Number(args[0] ?? 0) * Number(args[1] ?? 0);
748
+ if (args[0] === undefined || args[1] === undefined) return undefined;
749
+ return Number(args[0]) * Number(args[1]);
669
750
  case OPCODES.div:
670
- return Number(args[0] ?? 0) / Number(args[1] ?? 0);
751
+ if (args[0] === undefined || args[1] === undefined) return undefined;
752
+ return Number(args[0]) / Number(args[1]);
671
753
  case OPCODES.mod:
672
- return Number(args[0] ?? 0) % Number(args[1] ?? 0);
754
+ if (args[0] === undefined || args[1] === undefined) return undefined;
755
+ return Number(args[0]) % Number(args[1]);
673
756
  case OPCODES.neg:
674
- return -Number(args[0] ?? 0);
757
+ if (args[0] === undefined) return undefined;
758
+ return -Number(args[0]);
675
759
  case OPCODES.not: {
676
760
  const value = args[0];
761
+ if (value === undefined) return undefined;
677
762
  if (typeof value === "boolean") return !value;
678
- return ~Number(value ?? 0);
763
+ return ~Number(value);
679
764
  }
680
765
  case OPCODES.and: {
681
766
  const [a, b] = args;
@@ -714,6 +799,22 @@ class CursorInterpreter {
714
799
  return Array.isArray(args[0]) ? args[0] : undefined;
715
800
  case OPCODES.object:
716
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
+ }
717
818
  default:
718
819
  throw new Error(`Unknown opcode ${id}`);
719
820
  }
@@ -723,12 +824,55 @@ class CursorInterpreter {
723
824
  let current = base;
724
825
  for (const key of keys) {
725
826
  if (current === undefined || current === null) return undefined;
726
- current = (current as Record<string, unknown>)[String(key)];
827
+ current = this.readProperty(current, key);
828
+ if (current === undefined) return undefined;
727
829
  }
728
830
  return current;
729
831
  }
730
832
 
731
- private readPlace(): { root: string | number; keys: unknown[]; isRef: boolean } {
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 } {
732
876
  this.skipNonCode();
733
877
  const direct = this.readRootVarOrRefIfPresent();
734
878
  if (direct) {
@@ -774,7 +918,7 @@ class CursorInterpreter {
774
918
  throw new Error(`Invalid place at ${this.pos}`);
775
919
  }
776
920
 
777
- private readRootVarOrRefIfPresent(): { root: string | number; isRef: boolean } | undefined {
921
+ private readRootVarOrRefIfPresent(): { root: string; isRef: boolean } | undefined {
778
922
  const save = this.pos;
779
923
  const prefix = this.readPrefix();
780
924
  const tag = this.text[this.pos];
@@ -784,14 +928,14 @@ class CursorInterpreter {
784
928
  }
785
929
  this.pos += 1;
786
930
  return {
787
- root: tag === "$" ? prefix.raw : prefix.value,
931
+ root: prefix.raw,
788
932
  isRef: tag === "'",
789
933
  };
790
934
  }
791
935
 
792
- private writePlace(place: { root: string | number; keys: unknown[]; isRef: boolean }, value: unknown) {
936
+ private writePlace(place: { root: string; keys: unknown[]; isRef: boolean }, value: unknown) {
793
937
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
794
- const rootKey = String(place.root);
938
+ const rootKey = place.root;
795
939
  if (place.keys.length === 0) {
796
940
  rootTable[rootKey] = value;
797
941
  return;
@@ -802,17 +946,44 @@ class CursorInterpreter {
802
946
  rootTable[rootKey] = target;
803
947
  }
804
948
  for (let index = 0; index < place.keys.length - 1; index += 1) {
805
- const key = String(place.keys[index]);
806
- const next = (target as Record<string, unknown>)[key];
807
- if (!next || typeof next !== "object") (target as Record<string, unknown>)[key] = {};
808
- target = (target as Record<string, unknown>)[key];
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];
809
962
  }
810
- (target as Record<string, unknown>)[String(place.keys[place.keys.length - 1])] = value;
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;
811
982
  }
812
983
 
813
- private deletePlace(place: { root: string | number; keys: unknown[]; isRef: boolean }) {
984
+ private deletePlace(place: { root: string; keys: unknown[]; isRef: boolean }) {
814
985
  const rootTable = place.isRef ? this.state.refs : this.state.vars;
815
- const rootKey = String(place.root);
986
+ const rootKey = place.root;
816
987
  if (place.keys.length === 0) {
817
988
  delete rootTable[rootKey];
818
989
  return;
@@ -820,10 +991,25 @@ class CursorInterpreter {
820
991
  let target = rootTable[rootKey];
821
992
  if (!target || typeof target !== "object") return;
822
993
  for (let index = 0; index < place.keys.length - 1; index += 1) {
823
- target = (target as Record<string, unknown>)[String(place.keys[index])];
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)];
824
1003
  if (!target || typeof target !== "object") return;
825
1004
  }
826
- delete (target as Record<string, unknown>)[String(place.keys[place.keys.length - 1])];
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)];
827
1013
  }
828
1014
 
829
1015
  private skipValue() {
@@ -842,12 +1028,12 @@ class CursorInterpreter {
842
1028
  }
843
1029
 
844
1030
  if (tag === ",") {
845
- this.pos += 1 + prefix.value;
1031
+ this.pos = this.advanceByBytes(this.pos + 1, prefix.value);
846
1032
  const end = this.pos;
847
1033
  this.pos = save;
848
1034
  return end;
849
1035
  }
850
- if (tag === "=") {
1036
+ if (tag === "=" || tag === "/") {
851
1037
  this.pos += 1;
852
1038
  this.skipValue();
853
1039
  this.skipValue();
@@ -882,7 +1068,8 @@ class CursorInterpreter {
882
1068
  if (opener && "([{".includes(opener)) {
883
1069
  const close = opener === "(" ? ")" : opener === "[" ? "]" : "}";
884
1070
  if (prefix.value > 0) {
885
- this.pos += 1 + prefix.value + 1;
1071
+ const bodyEnd = this.advanceByBytes(this.pos + 1, prefix.value);
1072
+ this.pos = bodyEnd + 1;
886
1073
  const end = this.pos;
887
1074
  this.pos = save;
888
1075
  return end;