@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.
Files changed (116) hide show
  1. package/CHANGELOG.md +121 -1
  2. package/dist/api/create-dsl.d.ts +18 -2
  3. package/dist/api/create-dsl.d.ts.map +1 -1
  4. package/dist/api/index.d.ts +1 -0
  5. package/dist/api/index.d.ts.map +1 -1
  6. package/dist/api/index.js +1799 -713
  7. package/dist/api/index.js.map +1 -1
  8. package/dist/api/translation-loss.d.ts +84 -0
  9. package/dist/api/translation-loss.d.ts.map +1 -0
  10. package/dist/core/index.d.ts +1 -0
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/core/index.js +1057 -684
  13. package/dist/core/index.js.map +1 -1
  14. package/dist/core/matching/confidence-model.d.ts +54 -0
  15. package/dist/core/matching/confidence-model.d.ts.map +1 -0
  16. package/dist/core/matching/index.d.ts +12 -0
  17. package/dist/core/matching/index.d.ts.map +1 -0
  18. package/dist/core/matching/match-skeleton.d.ts +114 -0
  19. package/dist/core/matching/match-skeleton.d.ts.map +1 -0
  20. package/dist/core/matching/stock-confidence.d.ts +15 -0
  21. package/dist/core/matching/stock-confidence.d.ts.map +1 -0
  22. package/dist/core/matching/stock-reader.d.ts +63 -0
  23. package/dist/core/matching/stock-reader.d.ts.map +1 -0
  24. package/dist/core/matching/stock-reading.d.ts +44 -0
  25. package/dist/core/matching/stock-reading.d.ts.map +1 -0
  26. package/dist/core/matching/value-reader.d.ts +62 -0
  27. package/dist/core/matching/value-reader.d.ts.map +1 -0
  28. package/dist/core/pattern-matching/index.js +561 -660
  29. package/dist/core/pattern-matching/index.js.map +1 -1
  30. package/dist/core/pattern-matching/pattern-matcher.d.ts +0 -117
  31. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -1
  32. package/dist/core/types.d.ts +53 -0
  33. package/dist/core/types.d.ts.map +1 -1
  34. package/dist/core/types.js.map +1 -1
  35. package/dist/generation/index.d.ts +2 -0
  36. package/dist/generation/index.d.ts.map +1 -1
  37. package/dist/generation/index.js +593 -1
  38. package/dist/generation/index.js.map +1 -1
  39. package/dist/generation/pattern-render.d.ts +72 -0
  40. package/dist/generation/pattern-render.d.ts.map +1 -0
  41. package/dist/generation/schema-patterns.d.ts +152 -0
  42. package/dist/generation/schema-patterns.d.ts.map +1 -0
  43. package/dist/index.cjs +2016 -904
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.js +1998 -903
  46. package/dist/index.js.map +1 -1
  47. package/dist/ir/index.js.map +1 -1
  48. package/dist/parsing/index.js +561 -660
  49. package/dist/parsing/index.js.map +1 -1
  50. package/dist/testing/gate/index.d.ts +8 -0
  51. package/dist/testing/gate/index.d.ts.map +1 -0
  52. package/dist/testing/gate/index.js +664 -0
  53. package/dist/testing/gate/index.js.map +1 -0
  54. package/dist/testing/gate/lint/format.d.ts +7 -0
  55. package/dist/testing/gate/lint/format.d.ts.map +1 -0
  56. package/dist/testing/gate/lint/index.d.ts +30 -0
  57. package/dist/testing/gate/lint/index.d.ts.map +1 -0
  58. package/dist/testing/gate/lint/rules/extractor-coverage.d.ts +18 -0
  59. package/dist/testing/gate/lint/rules/extractor-coverage.d.ts.map +1 -0
  60. package/dist/testing/gate/lint/rules/index.d.ts +14 -0
  61. package/dist/testing/gate/lint/rules/index.d.ts.map +1 -0
  62. package/dist/testing/gate/lint/rules/keyword-classification.d.ts +19 -0
  63. package/dist/testing/gate/lint/rules/keyword-classification.d.ts.map +1 -0
  64. package/dist/testing/gate/lint/rules/keyword-coverage.d.ts +13 -0
  65. package/dist/testing/gate/lint/rules/keyword-coverage.d.ts.map +1 -0
  66. package/dist/testing/gate/lint/rules/marker-tokenization.d.ts +20 -0
  67. package/dist/testing/gate/lint/rules/marker-tokenization.d.ts.map +1 -0
  68. package/dist/testing/gate/lint/rules/position-ordering.d.ts +25 -0
  69. package/dist/testing/gate/lint/rules/position-ordering.d.ts.map +1 -0
  70. package/dist/testing/gate/lint/rules/renderer-coherence.d.ts +24 -0
  71. package/dist/testing/gate/lint/rules/renderer-coherence.d.ts.map +1 -0
  72. package/dist/testing/gate/lint/rules/schema-rules.d.ts +10 -0
  73. package/dist/testing/gate/lint/rules/schema-rules.d.ts.map +1 -0
  74. package/dist/testing/gate/lint/types.d.ts +74 -0
  75. package/dist/testing/gate/lint/types.d.ts.map +1 -0
  76. package/dist/testing/gate/round-trip.d.ts +184 -0
  77. package/dist/testing/gate/round-trip.d.ts.map +1 -0
  78. package/package.json +6 -2
  79. package/src/__test__/fixtures/sql-dsl.ts +8 -2
  80. package/src/api/create-dsl-engine.test.ts +150 -0
  81. package/src/api/create-dsl.test.ts +7 -0
  82. package/src/api/create-dsl.ts +173 -22
  83. package/src/api/index.ts +1 -0
  84. package/src/api/translation-loss.test.ts +89 -0
  85. package/src/api/translation-loss.ts +128 -0
  86. package/src/core/index.ts +1 -0
  87. package/src/core/matching/confidence-model.ts +58 -0
  88. package/src/core/matching/index.ts +12 -0
  89. package/src/core/matching/match-skeleton.test.ts +179 -0
  90. package/src/core/matching/match-skeleton.ts +621 -0
  91. package/src/core/matching/stock-confidence.ts +238 -0
  92. package/src/core/matching/stock-reader.test.ts +265 -0
  93. package/src/core/matching/stock-reader.ts +205 -0
  94. package/src/core/matching/stock-reading.ts +681 -0
  95. package/src/core/matching/value-reader.ts +85 -0
  96. package/src/core/pattern-matching/pattern-matcher.ts +11 -876
  97. package/src/core/types.ts +53 -0
  98. package/src/generation/index.ts +2 -0
  99. package/src/generation/pattern-render.ts +378 -0
  100. package/src/generation/schema-patterns.test.ts +139 -0
  101. package/src/generation/schema-patterns.ts +974 -0
  102. package/src/testing/gate/index.ts +8 -0
  103. package/src/testing/gate/lint/format.ts +23 -0
  104. package/src/testing/gate/lint/index.ts +73 -0
  105. package/src/testing/gate/lint/lint.test.ts +131 -0
  106. package/src/testing/gate/lint/rules/extractor-coverage.ts +63 -0
  107. package/src/testing/gate/lint/rules/index.ts +37 -0
  108. package/src/testing/gate/lint/rules/keyword-classification.ts +52 -0
  109. package/src/testing/gate/lint/rules/keyword-coverage.ts +38 -0
  110. package/src/testing/gate/lint/rules/marker-tokenization.ts +129 -0
  111. package/src/testing/gate/lint/rules/position-ordering.ts +87 -0
  112. package/src/testing/gate/lint/rules/renderer-coherence.ts +100 -0
  113. package/src/testing/gate/lint/rules/schema-rules.ts +35 -0
  114. package/src/testing/gate/lint/types.ts +83 -0
  115. package/src/testing/gate/round-trip.test.ts +208 -0
  116. 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.6.0",
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.6.0"
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');