Skip to content

Commit ed079df

Browse files
committed
refactor(vnext): share bounded SQL lexer
1 parent 8347817 commit ed079df

3 files changed

Lines changed: 480 additions & 268 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
BoundedSqlLexer,
4+
MAX_BOUNDED_SQL_LEXEMES,
5+
type BoundedSqlLexeme,
6+
} from "../bounded-sql-lexer.js";
7+
import {
8+
BIGQUERY_SQL_LEXICAL_PROFILE,
9+
POSTGRESQL_SQL_LEXICAL_PROFILE,
10+
type SqlLexicalProfile,
11+
} from "../lexical.js";
12+
import {
13+
createIdentitySqlSource,
14+
createMaskedSqlSource,
15+
type SqlSourceSnapshot,
16+
} from "../source.js";
17+
18+
function lex(
19+
source: SqlSourceSnapshot,
20+
profile: SqlLexicalProfile = POSTGRESQL_SQL_LEXICAL_PROFILE,
21+
): {
22+
readonly lexemes: readonly BoundedSqlLexeme[];
23+
readonly resource: BoundedSqlLexer["resource"];
24+
} {
25+
const lexer = new BoundedSqlLexer(
26+
source,
27+
0,
28+
source.analysisText.length,
29+
profile,
30+
);
31+
const lexemes: BoundedSqlLexeme[] = [];
32+
while (true) {
33+
const lexeme = lexer.next();
34+
if (!lexeme) {
35+
return { lexemes, resource: lexer.resource };
36+
}
37+
lexemes.push(lexeme);
38+
}
39+
}
40+
41+
describe("bounded SQL lexer", () => {
42+
it("streams words, punctuation, strings, comments, and UTF-16 ranges", () => {
43+
const text = "Se😀lect . 'x' -- note\n/* nested /* x */ */ end";
44+
expect(lex(createIdentitySqlSource(text))).toEqual({
45+
lexemes: [
46+
{ closed: true, from: 0, kind: "word", to: 8 },
47+
{ closed: true, from: 9, kind: "punctuation", to: 10 },
48+
{ closed: true, from: 11, kind: "string", to: 14 },
49+
{ closed: true, from: 15, kind: "line-comment", to: 22 },
50+
{ closed: true, from: 23, kind: "comment", to: 43 },
51+
{ closed: true, from: 44, kind: "word", to: 47 },
52+
],
53+
resource: null,
54+
});
55+
});
56+
57+
it("keeps BigQuery backticks, raw triples, and hash comments atomic", () => {
58+
const text = "`a.b` R'''raw\\value''' # comment";
59+
expect(
60+
lex(
61+
createIdentitySqlSource(text),
62+
BIGQUERY_SQL_LEXICAL_PROFILE,
63+
),
64+
).toEqual({
65+
lexemes: [
66+
{
67+
closed: true,
68+
from: 0,
69+
kind: "quoted-identifier",
70+
to: 5,
71+
},
72+
{ closed: true, from: 6, kind: "word", to: 7 },
73+
{ closed: true, from: 7, kind: "string", to: 22 },
74+
{
75+
closed: true,
76+
from: 23,
77+
kind: "line-comment",
78+
to: 32,
79+
},
80+
],
81+
resource: null,
82+
});
83+
});
84+
85+
it("emits embedded regions as barriers and finds exact boundaries", () => {
86+
const source = createMaskedSqlSource("a {value} b", [
87+
{ from: 2, language: "python", to: 9 },
88+
]);
89+
expect(lex(source).lexemes).toEqual([
90+
{ closed: true, from: 0, kind: "word", to: 1 },
91+
{ closed: true, from: 2, kind: "barrier", to: 9 },
92+
{ closed: true, from: 10, kind: "word", to: 11 },
93+
]);
94+
});
95+
96+
it("pushes back one token without spending the budget twice", () => {
97+
const source = createIdentitySqlSource("one two");
98+
const lexer = new BoundedSqlLexer(
99+
source,
100+
0,
101+
source.analysisText.length,
102+
POSTGRESQL_SQL_LEXICAL_PROFILE,
103+
);
104+
const first = lexer.next();
105+
expect(first).not.toBeNull();
106+
if (!first) {
107+
throw new Error("Expected first lexeme");
108+
}
109+
lexer.pushBack(first);
110+
expect(lexer.next()).toBe(first);
111+
expect(lexer.next()).toEqual({
112+
closed: true,
113+
from: 4,
114+
kind: "word",
115+
to: 7,
116+
});
117+
expect(lexer.next()).toBeNull();
118+
expect(lexer.resource).toBeNull();
119+
});
120+
121+
it("fails closed immediately after the shared lexeme budget", () => {
122+
const words = Array.from(
123+
{ length: MAX_BOUNDED_SQL_LEXEMES + 1 },
124+
() => "x",
125+
).join(" ");
126+
const result = lex(createIdentitySqlSource(words));
127+
expect(result.lexemes).toHaveLength(MAX_BOUNDED_SQL_LEXEMES);
128+
expect(result.resource).toBe("lexical-token");
129+
});
130+
131+
it("reports oversized dollar-quote delimiters without emitting a token", () => {
132+
const source = createIdentitySqlSource(
133+
`$${"a".repeat(257)}$unterminated`,
134+
);
135+
expect(lex(source)).toEqual({
136+
lexemes: [],
137+
resource: "dollar-quote-delimiter",
138+
});
139+
});
140+
});

0 commit comments

Comments
 (0)