@malloydata/malloy 0.0.374 → 0.0.376
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/api/foundation/config.d.ts +6 -4
- package/dist/api/foundation/config.js +43 -19
- package/dist/api/foundation/config_lookup.d.ts +31 -0
- package/dist/api/foundation/config_lookup.js +180 -0
- package/dist/api/foundation/config_overlays.d.ts +8 -1
- package/dist/api/foundation/config_resolve.d.ts +26 -36
- package/dist/api/foundation/config_resolve.js +66 -180
- package/dist/connection/registry.d.ts +6 -0
- package/dist/connection/registry.js +9 -0
- package/dist/dialect/duckdb/duckdb.js +17 -27
- package/dist/dialect/tiny_parser.d.ts +53 -18
- package/dist/dialect/tiny_parser.js +182 -76
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -3
- package/dist/internal.d.ts +3 -0
- package/dist/internal.js +22 -0
- package/dist/model/index.d.ts +1 -1
- package/dist/model/index.js +2 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -4
|
@@ -4,227 +4,113 @@
|
|
|
4
4
|
* SPDX-License-Identifier: MIT
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
7
|
+
exports.prepareConfig = prepareConfig;
|
|
8
8
|
const registry_1 = require("../../connection/registry");
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Synchronous top-level walk of a compiled config tree. Extracts the
|
|
11
|
+
* non-connection sections (which only contain literals — see the section
|
|
12
|
+
* compilers) and hands back the compiled connection subtrees untouched.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
+
* Reference resolution for connection properties is *not* done here. It is
|
|
15
|
+
* deferred until `lookupConnection` is called, at which point the async
|
|
16
|
+
* walker in `config_lookup.ts` can `await` overlays that do IO (secret
|
|
17
|
+
* stores, session fetches, etc.). This keeps `MalloyConfig` construction
|
|
18
|
+
* synchronous and zero-IO.
|
|
14
19
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 2. **`includeDefaultConnections`** (`fabricateMissingConnections`) —
|
|
21
|
-
* fabricate a bare `{is: typeName}` entry for each registered backend
|
|
22
|
-
* not already represented. Property defaults then fill in their
|
|
23
|
-
* properties via (1).
|
|
24
|
-
*
|
|
25
|
-
* Order matters: fabrication runs before property defaults so that
|
|
26
|
-
* fabricated entries pick up defaults in the same pass as user-listed
|
|
27
|
-
* ones.
|
|
28
|
-
*
|
|
29
|
-
* Three unresolved-reference cases, each with different handling:
|
|
30
|
-
* 1. Unknown overlay source → warning, drop property
|
|
31
|
-
* 2. Known overlay → undefined → silent drop
|
|
32
|
-
* 3. Property default → unresolved (either of the above inside a default)
|
|
33
|
-
* → silent drop (a default is a hint, not a requirement)
|
|
20
|
+
* Fabrication of bare `{is: typeName}` compiled entries for registered
|
|
21
|
+
* backends not otherwise represented happens here when the config opts in
|
|
22
|
+
* via `includeDefaultConnections`. Property defaults are filled in at
|
|
23
|
+
* lookup time alongside reference resolution.
|
|
34
24
|
*/
|
|
35
|
-
function
|
|
36
|
-
|
|
25
|
+
function prepareConfig(compiled, _log) {
|
|
26
|
+
let compiledConnections = {};
|
|
27
|
+
let manifestPath;
|
|
28
|
+
let virtualMap;
|
|
37
29
|
let includeDefaultConnections = false;
|
|
38
30
|
for (const [key, node] of Object.entries(compiled.entries)) {
|
|
39
31
|
switch (key) {
|
|
40
32
|
case 'connections': {
|
|
41
33
|
if (node.kind !== 'dict')
|
|
42
34
|
break;
|
|
43
|
-
|
|
35
|
+
compiledConnections = extractCompiledConnections(node);
|
|
44
36
|
break;
|
|
45
37
|
}
|
|
46
38
|
case 'manifestPath': {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
if (node.kind === 'value' && typeof node.value === 'string') {
|
|
40
|
+
manifestPath = node.value;
|
|
41
|
+
}
|
|
50
42
|
break;
|
|
51
43
|
}
|
|
52
44
|
case 'virtualMap': {
|
|
53
|
-
// virtualMap is literal
|
|
54
|
-
//
|
|
55
|
-
|
|
45
|
+
// virtualMap is a literal dict slot — compileVirtualMap never
|
|
46
|
+
// produces a reference node. MalloyConfig converts the raw POJO
|
|
47
|
+
// into its runtime Map-of-Maps representation.
|
|
48
|
+
if (node.kind === 'value')
|
|
49
|
+
virtualMap = node.value;
|
|
56
50
|
break;
|
|
57
51
|
}
|
|
58
52
|
case 'includeDefaultConnections': {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
if (node.kind === 'value' && typeof node.value === 'boolean') {
|
|
54
|
+
includeDefaultConnections = node.value;
|
|
55
|
+
}
|
|
62
56
|
break;
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
}
|
|
66
60
|
if (includeDefaultConnections) {
|
|
67
|
-
fabricateMissingConnections(
|
|
61
|
+
fabricateMissingConnections(compiledConnections);
|
|
68
62
|
}
|
|
69
|
-
|
|
70
|
-
// alike. This is the fix for the earlier bug where defaults only fired
|
|
71
|
-
// during fabrication, leaving explicit entries silently underconfigured.
|
|
72
|
-
applyPropertyDefaults(resolved.connections, overlays);
|
|
73
|
-
return resolved;
|
|
63
|
+
return { compiledConnections, manifestPath, virtualMap };
|
|
74
64
|
}
|
|
75
|
-
// =============================================================================
|
|
76
|
-
// Generic walk
|
|
77
|
-
// =============================================================================
|
|
78
65
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
66
|
+
* Pull each well-formed compiled connection entry out of the `connections`
|
|
67
|
+
* subtree. Entries are already validated by `compileConnections` — anything
|
|
68
|
+
* shaped wrong was dropped or reported during compile. We defensively skip
|
|
69
|
+
* non-dict children here anyway.
|
|
82
70
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return resolveReference(node, overlays, log);
|
|
89
|
-
case 'dict': {
|
|
90
|
-
const out = {};
|
|
91
|
-
for (const [k, child] of Object.entries(node.entries)) {
|
|
92
|
-
const r = resolveNode(child, overlays, log);
|
|
93
|
-
if (r !== undefined)
|
|
94
|
-
out[k] = r;
|
|
95
|
-
}
|
|
96
|
-
return out;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function resolveReference(ref, overlays, log) {
|
|
101
|
-
const overlay = overlays[ref.source];
|
|
102
|
-
if (!overlay) {
|
|
103
|
-
// Case 1: unknown overlay source — warn and drop.
|
|
104
|
-
log.push({
|
|
105
|
-
message: `unknown overlay source "${ref.source}" for reference path ${JSON.stringify(ref.path)}`,
|
|
106
|
-
severity: 'warn',
|
|
107
|
-
code: 'config-overlay',
|
|
108
|
-
});
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
// Case 2: overlay returns undefined — silent drop (no log push).
|
|
112
|
-
return overlay(ref.path);
|
|
113
|
-
}
|
|
114
|
-
// =============================================================================
|
|
115
|
-
// Connections
|
|
116
|
-
// =============================================================================
|
|
117
|
-
function resolveConnections(node, overlays, log) {
|
|
118
|
-
const result = {};
|
|
119
|
-
for (const [name, connNode] of Object.entries(node.entries)) {
|
|
120
|
-
if (connNode.kind !== 'dict')
|
|
121
|
-
continue;
|
|
122
|
-
const resolved = resolveNode(connNode, overlays, log);
|
|
123
|
-
// compileConnectionEntry guarantees `is` is a string value node, and
|
|
124
|
-
// resolveNode preserves it. Any connection without `is` is a bug in the
|
|
125
|
-
// compiler; skip it defensively.
|
|
126
|
-
if (typeof resolved['is'] !== 'string')
|
|
127
|
-
continue;
|
|
128
|
-
result[name] = resolved;
|
|
71
|
+
function extractCompiledConnections(node) {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const [name, child] of Object.entries(node.entries)) {
|
|
74
|
+
if (child.kind === 'dict')
|
|
75
|
+
out[name] = child;
|
|
129
76
|
}
|
|
130
|
-
return
|
|
77
|
+
return out;
|
|
131
78
|
}
|
|
132
|
-
// =============================================================================
|
|
133
|
-
// Fabrication and property defaults
|
|
134
|
-
// =============================================================================
|
|
135
79
|
/**
|
|
136
|
-
*
|
|
137
|
-
* type not already represented in `
|
|
138
|
-
* `includeDefaultConnections
|
|
139
|
-
*
|
|
140
|
-
*
|
|
80
|
+
* Add a bare `{is: typeName}` compiled entry for each registered connection
|
|
81
|
+
* type not already represented in `compiledConnections`. Only runs when the
|
|
82
|
+
* POJO sets `includeDefaultConnections: true`. Property values (including
|
|
83
|
+
* reference-shaped defaults like DuckDB's `{config: 'rootDirectory'}`) are
|
|
84
|
+
* *not* filled in here — that is the job of the async lookup resolver.
|
|
141
85
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
86
|
+
* Skip rules:
|
|
87
|
+
* - Type already in use: some existing entry has `is: typeName`.
|
|
88
|
+
* - Name already taken: some existing entry is *named* `typeName`, even
|
|
89
|
+
* if its `is` points elsewhere. This protects a user who writes
|
|
90
|
+
* `{duckdb: {is: 'postgres', ...}}` — naming an entry after a type but
|
|
91
|
+
* pointing at a different backend — from being clobbered.
|
|
144
92
|
*
|
|
145
|
-
* Mutates `
|
|
146
|
-
* own freshly-built object.
|
|
93
|
+
* Mutates `compiledConnections` in place.
|
|
147
94
|
*/
|
|
148
|
-
function fabricateMissingConnections(
|
|
95
|
+
function fabricateMissingConnections(compiledConnections) {
|
|
149
96
|
const presentTypes = new Set();
|
|
150
|
-
for (const entry of Object.values(
|
|
151
|
-
|
|
152
|
-
|
|
97
|
+
for (const entry of Object.values(compiledConnections)) {
|
|
98
|
+
const isNode = entry.entries['is'];
|
|
99
|
+
if ((isNode === null || isNode === void 0 ? void 0 : isNode.kind) === 'value' && typeof isNode.value === 'string') {
|
|
100
|
+
presentTypes.add(isNode.value);
|
|
101
|
+
}
|
|
153
102
|
}
|
|
154
103
|
for (const typeName of (0, registry_1.getRegisteredConnectionTypes)()) {
|
|
155
|
-
// `presentTypes` catches {mydb: {is: 'duckdb'}}; the name check catches
|
|
156
|
-
// {duckdb: {is: 'jsondb'}}. Both cases leave the registered `duckdb`
|
|
157
|
-
// type alone — the first because it's already represented, the second
|
|
158
|
-
// because we can't use the obvious name without clobbering.
|
|
159
104
|
if (presentTypes.has(typeName))
|
|
160
105
|
continue;
|
|
161
|
-
if (
|
|
106
|
+
if (compiledConnections[typeName])
|
|
162
107
|
continue;
|
|
163
|
-
|
|
108
|
+
compiledConnections[typeName] = {
|
|
109
|
+
kind: 'dict',
|
|
110
|
+
entries: {
|
|
111
|
+
is: { kind: 'value', value: typeName },
|
|
112
|
+
},
|
|
113
|
+
};
|
|
164
114
|
}
|
|
165
115
|
}
|
|
166
|
-
/**
|
|
167
|
-
* For every connection entry, fill in any property that the user didn't
|
|
168
|
-
* specify and whose `ConnectionPropertyDefinition` declares a `default`.
|
|
169
|
-
* Runs uniformly on both user-listed and fabricated entries — the earlier
|
|
170
|
-
* behavior of only firing during fabrication was a bug that left explicit
|
|
171
|
-
* entries silently underconfigured (e.g. a user-listed `duckdb` never
|
|
172
|
-
* picked up `workingDirectory: {config: 'rootDirectory'}`).
|
|
173
|
-
*
|
|
174
|
-
* Defaults that are reference-shaped resolve through the overlays via
|
|
175
|
-
* `resolveDefault`. Unresolved defaults are silently dropped (case 3).
|
|
176
|
-
* User-specified values are never overwritten.
|
|
177
|
-
*
|
|
178
|
-
* Interaction with inline references: if the user specified a property
|
|
179
|
-
* as a reference-shaped value that failed to resolve (e.g. `{env: 'UNSET'}`),
|
|
180
|
-
* `resolveConnections` already dropped it before we see the entry. From
|
|
181
|
-
* our perspective the property is simply absent, so the default applies —
|
|
182
|
-
* effectively turning inline references into "try this first, else fall
|
|
183
|
-
* back to the default." This is almost always what users want.
|
|
184
|
-
*
|
|
185
|
-
* Mutates `connections` in place. Called only by `resolveConfig` on its
|
|
186
|
-
* own freshly-built object.
|
|
187
|
-
*/
|
|
188
|
-
function applyPropertyDefaults(connections, overlays) {
|
|
189
|
-
var _a;
|
|
190
|
-
for (const entry of Object.values(connections)) {
|
|
191
|
-
const typeName = entry.is;
|
|
192
|
-
if (typeof typeName !== 'string')
|
|
193
|
-
continue;
|
|
194
|
-
const props = (_a = (0, registry_1.getConnectionProperties)(typeName)) !== null && _a !== void 0 ? _a : [];
|
|
195
|
-
for (const prop of props) {
|
|
196
|
-
if (prop.default === undefined)
|
|
197
|
-
continue;
|
|
198
|
-
if (entry[prop.name] !== undefined)
|
|
199
|
-
continue;
|
|
200
|
-
const v = resolveDefault(prop.default, overlays);
|
|
201
|
-
if (v !== undefined)
|
|
202
|
-
entry[prop.name] = v;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Resolve a property `default` field. Literals pass through; single-key
|
|
208
|
-
* reference-shaped objects are resolved through the overlays. Case 3:
|
|
209
|
-
* an unresolved default is a hint, not a requirement — always silent drop.
|
|
210
|
-
*/
|
|
211
|
-
function resolveDefault(def, overlays) {
|
|
212
|
-
if (typeof def !== 'object')
|
|
213
|
-
return def;
|
|
214
|
-
const keys = Object.keys(def);
|
|
215
|
-
if (keys.length !== 1)
|
|
216
|
-
return undefined;
|
|
217
|
-
const source = keys[0];
|
|
218
|
-
const raw = def[source];
|
|
219
|
-
const path = typeof raw === 'string' ? [raw] : raw;
|
|
220
|
-
// The type says `raw` is `string | string[]`, but `default` comes from
|
|
221
|
-
// registered backend definitions which are runtime-dynamic — a
|
|
222
|
-
// malformed registration would blow up inside the overlay otherwise.
|
|
223
|
-
if (!Array.isArray(path))
|
|
224
|
-
return undefined;
|
|
225
|
-
const overlay = overlays[source];
|
|
226
|
-
if (!overlay)
|
|
227
|
-
return undefined;
|
|
228
|
-
return overlay(path);
|
|
229
|
-
}
|
|
230
116
|
//# sourceMappingURL=config_resolve.js.map
|
|
@@ -87,6 +87,12 @@ export declare function getConnectionTypeDisplayName(typeName: string): string |
|
|
|
87
87
|
* Get the names of all registered connection types.
|
|
88
88
|
*/
|
|
89
89
|
export declare function getRegisteredConnectionTypes(): string[];
|
|
90
|
+
/**
|
|
91
|
+
* Get the full definition (factory + properties + displayName) for a
|
|
92
|
+
* registered connection type. Used by the foundation layer's connection
|
|
93
|
+
* lookup to hand fully-resolved configs to the right factory.
|
|
94
|
+
*/
|
|
95
|
+
export declare function getConnectionTypeDef(typeName: string): ConnectionTypeDef | undefined;
|
|
90
96
|
/**
|
|
91
97
|
* Parse a JSON config string into a ConnectionsConfig.
|
|
92
98
|
* Entries without a valid `is` field are silently dropped.
|
|
@@ -9,6 +9,7 @@ exports.registerConnectionType = registerConnectionType;
|
|
|
9
9
|
exports.getConnectionProperties = getConnectionProperties;
|
|
10
10
|
exports.getConnectionTypeDisplayName = getConnectionTypeDisplayName;
|
|
11
11
|
exports.getRegisteredConnectionTypes = getRegisteredConnectionTypes;
|
|
12
|
+
exports.getConnectionTypeDef = getConnectionTypeDef;
|
|
12
13
|
exports.readConnectionsConfig = readConnectionsConfig;
|
|
13
14
|
exports.writeConnectionsConfig = writeConnectionsConfig;
|
|
14
15
|
exports.createConnectionsFromConfig = createConnectionsFromConfig;
|
|
@@ -57,6 +58,14 @@ function getConnectionTypeDisplayName(typeName) {
|
|
|
57
58
|
function getRegisteredConnectionTypes() {
|
|
58
59
|
return [...registry.keys()];
|
|
59
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Get the full definition (factory + properties + displayName) for a
|
|
63
|
+
* registered connection type. Used by the foundation layer's connection
|
|
64
|
+
* lookup to hand fully-resolved configs to the right factory.
|
|
65
|
+
*/
|
|
66
|
+
function getConnectionTypeDef(typeName) {
|
|
67
|
+
return registry.get(typeName);
|
|
68
|
+
}
|
|
60
69
|
/**
|
|
61
70
|
* Parse a JSON config string into a ConnectionsConfig.
|
|
62
71
|
* Entries without a valid `is` field are silently dropped.
|
|
@@ -420,22 +420,18 @@ class DuckDBTypeParser extends tiny_parser_1.TinyParser {
|
|
|
420
420
|
return token.text.toUpperCase();
|
|
421
421
|
}
|
|
422
422
|
typeDef() {
|
|
423
|
-
|
|
423
|
+
var _a, _b;
|
|
424
|
+
const wantID = this.expect('id');
|
|
424
425
|
const id = this.sqlID(wantID);
|
|
425
426
|
let baseType;
|
|
426
427
|
if (id === 'VARCHAR') {
|
|
427
|
-
|
|
428
|
-
this.next();
|
|
429
|
-
}
|
|
428
|
+
this.match('size');
|
|
430
429
|
}
|
|
431
|
-
if ((id === 'DECIMAL' || id === 'NUMERIC') &&
|
|
432
|
-
this.peek().type === 'precision') {
|
|
433
|
-
this.next();
|
|
430
|
+
if ((id === 'DECIMAL' || id === 'NUMERIC') && this.match('precision')) {
|
|
434
431
|
baseType = { type: 'number', numberType: 'float' };
|
|
435
432
|
}
|
|
436
433
|
else if (id === 'TIMESTAMP') {
|
|
437
|
-
if (this.
|
|
438
|
-
this.nextText('WITH', 'TIME', 'ZONE');
|
|
434
|
+
if (this.matchText('WITH', 'TIME', 'ZONE')) {
|
|
439
435
|
baseType = { type: 'timestamptz' };
|
|
440
436
|
}
|
|
441
437
|
else {
|
|
@@ -446,24 +442,21 @@ class DuckDBTypeParser extends tiny_parser_1.TinyParser {
|
|
|
446
442
|
baseType = duckDBToMalloyTypes[id];
|
|
447
443
|
}
|
|
448
444
|
else if (id === 'STRUCT') {
|
|
449
|
-
this.
|
|
445
|
+
this.expect('(');
|
|
450
446
|
baseType = { type: 'record', fields: [] };
|
|
451
447
|
for (;;) {
|
|
452
|
-
const fieldName = this.
|
|
453
|
-
if (fieldName
|
|
454
|
-
|
|
455
|
-
fieldName.type === 'id') {
|
|
456
|
-
const fieldType = this.typeDef();
|
|
457
|
-
baseType.fields.push((0, malloy_types_1.mkFieldDef)(fieldType, this.unquoteName(fieldName)));
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
if (fieldName.type !== ')') {
|
|
448
|
+
const fieldName = (_b = (_a = this.match('qsingle')) !== null && _a !== void 0 ? _a : this.match('qdouble')) !== null && _b !== void 0 ? _b : this.match('id');
|
|
449
|
+
if (!fieldName) {
|
|
450
|
+
if (!this.match(')')) {
|
|
461
451
|
throw this.parseError('Expected identifier or ) to end STRUCT');
|
|
462
452
|
}
|
|
463
453
|
break;
|
|
464
454
|
}
|
|
465
|
-
|
|
466
|
-
|
|
455
|
+
const fieldType = this.typeDef();
|
|
456
|
+
baseType.fields.push((0, malloy_types_1.mkFieldDef)(fieldType, this.unquoteName(fieldName)));
|
|
457
|
+
if (!this.match(',')) {
|
|
458
|
+
this.expect(')');
|
|
459
|
+
break;
|
|
467
460
|
}
|
|
468
461
|
}
|
|
469
462
|
}
|
|
@@ -472,10 +465,8 @@ class DuckDBTypeParser extends tiny_parser_1.TinyParser {
|
|
|
472
465
|
// unknown field type, strip all type decorations, there was a regex for this
|
|
473
466
|
// in the pre-parser code, but no tests, so this is also untested
|
|
474
467
|
let idEnd = wantID.cursor + wantID.text.length;
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
if (this.peek().type === 'eof') {
|
|
468
|
+
this.match('precision');
|
|
469
|
+
if (this.eof()) {
|
|
479
470
|
idEnd = this.input.length;
|
|
480
471
|
}
|
|
481
472
|
baseType = {
|
|
@@ -487,8 +478,7 @@ class DuckDBTypeParser extends tiny_parser_1.TinyParser {
|
|
|
487
478
|
throw this.parseError('Could not understand type');
|
|
488
479
|
}
|
|
489
480
|
}
|
|
490
|
-
while (this.
|
|
491
|
-
this.next();
|
|
481
|
+
while (this.match('arrayOf')) {
|
|
492
482
|
if (baseType.type === 'record') {
|
|
493
483
|
baseType = {
|
|
494
484
|
type: 'array',
|
|
@@ -4,9 +4,41 @@ export interface TinyToken {
|
|
|
4
4
|
text: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Tiny combined lexer/parser for short recursive-descent parsers.
|
|
8
|
+
*
|
|
9
|
+
* TinyParser is intentionally small and biased toward readability over
|
|
10
|
+
* framework features. It is primarily used for schema and type parsers where
|
|
11
|
+
* a hand-written parser is clearer than ad-hoc regex matching, but a parser
|
|
12
|
+
* generator would be overkill.
|
|
13
|
+
*
|
|
14
|
+
* Design goals:
|
|
15
|
+
* - Keep parser implementations short and readable.
|
|
16
|
+
* - Support custom tokenization rules per parser.
|
|
17
|
+
* - Make parser intent obvious at call sites.
|
|
18
|
+
* - Minimize hidden consumption and parser-state surprises.
|
|
19
|
+
*
|
|
20
|
+
* Core parser API:
|
|
21
|
+
* - peek(): inspect the next token without consuming it.
|
|
22
|
+
* - read(): consume and return the next token, regardless of type.
|
|
23
|
+
* - eof(): true if the next token is end-of-input.
|
|
24
|
+
* - expect(...types): consume a required sequence of token types.
|
|
25
|
+
* - expectText(...texts): consume a required sequence of token texts.
|
|
26
|
+
* - match(...types): consume an optional sequence of token types.
|
|
27
|
+
* - matchText(...texts): consume an optional sequence of token texts.
|
|
28
|
+
*
|
|
29
|
+
* Semantics:
|
|
30
|
+
* - expect*() is for required grammar and throws on failure.
|
|
31
|
+
* - match*() is for optional grammar and is atomic. If the full sequence does
|
|
32
|
+
* not match, nothing is consumed.
|
|
33
|
+
* - peek() remains available, but most optional syntax should prefer match*().
|
|
34
|
+
*
|
|
35
|
+
* Token rules are tested in order. The first matching rule wins.
|
|
36
|
+
*
|
|
37
|
+
* Special token rule names:
|
|
38
|
+
* - space: matched text is skipped and never returned.
|
|
39
|
+
* - char: matched text becomes both token.type and token.text.
|
|
40
|
+
* - q*: any token name starting with q is treated as quoted text and has its
|
|
41
|
+
* first and last characters stripped from token.text.
|
|
10
42
|
*
|
|
11
43
|
* NOTE: All parse errors are exceptions.
|
|
12
44
|
*/
|
|
@@ -14,33 +46,36 @@ export declare class TinyParseError extends Error {
|
|
|
14
46
|
}
|
|
15
47
|
export declare class TinyParser {
|
|
16
48
|
readonly input: string;
|
|
17
|
-
private tokens;
|
|
49
|
+
private readonly tokens;
|
|
50
|
+
private tokenCursor;
|
|
51
|
+
private scanCursor;
|
|
52
|
+
private scanState;
|
|
18
53
|
protected parseCursor: number;
|
|
19
|
-
private
|
|
20
|
-
private tokenMap;
|
|
54
|
+
private readonly tokenMap;
|
|
21
55
|
/**
|
|
22
|
-
* The token map is tested in order
|
|
23
|
-
* is {type: tokenMapKey, text: matchingText }, except
|
|
24
|
-
* for the special tokenMapKeys:
|
|
25
|
-
* * space: skipped and never returned
|
|
26
|
-
* * char: matched string return in both .type and .text
|
|
27
|
-
* * q*: any token name starting with 'q' is assumed to be
|
|
28
|
-
* a quoted string and the text will have the first and
|
|
29
|
-
* last characters stripped
|
|
56
|
+
* The token map is tested in order and the first matching rule wins.
|
|
30
57
|
*/
|
|
31
58
|
constructor(input: string, tokenMap?: Record<string, RegExp>);
|
|
32
59
|
parseError(str: string): TinyParseError;
|
|
33
60
|
peek(): TinyToken;
|
|
34
|
-
|
|
61
|
+
read(): TinyToken;
|
|
62
|
+
eof(): boolean;
|
|
63
|
+
private peekAt;
|
|
35
64
|
/**
|
|
36
65
|
* Return next token, if any token types are passed, read and require those
|
|
37
66
|
* tokens, then return the last one.
|
|
38
67
|
* @param types list of token types
|
|
39
68
|
* @returns The last token read
|
|
40
69
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
expect(...types: string[]): TinyToken;
|
|
71
|
+
expectText(...texts: string[]): TinyToken;
|
|
72
|
+
match(...types: string[]): TinyToken | undefined;
|
|
73
|
+
matchText(...texts: string[]): TinyToken | undefined;
|
|
43
74
|
skipTo(type: string): void;
|
|
44
75
|
dump(): TinyToken[];
|
|
45
|
-
private
|
|
76
|
+
private fillBufferTo;
|
|
77
|
+
private consume;
|
|
78
|
+
private readNextToken;
|
|
79
|
+
private scanToken;
|
|
80
|
+
private eofToken;
|
|
46
81
|
}
|