@lokascript/framework 2.0.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/LICENSE +20 -0
- package/README.md +142 -0
- package/dist/aot/aot-orchestrator.d.ts +75 -0
- package/dist/aot/aot-orchestrator.d.ts.map +1 -0
- package/dist/aot/domain-scanner.d.ts +27 -0
- package/dist/aot/domain-scanner.d.ts.map +1 -0
- package/dist/aot/index.d.ts +8 -0
- package/dist/aot/index.d.ts.map +1 -0
- package/dist/aot/types.d.ts +103 -0
- package/dist/aot/types.d.ts.map +1 -0
- package/dist/api/create-dsl.d.ts +91 -0
- package/dist/api/create-dsl.d.ts.map +1 -0
- package/dist/api/dispatcher.d.ts +108 -0
- package/dist/api/dispatcher.d.ts.map +1 -0
- package/dist/api/domain-registry.d.ts +152 -0
- package/dist/api/domain-registry.d.ts.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2082 -0
- package/dist/api/index.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +2674 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +32 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.d.ts +6 -0
- package/dist/core/pattern-matching/index.d.ts.map +1 -0
- package/dist/core/pattern-matching/index.js +1239 -0
- package/dist/core/pattern-matching/index.js.map +1 -0
- package/dist/core/pattern-matching/pattern-matcher.d.ts +239 -0
- package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/index.d.ts +6 -0
- package/dist/core/pattern-matching/utils/index.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts +38 -0
- package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +1 -0
- package/dist/core/pattern-matching/utils/type-validation.d.ts +63 -0
- package/dist/core/pattern-matching/utils/type-validation.d.ts.map +1 -0
- package/dist/core/tokenization/base-tokenizer.d.ts +344 -0
- package/dist/core/tokenization/base-tokenizer.d.ts.map +1 -0
- package/dist/core/tokenization/char-classifiers.d.ts +56 -0
- package/dist/core/tokenization/char-classifiers.d.ts.map +1 -0
- package/dist/core/tokenization/default-extractors.d.ts +48 -0
- package/dist/core/tokenization/default-extractors.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/index.d.ts +9 -0
- package/dist/core/tokenization/extractors/index.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/operator.d.ts +23 -0
- package/dist/core/tokenization/extractors/operator.d.ts.map +1 -0
- package/dist/core/tokenization/extractors/punctuation.d.ts +22 -0
- package/dist/core/tokenization/extractors/punctuation.d.ts.map +1 -0
- package/dist/core/tokenization/extractors.d.ts +61 -0
- package/dist/core/tokenization/extractors.d.ts.map +1 -0
- package/dist/core/tokenization/index.d.ts +11 -0
- package/dist/core/tokenization/index.d.ts.map +1 -0
- package/dist/core/tokenization/index.js +1345 -0
- package/dist/core/tokenization/index.js.map +1 -0
- package/dist/core/tokenization/morphology/index.d.ts +5 -0
- package/dist/core/tokenization/morphology/index.d.ts.map +1 -0
- package/dist/core/tokenization/morphology/types.d.ts +110 -0
- package/dist/core/tokenization/morphology/types.d.ts.map +1 -0
- package/dist/core/tokenization/token-utils.d.ts +111 -0
- package/dist/core/tokenization/token-utils.d.ts.map +1 -0
- package/dist/core/types.d.ts +382 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +108 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generation/diagnostics.d.ts +120 -0
- package/dist/generation/diagnostics.d.ts.map +1 -0
- package/dist/generation/index.d.ts +7 -0
- package/dist/generation/index.d.ts.map +1 -0
- package/dist/generation/index.js +339 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/generation/pattern-generator.d.ts +48 -0
- package/dist/generation/pattern-generator.d.ts.map +1 -0
- package/dist/generation/renderer.d.ts +115 -0
- package/dist/generation/renderer.d.ts.map +1 -0
- package/dist/grammar/index.d.ts +10 -0
- package/dist/grammar/index.d.ts.map +1 -0
- package/dist/grammar/index.js +391 -0
- package/dist/grammar/index.js.map +1 -0
- package/dist/grammar/transformer.d.ts +56 -0
- package/dist/grammar/transformer.d.ts.map +1 -0
- package/dist/grammar/types.d.ts +236 -0
- package/dist/grammar/types.d.ts.map +1 -0
- package/dist/index.cjs +4454 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4336 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/dictionary.d.ts +82 -0
- package/dist/interfaces/dictionary.d.ts.map +1 -0
- package/dist/interfaces/index.d.ts +10 -0
- package/dist/interfaces/index.d.ts.map +1 -0
- package/dist/interfaces/profile-provider.d.ts +67 -0
- package/dist/interfaces/profile-provider.d.ts.map +1 -0
- package/dist/interfaces/value-extractor.d.ts +168 -0
- package/dist/interfaces/value-extractor.d.ts.map +1 -0
- package/dist/multilingual/index.d.ts +8 -0
- package/dist/multilingual/index.d.ts.map +1 -0
- package/dist/multilingual/index.js +1 -0
- package/dist/multilingual/index.js.map +1 -0
- package/dist/parsing/index.d.ts +8 -0
- package/dist/parsing/index.d.ts.map +1 -0
- package/dist/parsing/index.js +1415 -0
- package/dist/parsing/index.js.map +1 -0
- package/dist/parsing/multi-statement.d.ts +265 -0
- package/dist/parsing/multi-statement.d.ts.map +1 -0
- package/dist/schema/command-schema.d.ts +78 -0
- package/dist/schema/command-schema.d.ts.map +1 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +25 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/test-setup.d.ts +9 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/testing/index.d.ts +50 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +16969 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +122 -0
- package/src/__test__/fixtures/sql-dsl.ts +232 -0
- package/src/__test__/sql-integration.test.ts +189 -0
- package/src/__test__/test-utils.ts +260 -0
- package/src/aot/aot-orchestrator.test.ts +413 -0
- package/src/aot/aot-orchestrator.ts +238 -0
- package/src/aot/domain-scanner.ts +178 -0
- package/src/aot/index.ts +8 -0
- package/src/aot/types.ts +124 -0
- package/src/api/create-dsl.ts +367 -0
- package/src/api/dispatcher.test.ts +336 -0
- package/src/api/dispatcher.ts +222 -0
- package/src/api/domain-registry.test.ts +336 -0
- package/src/api/domain-registry.ts +500 -0
- package/src/api/index.ts +7 -0
- package/src/core/index.ts +7 -0
- package/src/core/logger.ts +130 -0
- package/src/core/pattern-matching/index.ts +6 -0
- package/src/core/pattern-matching/pattern-matcher.test.ts +900 -0
- package/src/core/pattern-matching/pattern-matcher.ts +1548 -0
- package/src/core/pattern-matching/pattern-matcher.ts.backup +1267 -0
- package/src/core/pattern-matching/utils/index.ts +6 -0
- package/src/core/pattern-matching/utils/possessive-keywords.ts +55 -0
- package/src/core/pattern-matching/utils/type-validation.test.ts +316 -0
- package/src/core/pattern-matching/utils/type-validation.ts +134 -0
- package/src/core/tokenization/base-tokenizer.ts +916 -0
- package/src/core/tokenization/char-classifiers.ts +79 -0
- package/src/core/tokenization/create-simple-tokenizer.test.ts +260 -0
- package/src/core/tokenization/default-extractors.ts +69 -0
- package/src/core/tokenization/extractors/index.ts +9 -0
- package/src/core/tokenization/extractors/operator.ts +75 -0
- package/src/core/tokenization/extractors/punctuation.ts +39 -0
- package/src/core/tokenization/extractors.ts +452 -0
- package/src/core/tokenization/index.ts +11 -0
- package/src/core/tokenization/morphology/index.ts +5 -0
- package/src/core/tokenization/morphology/types.ts +211 -0
- package/src/core/tokenization/token-utils.ts +252 -0
- package/src/core/types.ts +589 -0
- package/src/generation/diagnostics.test.ts +171 -0
- package/src/generation/diagnostics.ts +239 -0
- package/src/generation/index.ts +7 -0
- package/src/generation/pattern-generator.test.ts +430 -0
- package/src/generation/pattern-generator.ts +315 -0
- package/src/generation/renderer.test.ts +266 -0
- package/src/generation/renderer.ts +244 -0
- package/src/grammar/index.ts +12 -0
- package/src/grammar/transformer.ts +159 -0
- package/src/grammar/types.ts +630 -0
- package/src/index.ts +157 -0
- package/src/interfaces/dictionary.ts +123 -0
- package/src/interfaces/index.ts +10 -0
- package/src/interfaces/profile-provider.ts +88 -0
- package/src/interfaces/value-extractor.ts +435 -0
- package/src/multilingual/index.ts +9 -0
- package/src/parsing/index.ts +27 -0
- package/src/parsing/multi-statement.test.ts +480 -0
- package/src/parsing/multi-statement.ts +648 -0
- package/src/schema/command-schema.ts +118 -0
- package/src/schema/index.ts +5 -0
- package/src/test-setup.ts +45 -0
- package/src/testing/index.ts +137 -0
package/package.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lokascript/framework",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Generic framework for building multilingual DSLs with semantic parsing and grammar transformation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./core": {
|
|
16
|
+
"types": "./dist/core/index.d.ts",
|
|
17
|
+
"import": "./dist/core/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./core/types": {
|
|
20
|
+
"types": "./dist/core/types.d.ts",
|
|
21
|
+
"import": "./dist/core/types.js"
|
|
22
|
+
},
|
|
23
|
+
"./core/tokenization": {
|
|
24
|
+
"types": "./dist/core/tokenization/index.d.ts",
|
|
25
|
+
"import": "./dist/core/tokenization/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./core/pattern-matching": {
|
|
28
|
+
"types": "./dist/core/pattern-matching/index.d.ts",
|
|
29
|
+
"import": "./dist/core/pattern-matching/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./multilingual": {
|
|
32
|
+
"types": "./dist/multilingual/index.d.ts",
|
|
33
|
+
"import": "./dist/multilingual/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./schema": {
|
|
36
|
+
"types": "./dist/schema/index.d.ts",
|
|
37
|
+
"import": "./dist/schema/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./parsing": {
|
|
40
|
+
"types": "./dist/parsing/index.d.ts",
|
|
41
|
+
"import": "./dist/parsing/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./generation": {
|
|
44
|
+
"types": "./dist/generation/index.d.ts",
|
|
45
|
+
"import": "./dist/generation/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./grammar": {
|
|
48
|
+
"types": "./dist/grammar/index.d.ts",
|
|
49
|
+
"import": "./dist/grammar/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./api": {
|
|
52
|
+
"types": "./dist/api/index.d.ts",
|
|
53
|
+
"import": "./dist/api/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./testing": {
|
|
56
|
+
"types": "./dist/testing/index.d.ts",
|
|
57
|
+
"import": "./dist/testing/index.js"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsup && npm run build:types",
|
|
62
|
+
"build:types": "tsc --emitDeclarationOnly --outDir dist",
|
|
63
|
+
"dev": "tsup --watch",
|
|
64
|
+
"test": "vitest",
|
|
65
|
+
"test:run": "vitest run",
|
|
66
|
+
"test:watch": "vitest watch",
|
|
67
|
+
"test:check": "vitest run --reporter=dot 2>&1 | tail -5",
|
|
68
|
+
"test:coverage": "vitest run --coverage",
|
|
69
|
+
"test:unit": "vitest run src/core/**/*.test.ts src/generation/**/*.test.ts",
|
|
70
|
+
"test:integration": "vitest run src/__test__/**/*.test.ts src/api/**/*.test.ts",
|
|
71
|
+
"test:pattern-matcher": "vitest run src/core/pattern-matching",
|
|
72
|
+
"test:tokenizer": "vitest run src/core/tokenization",
|
|
73
|
+
"typecheck": "tsc --noEmit",
|
|
74
|
+
"create-domain": "npx tsx scripts/create-domain.ts"
|
|
75
|
+
},
|
|
76
|
+
"keywords": [
|
|
77
|
+
"dsl",
|
|
78
|
+
"multilingual",
|
|
79
|
+
"i18n",
|
|
80
|
+
"parser",
|
|
81
|
+
"semantic",
|
|
82
|
+
"pattern-matching",
|
|
83
|
+
"grammar-transformation",
|
|
84
|
+
"natural-language",
|
|
85
|
+
"framework",
|
|
86
|
+
"compiler",
|
|
87
|
+
"svo",
|
|
88
|
+
"sov",
|
|
89
|
+
"vso"
|
|
90
|
+
],
|
|
91
|
+
"author": "LokaScript Contributors",
|
|
92
|
+
"license": "MIT",
|
|
93
|
+
"devDependencies": {
|
|
94
|
+
"@types/node": "^20.0.0",
|
|
95
|
+
"@vitest/coverage-v8": "^4.0.17",
|
|
96
|
+
"tsup": "^8.0.0",
|
|
97
|
+
"typescript": "^5.0.0",
|
|
98
|
+
"vitest": "^4.0.0"
|
|
99
|
+
},
|
|
100
|
+
"files": [
|
|
101
|
+
"dist",
|
|
102
|
+
"src",
|
|
103
|
+
"LICENSE",
|
|
104
|
+
"README.md"
|
|
105
|
+
],
|
|
106
|
+
"repository": {
|
|
107
|
+
"type": "git",
|
|
108
|
+
"url": "git+https://github.com/codetalcott/hyperfixi.git",
|
|
109
|
+
"directory": "packages/framework"
|
|
110
|
+
},
|
|
111
|
+
"bugs": {
|
|
112
|
+
"url": "https://github.com/codetalcott/hyperfixi/issues"
|
|
113
|
+
},
|
|
114
|
+
"homepage": "https://github.com/codetalcott/hyperfixi/tree/main/packages/framework#readme",
|
|
115
|
+
"engines": {
|
|
116
|
+
"node": ">=18.0.0"
|
|
117
|
+
},
|
|
118
|
+
"publishConfig": {
|
|
119
|
+
"access": "public"
|
|
120
|
+
},
|
|
121
|
+
"sideEffects": false
|
|
122
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL DSL Test Fixture
|
|
3
|
+
*
|
|
4
|
+
* Reusable SQL DSL configuration for integration tests.
|
|
5
|
+
* Based on examples/sql-dsl/simple-sql.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
createMultilingualDSL,
|
|
10
|
+
defineCommand,
|
|
11
|
+
defineRole,
|
|
12
|
+
BaseTokenizer,
|
|
13
|
+
getDefaultExtractors,
|
|
14
|
+
type SemanticNode,
|
|
15
|
+
type MultilingualDSL,
|
|
16
|
+
} from '../../index';
|
|
17
|
+
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Command Schemas
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
export const selectCommand = defineCommand({
|
|
23
|
+
action: 'select',
|
|
24
|
+
description: 'Retrieve data from a table',
|
|
25
|
+
category: 'query',
|
|
26
|
+
primaryRole: 'columns',
|
|
27
|
+
roles: [
|
|
28
|
+
defineRole({
|
|
29
|
+
role: 'columns',
|
|
30
|
+
description: 'Columns to select',
|
|
31
|
+
required: true,
|
|
32
|
+
expectedTypes: ['expression'],
|
|
33
|
+
svoPosition: 2,
|
|
34
|
+
sovPosition: 1,
|
|
35
|
+
}),
|
|
36
|
+
defineRole({
|
|
37
|
+
role: 'source',
|
|
38
|
+
description: 'Table to select from',
|
|
39
|
+
required: true,
|
|
40
|
+
expectedTypes: ['expression'],
|
|
41
|
+
svoPosition: 1,
|
|
42
|
+
sovPosition: 2,
|
|
43
|
+
markerOverride: { en: 'from', es: 'de' },
|
|
44
|
+
}),
|
|
45
|
+
defineRole({
|
|
46
|
+
role: 'condition',
|
|
47
|
+
description: 'WHERE clause condition',
|
|
48
|
+
required: false,
|
|
49
|
+
expectedTypes: ['expression'],
|
|
50
|
+
svoPosition: 0,
|
|
51
|
+
sovPosition: 0,
|
|
52
|
+
markerOverride: { en: 'where', es: 'donde' },
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// Tokenizers
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
class EnglishSQLTokenizer extends BaseTokenizer {
|
|
62
|
+
readonly language = 'en';
|
|
63
|
+
readonly direction = 'ltr' as const;
|
|
64
|
+
|
|
65
|
+
constructor() {
|
|
66
|
+
super();
|
|
67
|
+
this.registerExtractors(getDefaultExtractors());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
classifyToken(token: string): 'keyword' | 'identifier' | 'literal' | 'operator' {
|
|
71
|
+
const keywords = ['select', 'from', 'where'];
|
|
72
|
+
if (keywords.includes(token.toLowerCase())) {
|
|
73
|
+
return 'keyword';
|
|
74
|
+
}
|
|
75
|
+
if (/^\d+$/.test(token)) {
|
|
76
|
+
return 'literal';
|
|
77
|
+
}
|
|
78
|
+
if (['>', '<', '=', '>=', '<=', '!='].includes(token)) {
|
|
79
|
+
return 'operator';
|
|
80
|
+
}
|
|
81
|
+
return 'identifier';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class SpanishSQLTokenizer extends BaseTokenizer {
|
|
86
|
+
readonly language = 'es';
|
|
87
|
+
readonly direction = 'ltr' as const;
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
super();
|
|
91
|
+
this.registerExtractors(getDefaultExtractors());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
classifyToken(token: string): 'keyword' | 'identifier' | 'literal' | 'operator' {
|
|
95
|
+
const keywords = ['seleccionar', 'de', 'donde'];
|
|
96
|
+
if (keywords.includes(token.toLowerCase())) {
|
|
97
|
+
return 'keyword';
|
|
98
|
+
}
|
|
99
|
+
if (/^\d+$/.test(token)) {
|
|
100
|
+
return 'literal';
|
|
101
|
+
}
|
|
102
|
+
if (['>', '<', '=', '>=', '<=', '!='].includes(token)) {
|
|
103
|
+
return 'operator';
|
|
104
|
+
}
|
|
105
|
+
return 'identifier';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// =============================================================================
|
|
110
|
+
// Language Profiles
|
|
111
|
+
// =============================================================================
|
|
112
|
+
|
|
113
|
+
const englishProfile = {
|
|
114
|
+
code: 'en',
|
|
115
|
+
wordOrder: 'SVO' as const,
|
|
116
|
+
keywords: {
|
|
117
|
+
select: { primary: 'select' },
|
|
118
|
+
},
|
|
119
|
+
roleMarkers: {
|
|
120
|
+
source: { primary: 'from', position: 'before' as const },
|
|
121
|
+
condition: { primary: 'where', position: 'before' as const },
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const spanishProfile = {
|
|
126
|
+
code: 'es',
|
|
127
|
+
wordOrder: 'SVO' as const,
|
|
128
|
+
keywords: {
|
|
129
|
+
select: { primary: 'seleccionar' },
|
|
130
|
+
},
|
|
131
|
+
roleMarkers: {
|
|
132
|
+
source: { primary: 'de', position: 'before' as const },
|
|
133
|
+
condition: { primary: 'donde', position: 'before' as const },
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// Code Generator
|
|
139
|
+
// =============================================================================
|
|
140
|
+
|
|
141
|
+
function generateSQL(node: SemanticNode): string {
|
|
142
|
+
if (node.action === 'select') {
|
|
143
|
+
const columns = node.roles.get('columns');
|
|
144
|
+
const source = node.roles.get('source');
|
|
145
|
+
const condition = node.roles.get('condition');
|
|
146
|
+
|
|
147
|
+
const columnsValue = columns ? ('raw' in columns ? columns.raw : columns.value) : '*';
|
|
148
|
+
const sourceValue = source ? ('raw' in source ? source.raw : source.value) : 'table';
|
|
149
|
+
|
|
150
|
+
let sql = `SELECT ${columnsValue} FROM ${sourceValue}`;
|
|
151
|
+
|
|
152
|
+
if (condition) {
|
|
153
|
+
const conditionValue = 'raw' in condition ? condition.raw : condition.value;
|
|
154
|
+
sql += ` WHERE ${conditionValue}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return sql;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
throw new Error(`Unknown SQL command: ${node.action}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// =============================================================================
|
|
164
|
+
// SQL DSL Instance
|
|
165
|
+
// =============================================================================
|
|
166
|
+
|
|
167
|
+
export function createSQLDSL(): MultilingualDSL {
|
|
168
|
+
return createMultilingualDSL({
|
|
169
|
+
name: 'SQL DSL Test Fixture',
|
|
170
|
+
schemas: [selectCommand],
|
|
171
|
+
languages: [
|
|
172
|
+
{
|
|
173
|
+
code: 'en',
|
|
174
|
+
name: 'English',
|
|
175
|
+
nativeName: 'English',
|
|
176
|
+
tokenizer: new EnglishSQLTokenizer(),
|
|
177
|
+
patternProfile: englishProfile,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
code: 'es',
|
|
181
|
+
name: 'Spanish',
|
|
182
|
+
nativeName: 'Español',
|
|
183
|
+
tokenizer: new SpanishSQLTokenizer(),
|
|
184
|
+
patternProfile: spanishProfile,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
codeGenerator: {
|
|
188
|
+
generate: generateSQL,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// =============================================================================
|
|
194
|
+
// Test Data
|
|
195
|
+
// =============================================================================
|
|
196
|
+
|
|
197
|
+
export const testQueries = {
|
|
198
|
+
english: {
|
|
199
|
+
simple: 'select name from users',
|
|
200
|
+
withWhere: 'select name from users where age > 18',
|
|
201
|
+
multipleColumns: 'select name, email from users',
|
|
202
|
+
},
|
|
203
|
+
spanish: {
|
|
204
|
+
simple: 'seleccionar nombre de usuarios',
|
|
205
|
+
withWhere: 'seleccionar nombre de usuarios donde edad > 18',
|
|
206
|
+
multipleColumns: 'seleccionar nombre, email de usuarios',
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const expectedSQL = {
|
|
211
|
+
simple: 'SELECT name FROM users',
|
|
212
|
+
withWhere: 'SELECT name FROM users WHERE age > 18',
|
|
213
|
+
multipleColumns: 'SELECT name, email FROM users',
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const expectedNodes = {
|
|
217
|
+
simple: {
|
|
218
|
+
action: 'select',
|
|
219
|
+
roles: {
|
|
220
|
+
columns: 'name',
|
|
221
|
+
source: 'users',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
withWhere: {
|
|
225
|
+
action: 'select',
|
|
226
|
+
roles: {
|
|
227
|
+
columns: 'name',
|
|
228
|
+
source: 'users',
|
|
229
|
+
condition: 'age > 18',
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQL DSL Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* End-to-end tests using the SQL DSL fixture to validate the full pipeline:
|
|
5
|
+
* Tokenization → Pattern Matching → Semantic Parsing → Code Generation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { createSQLDSL, testQueries, expectedSQL, expectedNodes } from './fixtures/sql-dsl';
|
|
10
|
+
|
|
11
|
+
describe('SQL DSL Integration', () => {
|
|
12
|
+
let sqlDSL: ReturnType<typeof createSQLDSL>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
sqlDSL = createSQLDSL();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('English Queries', () => {
|
|
19
|
+
it('should parse simple SELECT query', () => {
|
|
20
|
+
const result = sqlDSL.parse(testQueries.english.simple, 'en');
|
|
21
|
+
|
|
22
|
+
expect(result.kind).toBe('command');
|
|
23
|
+
expect(result.action).toBe('select');
|
|
24
|
+
expect(result.roles.has('columns')).toBe(true);
|
|
25
|
+
expect(result.roles.has('source')).toBe(true);
|
|
26
|
+
|
|
27
|
+
const columns = result.roles.get('columns');
|
|
28
|
+
const source = result.roles.get('source');
|
|
29
|
+
|
|
30
|
+
if (columns && 'raw' in columns) {
|
|
31
|
+
expect(columns.raw).toBe('name');
|
|
32
|
+
}
|
|
33
|
+
if (source && 'raw' in source) {
|
|
34
|
+
expect(source.raw).toBe('users');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should parse SELECT with WHERE clause', () => {
|
|
39
|
+
const result = sqlDSL.parse(testQueries.english.withWhere, 'en');
|
|
40
|
+
|
|
41
|
+
expect(result.action).toBe('select');
|
|
42
|
+
expect(result.roles.has('columns')).toBe(true);
|
|
43
|
+
expect(result.roles.has('source')).toBe(true);
|
|
44
|
+
expect(result.roles.has('condition')).toBe(true);
|
|
45
|
+
|
|
46
|
+
const condition = result.roles.get('condition');
|
|
47
|
+
expect(condition).toBeDefined();
|
|
48
|
+
if (condition && 'raw' in condition) {
|
|
49
|
+
// The condition role captures the expression after WHERE
|
|
50
|
+
expect(condition.raw).toBeTruthy();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('Spanish Queries', () => {
|
|
56
|
+
it('should parse Spanish SELECT query', () => {
|
|
57
|
+
const result = sqlDSL.parse(testQueries.spanish.simple, 'es');
|
|
58
|
+
|
|
59
|
+
expect(result.action).toBe('select');
|
|
60
|
+
expect(result.roles.has('columns')).toBe(true);
|
|
61
|
+
expect(result.roles.has('source')).toBe(true);
|
|
62
|
+
|
|
63
|
+
const columns = result.roles.get('columns');
|
|
64
|
+
const source = result.roles.get('source');
|
|
65
|
+
|
|
66
|
+
if (columns && 'raw' in columns) {
|
|
67
|
+
expect(columns.raw).toBe('nombre');
|
|
68
|
+
}
|
|
69
|
+
if (source && 'raw' in source) {
|
|
70
|
+
expect(source.raw).toBe('usuarios');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should parse Spanish SELECT with WHERE clause', () => {
|
|
75
|
+
const result = sqlDSL.parse(testQueries.spanish.withWhere, 'es');
|
|
76
|
+
|
|
77
|
+
expect(result.action).toBe('select');
|
|
78
|
+
expect(result.roles.has('condition')).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('Code Generation', () => {
|
|
83
|
+
it('should compile English query to SQL', () => {
|
|
84
|
+
const result = sqlDSL.compile(testQueries.english.simple, 'en');
|
|
85
|
+
|
|
86
|
+
expect(result.ok).toBe(true);
|
|
87
|
+
expect(result.code).toBe(expectedSQL.simple);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should compile query with WHERE clause', () => {
|
|
91
|
+
const result = sqlDSL.compile(testQueries.english.withWhere, 'en');
|
|
92
|
+
|
|
93
|
+
expect(result.ok).toBe(true);
|
|
94
|
+
// Should contain all parts
|
|
95
|
+
expect(result.code).toContain('SELECT');
|
|
96
|
+
expect(result.code).toContain('FROM');
|
|
97
|
+
expect(result.code).toContain('WHERE');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should preserve identifiers from source language', () => {
|
|
101
|
+
const enResult = sqlDSL.compile(testQueries.english.simple, 'en');
|
|
102
|
+
const esResult = sqlDSL.compile(testQueries.spanish.simple, 'es');
|
|
103
|
+
|
|
104
|
+
// English uses English identifiers
|
|
105
|
+
expect(enResult.code).toContain('name');
|
|
106
|
+
expect(enResult.code).toContain('users');
|
|
107
|
+
|
|
108
|
+
// Spanish uses Spanish identifiers
|
|
109
|
+
expect(esResult.code).toContain('nombre');
|
|
110
|
+
expect(esResult.code).toContain('usuarios');
|
|
111
|
+
|
|
112
|
+
// But both have SQL structure
|
|
113
|
+
expect(enResult.code).toContain('SELECT');
|
|
114
|
+
expect(esResult.code).toContain('SELECT');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('Validation', () => {
|
|
119
|
+
it('should validate correct queries', () => {
|
|
120
|
+
const result = sqlDSL.validate(testQueries.english.simple, 'en');
|
|
121
|
+
|
|
122
|
+
expect(result.valid).toBe(true);
|
|
123
|
+
expect(result.node).toBeDefined();
|
|
124
|
+
expect(result.errors).toBeUndefined();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should handle invalid queries gracefully', () => {
|
|
128
|
+
// Query with unknown keyword
|
|
129
|
+
const result = sqlDSL.validate('invalid query syntax', 'en');
|
|
130
|
+
|
|
131
|
+
expect(result.valid).toBe(false);
|
|
132
|
+
expect(result.errors).toBeDefined();
|
|
133
|
+
expect(result.errors?.length).toBeGreaterThan(0);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('Multi-language Support', () => {
|
|
138
|
+
it('should support both English and Spanish', () => {
|
|
139
|
+
const languages = sqlDSL.getSupportedLanguages();
|
|
140
|
+
|
|
141
|
+
expect(languages).toContain('en');
|
|
142
|
+
expect(languages).toContain('es');
|
|
143
|
+
expect(languages).toHaveLength(2);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should reject unsupported language', () => {
|
|
147
|
+
expect(() => {
|
|
148
|
+
sqlDSL.parse('select name from users', 'fr');
|
|
149
|
+
}).toThrow();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('Semantic Equivalence', () => {
|
|
154
|
+
it('should parse English and Spanish to equivalent semantic structures', () => {
|
|
155
|
+
const enNode = sqlDSL.parse(testQueries.english.simple, 'en');
|
|
156
|
+
const esNode = sqlDSL.parse(testQueries.spanish.simple, 'es');
|
|
157
|
+
|
|
158
|
+
expect(enNode.action).toBe(esNode.action);
|
|
159
|
+
expect(enNode.roles.size).toBe(esNode.roles.size);
|
|
160
|
+
expect(enNode.roles.has('columns')).toBe(esNode.roles.has('columns'));
|
|
161
|
+
expect(enNode.roles.has('source')).toBe(esNode.roles.has('source'));
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('Error Handling', () => {
|
|
166
|
+
it('should handle empty input', () => {
|
|
167
|
+
expect(() => {
|
|
168
|
+
sqlDSL.parse('', 'en');
|
|
169
|
+
}).toThrow();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should handle whitespace-only input', () => {
|
|
173
|
+
expect(() => {
|
|
174
|
+
sqlDSL.parse(' ', 'en');
|
|
175
|
+
}).toThrow();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should provide helpful error messages', () => {
|
|
179
|
+
try {
|
|
180
|
+
sqlDSL.parse('unknown command', 'en');
|
|
181
|
+
// Should have thrown
|
|
182
|
+
expect(true).toBe(false);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
expect(error).toBeDefined();
|
|
185
|
+
expect(error instanceof Error).toBe(true);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|