sqllens 1.8.0 → 1.9.0
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.
- package/dist/bigquery/parse.d.ts +4 -0
- package/dist/bigquery/parse.js +28 -1
- package/dist/databricks/parse.d.ts +4 -0
- package/dist/databricks/parse.js +16 -0
- package/dist/duckdb/parse.d.ts +4 -0
- package/dist/duckdb/parse.js +16 -0
- package/dist/fragment-grammar.d.ts +51 -0
- package/dist/fragment-grammar.js +103 -0
- package/dist/fragment.d.ts +32 -0
- package/dist/fragment.js +93 -0
- package/dist/minijinja/index.d.ts +2 -1
- package/dist/minijinja/index.js +1 -1
- package/dist/minijinja/parse.d.ts +12 -2
- package/dist/minijinja/parse.js +260 -18
- package/dist/minijinja/regions.d.ts +7 -0
- package/dist/minijinja/segment.js +21 -11
- package/dist/mysql/parse.d.ts +4 -0
- package/dist/mysql/parse.js +16 -0
- package/dist/postgres/parse.d.ts +4 -0
- package/dist/postgres/parse.js +16 -0
- package/dist/qualify/template-provider.d.ts +8 -3
- package/dist/qualify/template-provider.js +5 -1
- package/dist/redshift/parse.d.ts +4 -0
- package/dist/redshift/parse.js +16 -0
- package/dist/snowflake/parse.d.ts +4 -0
- package/dist/snowflake/parse.js +16 -0
- package/dist/sqlite/parse.d.ts +4 -0
- package/dist/sqlite/parse.js +16 -0
- package/dist/symbols/symbols.js +5 -0
- package/dist/template/engine.d.ts +38 -1
- package/dist/trino/parse.d.ts +4 -0
- package/dist/trino/parse.js +16 -0
- package/dist/tsql/parse.d.ts +4 -0
- package/dist/tsql/parse.js +16 -0
- package/package.json +1 -1
package/dist/bigquery/parse.d.ts
CHANGED
|
@@ -9,3 +9,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
9
9
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
10
10
|
*/
|
|
11
11
|
export declare function parseBigQuery(sql: string): ParseResult;
|
|
12
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
13
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
14
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
15
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/bigquery/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode } from "antlr4ng";
|
|
1
|
+
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, Token as AntlrToken, } from "antlr4ng";
|
|
2
2
|
import { GoogleSQLLexer } from "../generated/bigquery/GoogleSQLLexer.js";
|
|
3
3
|
import { GoogleSQLParser } from "../generated/bigquery/GoogleSQLParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { dotPathTokenSource } from "./dot-path.js";
|
|
5
6
|
import { postParseDiagnostics } from "./post-validate.js";
|
|
6
7
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
@@ -67,3 +68,29 @@ export function parseBigQuery(sql) {
|
|
|
67
68
|
return withTokens({ tree, errors: diagnostics.length, diagnostics, sllFallback: true });
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
72
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
73
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
74
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
75
|
+
// The same token pipeline as parseBigQuery: the DOT_IDENTIFIER path rewrite and the
|
|
76
|
+
// literal-escape validation ride the lex; the post-parse tree checks ride each parse.
|
|
77
|
+
lex: (text) => {
|
|
78
|
+
const lexer = new GoogleSQLLexer(CharStream.fromString(text));
|
|
79
|
+
lexer.removeErrorListeners();
|
|
80
|
+
const { source, escapeDiagnostics } = dotPathTokenSource(text, lexer);
|
|
81
|
+
const stream = new CommonTokenStream(source);
|
|
82
|
+
stream.fill();
|
|
83
|
+
return { tokens: stream.getTokens().filter((t) => t.type !== AntlrToken.EOF), diagnostics: escapeDiagnostics };
|
|
84
|
+
},
|
|
85
|
+
newLexer: (input) => new GoogleSQLLexer(input),
|
|
86
|
+
newParser: (tokens) => new GoogleSQLParser(tokens),
|
|
87
|
+
postParse: postParseDiagnostics,
|
|
88
|
+
separator: GoogleSQLLexer.COMMA_SYMBOL,
|
|
89
|
+
entries: {
|
|
90
|
+
statement: (p) => p.root(),
|
|
91
|
+
expression: (p) => p.expression(),
|
|
92
|
+
tableSource: (p) => p.from_clause_contents(),
|
|
93
|
+
cteList: separatedList((p) => p.with_clause_entry(), GoogleSQLLexer.COMMA_SYMBOL),
|
|
94
|
+
selectList: (p) => p.select_list(),
|
|
95
|
+
},
|
|
96
|
+
});
|
|
@@ -9,3 +9,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
9
9
|
* result LL alone would produce, so correctness is unchanged — just faster.
|
|
10
10
|
*/
|
|
11
11
|
export declare function parseDatabricks(sql: string): ParseResult;
|
|
12
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
13
|
+
* (`multiStatement`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
14
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
15
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/databricks/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { DatabricksLexer } from "../generated/databricks/DatabricksLexer.js";
|
|
3
3
|
import { DatabricksParser } from "../generated/databricks/DatabricksParser.js";
|
|
4
|
+
import { defineFragmentGrammar, separatedList } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -72,3 +73,18 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
72
73
|
parser.removeErrorListeners();
|
|
73
74
|
parser.addErrorListener(listener);
|
|
74
75
|
}
|
|
76
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
77
|
+
* (`multiStatement`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
78
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
79
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
80
|
+
newLexer: (input) => new DatabricksLexer(input),
|
|
81
|
+
newParser: (tokens) => new DatabricksParser(tokens),
|
|
82
|
+
separator: DatabricksLexer.COMMA,
|
|
83
|
+
entries: {
|
|
84
|
+
statement: (p) => p.multiStatement(),
|
|
85
|
+
expression: (p) => p.expression(),
|
|
86
|
+
tableSource: (p) => p.relation(),
|
|
87
|
+
cteList: separatedList((p) => p.namedQuery(), DatabricksLexer.COMMA),
|
|
88
|
+
selectList: (p) => p.namedExpressionSeq(),
|
|
89
|
+
},
|
|
90
|
+
});
|
package/dist/duckdb/parse.d.ts
CHANGED
|
@@ -8,3 +8,7 @@ export type { ParseResult } from "../parse-result.js";
|
|
|
8
8
|
* only when SLL fails — same result LL alone would give, just faster on valid input.
|
|
9
9
|
*/
|
|
10
10
|
export declare function parseDuckdb(sql: string): ParseResult;
|
|
11
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
12
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
13
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
14
|
+
export declare const fragmentGrammar: import("../fragment-grammar.js").FragmentGrammar;
|
package/dist/duckdb/parse.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BailErrorStrategy, CharStream, CommonTokenStream, PredictionMode, } from "antlr4ng";
|
|
2
2
|
import { DuckdbLexer } from "../generated/duckdb/DuckdbLexer.js";
|
|
3
3
|
import { DuckdbParser } from "../generated/duckdb/DuckdbParser.js";
|
|
4
|
+
import { defineFragmentGrammar } from "../fragment-grammar.js";
|
|
4
5
|
import { makeErrorCollector } from "../parse-diagnostics.js";
|
|
5
6
|
import { CONSUMED_AS_RULES, deriveConsumedAs } from "../token/consumed-as.js";
|
|
6
7
|
import { mapTokens } from "../token/map.js";
|
|
@@ -63,3 +64,18 @@ function attachErrorCounter(lexer, parser, listener) {
|
|
|
63
64
|
parser.removeErrorListeners();
|
|
64
65
|
parser.addErrorListener(listener);
|
|
65
66
|
}
|
|
67
|
+
/** The fragment entries (src/fragment.ts): the grammar's own rules for a statement batch
|
|
68
|
+
* (`root`), an expression, a FROM-slot source, a CTE list (no WITH) and a select list;
|
|
69
|
+
* the driver anchors each at EOF. For templated macro bodies. */
|
|
70
|
+
export const fragmentGrammar = defineFragmentGrammar({
|
|
71
|
+
newLexer: (input) => new DuckdbLexer(input),
|
|
72
|
+
newParser: (tokens) => new DuckdbParser(tokens),
|
|
73
|
+
separator: DuckdbLexer.COMMA,
|
|
74
|
+
entries: {
|
|
75
|
+
statement: (p) => p.root(),
|
|
76
|
+
expression: (p) => p.a_expr(),
|
|
77
|
+
tableSource: (p) => p.table_ref(),
|
|
78
|
+
cteList: (p) => p.cte_list(),
|
|
79
|
+
selectList: (p) => p.target_list(),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CharStream, CommonTokenStream, type Lexer, type Parser, type ParserRuleContext, Token as AntlrToken } from "antlr4ng";
|
|
2
|
+
import { type SyntaxDiagnostic } from "./parse-diagnostics.js";
|
|
3
|
+
/** What a fragment parses as. `statement` is the dialect's ordinary full-file entry. */
|
|
4
|
+
export type FragmentKind = "statement" | "expression" | "tableSource" | "cteList" | "selectList";
|
|
5
|
+
/** Every kind, in the order a macro body is tried: the most common reading first. */
|
|
6
|
+
export declare const FRAGMENT_KINDS: readonly FragmentKind[];
|
|
7
|
+
export interface FragmentLex {
|
|
8
|
+
/** Text-native tokens, trivia included, EOF excluded. */
|
|
9
|
+
tokens: AntlrToken[];
|
|
10
|
+
/** Token-derived diagnostics the dialect's statement entry would also report (bigquery's
|
|
11
|
+
* literal-escape validation); positioned in the text's coordinates. */
|
|
12
|
+
diagnostics: SyntaxDiagnostic[];
|
|
13
|
+
}
|
|
14
|
+
export interface FragmentParse {
|
|
15
|
+
tree: ParserRuleContext;
|
|
16
|
+
/** Empty when the slice parsed clean as this kind (the rule is EOF-anchored). */
|
|
17
|
+
diagnostics: SyntaxDiagnostic[];
|
|
18
|
+
}
|
|
19
|
+
export interface FragmentGrammar {
|
|
20
|
+
/** Lex a whole text exactly as the dialect's statement entry does (same token rewrites). */
|
|
21
|
+
lex(text: string): FragmentLex;
|
|
22
|
+
/**
|
|
23
|
+
* Parse an already-lexed slice as `kind`. `bail` = SLL + bail strategy: returns undefined on
|
|
24
|
+
* the first syntax error (fast clean/not-clean probe); otherwise full LL with recovery and
|
|
25
|
+
* positioned diagnostics.
|
|
26
|
+
*/
|
|
27
|
+
parse(slice: readonly AntlrToken[], kind: FragmentKind, bail: boolean): FragmentParse | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** A fragment entry: drives one or more of the grammar's OWN rules over the parser. The grammar
|
|
30
|
+
* is untouched (no `x EOF` wrapper rules: an extra caller widens a rule's SLL follow set and
|
|
31
|
+
* flips prediction on unrelated input); the EOF anchor is checked by the driver instead. */
|
|
32
|
+
export type FragmentEntry<P extends Parser> = (parser: P) => ParserRuleContext;
|
|
33
|
+
export interface FragmentGrammarSpec<P extends Parser> {
|
|
34
|
+
/** Lex a whole text; defaults to the plain lexer with no diagnostics. */
|
|
35
|
+
lex?: (text: string) => FragmentLex;
|
|
36
|
+
newLexer: (input: CharStream) => Lexer;
|
|
37
|
+
newParser: (tokens: CommonTokenStream) => P;
|
|
38
|
+
entries: {
|
|
39
|
+
readonly [K in FragmentKind]: FragmentEntry<P>;
|
|
40
|
+
};
|
|
41
|
+
/** The list separator token type (COMMA). A list body (`cteList`/`selectList`) may end with
|
|
42
|
+
* one: a macro's CTE list often ends `),` because the caller appends more CTEs. */
|
|
43
|
+
separator: number;
|
|
44
|
+
/** Tree-walking checks the dialect's statement entry runs after the parse (bigquery). */
|
|
45
|
+
postParse?: (tree: ParserRuleContext) => SyntaxDiagnostic[];
|
|
46
|
+
}
|
|
47
|
+
/** `item (sep item)*` driven from outside the grammar, for dialects without a list rule
|
|
48
|
+
* (a CTE list without its WITH, a select list). The tree is the FIRST item's. */
|
|
49
|
+
export declare function separatedList<P extends Parser>(item: FragmentEntry<P>, separator: number): FragmentEntry<P>;
|
|
50
|
+
/** Bind a dialect's lexer, parser and fragment entry rules into a `FragmentGrammar`. */
|
|
51
|
+
export declare function defineFragmentGrammar<P extends Parser>(spec: FragmentGrammarSpec<P>): FragmentGrammar;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Fragment grammar: the per-dialect entry set for parsing an SQL FRAGMENT — a
|
|
3
|
+
// templated macro body that is not a statement (an expression, a FROM-slot
|
|
4
|
+
// source, a CTE list pasted after WITH, a select list). Each dialect's parse.ts
|
|
5
|
+
// binds its lexer, parser and the five EOF-anchored `*_fragment` rules through
|
|
6
|
+
// `defineFragmentGrammar`; src/fragment.ts holds the registry and the driver.
|
|
7
|
+
//
|
|
8
|
+
// The parse runs over an already-lexed token slice (antlr4ng's ListTokenSource),
|
|
9
|
+
// never over a re-lexed substring, so every token, span and diagnostic stays in
|
|
10
|
+
// the coordinates of the text the tokens were lexed from.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
import { BailErrorStrategy, CharStream, CommonTokenStream, ListTokenSource, PredictionMode, Token as AntlrToken, } from "antlr4ng";
|
|
13
|
+
import { makeErrorCollector } from "./parse-diagnostics.js";
|
|
14
|
+
/** Every kind, in the order a macro body is tried: the most common reading first. */
|
|
15
|
+
export const FRAGMENT_KINDS = [
|
|
16
|
+
"statement",
|
|
17
|
+
"expression",
|
|
18
|
+
"tableSource",
|
|
19
|
+
"cteList",
|
|
20
|
+
"selectList",
|
|
21
|
+
];
|
|
22
|
+
const LIST_KINDS = new Set(["cteList", "selectList"]);
|
|
23
|
+
/** `item (sep item)*` driven from outside the grammar, for dialects without a list rule
|
|
24
|
+
* (a CTE list without its WITH, a select list). The tree is the FIRST item's. */
|
|
25
|
+
export function separatedList(item, separator) {
|
|
26
|
+
return (parser) => {
|
|
27
|
+
const first = item(parser);
|
|
28
|
+
// A separator followed by EOF is a trailing one (`run` consumes it), not another item.
|
|
29
|
+
while (parser.inputStream.LA(1) === separator && parser.inputStream.LA(2) !== AntlrToken.EOF) {
|
|
30
|
+
parser.inputStream.consume(); // between rules there is no context to attach the separator to
|
|
31
|
+
item(parser);
|
|
32
|
+
}
|
|
33
|
+
return first;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** The EOF anchor: the entry returned but input remains — the same diagnostic shape ANTLR
|
|
37
|
+
* emits for an EOF-anchored rule, positioned at the leftover token. */
|
|
38
|
+
function trailingInput(parser) {
|
|
39
|
+
const tok = parser.inputStream.LT(1);
|
|
40
|
+
if (!tok || tok.type === AntlrToken.EOF)
|
|
41
|
+
return undefined;
|
|
42
|
+
const text = tok.text ?? "";
|
|
43
|
+
return {
|
|
44
|
+
message: `extraneous input '${text}' expecting <EOF>`,
|
|
45
|
+
line: tok.line,
|
|
46
|
+
column: tok.column,
|
|
47
|
+
offset: tok.start,
|
|
48
|
+
length: text.length || 1,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Bind a dialect's lexer, parser and fragment entry rules into a `FragmentGrammar`. */
|
|
52
|
+
export function defineFragmentGrammar(spec) {
|
|
53
|
+
const { newLexer, newParser, entries, separator, postParse } = spec;
|
|
54
|
+
/** The entry, then a trailing separator on a list body is consumed rather than left over. */
|
|
55
|
+
const run = (parser, kind) => {
|
|
56
|
+
const tree = entries[kind](parser);
|
|
57
|
+
const input = parser.inputStream;
|
|
58
|
+
if (LIST_KINDS.has(kind) && input.LA(1) === separator && input.LA(2) === AntlrToken.EOF)
|
|
59
|
+
input.consume();
|
|
60
|
+
return tree;
|
|
61
|
+
};
|
|
62
|
+
const lex = spec.lex ??
|
|
63
|
+
((text) => {
|
|
64
|
+
const lexer = newLexer(CharStream.fromString(text));
|
|
65
|
+
lexer.removeErrorListeners();
|
|
66
|
+
return { tokens: lexer.getAllTokens(), diagnostics: [] };
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
lex,
|
|
70
|
+
parse(slice, kind, bail) {
|
|
71
|
+
const tokens = new CommonTokenStream(new ListTokenSource([...slice]));
|
|
72
|
+
const parser = newParser(tokens);
|
|
73
|
+
const collector = makeErrorCollector();
|
|
74
|
+
parser.removeErrorListeners();
|
|
75
|
+
parser.addErrorListener(collector.listener);
|
|
76
|
+
const sim = parser.interpreter;
|
|
77
|
+
if (bail) {
|
|
78
|
+
parser.errorHandler = new BailErrorStrategy();
|
|
79
|
+
sim.predictionMode = PredictionMode.SLL;
|
|
80
|
+
let tree;
|
|
81
|
+
try {
|
|
82
|
+
tree = run(parser, kind);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
// A grammar action can report through the listener without throwing (bigquery's
|
|
88
|
+
// join-balance check); that is not a clean parse either. Nor is leftover input.
|
|
89
|
+
if (collector.diagnostics.length > 0 || trailingInput(parser))
|
|
90
|
+
return undefined;
|
|
91
|
+
const post = postParse?.(tree) ?? [];
|
|
92
|
+
return post.length === 0 ? { tree, diagnostics: [] } : undefined;
|
|
93
|
+
}
|
|
94
|
+
sim.predictionMode = PredictionMode.LL;
|
|
95
|
+
const tree = run(parser, kind);
|
|
96
|
+
const trailing = trailingInput(parser);
|
|
97
|
+
return {
|
|
98
|
+
tree,
|
|
99
|
+
diagnostics: [...collector.diagnostics, ...(trailing ? [trailing] : []), ...(postParse?.(tree) ?? [])],
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ParserRuleContext } from "antlr4ng";
|
|
2
|
+
import type { Dialect } from "./dialect.js";
|
|
3
|
+
import { type FragmentKind } from "./fragment-grammar.js";
|
|
4
|
+
import type { SyntaxDiagnostic } from "./parse-diagnostics.js";
|
|
5
|
+
export type { FragmentKind } from "./fragment-grammar.js";
|
|
6
|
+
/** A half-open [start, end) offset range of the lexed text (a `PartSpan` fits). */
|
|
7
|
+
export interface FragmentRange {
|
|
8
|
+
start: number;
|
|
9
|
+
end: number;
|
|
10
|
+
}
|
|
11
|
+
export interface FragmentResult {
|
|
12
|
+
/** The kind the ranges parsed as; when `clean` is false, the kind that got furthest. */
|
|
13
|
+
kind: FragmentKind;
|
|
14
|
+
/** True when the ranges parsed with zero syntax errors as `kind`. */
|
|
15
|
+
clean: boolean;
|
|
16
|
+
tree: ParserRuleContext;
|
|
17
|
+
/** Positioned in the text's coordinates. Empty when clean. */
|
|
18
|
+
diagnostics: SyntaxDiagnostic[];
|
|
19
|
+
}
|
|
20
|
+
export interface FragmentSession {
|
|
21
|
+
/**
|
|
22
|
+
* Parse the tokens inside `ranges` (a token counts when it starts inside one) as a fragment.
|
|
23
|
+
* Returns undefined when the ranges hold no default-channel token (nothing to parse; no
|
|
24
|
+
* verdict). Never throws: every attempt runs a recovering parser over a fixed kind list.
|
|
25
|
+
*/
|
|
26
|
+
parse(ranges: readonly FragmentRange[], kinds?: readonly FragmentKind[]): FragmentResult | undefined;
|
|
27
|
+
/** The first kind the ranges parse clean as, or undefined (no token, or no clean reading).
|
|
28
|
+
* Probes only (SLL + bail); never a diagnostic. */
|
|
29
|
+
verdict(ranges: readonly FragmentRange[], kinds?: readonly FragmentKind[]): FragmentKind | undefined;
|
|
30
|
+
}
|
|
31
|
+
/** Lex `text` once with `dialect`'s statement-entry token pipeline; parse ranges of it after. */
|
|
32
|
+
export declare function openFragments(text: string, dialect: Dialect): FragmentSession;
|
package/dist/fragment.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Fragment parsing: parse RANGES of a text as an SQL fragment, trying each
|
|
3
|
+
// FragmentKind in order until one parses clean. The consumer is the minijinja
|
|
4
|
+
// front end (a `{% macro %}` body is whatever gets pasted at the call site: a
|
|
5
|
+
// statement, an expression, a FROM-slot source, a CTE list, a select list); the
|
|
6
|
+
// statement entry is right for a model file and wrong for most macro bodies.
|
|
7
|
+
//
|
|
8
|
+
// The text is lexed ONCE (`openFragments`) and every parse cuts its token slice
|
|
9
|
+
// from that lex, so the tokens (and every diagnostic they position) stay in the
|
|
10
|
+
// text's own coordinates: no remap. A fast SLL+bail probe per kind finds the
|
|
11
|
+
// clean reading; when none is clean the kinds are re-run with recovery and the
|
|
12
|
+
// reading that got furthest before its first error supplies the diagnostics —
|
|
13
|
+
// a heuristic for the broken-input case only (the reading closest to what was
|
|
14
|
+
// meant), never a claim: `clean` is false and the region carries no verdict.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
import { FRAGMENT_KINDS } from "./fragment-grammar.js";
|
|
17
|
+
import { fragmentGrammar as bigquery } from "./bigquery/parse.js";
|
|
18
|
+
import { fragmentGrammar as databricks } from "./databricks/parse.js";
|
|
19
|
+
import { fragmentGrammar as duckdb } from "./duckdb/parse.js";
|
|
20
|
+
import { fragmentGrammar as mysql } from "./mysql/parse.js";
|
|
21
|
+
import { fragmentGrammar as postgres } from "./postgres/parse.js";
|
|
22
|
+
import { fragmentGrammar as redshift } from "./redshift/parse.js";
|
|
23
|
+
import { fragmentGrammar as snowflake } from "./snowflake/parse.js";
|
|
24
|
+
import { fragmentGrammar as sqlite } from "./sqlite/parse.js";
|
|
25
|
+
import { fragmentGrammar as trino } from "./trino/parse.js";
|
|
26
|
+
import { fragmentGrammar as tsql } from "./tsql/parse.js";
|
|
27
|
+
const FRAGMENT_GRAMMARS = {
|
|
28
|
+
databricks,
|
|
29
|
+
tsql,
|
|
30
|
+
snowflake,
|
|
31
|
+
bigquery,
|
|
32
|
+
redshift,
|
|
33
|
+
postgres,
|
|
34
|
+
duckdb,
|
|
35
|
+
trino,
|
|
36
|
+
sqlite,
|
|
37
|
+
mysql,
|
|
38
|
+
};
|
|
39
|
+
/** Lex `text` once with `dialect`'s statement-entry token pipeline; parse ranges of it after. */
|
|
40
|
+
export function openFragments(text, dialect) {
|
|
41
|
+
const grammar = FRAGMENT_GRAMMARS[dialect];
|
|
42
|
+
const lexed = grammar.lex(text);
|
|
43
|
+
const inRanges = (offset, ranges) => ranges.some((r) => offset >= r.start && offset < r.end);
|
|
44
|
+
const sliceOf = (ranges) => {
|
|
45
|
+
const slice = lexed.tokens.filter((t) => inRanges(t.start, ranges));
|
|
46
|
+
return slice.some((t) => t.channel === 0) ? slice : undefined;
|
|
47
|
+
};
|
|
48
|
+
// Token-derived diagnostics of a slice (bigquery literal escapes) hold under every reading.
|
|
49
|
+
const lexDiagsOf = (ranges) => lexed.diagnostics.filter((d) => d.offset !== undefined && inRanges(d.offset, ranges));
|
|
50
|
+
const probe = (slice, kinds) => {
|
|
51
|
+
for (const kind of kinds) {
|
|
52
|
+
const clean = grammar.parse(slice, kind, true);
|
|
53
|
+
if (clean)
|
|
54
|
+
return { kind, clean: true, tree: clean.tree, diagnostics: [] };
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
verdict(ranges, kinds = FRAGMENT_KINDS) {
|
|
60
|
+
const slice = sliceOf(ranges);
|
|
61
|
+
if (!slice || lexDiagsOf(ranges).length > 0)
|
|
62
|
+
return undefined;
|
|
63
|
+
return probe(slice, kinds)?.kind;
|
|
64
|
+
},
|
|
65
|
+
parse(ranges, kinds = FRAGMENT_KINDS) {
|
|
66
|
+
const slice = sliceOf(ranges);
|
|
67
|
+
if (!slice)
|
|
68
|
+
return undefined;
|
|
69
|
+
const lexDiags = lexDiagsOf(ranges);
|
|
70
|
+
if (lexDiags.length === 0) {
|
|
71
|
+
const clean = probe(slice, kinds);
|
|
72
|
+
if (clean)
|
|
73
|
+
return clean;
|
|
74
|
+
}
|
|
75
|
+
let best;
|
|
76
|
+
let bestAt = -1;
|
|
77
|
+
for (const kind of kinds) {
|
|
78
|
+
const attempt = grammar.parse(slice, kind, false);
|
|
79
|
+
if (!attempt)
|
|
80
|
+
continue;
|
|
81
|
+
const diagnostics = [...lexDiags, ...attempt.diagnostics];
|
|
82
|
+
if (diagnostics.length === 0)
|
|
83
|
+
return { kind, clean: true, tree: attempt.tree, diagnostics };
|
|
84
|
+
const at = attempt.diagnostics[0]?.offset ?? Number.MAX_SAFE_INTEGER;
|
|
85
|
+
if (at > bestAt) {
|
|
86
|
+
bestAt = at;
|
|
87
|
+
best = { kind, clean: false, tree: attempt.tree, diagnostics };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return best;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { minijinja } from "./engine.js";
|
|
2
|
-
export { parseTemplated, tokenizeTemplated } from "./parse.js";
|
|
2
|
+
export { parseTemplated, tokenizeTemplated, shapesForCall } from "./parse.js";
|
|
3
|
+
export type { MacroShape } from "../template/engine.js";
|
|
3
4
|
export type { TemplatedParseResult, TemplatedParseOptions } from "../template/engine.js";
|
|
4
5
|
export type { TagNode, MacroCall } from "./parse.js";
|
|
5
6
|
export { templateRegions, templateSymbols } from "./regions.js";
|
package/dist/minijinja/index.js
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
// canonically declared in src/template/engine.ts and re-exported both there
|
|
4
4
|
// and here.
|
|
5
5
|
export { minijinja } from "./engine.js";
|
|
6
|
-
export { parseTemplated, tokenizeTemplated } from "./parse.js";
|
|
6
|
+
export { parseTemplated, tokenizeTemplated, shapesForCall } from "./parse.js";
|
|
7
7
|
export { templateRegions, templateSymbols } from "./regions.js";
|
|
8
8
|
export { templateVariants } from "./variants.js";
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import type { Dialect } from "../dialect.js";
|
|
2
2
|
import type { Token } from "../token/token.js";
|
|
3
|
-
import type { TemplatedParseOptions, TemplatedParseResult } from "../template/engine.js";
|
|
3
|
+
import type { MacroShape, TemplatedParseOptions, TemplatedParseResult } from "../template/engine.js";
|
|
4
|
+
import type { ExpansionShape } from "../qualify/template-provider.js";
|
|
5
|
+
import type { TemplateCall } from "../ir/ir.js";
|
|
4
6
|
export type { TagNode, MacroCall } from "./tag-ast.js";
|
|
5
7
|
export type { TemplateRegion, TemplateArm, TemplateSymbol } from "./regions.js";
|
|
6
8
|
export { templateRegions, templateSymbols } from "./regions.js";
|
|
7
|
-
export type { TemplatedParseOptions, TemplatedParseResult } from "../template/engine.js";
|
|
9
|
+
export type { MacroShape, TemplatedParseOptions, TemplatedParseResult } from "../template/engine.js";
|
|
10
|
+
/**
|
|
11
|
+
* The shapes a specific CALL of a macro takes: `macro.shapes`, with the keyword-parameter hole
|
|
12
|
+
* (if any) resolved from the call's own literal argument (positional or keyword) or the
|
|
13
|
+
* parameter's default. A call whose keyword is not a literal, or names a word that opens no
|
|
14
|
+
* known clause, resolves to nothing for that hole (never-wrong). Pure: definition text + call
|
|
15
|
+
* text, no project knowledge; a host's `shapeOf(call)` is `shapesForCall(index.get(call.name), call)`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function shapesForCall(macro: MacroShape, call: TemplateCall): ExpansionShape[];
|
|
8
18
|
/**
|
|
9
19
|
* Parse raw jinja-SQL: one whole-document jinja lex (segment()), the untouched
|
|
10
20
|
* per-dialect SQL parse over the resulting placeholder, a per-tag jinja parse over
|