@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.
@@ -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,31 +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,
104
- 5: ctx.refs?.[5] ?? NaN,
105
- 6: ctx.refs?.[6] ?? Infinity,
106
- 7: ctx.refs?.[7] ?? -Infinity,
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 [idText, op] of Object.entries(ctx.opcodes)) {
118
- 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);
119
115
  }
120
116
  }
121
117
  }
122
118
 
123
- private readonly customOpcodes = new Map<number, (args: unknown[], state: RexcRuntimeState) => unknown>();
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 * 10 ** power;
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 this.opcodeMarkers[prefix.value] ?? { __opcode: prefix.value };
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.value];
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 + prefix.value;
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 + prefix.value;
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.evalWhileLoop();
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 === "number" && Number.isFinite(iterable) && iterable > 0) {
502
- const out: Array<{ key: unknown; value: unknown }> = [];
503
- for (let index = 0; index < Math.floor(iterable); index += 1) {
504
- if (keysOnly) out.push({ key: index, value: index });
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 evalWhileLoop(): unknown {
568
+ private evalWhileLike(): unknown {
555
569
  this.pos += 1; // skip '#'
556
- 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;
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 the body to find the closing paren
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
- // 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 {
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: number, args: unknown[]): unknown {
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 as Record<string, unknown>)[String(key)];
827
+ current = this.readProperty(current, key);
828
+ if (current === undefined) return undefined;
737
829
  }
738
830
  return current;
739
831
  }
740
832
 
741
- 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 } {
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 | number; isRef: boolean } | undefined {
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: tag === "$" ? prefix.raw : prefix.value,
931
+ root: prefix.raw,
798
932
  isRef: tag === "'",
799
933
  };
800
934
  }
801
935
 
802
- private writePlace(place: { root: string | number; keys: unknown[]; isRef: boolean }, value: unknown) {
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 = String(place.root);
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 = String(place.keys[index]);
816
- const next = (target as Record<string, unknown>)[key];
817
- if (!next || typeof next !== "object") (target as Record<string, unknown>)[key] = {};
818
- 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];
819
962
  }
820
- (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;
821
982
  }
822
983
 
823
- private deletePlace(place: { root: string | number; keys: unknown[]; isRef: boolean }) {
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 = String(place.root);
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
- 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)];
834
1003
  if (!target || typeof target !== "object") return;
835
1004
  }
836
- 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)];
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 += 1 + prefix.value;
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 += 1 + prefix.value + 1;
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;