@lokascript/framework 4.6.0 → 4.8.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/CHANGELOG.md +121 -1
- package/dist/api/create-dsl.d.ts +18 -2
- package/dist/api/create-dsl.d.ts.map +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1799 -713
- package/dist/api/index.js.map +1 -1
- package/dist/api/translation-loss.d.ts +84 -0
- package/dist/api/translation-loss.d.ts.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1057 -684
- package/dist/core/index.js.map +1 -1
- package/dist/core/matching/confidence-model.d.ts +54 -0
- package/dist/core/matching/confidence-model.d.ts.map +1 -0
- package/dist/core/matching/index.d.ts +12 -0
- package/dist/core/matching/index.d.ts.map +1 -0
- package/dist/core/matching/match-skeleton.d.ts +114 -0
- package/dist/core/matching/match-skeleton.d.ts.map +1 -0
- package/dist/core/matching/stock-confidence.d.ts +15 -0
- package/dist/core/matching/stock-confidence.d.ts.map +1 -0
- package/dist/core/matching/stock-reader.d.ts +63 -0
- package/dist/core/matching/stock-reader.d.ts.map +1 -0
- package/dist/core/matching/stock-reading.d.ts +44 -0
- package/dist/core/matching/stock-reading.d.ts.map +1 -0
- package/dist/core/matching/value-reader.d.ts +62 -0
- package/dist/core/matching/value-reader.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.js +561 -660
- package/dist/core/pattern-matching/index.js.map +1 -1
- package/dist/core/pattern-matching/pattern-matcher.d.ts +0 -117
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
- package/dist/core/types.d.ts +53 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/generation/index.d.ts +2 -0
- package/dist/generation/index.d.ts.map +1 -1
- package/dist/generation/index.js +593 -1
- package/dist/generation/index.js.map +1 -1
- package/dist/generation/pattern-render.d.ts +72 -0
- package/dist/generation/pattern-render.d.ts.map +1 -0
- package/dist/generation/schema-patterns.d.ts +152 -0
- package/dist/generation/schema-patterns.d.ts.map +1 -0
- package/dist/index.cjs +2016 -904
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1998 -903
- package/dist/index.js.map +1 -1
- package/dist/ir/index.js.map +1 -1
- package/dist/parsing/index.js +561 -660
- package/dist/parsing/index.js.map +1 -1
- package/dist/testing/gate/index.d.ts +8 -0
- package/dist/testing/gate/index.d.ts.map +1 -0
- package/dist/testing/gate/index.js +664 -0
- package/dist/testing/gate/index.js.map +1 -0
- package/dist/testing/gate/lint/format.d.ts +7 -0
- package/dist/testing/gate/lint/format.d.ts.map +1 -0
- package/dist/testing/gate/lint/index.d.ts +30 -0
- package/dist/testing/gate/lint/index.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/extractor-coverage.d.ts +18 -0
- package/dist/testing/gate/lint/rules/extractor-coverage.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/index.d.ts +14 -0
- package/dist/testing/gate/lint/rules/index.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/keyword-classification.d.ts +19 -0
- package/dist/testing/gate/lint/rules/keyword-classification.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/keyword-coverage.d.ts +13 -0
- package/dist/testing/gate/lint/rules/keyword-coverage.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/marker-tokenization.d.ts +20 -0
- package/dist/testing/gate/lint/rules/marker-tokenization.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/position-ordering.d.ts +25 -0
- package/dist/testing/gate/lint/rules/position-ordering.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/renderer-coherence.d.ts +24 -0
- package/dist/testing/gate/lint/rules/renderer-coherence.d.ts.map +1 -0
- package/dist/testing/gate/lint/rules/schema-rules.d.ts +10 -0
- package/dist/testing/gate/lint/rules/schema-rules.d.ts.map +1 -0
- package/dist/testing/gate/lint/types.d.ts +74 -0
- package/dist/testing/gate/lint/types.d.ts.map +1 -0
- package/dist/testing/gate/round-trip.d.ts +184 -0
- package/dist/testing/gate/round-trip.d.ts.map +1 -0
- package/package.json +6 -2
- package/src/__test__/fixtures/sql-dsl.ts +8 -2
- package/src/api/create-dsl-engine.test.ts +150 -0
- package/src/api/create-dsl.test.ts +7 -0
- package/src/api/create-dsl.ts +173 -22
- package/src/api/index.ts +1 -0
- package/src/api/translation-loss.test.ts +89 -0
- package/src/api/translation-loss.ts +128 -0
- package/src/core/index.ts +1 -0
- package/src/core/matching/confidence-model.ts +58 -0
- package/src/core/matching/index.ts +12 -0
- package/src/core/matching/match-skeleton.test.ts +179 -0
- package/src/core/matching/match-skeleton.ts +621 -0
- package/src/core/matching/stock-confidence.ts +238 -0
- package/src/core/matching/stock-reader.test.ts +265 -0
- package/src/core/matching/stock-reader.ts +205 -0
- package/src/core/matching/stock-reading.ts +681 -0
- package/src/core/matching/value-reader.ts +85 -0
- package/src/core/pattern-matching/pattern-matcher.ts +11 -876
- package/src/core/types.ts +53 -0
- package/src/generation/index.ts +2 -0
- package/src/generation/pattern-render.ts +378 -0
- package/src/generation/schema-patterns.test.ts +139 -0
- package/src/generation/schema-patterns.ts +974 -0
- package/src/testing/gate/index.ts +8 -0
- package/src/testing/gate/lint/format.ts +23 -0
- package/src/testing/gate/lint/index.ts +73 -0
- package/src/testing/gate/lint/lint.test.ts +131 -0
- package/src/testing/gate/lint/rules/extractor-coverage.ts +63 -0
- package/src/testing/gate/lint/rules/index.ts +37 -0
- package/src/testing/gate/lint/rules/keyword-classification.ts +52 -0
- package/src/testing/gate/lint/rules/keyword-coverage.ts +38 -0
- package/src/testing/gate/lint/rules/marker-tokenization.ts +129 -0
- package/src/testing/gate/lint/rules/position-ordering.ts +87 -0
- package/src/testing/gate/lint/rules/renderer-coherence.ts +100 -0
- package/src/testing/gate/lint/rules/schema-rules.ts +35 -0
- package/src/testing/gate/lint/types.ts +83 -0
- package/src/testing/gate/round-trip.test.ts +208 -0
- package/src/testing/gate/round-trip.ts +503 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The round-trip gate kit (domains rethink Phase 4.6, `docs-internal/DOMAINS_RETHINK.md`): every
|
|
3
|
+
* command of a grammar, written in each of its languages by its own renderer and read back by its own
|
|
4
|
+
* parser. Moved from lokascript-domains' `packages/domains/gate/round-trip.ts` (the domain gate, Phase
|
|
5
|
+
* 2), so any grammar on the framework runs the same gate; the domains repo keeps its fixtures
|
|
6
|
+
* (`gate/values.ts`), baselines and regen tool.
|
|
7
|
+
*
|
|
8
|
+
* A case is a command (bracket syntax, parsed in English) and a language:
|
|
9
|
+
*
|
|
10
|
+
* - **round trip**: `render(ref, lang)`, then `parse`. `pass` when the parse is the reference node, or,
|
|
11
|
+
* for a grammar whose compiled output does not depend on the language, when the text compiles to what
|
|
12
|
+
* the command itself (its bracket form) compiles to (a parser may keep a translated word that the
|
|
13
|
+
* generator normalizes: bdd ja `#box0 が 表示 前提` is `visible`). English is a lane like the others.
|
|
14
|
+
* `refused` when the render is null or the parse throws: the caller is told. `silent` otherwise: it
|
|
15
|
+
* reads, or compiles to, something else.
|
|
16
|
+
* - **leftover**: the same render plus one unknown word. `refused` is the expected outcome: the parse
|
|
17
|
+
* throws, or the input does not compile. `pass` where the word is READ: the parse is the text's own
|
|
18
|
+
* with the word in one role (an open-text role extended by it, `where active zzqx`, or a role the
|
|
19
|
+
* text left empty, `list zzqx`), and the word is in the code (for a command that never compiles on
|
|
20
|
+
* its own, `englishDoesNotCompile`, the parse alone). `silent` otherwise: the input is accepted and
|
|
21
|
+
* read as something else (`when clicks zzqx`: actor `clicks`, action `zzqx`), or the word goes
|
|
22
|
+
* nowhere (voice `back zzqx` compiles as `back`).
|
|
23
|
+
*
|
|
24
|
+
* The outcomes are the command-shape gate's (hyperfixi `testing-framework`). A baseline lists every
|
|
25
|
+
* case that is not at its best outcome; `compare` reports a case worse than listed and one better than
|
|
26
|
+
* listed, so a gate built on it only shrinks.
|
|
27
|
+
*/
|
|
28
|
+
export type Outcome = 'pass' | 'refused' | 'silent';
|
|
29
|
+
export type CaseKind = 'roundTrip' | 'leftover';
|
|
30
|
+
/** Best first. A round trip's best is `pass`; leftover input's is `refused`. */
|
|
31
|
+
export declare const RANK: Record<CaseKind, Record<Outcome, number>>;
|
|
32
|
+
export declare const BEST: Record<CaseKind, Outcome>;
|
|
33
|
+
/** Appended for the leftover cases: a word no grammar's vocabulary has. */
|
|
34
|
+
export declare const UNKNOWN_WORD = "zzqx";
|
|
35
|
+
export interface CaseResult {
|
|
36
|
+
/** The grammar (a domain's name). */
|
|
37
|
+
grammar: string;
|
|
38
|
+
kind: CaseKind;
|
|
39
|
+
/** The command, in bracket syntax (read in English). */
|
|
40
|
+
source: string;
|
|
41
|
+
lang: string;
|
|
42
|
+
outcome: Outcome;
|
|
43
|
+
/** What the renderer wrote (plus the unknown word, for leftover). */
|
|
44
|
+
text?: string;
|
|
45
|
+
/** Why it is not `pass`: the error, or what the parse read instead. */
|
|
46
|
+
why?: string;
|
|
47
|
+
/** The round trip's parse, summarized (`summarize`); absent when it was refused. */
|
|
48
|
+
read?: string;
|
|
49
|
+
}
|
|
50
|
+
/** What the gate reads of a role in a schema. */
|
|
51
|
+
export interface GateRoleSpec {
|
|
52
|
+
readonly role: string;
|
|
53
|
+
readonly required?: boolean;
|
|
54
|
+
readonly expectedTypes?: readonly string[];
|
|
55
|
+
}
|
|
56
|
+
/** What the gate reads of a command schema. */
|
|
57
|
+
export interface GateSchema {
|
|
58
|
+
readonly action: string;
|
|
59
|
+
readonly roles: readonly GateRoleSpec[];
|
|
60
|
+
}
|
|
61
|
+
/** What the gate reads of a parse. */
|
|
62
|
+
export interface GateNode {
|
|
63
|
+
readonly action: string;
|
|
64
|
+
readonly roles: ReadonlyMap<string, unknown> | Readonly<Record<string, unknown>>;
|
|
65
|
+
}
|
|
66
|
+
/** The grammar's DSL, as the gate drives it. */
|
|
67
|
+
export interface GateDSL {
|
|
68
|
+
parse(input: string, language: string): GateNode;
|
|
69
|
+
compile(input: string, language: string): {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
code?: string;
|
|
72
|
+
errors?: string[];
|
|
73
|
+
};
|
|
74
|
+
render?(node: GateNode, language: string): string | null;
|
|
75
|
+
getSupportedLanguages(): string[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The values the gate writes into each role. A role not listed gets a plain name no vocabulary uses
|
|
79
|
+
* (`v0x`, or `#box0` where only a selector fits): its value must come back verbatim. A role that takes
|
|
80
|
+
* FIXED words lists every word the grammar's renderer and generator know, in English: the gate writes
|
|
81
|
+
* the command once per word, in every language, so a word one of the two tables lacks fails in the
|
|
82
|
+
* languages where it is missing.
|
|
83
|
+
*/
|
|
84
|
+
export interface GateFixture {
|
|
85
|
+
/**
|
|
86
|
+
* `compile`: a round trip also passes when the text compiles to the command's own code (the grammar
|
|
87
|
+
* compiles to something that does not depend on the language). `node`: only the parse counts (a
|
|
88
|
+
* grammar that compiles to a sentence in each language).
|
|
89
|
+
*/
|
|
90
|
+
oracle: 'compile' | 'node';
|
|
91
|
+
/** Keys are `<action>.<role>` or `<role>` (every action). The first value is the one the other cases use. */
|
|
92
|
+
values: Record<string, string[]>;
|
|
93
|
+
/**
|
|
94
|
+
* Commands (bracket syntax) written besides the generated ones, where one role's value depends on
|
|
95
|
+
* another's and no single list fits both: behaviorspec's `[given subject:#box value:visible]` beside
|
|
96
|
+
* `[given subject:viewport value:1024x768]`.
|
|
97
|
+
*/
|
|
98
|
+
commands?: string[];
|
|
99
|
+
/**
|
|
100
|
+
* Actions that do not compile on their own (a gate may check every other one does). Leftover input
|
|
101
|
+
* in such a command is judged by its parse alone.
|
|
102
|
+
*/
|
|
103
|
+
englishDoesNotCompile?: string[];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The commands a grammar is checked on: for each schema, its required roles alone and all its roles
|
|
107
|
+
* (each with its first value), then each further value of a role that takes fixed words, alone.
|
|
108
|
+
*/
|
|
109
|
+
export declare function commandsFor(schemas: readonly GateSchema[], fixture: GateFixture): string[];
|
|
110
|
+
/** A parse as one line: `action role:type=value …`, roles sorted. What the parse snapshot records. */
|
|
111
|
+
export declare function summarize(node: GateNode): string;
|
|
112
|
+
/**
|
|
113
|
+
* Whether `read`, the parse of a text plus `word`, is `back`, the text's own parse, with the word read
|
|
114
|
+
* into it: the same action and roles, except one role, whose value is its value in `back` followed by
|
|
115
|
+
* the word (an open-text role at the end: sql `where active zzqx`) or, where `back` has no such role,
|
|
116
|
+
* the word itself (todo `list zzqx`).
|
|
117
|
+
*/
|
|
118
|
+
export declare function readsWord(back: GateNode, read: GateNode, word: string): boolean;
|
|
119
|
+
/** What a command (bracket syntax) compiles to; null when it does not compile on its own. */
|
|
120
|
+
export declare function referenceCode(dsl: GateDSL, source: string): string | null;
|
|
121
|
+
/** Run every case of one grammar: each command of `commandsFor` in each of the DSL's languages. */
|
|
122
|
+
export declare function runRoundTrip(grammar: string, dsl: GateDSL, schemas: readonly GateSchema[], fixture: GateFixture): CaseResult[];
|
|
123
|
+
export interface ListedCase {
|
|
124
|
+
outcome: Exclude<Outcome, 'pass'>;
|
|
125
|
+
text?: string;
|
|
126
|
+
why?: string;
|
|
127
|
+
}
|
|
128
|
+
export interface GateBaseline {
|
|
129
|
+
/** The upstream packages this was measured on (the grammar's lockfile). */
|
|
130
|
+
upstream: Record<string, string>;
|
|
131
|
+
/** Per grammar and kind: cases and outcomes. Written by the regen tool for the reader; not checked. */
|
|
132
|
+
counts: Record<string, Record<CaseKind, Record<Outcome | 'cases', number>>>;
|
|
133
|
+
/** Per grammar, command and language: every round trip that is not `pass`. */
|
|
134
|
+
roundTrip: Record<string, Record<string, Record<string, ListedCase>>>;
|
|
135
|
+
/** Per grammar and command: the languages whose leftover case was accepted (`silent`). */
|
|
136
|
+
leftover: Record<string, Record<string, string[]>>;
|
|
137
|
+
}
|
|
138
|
+
export interface DriftCase {
|
|
139
|
+
kind: CaseKind;
|
|
140
|
+
source: string;
|
|
141
|
+
lang: string;
|
|
142
|
+
listed: Outcome;
|
|
143
|
+
now: Outcome;
|
|
144
|
+
text?: string;
|
|
145
|
+
why?: string;
|
|
146
|
+
}
|
|
147
|
+
export interface Drift {
|
|
148
|
+
worse: DriftCase[];
|
|
149
|
+
better: DriftCase[];
|
|
150
|
+
/** Listed cases that are no longer cases at all. */
|
|
151
|
+
stale: {
|
|
152
|
+
kind: CaseKind;
|
|
153
|
+
source: string;
|
|
154
|
+
lang: string;
|
|
155
|
+
}[];
|
|
156
|
+
}
|
|
157
|
+
/** How one grammar's results moved against the baseline. */
|
|
158
|
+
export declare function compare(grammar: string, results: CaseResult[], baseline: GateBaseline): Drift;
|
|
159
|
+
/** The baseline entries for one grammar's results. */
|
|
160
|
+
export declare function listingOf(results: CaseResult[]): {
|
|
161
|
+
roundTrip: Record<string, Record<string, ListedCase>>;
|
|
162
|
+
leftover: Record<string, string[]>;
|
|
163
|
+
};
|
|
164
|
+
export declare function countsOf(results: CaseResult[]): Record<CaseKind, Record<Outcome | 'cases', number>>;
|
|
165
|
+
/**
|
|
166
|
+
* Every round trip's parse, so a change that moves a parse shows it, whether or not an outcome moved
|
|
167
|
+
* (a value's type, a role a passing case gained, what a silent case reads now). `refused` where the
|
|
168
|
+
* parse threw. Leftover cases are not in it: their outcome is the baseline's.
|
|
169
|
+
*/
|
|
170
|
+
export interface ParseSnapshot {
|
|
171
|
+
upstream: Record<string, string>;
|
|
172
|
+
/** Per grammar, command and language. */
|
|
173
|
+
parses: Record<string, Record<string, Record<string, string>>>;
|
|
174
|
+
}
|
|
175
|
+
export declare function snapshotOf(results: CaseResult[]): Record<string, Record<string, string>>;
|
|
176
|
+
export interface SnapshotChange {
|
|
177
|
+
source: string;
|
|
178
|
+
lang: string;
|
|
179
|
+
was?: string;
|
|
180
|
+
now?: string;
|
|
181
|
+
}
|
|
182
|
+
/** Every round trip whose parse differs from the snapshot's, for one grammar. */
|
|
183
|
+
export declare function snapshotChanges(grammar: string, results: CaseResult[], snapshot: ParseSnapshot): SnapshotChange[];
|
|
184
|
+
//# sourceMappingURL=round-trip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round-trip.d.ts","sourceRoot":"","sources":["../../../src/testing/gate/round-trip.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lokascript/framework",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "Generic framework for building multilingual DSLs with semantic parsing and grammar transformation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
"types": "./dist/testing/index.d.ts",
|
|
57
57
|
"import": "./dist/testing/index.js"
|
|
58
58
|
},
|
|
59
|
+
"./testing/gate": {
|
|
60
|
+
"types": "./dist/testing/gate/index.d.ts",
|
|
61
|
+
"import": "./dist/testing/gate/index.js"
|
|
62
|
+
},
|
|
59
63
|
"./ir": {
|
|
60
64
|
"types": "./dist/ir/index.d.ts",
|
|
61
65
|
"import": "./dist/ir/index.js"
|
|
@@ -96,7 +100,7 @@
|
|
|
96
100
|
"author": "LokaScript Contributors",
|
|
97
101
|
"license": "MIT",
|
|
98
102
|
"dependencies": {
|
|
99
|
-
"@lokascript/intent": "^4.
|
|
103
|
+
"@lokascript/intent": "^4.8.0"
|
|
100
104
|
},
|
|
101
105
|
"devDependencies": {
|
|
102
106
|
"@lokascript/domains": "^4.0.0",
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getDefaultExtractors,
|
|
14
14
|
type SemanticNode,
|
|
15
15
|
type MultilingualDSL,
|
|
16
|
+
type DSLConfig,
|
|
16
17
|
} from '../../index';
|
|
17
18
|
|
|
18
19
|
// =============================================================================
|
|
@@ -168,7 +169,12 @@ function generateSQL(node: SemanticNode): string {
|
|
|
168
169
|
// =============================================================================
|
|
169
170
|
|
|
170
171
|
export function createSQLDSL(): MultilingualDSL {
|
|
171
|
-
return createMultilingualDSL(
|
|
172
|
+
return createMultilingualDSL(sqlDSLConfig());
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** The fixture's config, for a test that builds the DSL with another factory. */
|
|
176
|
+
export function sqlDSLConfig(): DSLConfig {
|
|
177
|
+
return {
|
|
172
178
|
name: 'SQL DSL Test Fixture',
|
|
173
179
|
schemas: [selectCommand],
|
|
174
180
|
languages: [
|
|
@@ -190,7 +196,7 @@ export function createSQLDSL(): MultilingualDSL {
|
|
|
190
196
|
codeGenerator: {
|
|
191
197
|
generate: generateSQL,
|
|
192
198
|
},
|
|
193
|
-
}
|
|
199
|
+
};
|
|
194
200
|
}
|
|
195
201
|
|
|
196
202
|
// =============================================================================
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `engine: 'core'` (domains rethink Phase 4.7): the same config and DSL as the
|
|
3
|
+
* classic engine, read by the core. framework's SQL fixture reads, compiles and
|
|
4
|
+
* translates as it does on the classic engine, refuses what it does not read,
|
|
5
|
+
* and renders through its own patterns where no renderer is configured. A role
|
|
6
|
+
* with a word list reads only its words.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import { createMultilingualDSL, UnreadInputError, type DSLConfig } from './create-dsl';
|
|
10
|
+
import { defineCommand, defineRole } from '../schema';
|
|
11
|
+
import { BaseTokenizer, getDefaultExtractors } from '../index';
|
|
12
|
+
import { sqlDSLConfig, testQueries, expectedSQL } from '../__test__/fixtures/sql-dsl';
|
|
13
|
+
import type { SemanticNode } from '../core/types';
|
|
14
|
+
import { generatePattern } from '../generation/pattern-generator';
|
|
15
|
+
import { createSchemaPatternGenerator } from '../generation/schema-patterns';
|
|
16
|
+
|
|
17
|
+
// Pass-through spies. On the SQL fixture the two engines read, compile and render alike, so
|
|
18
|
+
// these are what show which engine a DSL was built on.
|
|
19
|
+
vi.mock('../generation/pattern-generator', async importOriginal => {
|
|
20
|
+
const actual = await importOriginal<typeof import('../generation/pattern-generator')>();
|
|
21
|
+
return { ...actual, generatePattern: vi.fn(actual.generatePattern) };
|
|
22
|
+
});
|
|
23
|
+
vi.mock('../generation/schema-patterns', async importOriginal => {
|
|
24
|
+
const actual = await importOriginal<typeof import('../generation/schema-patterns')>();
|
|
25
|
+
return { ...actual, createSchemaPatternGenerator: vi.fn(actual.createSchemaPatternGenerator) };
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const core = createMultilingualDSL({ ...sqlDSLConfig(), engine: 'core' });
|
|
29
|
+
const classic = createMultilingualDSL(sqlDSLConfig());
|
|
30
|
+
|
|
31
|
+
const reading = (node: SemanticNode) =>
|
|
32
|
+
[
|
|
33
|
+
node.action,
|
|
34
|
+
...[...node.roles].map(
|
|
35
|
+
([role, v]) =>
|
|
36
|
+
`${role}=${String((v as { value?: unknown; raw?: unknown }).value ?? (v as { raw?: unknown }).raw)}`
|
|
37
|
+
),
|
|
38
|
+
].join(' ');
|
|
39
|
+
|
|
40
|
+
describe("createMultilingualDSL with engine: 'core'", () => {
|
|
41
|
+
const queries = [
|
|
42
|
+
['en', testQueries.english.simple, expectedSQL.simple],
|
|
43
|
+
['en', testQueries.english.withWhere, expectedSQL.withWhere],
|
|
44
|
+
['es', testQueries.spanish.simple, 'SELECT nombre FROM usuarios'],
|
|
45
|
+
['es', testQueries.spanish.withWhere, 'SELECT nombre FROM usuarios WHERE edad > 18'],
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
it.each(queries)('reads and compiles %s `%s` as the classic engine does', (lang, input, sql) => {
|
|
49
|
+
expect(reading(core.parse(input, lang))).toBe(reading(classic.parse(input, lang)));
|
|
50
|
+
expect(core.compile(input, lang).code).toBe(sql);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it.each(queries)('translates %s `%s` as the classic engine does', (lang, input) => {
|
|
54
|
+
const to = lang === 'en' ? 'es' : 'en';
|
|
55
|
+
expect(core.translate(input, lang, to)).toBe(classic.translate(input, lang, to));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('refuses input it does not read', () => {
|
|
59
|
+
expect(() => core.parse('select name from users banana', 'en')).toThrow(UnreadInputError);
|
|
60
|
+
expect(core.validate('select name from users banana', 'en')).toMatchObject({
|
|
61
|
+
valid: false,
|
|
62
|
+
errorCode: 'unread-input',
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('renders through its patterns, in each language, what it reads back', () => {
|
|
67
|
+
const node = core.parse(testQueries.english.withWhere, 'en');
|
|
68
|
+
expect(core.render?.(node, 'en')).toBe('select name from users where age > 18');
|
|
69
|
+
expect(core.render?.(node, 'es')).toBe('seleccionar name de users donde age > 18');
|
|
70
|
+
expect(reading(core.parse(core.render!(node, 'es')!, 'es'))).toBe(reading(node));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it.each([
|
|
74
|
+
['core', { engine: 'core' }],
|
|
75
|
+
['classic', { engine: 'classic' }],
|
|
76
|
+
['classic', {}],
|
|
77
|
+
] as const)('builds on the %s engine with %o', (expected, option) => {
|
|
78
|
+
vi.mocked(generatePattern).mockClear();
|
|
79
|
+
vi.mocked(createSchemaPatternGenerator).mockClear();
|
|
80
|
+
createMultilingualDSL({ ...sqlDSLConfig(), ...option }).parse(testQueries.english.simple, 'en');
|
|
81
|
+
expect(vi.mocked(createSchemaPatternGenerator).mock.calls.length > 0).toBe(expected === 'core');
|
|
82
|
+
expect(vi.mocked(generatePattern).mock.calls.length > 0).toBe(expected === 'classic');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('throws on an engine it does not know, rather than reading with the classic one', () => {
|
|
86
|
+
const misspelled = { ...sqlDSLConfig(), engine: 'cor' } as unknown as DSLConfig;
|
|
87
|
+
expect(() => createMultilingualDSL(misspelled)).toThrow('Unknown DSL engine "cor"');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('a role with a word list, on the core', () => {
|
|
92
|
+
class WordTokenizer extends BaseTokenizer {
|
|
93
|
+
readonly language = 'en';
|
|
94
|
+
readonly direction = 'ltr' as const;
|
|
95
|
+
constructor() {
|
|
96
|
+
super();
|
|
97
|
+
this.registerExtractors(getDefaultExtractors());
|
|
98
|
+
}
|
|
99
|
+
classifyToken(token: string): 'keyword' | 'identifier' {
|
|
100
|
+
return token.toLowerCase() === 'when' ? 'keyword' : 'identifier';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// behaviorspec's `when [actor] action`: both are bare words.
|
|
104
|
+
const when = defineCommand({
|
|
105
|
+
action: 'when',
|
|
106
|
+
primaryRole: 'action',
|
|
107
|
+
roles: [
|
|
108
|
+
defineRole({ role: 'actor', required: false, expectedTypes: ['expression'], svoPosition: 2 }),
|
|
109
|
+
defineRole({
|
|
110
|
+
role: 'action',
|
|
111
|
+
required: true,
|
|
112
|
+
expectedTypes: ['expression'],
|
|
113
|
+
svoPosition: 1,
|
|
114
|
+
words: { '*': ['clicks', 'types'] },
|
|
115
|
+
}),
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
const dsl = createMultilingualDSL({
|
|
119
|
+
name: 'when',
|
|
120
|
+
schemas: [when],
|
|
121
|
+
languages: [
|
|
122
|
+
{
|
|
123
|
+
code: 'en',
|
|
124
|
+
name: 'English',
|
|
125
|
+
nativeName: 'English',
|
|
126
|
+
tokenizer: new WordTokenizer(),
|
|
127
|
+
patternProfile: {
|
|
128
|
+
code: 'en',
|
|
129
|
+
wordOrder: 'SVO',
|
|
130
|
+
keywords: { when: { primary: 'when' } },
|
|
131
|
+
roleMarkers: {},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
engine: 'core',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('reads a listed word, with or without the optional role before it', () => {
|
|
139
|
+
expect(reading(dsl.parse('when clicks', 'en'))).toBe('when action=clicks');
|
|
140
|
+
expect(reading(dsl.parse('when user clicks', 'en'))).toBe('when actor=user action=clicks');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('refuses a word it does not list, rather than reading the listed word as the role before it', () => {
|
|
144
|
+
// Without the list: actor `clicks`, action `zzqx`.
|
|
145
|
+
expect(() => dsl.parse('when clicks zzqx', 'en')).toThrow(UnreadInputError);
|
|
146
|
+
// A listed word is not part of a longer value, so the refusal does not say to quote it.
|
|
147
|
+
expect(() => dsl.parse('when clicks zzqx', 'en')).toThrow(/leaving "zzqx" unread$/);
|
|
148
|
+
expect(() => dsl.parse('when user frobs', 'en')).toThrow();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -449,6 +449,13 @@ describe('MultilingualDSL: Explicit Syntax Support', () => {
|
|
|
449
449
|
expect(refusal.message).toMatch(/leaving "banana" unread/);
|
|
450
450
|
});
|
|
451
451
|
|
|
452
|
+
it('says when the bare word read last and the next may be one value, to be quoted', () => {
|
|
453
|
+
const dsl = createTestDSL();
|
|
454
|
+
expect(() => dsl.parse('select name from users banana', 'en')).toThrow(
|
|
455
|
+
/unread; if "users banana" is one value, quote it$/
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
452
459
|
it('compile and validate report the refusal instead of compiling the shorter reading', () => {
|
|
453
460
|
const dsl = createTestDSL();
|
|
454
461
|
const compiled = dsl.compile('select name from users banana banana', 'en');
|