Skip to content

Commit f793751

Browse files
committed
Handle decoding of simple bindings
1 parent 0ee16a3 commit f793751

File tree

5 files changed

+103
-7
lines changed

5 files changed

+103
-7
lines changed

src/codec.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ export const enum Tag {
66
ORIGINAL_SCOPE_START = 0x1,
77
ORIGINAL_SCOPE_END = 0x2,
88
ORIGINAL_SCOPE_VARIABLES = 0x3,
9-
GENERATED_RANGE_START = 0x4,
10-
GENERATED_RANGE_END = 0x5,
9+
GENERATED_RANGE_START = 0x5,
10+
GENERATED_RANGE_END = 0x6,
11+
GENERATED_RANGE_BINDINGS = 0x7,
1112
}
1213

1314
export const enum EncodedTag {
1415
ORIGINAL_SCOPE_START = "B", // 0x1
1516
ORIGINAL_SCOPE_END = "C", // 0x2
1617
ORIGINAL_SCOPE_VARIABLES = "D", // 0x3
17-
GENERATED_RANGE_START = "E", // 0x4
18-
GENERATED_RANGE_END = "F", // 0x5
18+
GENERATED_RANGE_START = "F", // 0x5
19+
GENERATED_RANGE_END = "G", // 0x6
20+
GENERATED_RANGE_BINDINGS = "H", // 0x7
1921
}
2022

2123
export const enum OriginalScopeFlags {
@@ -39,7 +41,8 @@ export type Item =
3941
| OriginalScopeEndItem
4042
| OriginalScopeVariablesItem
4143
| GeneratedRangeStartItem
42-
| GeneratedRangeEndItem;
44+
| GeneratedRangeEndItem
45+
| GeneratedRangeBindingsItem;
4346

4447
export interface OriginalScopeStartItem {
4548
tag: Tag.ORIGINAL_SCOPE_START;
@@ -74,3 +77,8 @@ interface GeneratedRangeEndItem {
7477
line?: number;
7578
column: number;
7679
}
80+
81+
interface GeneratedRangeBindingsItem {
82+
tag: Tag.GENERATED_RANGE_BINDINGS;
83+
valueIdxs: number[];
84+
}

src/decode/decode.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ class Decoder {
181181
const range = this.#rangeStack.pop();
182182
if (!range) {
183183
throw new Error(
184-
"Encountered GENERATED_RANGE_END wtihout matching GENERATED_RANGE_START!",
184+
"Encountered GENERATED_RANGE_END without matching GENERATED_RANGE_START!",
185185
);
186186
}
187187

@@ -200,6 +200,25 @@ class Decoder {
200200
}
201201
break;
202202
}
203+
case Tag.GENERATED_RANGE_BINDINGS: {
204+
const range = this.#rangeStack.at(-1);
205+
if (!range) {
206+
throw new Error(
207+
"Encountered GENERATED_RANGE_BINDINGS without surrounding GENERATED_RANGE_START",
208+
);
209+
}
210+
211+
for (const valueIdx of item.valueIdxs) {
212+
if (valueIdx === -1) {
213+
range.values.push(null);
214+
} else {
215+
range.values.push(this.#names[valueIdx]);
216+
}
217+
218+
// TODO: Potentially throw if we decode an illegal index.
219+
}
220+
break;
221+
}
203222
}
204223
}
205224

@@ -294,6 +313,16 @@ class Decoder {
294313
}
295314
break;
296315
}
316+
case Tag.GENERATED_RANGE_BINDINGS: {
317+
const valueIdxs: number[] = [];
318+
319+
while (iter.hasNext() && iter.peek() !== ",") {
320+
valueIdxs.push(iter.nextSignedVLQ());
321+
}
322+
323+
yield { tag, valueIdxs };
324+
break;
325+
}
297326
}
298327

299328
// Consume any trailing VLQ and the the ","

src/encode/encode.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe("encode", () => {
7777
assertThrows(() => encode(info));
7878
});
7979

80-
it("throws when a ranges' definition scope has not known to the encoder", () => {
80+
it("throws when a ranges' definition scope is not known to the encoder", () => {
8181
const scope = builder.startScope(0, 0).endScope(10, 0).lastScope()!;
8282
const info = builder.startRange(0, 10).endRange(0, 20).build();
8383

@@ -86,4 +86,25 @@ describe("encode", () => {
8686

8787
assertThrows(() => encode(info));
8888
});
89+
90+
it("throws when a range has bindings but no definition scope", () => {
91+
const info = builder.startRange(0, 0, { values: ["a", null] }).endRange(
92+
0,
93+
10,
94+
).build();
95+
96+
assertThrows(() => encode(info));
97+
});
98+
99+
it("throws when range bindings don't match with scope variables", () => {
100+
const info = builder.startScope(0, 0, {
101+
key: "key",
102+
variables: ["foo", "bar"],
103+
}).endScope(10, 0).startRange(0, 0, {
104+
scopeKey: "key",
105+
values: ["a", null, "b"],
106+
}).endRange(0, 10).build();
107+
108+
assertThrows(() => encode(info));
109+
});
89110
});

src/encode/encoder.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export class Encoder {
135135

136136
#encodeGeneratedRange(range: GeneratedRange): void {
137137
this.#encodeGeneratedRangeStart(range);
138+
this.#encodeGeneratedRangeBindings(range);
138139
range.children.forEach((child) => this.#encodeGeneratedRange(child));
139140
this.#encodeGeneratedRangeEnd(range);
140141
}
@@ -177,6 +178,30 @@ export class Encoder {
177178
this.#finishItem();
178179
}
179180

181+
#encodeGeneratedRangeBindings(range: GeneratedRange) {
182+
if (range.values.length === 0) return;
183+
184+
if (!range.originalScope) {
185+
throw new Error("Range has binding expressions but no OriginalScope");
186+
} else if (range.originalScope.variables.length !== range.values.length) {
187+
throw new Error(
188+
"Range's binding expressions don't match OriginalScopes' variables",
189+
);
190+
}
191+
192+
this.#encodeTag(EncodedTag.GENERATED_RANGE_BINDINGS);
193+
for (const val of range.values) {
194+
if (val === null) {
195+
this.#encodeSigned(-1);
196+
} else if (typeof val === "string") {
197+
this.#encodeSigned(this.#resolveNamesIdx(val));
198+
} else {
199+
throw new Error("Sub-range bindings not implemented yet!");
200+
}
201+
}
202+
this.#finishItem();
203+
}
204+
180205
#encodeGeneratedRangeEnd(range: GeneratedRange) {
181206
const { line, column } = range.end;
182207
this.#verifyPositionWithRangeState(line, column);

src/roundtrip.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,17 @@ describe("round trip", () => {
144144

145145
assertCodec(builder.build());
146146
});
147+
148+
it("handles value bindings expressions", () => {
149+
builder.startScope(0, 0, { variables: ["foo", "bar"], key: "outer" })
150+
.startScope(10, 0, { variables: ["local1", "local2"], key: "inner" })
151+
.endScope(20, 0).endScope(30, 0)
152+
.startRange(0, 0, { scopeKey: "outer", values: ["f", "b"] }).startRange(
153+
0,
154+
10,
155+
{ scopeKey: "inner", values: [null, "g"] },
156+
).endRange(0, 20).endRange(0, 30);
157+
158+
assertCodec(builder.build());
159+
});
147160
});

0 commit comments

Comments
 (0)