Skip to content

Commit 910999f

Browse files
committed
Implement encoding of sub-range bindings
1 parent 0d0697a commit 910999f

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

src/codec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const enum Tag {
99
GENERATED_RANGE_START = 0x4,
1010
GENERATED_RANGE_END = 0x5,
1111
GENERATED_RANGE_BINDINGS = 0x6,
12+
GENERATED_RANGE_SUBRANGE_BINDING = 0x7,
1213
GENERATED_RANGE_CALL_SITE = 0x8,
1314
}
1415

@@ -19,6 +20,7 @@ export const enum EncodedTag {
1920
GENERATED_RANGE_START = "E", // 0x4
2021
GENERATED_RANGE_END = "F", // 0x5
2122
GENERATED_RANGE_BINDINGS = "G", // 0x6
23+
GENERATED_RANGE_SUBRANGE_BINDING = "H", // 0x7
2224
GENERATED_RANGE_CALL_SITE = "I", // 0x8
2325
}
2426

src/encode/encode.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,36 @@ describe("encode", () => {
107107

108108
assertThrows(() => encode(info));
109109
});
110+
111+
it("throws when sub-range bindings are not sorted", () => {
112+
const info = builder.startScope(0, 0, { key: "key", variables: ["a"] })
113+
.endScope(10, 0).startRange(0, 0, {
114+
scopeKey: "key",
115+
values: [[{
116+
from: { line: 5, column: 0 },
117+
to: { line: 10, column: 0 },
118+
}, {
119+
from: { line: 0, column: 0 },
120+
to: { line: 5, column: 0 },
121+
}]],
122+
}).endRange(10, 0).build();
123+
124+
assertThrows(() => encode(info));
125+
});
126+
127+
it("throws when sub-range bindings have a gap", () => {
128+
const info = builder.startScope(0, 0, { key: "key", variables: ["a"] })
129+
.endScope(10, 0).startRange(0, 0, {
130+
scopeKey: "key",
131+
values: [[{
132+
from: { line: 0, column: 0 },
133+
to: { line: 4, column: 0 },
134+
}, {
135+
from: { line: 5, column: 0 },
136+
to: { line: 10, column: 0 },
137+
}]],
138+
}).endRange(10, 0).build();
139+
140+
assertThrows(() => encode(info));
141+
});
110142
});

src/encode/encoder.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "../codec.ts";
1010
import type { GeneratedRange, OriginalScope, ScopeInfo } from "../scopes.d.ts";
1111
import { encodeSigned, encodeUnsigned } from "../vlq.ts";
12+
import { comparePositions } from "../util.ts";
1213

1314
const DEFAULT_SCOPE_STATE = {
1415
line: 0,
@@ -147,6 +148,7 @@ export class Encoder {
147148
#encodeGeneratedRange(range: GeneratedRange): void {
148149
this.#encodeGeneratedRangeStart(range);
149150
this.#encodeGeneratedRangeBindings(range);
151+
this.#encodeGeneratedRangeSubRangeBindings(range);
150152
this.#encodeGeneratedRangeCallSite(range);
151153
range.children.forEach((child) => this.#encodeGeneratedRange(child));
152154
this.#encodeGeneratedRangeEnd(range);
@@ -190,6 +192,49 @@ export class Encoder {
190192
this.#finishItem();
191193
}
192194

195+
#encodeGeneratedRangeSubRangeBindings(range: GeneratedRange) {
196+
if (range.values.length === 0) return;
197+
198+
for (let i = 0; i < range.values.length; ++i) {
199+
const value = range.values[i];
200+
if (!Array.isArray(value) || value.length <= 1) {
201+
continue;
202+
}
203+
204+
this.#encodeTag(EncodedTag.GENERATED_RANGE_SUBRANGE_BINDING)
205+
.#encodeUnsigned(i);
206+
207+
let lastLine = range.start.line;
208+
let lastColumn = range.start.column;
209+
for (let j = 1; j < value.length; ++j) {
210+
const subRange = value[j];
211+
const prevSubRange = value[j - 1];
212+
213+
if (comparePositions(prevSubRange.to, subRange.from) !== 0) {
214+
throw new Error("Sub-range bindings must not have gaps");
215+
}
216+
217+
const encodedLine = subRange.from.line - lastLine;
218+
const encodedColumn = encodedLine === 0
219+
? subRange.from.column - lastColumn
220+
: subRange.from.column;
221+
if (encodedLine < 0 || encodedColumn < 0) {
222+
throw new Error("Sub-range bindings must be sorted");
223+
}
224+
225+
lastLine = subRange.from.line;
226+
lastColumn = subRange.from.column;
227+
228+
const binding = subRange.value === undefined
229+
? 0
230+
: this.#resolveNamesIdx(subRange.value) + 1;
231+
this.#encodeUnsigned(binding).#encodeUnsigned(encodedLine)
232+
.#encodeUnsigned(encodedColumn);
233+
}
234+
this.#finishItem();
235+
}
236+
}
237+
193238
#encodeGeneratedRangeBindings(range: GeneratedRange) {
194239
if (range.values.length === 0) return;
195240

@@ -203,12 +248,16 @@ export class Encoder {
203248

204249
this.#encodeTag(EncodedTag.GENERATED_RANGE_BINDINGS);
205250
for (const val of range.values) {
206-
if (val === null || val == undefined) {
251+
if (val === null || val === undefined) {
207252
this.#encodeUnsigned(0);
208253
} else if (typeof val === "string") {
209254
this.#encodeUnsigned(this.#resolveNamesIdx(val) + 1);
210255
} else {
211-
throw new Error("Sub-range bindings not implemented yet!");
256+
const initialValue = val[0];
257+
const binding = initialValue.value === undefined
258+
? 0
259+
: this.#resolveNamesIdx(initialValue.value) + 1;
260+
this.#encodeUnsigned(binding);
212261
}
213262
}
214263
this.#finishItem();

0 commit comments

Comments
 (0)