@malloydata/malloy-tag 0.0.339 → 0.0.340
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/index.d.ts +1 -3
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/{peggy/index.d.ts → parser.d.ts} +13 -4
- package/dist/parser.js +181 -0
- package/dist/parser.js.map +1 -0
- package/package.json +13 -6
- package/src/index.ts +1 -3
- package/src/parser.ts +203 -0
- package/CONTEXT.md +0 -173
- package/README.md +0 -0
- package/dist/peggy/dist/peg-tag-parser.d.ts +0 -11
- package/dist/peggy/dist/peg-tag-parser.js +0 -3130
- package/dist/peggy/dist/peg-tag-parser.js.map +0 -1
- package/dist/peggy/index.js +0 -117
- package/dist/peggy/index.js.map +0 -1
- package/dist/peggy/interpreter.d.ts +0 -32
- package/dist/peggy/interpreter.js +0 -208
- package/dist/peggy/interpreter.js.map +0 -1
- package/dist/peggy/statements.d.ts +0 -51
- package/dist/peggy/statements.js +0 -7
- package/dist/peggy/statements.js.map +0 -1
- package/dist/schema.d.ts +0 -41
- package/dist/schema.js +0 -573
- package/dist/schema.js.map +0 -1
- package/dist/schema.spec.d.ts +0 -1
- package/dist/schema.spec.js +0 -980
- package/dist/schema.spec.js.map +0 -1
- package/dist/tags.spec.d.ts +0 -8
- package/dist/tags.spec.js +0 -884
- package/dist/tags.spec.js.map +0 -1
- package/dist/util.spec.d.ts +0 -1
- package/dist/util.spec.js +0 -43
- package/dist/util.spec.js.map +0 -1
- package/src/motly-schema.motly +0 -52
- package/src/peggy/dist/peg-tag-parser.js +0 -2790
- package/src/peggy/index.ts +0 -89
- package/src/peggy/interpreter.ts +0 -265
- package/src/peggy/malloy-tag.peggy +0 -224
- package/src/peggy/statements.ts +0 -49
- package/src/schema.spec.ts +0 -1280
- package/src/schema.ts +0 -852
- package/src/tags.spec.ts +0 -967
- package/src/util.spec.ts +0 -43
- package/tsconfig.json +0 -12
package/src/peggy/index.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright Contributors to the Malloy project
|
|
3
|
-
* SPDX-License-Identifier: MIT
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type {TagParse, TagError} from '../tags';
|
|
7
|
-
import {Tag} from '../tags';
|
|
8
|
-
import {Interpreter} from './interpreter';
|
|
9
|
-
import type {TagStatement} from './statements';
|
|
10
|
-
import * as parser from './dist/peg-tag-parser';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Parse a single line of Malloy tag language into a Tag.
|
|
14
|
-
* Internal helper - use parseTag() as the public API.
|
|
15
|
-
*/
|
|
16
|
-
function parseTagLine(source: string, extending: Tag | undefined): TagParse {
|
|
17
|
-
// Skip the prefix if present (e.g., "# " or "#(docs) ")
|
|
18
|
-
if (source[0] === '#') {
|
|
19
|
-
const skipTo = source.indexOf(' ');
|
|
20
|
-
if (skipTo > 0) {
|
|
21
|
-
source = source.slice(skipTo);
|
|
22
|
-
} else {
|
|
23
|
-
source = '';
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const errors: TagError[] = [];
|
|
28
|
-
let statements: TagStatement[] = [];
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
statements = parser.parse(source);
|
|
32
|
-
} catch (e: unknown) {
|
|
33
|
-
if (e && typeof e === 'object' && 'location' in e && 'message' in e) {
|
|
34
|
-
const peggyError = e as {
|
|
35
|
-
message: string;
|
|
36
|
-
location: {start: {line: number; column: number}};
|
|
37
|
-
};
|
|
38
|
-
// Return 0-based line and offset within the input string
|
|
39
|
-
errors.push({
|
|
40
|
-
code: 'tag-parse-syntax-error',
|
|
41
|
-
message: peggyError.message,
|
|
42
|
-
line: peggyError.location.start.line - 1,
|
|
43
|
-
offset: peggyError.location.start.column - 1,
|
|
44
|
-
});
|
|
45
|
-
} else {
|
|
46
|
-
errors.push({
|
|
47
|
-
code: 'tag-parse-syntax-error',
|
|
48
|
-
message: String(e),
|
|
49
|
-
line: 0,
|
|
50
|
-
offset: 0,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return {tag: extending?.clone() ?? new Tag({}), log: errors};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const interpreter = new Interpreter();
|
|
57
|
-
const tag = interpreter.execute(statements, extending);
|
|
58
|
-
|
|
59
|
-
return {tag, log: errors};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Parse Malloy tag language into a Tag which can be queried.
|
|
64
|
-
*
|
|
65
|
-
* @param source - A single string or array of strings to parse. If a string
|
|
66
|
-
* starts with #, all characters up to the first space are skipped.
|
|
67
|
-
* When an array is provided, strings are parsed sequentially and merged.
|
|
68
|
-
* @param extending - A tag which this parse will extend
|
|
69
|
-
* @returns TagParse with the resulting tag and any errors. For arrays,
|
|
70
|
-
* error line numbers indicate the index in the array where the error occurred.
|
|
71
|
-
*/
|
|
72
|
-
export function parseTag(source: string | string[], extending?: Tag): TagParse {
|
|
73
|
-
if (typeof source === 'string') {
|
|
74
|
-
return parseTagLine(source, extending);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const allErrs: TagError[] = [];
|
|
78
|
-
let current: Tag = extending ?? new Tag({});
|
|
79
|
-
for (let i = 0; i < source.length; i++) {
|
|
80
|
-
const text = source[i];
|
|
81
|
-
const noteParse = parseTagLine(text, current);
|
|
82
|
-
current = noteParse.tag;
|
|
83
|
-
// Adjust error line to be the index in the array
|
|
84
|
-
for (const err of noteParse.log) {
|
|
85
|
-
allErrs.push({...err, line: i + err.line});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return {tag: current, log: allErrs};
|
|
89
|
-
}
|
package/src/peggy/interpreter.ts
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright Contributors to the Malloy project
|
|
3
|
-
* SPDX-License-Identifier: MIT
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import {Tag, RefTag} from '../tags';
|
|
7
|
-
import type {TagStatement, TagValue, ArrayElement} from './statements';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Executes TagStatements to build a Tag object.
|
|
11
|
-
*/
|
|
12
|
-
export class Interpreter {
|
|
13
|
-
execute(statements: TagStatement[], extending?: Tag): Tag {
|
|
14
|
-
// Root tag has no parent
|
|
15
|
-
const tag = extending?.clone() ?? new Tag({});
|
|
16
|
-
|
|
17
|
-
for (const stmt of statements) {
|
|
18
|
-
this.executeStatement(stmt, tag);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return tag;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
private executeStatement(stmt: TagStatement, tag: Tag): void {
|
|
25
|
-
switch (stmt.kind) {
|
|
26
|
-
case 'setEq':
|
|
27
|
-
this.executeSetEq(stmt, tag);
|
|
28
|
-
break;
|
|
29
|
-
case 'replaceProperties':
|
|
30
|
-
this.executeReplaceProperties(stmt, tag);
|
|
31
|
-
break;
|
|
32
|
-
case 'updateProperties':
|
|
33
|
-
this.executeUpdateProperties(stmt, tag);
|
|
34
|
-
break;
|
|
35
|
-
case 'define':
|
|
36
|
-
this.executeDefine(stmt, tag);
|
|
37
|
-
break;
|
|
38
|
-
case 'clearAll':
|
|
39
|
-
tag.properties = {};
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
private executeSetEq(
|
|
45
|
-
stmt: {
|
|
46
|
-
kind: 'setEq';
|
|
47
|
-
path: string[];
|
|
48
|
-
value: TagValue;
|
|
49
|
-
properties?: TagStatement[];
|
|
50
|
-
preserveProperties?: boolean;
|
|
51
|
-
},
|
|
52
|
-
tag: Tag
|
|
53
|
-
): void {
|
|
54
|
-
const [writeKey, writeInto, parentTag] = this.buildAccessPath(
|
|
55
|
-
tag,
|
|
56
|
-
stmt.path
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
if (stmt.properties) {
|
|
60
|
-
// name = value { new_properties } - replace properties with new ones
|
|
61
|
-
const resultTag = this.createTagWithValue(stmt.value, parentTag);
|
|
62
|
-
for (const propStmt of stmt.properties) {
|
|
63
|
-
this.executeStatement(propStmt, resultTag);
|
|
64
|
-
}
|
|
65
|
-
writeInto[writeKey] = resultTag;
|
|
66
|
-
} else if (stmt.preserveProperties) {
|
|
67
|
-
// name = value { ... } - preserve existing properties, update value
|
|
68
|
-
const existing = writeInto[writeKey];
|
|
69
|
-
if (existing && stmt.value.kind !== 'reference') {
|
|
70
|
-
// Update value in place, preserving properties and parent chains
|
|
71
|
-
this.setTagValue(existing, stmt.value);
|
|
72
|
-
} else {
|
|
73
|
-
// No existing tag, or reference value (which requires a RefTag)
|
|
74
|
-
const resultTag = this.createTagWithValue(stmt.value, parentTag);
|
|
75
|
-
if (existing?.properties) {
|
|
76
|
-
// Clone properties with correct parent to preserve parent chains
|
|
77
|
-
resultTag.properties = {};
|
|
78
|
-
for (const [key, val] of Object.entries(existing.properties)) {
|
|
79
|
-
resultTag.properties[key] = val.clone(resultTag);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
writeInto[writeKey] = resultTag;
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
// name = value - simple assignment
|
|
86
|
-
writeInto[writeKey] = this.createTagWithValue(stmt.value, parentTag);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
private executeReplaceProperties(
|
|
91
|
-
stmt: {
|
|
92
|
-
kind: 'replaceProperties';
|
|
93
|
-
path: string[];
|
|
94
|
-
properties: TagStatement[];
|
|
95
|
-
preserveValue: boolean;
|
|
96
|
-
},
|
|
97
|
-
tag: Tag
|
|
98
|
-
): void {
|
|
99
|
-
const [writeKey, writeInto, parentTag] = this.buildAccessPath(
|
|
100
|
-
tag,
|
|
101
|
-
stmt.path
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
if (stmt.preserveValue) {
|
|
105
|
-
// name = ... { properties } - preserve value, replace properties
|
|
106
|
-
const existing = writeInto[writeKey];
|
|
107
|
-
const resultTag = new Tag({}, parentTag);
|
|
108
|
-
if (existing) {
|
|
109
|
-
resultTag.eq = existing.eq;
|
|
110
|
-
}
|
|
111
|
-
for (const propStmt of stmt.properties) {
|
|
112
|
-
this.executeStatement(propStmt, resultTag);
|
|
113
|
-
}
|
|
114
|
-
writeInto[writeKey] = resultTag;
|
|
115
|
-
} else {
|
|
116
|
-
// name = { properties } - no value, replace properties
|
|
117
|
-
const resultTag = new Tag({}, parentTag);
|
|
118
|
-
for (const propStmt of stmt.properties) {
|
|
119
|
-
this.executeStatement(propStmt, resultTag);
|
|
120
|
-
}
|
|
121
|
-
writeInto[writeKey] = resultTag;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private executeUpdateProperties(
|
|
126
|
-
stmt: {
|
|
127
|
-
kind: 'updateProperties';
|
|
128
|
-
path: string[];
|
|
129
|
-
properties: TagStatement[];
|
|
130
|
-
},
|
|
131
|
-
tag: Tag
|
|
132
|
-
): void {
|
|
133
|
-
const [writeKey, writeInto, parentTag] = this.buildAccessPath(
|
|
134
|
-
tag,
|
|
135
|
-
stmt.path
|
|
136
|
-
);
|
|
137
|
-
// Create or reuse the result tag - this is the tag that will be stored
|
|
138
|
-
// and that child tags will have as their parent
|
|
139
|
-
const resultTag = writeInto[writeKey] ?? new Tag({}, parentTag);
|
|
140
|
-
|
|
141
|
-
// Execute nested statements in the context of the result tag
|
|
142
|
-
for (const propStmt of stmt.properties) {
|
|
143
|
-
this.executeStatement(propStmt, resultTag);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
writeInto[writeKey] = resultTag;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private executeDefine(
|
|
150
|
-
stmt: {kind: 'define'; path: string[]; deleted: boolean},
|
|
151
|
-
tag: Tag
|
|
152
|
-
): void {
|
|
153
|
-
const [writeKey, writeInto, parentTag] = this.buildAccessPath(
|
|
154
|
-
tag,
|
|
155
|
-
stmt.path
|
|
156
|
-
);
|
|
157
|
-
if (stmt.deleted) {
|
|
158
|
-
writeInto[writeKey] = new Tag({deleted: true}, parentTag);
|
|
159
|
-
} else {
|
|
160
|
-
writeInto[writeKey] = new Tag({}, parentTag);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Navigate to the parent of the final path segment, creating intermediate
|
|
166
|
-
* tags as needed. Returns [finalKey, parentDict, parentTag] so caller can write to it.
|
|
167
|
-
*/
|
|
168
|
-
private buildAccessPath(
|
|
169
|
-
tag: Tag,
|
|
170
|
-
path: string[]
|
|
171
|
-
): [string, Record<string, Tag>, Tag] {
|
|
172
|
-
if (path.length === 0) {
|
|
173
|
-
throw new Error('INTERNAL ERROR: buildAccessPath called with empty path');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
let currentTag = tag;
|
|
177
|
-
let parentDict = tag.getProperties();
|
|
178
|
-
|
|
179
|
-
for (const segment of path.slice(0, -1)) {
|
|
180
|
-
let next: Tag;
|
|
181
|
-
if (parentDict[segment] === undefined) {
|
|
182
|
-
next = new Tag({}, currentTag);
|
|
183
|
-
parentDict[segment] = next;
|
|
184
|
-
} else {
|
|
185
|
-
// Ensure properties exists on this intermediate tag
|
|
186
|
-
parentDict[segment].properties ??= {};
|
|
187
|
-
next = parentDict[segment];
|
|
188
|
-
}
|
|
189
|
-
currentTag = next;
|
|
190
|
-
parentDict = next.getProperties();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return [path[path.length - 1], parentDict, currentTag];
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Resolve array elements to Tag[] with proper parent links.
|
|
198
|
-
*/
|
|
199
|
-
private resolveArrayWithParent(elements: ArrayElement[], parent: Tag): Tag[] {
|
|
200
|
-
return elements.map(el => {
|
|
201
|
-
// Reference without properties becomes a RefTag
|
|
202
|
-
if (el.value?.kind === 'reference' && !el.properties) {
|
|
203
|
-
return new RefTag(el.value.ups, el.value.path, parent);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const resultTag = new Tag({}, parent);
|
|
207
|
-
|
|
208
|
-
if (el.value) {
|
|
209
|
-
if (el.value.kind === 'array') {
|
|
210
|
-
// Nested array
|
|
211
|
-
resultTag.eq = this.resolveArrayWithParent(
|
|
212
|
-
el.value.elements,
|
|
213
|
-
resultTag
|
|
214
|
-
);
|
|
215
|
-
} else if (el.value.kind !== 'reference') {
|
|
216
|
-
resultTag.eq = el.value.value;
|
|
217
|
-
}
|
|
218
|
-
// References with properties are ignored (just the properties are kept)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (el.properties) {
|
|
222
|
-
for (const stmt of el.properties) {
|
|
223
|
-
this.executeStatement(stmt, resultTag);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return resultTag;
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Update an existing tag's value in place, preserving its properties.
|
|
233
|
-
* Note: References must be handled separately since they require a RefTag.
|
|
234
|
-
*/
|
|
235
|
-
private setTagValue(
|
|
236
|
-
tag: Tag,
|
|
237
|
-
valueData: Exclude<TagValue, {kind: 'reference'}>
|
|
238
|
-
): void {
|
|
239
|
-
if (valueData.kind === 'array') {
|
|
240
|
-
tag.eq = this.resolveArrayWithParent(valueData.elements, tag);
|
|
241
|
-
} else {
|
|
242
|
-
tag.eq = valueData.value;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Create a Tag with value, resolving arrays with proper parent links.
|
|
248
|
-
* Returns a RefTag for reference values.
|
|
249
|
-
*/
|
|
250
|
-
private createTagWithValue(valueData: TagValue, parent: Tag): Tag {
|
|
251
|
-
if (valueData.kind === 'reference') {
|
|
252
|
-
return new RefTag(valueData.ups, valueData.path, parent);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const resultTag = new Tag({}, parent);
|
|
256
|
-
|
|
257
|
-
if (valueData.kind === 'array') {
|
|
258
|
-
resultTag.eq = this.resolveArrayWithParent(valueData.elements, resultTag);
|
|
259
|
-
} else {
|
|
260
|
-
resultTag.eq = valueData.value;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return resultTag;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright Contributors to the Malloy project
|
|
3
|
-
* SPDX-License-Identifier: MIT
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
{{
|
|
7
|
-
// Helper to parse escape sequences in strings
|
|
8
|
-
// Handles: \b \f \n \r \t \uXXXX and passthrough for other escapes
|
|
9
|
-
function parseEscapes(str) {
|
|
10
|
-
return str.replace(/\\(u[0-9A-Fa-f]{4}|.)/g, (match, capture) => {
|
|
11
|
-
if (capture.startsWith('u') && capture.length === 5) {
|
|
12
|
-
return String.fromCharCode(parseInt(capture.slice(1), 16));
|
|
13
|
-
}
|
|
14
|
-
switch (capture) {
|
|
15
|
-
case 'b': return '\b';
|
|
16
|
-
case 'f': return '\f';
|
|
17
|
-
case 'n': return '\n';
|
|
18
|
-
case 'r': return '\r';
|
|
19
|
-
case 't': return '\t';
|
|
20
|
-
default: return capture;
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}}
|
|
25
|
-
|
|
26
|
-
// Entry point - supports multi-line input
|
|
27
|
-
tagLine = specs:(_ @tagSpec)* _ { return specs; }
|
|
28
|
-
|
|
29
|
-
// Line comment: # to end of line
|
|
30
|
-
comment = "#" [^\r\n]* ("\r\n" / "\r" / "\n" / !.)
|
|
31
|
-
|
|
32
|
-
tagSpec
|
|
33
|
-
= tagEq
|
|
34
|
-
/ tagReplaceProperties
|
|
35
|
-
/ tagUpdateProperties
|
|
36
|
-
/ tagEmpty
|
|
37
|
-
/ tagDef
|
|
38
|
-
|
|
39
|
-
// name = value { properties } or name = value { ... }
|
|
40
|
-
tagEq
|
|
41
|
-
= path:propName _ "=" _ value:eqValue props:(_ properties)? {
|
|
42
|
-
const result = {
|
|
43
|
-
kind: 'setEq',
|
|
44
|
-
path,
|
|
45
|
-
value
|
|
46
|
-
};
|
|
47
|
-
if (props) {
|
|
48
|
-
if (props[1].dotty) {
|
|
49
|
-
result.preserveProperties = true;
|
|
50
|
-
} else {
|
|
51
|
-
result.properties = props[1].statements;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// name = { properties } or name = ... { properties } or name: { properties }
|
|
58
|
-
tagReplaceProperties
|
|
59
|
-
= path:propName _ "=" _ dotty:"..."? _ props:properties {
|
|
60
|
-
return {
|
|
61
|
-
kind: 'replaceProperties',
|
|
62
|
-
path,
|
|
63
|
-
properties: props.statements,
|
|
64
|
-
preserveValue: dotty !== null
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
/ path:propName _ ":" _ props:properties {
|
|
68
|
-
return {
|
|
69
|
-
kind: 'replaceProperties',
|
|
70
|
-
path,
|
|
71
|
-
properties: props.statements,
|
|
72
|
-
preserveValue: false
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// name { properties }
|
|
77
|
-
tagUpdateProperties
|
|
78
|
-
= path:propName _ props:properties {
|
|
79
|
-
return {
|
|
80
|
-
kind: 'updateProperties',
|
|
81
|
-
path,
|
|
82
|
-
properties: props.statements
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// name or -name
|
|
87
|
-
tagDef
|
|
88
|
-
= deleted:"-"? path:propName {
|
|
89
|
-
return {
|
|
90
|
-
kind: 'define',
|
|
91
|
-
path,
|
|
92
|
-
deleted: deleted !== null
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// -...
|
|
97
|
-
tagEmpty
|
|
98
|
-
= "-..." { return { kind: 'clearAll' }; }
|
|
99
|
-
|
|
100
|
-
// Property path: a.b.c
|
|
101
|
-
propName
|
|
102
|
-
= head:identifier tail:("." @identifier)* {
|
|
103
|
-
return [head, ...tail];
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Values that can be assigned with =
|
|
107
|
-
eqValue
|
|
108
|
-
= arrayValue
|
|
109
|
-
/ booleanValue
|
|
110
|
-
/ dateValue
|
|
111
|
-
/ referenceValue
|
|
112
|
-
/ numberValue
|
|
113
|
-
/ stringValue
|
|
114
|
-
|
|
115
|
-
// Reference to another value in the tag tree
|
|
116
|
-
// $path.to.thing - absolute from root
|
|
117
|
-
// $^thing - up one level
|
|
118
|
-
// $^^thing - up two levels
|
|
119
|
-
// $items[0].name - with array indexing
|
|
120
|
-
referenceValue
|
|
121
|
-
= "$" ups:"^"* first:refPathElement rest:("." @refPathElement)* {
|
|
122
|
-
const path = [first, ...rest].flat();
|
|
123
|
-
return { kind: 'reference', ups: ups.length, path };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
refPathElement
|
|
127
|
-
= name:identifier index:("[" _ @$[0-9]+ _ "]")? {
|
|
128
|
-
return index !== null ? [name, parseInt(index, 10)] : [name];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
booleanValue
|
|
132
|
-
= "@true" { return { kind: 'boolean', value: true }; }
|
|
133
|
-
/ "@false" { return { kind: 'boolean', value: false }; }
|
|
134
|
-
|
|
135
|
-
dateValue
|
|
136
|
-
= "@" date:$isoDate { return { kind: 'date', value: new Date(date) }; }
|
|
137
|
-
|
|
138
|
-
// ISO 8601 date/datetime patterns
|
|
139
|
-
isoDate
|
|
140
|
-
= [0-9][0-9][0-9][0-9] "-" [0-9][0-9] "-" [0-9][0-9] ("T" [0-9][0-9] ":" [0-9][0-9] (":" [0-9][0-9] ("." [0-9]+)?)? ("Z" / [+-] [0-9][0-9] ":"? [0-9][0-9])?)?
|
|
141
|
-
|
|
142
|
-
numberValue
|
|
143
|
-
= n:numericLiteral { return { kind: 'number', value: parseFloat(n) }; }
|
|
144
|
-
|
|
145
|
-
stringValue
|
|
146
|
-
= s:textString { return { kind: 'string', value: s }; }
|
|
147
|
-
|
|
148
|
-
// Array: [element, element, ...]
|
|
149
|
-
arrayValue
|
|
150
|
-
= "[" _ "]" { return { kind: 'array', elements: [] }; }
|
|
151
|
-
/ "[" _ head:arrayElement tail:(_ "," _ @arrayElement)* _ ","? _ "]" {
|
|
152
|
-
return { kind: 'array', elements: [head, ...tail] };
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Scalar value for array elements (typed)
|
|
156
|
-
scalarValue
|
|
157
|
-
= booleanValue
|
|
158
|
-
/ dateValue
|
|
159
|
-
/ referenceValue
|
|
160
|
-
/ numberValue
|
|
161
|
-
/ s:textString { return { kind: 'string', value: s }; }
|
|
162
|
-
|
|
163
|
-
// Array element: can be value, value with props, just props, or nested array
|
|
164
|
-
arrayElement
|
|
165
|
-
= value:scalarValue _ props:properties {
|
|
166
|
-
return {
|
|
167
|
-
value,
|
|
168
|
-
properties: props.statements
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
/ value:scalarValue {
|
|
172
|
-
return { value };
|
|
173
|
-
}
|
|
174
|
-
/ props:properties {
|
|
175
|
-
return { properties: props.statements };
|
|
176
|
-
}
|
|
177
|
-
/ value:arrayValue {
|
|
178
|
-
return { value };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Properties block: { ... } or { tagSpec* }
|
|
182
|
-
properties
|
|
183
|
-
= "{" _ "..." _ "}" { return { dotty: true }; }
|
|
184
|
-
/ "{" specs:(_ @tagSpec)* _ "}" { return { dotty: false, statements: specs }; }
|
|
185
|
-
|
|
186
|
-
// Identifiers for property names
|
|
187
|
-
identifier
|
|
188
|
-
= bqString
|
|
189
|
-
/ bareString
|
|
190
|
-
|
|
191
|
-
// String literals (text only, not numbers)
|
|
192
|
-
textString
|
|
193
|
-
= tripleString
|
|
194
|
-
/ sqString
|
|
195
|
-
/ dqString
|
|
196
|
-
/ bareString
|
|
197
|
-
|
|
198
|
-
// Bare string: alphanumeric and underscore
|
|
199
|
-
bareString
|
|
200
|
-
= chars:$[0-9A-Za-z_\u00C0-\u024F\u1E00-\u1EFF]+ { return chars; }
|
|
201
|
-
|
|
202
|
-
// Triple-quoted string (multi-line allowed)
|
|
203
|
-
tripleString
|
|
204
|
-
= '"""' chars:$([^"\\] / '"' !('""') / "\\" .)* '"""' { return parseEscapes(chars); }
|
|
205
|
-
|
|
206
|
-
// Single-quoted string (no raw newlines)
|
|
207
|
-
sqString
|
|
208
|
-
= "'" chars:$([^'\\\r\n] / "\\" .)* "'" { return parseEscapes(chars); }
|
|
209
|
-
|
|
210
|
-
// Double-quoted string (no raw newlines)
|
|
211
|
-
dqString
|
|
212
|
-
= '"' chars:$([^"\\\r\n] / "\\" .)* '"' { return parseEscapes(chars); }
|
|
213
|
-
|
|
214
|
-
// Backtick-quoted string (for identifiers with special chars, no raw newlines)
|
|
215
|
-
bqString
|
|
216
|
-
= '`' chars:$([^`\\\r\n] / "\\" .)* '`' { return parseEscapes(chars); }
|
|
217
|
-
|
|
218
|
-
// Numeric literals
|
|
219
|
-
numericLiteral
|
|
220
|
-
= $("-"? [0-9]* "." [0-9]+ ([Ee] [+-]? [0-9]+)?)
|
|
221
|
-
/ $("-"? [0-9]+ ([Ee] [+-]? [0-9]+)?)
|
|
222
|
-
|
|
223
|
-
// Whitespace and comments (required between some tokens, optional elsewhere)
|
|
224
|
-
_ = ([ \t\r\n] / comment)*
|
package/src/peggy/statements.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright Contributors to the Malloy project
|
|
3
|
-
* SPDX-License-Identifier: MIT
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Intermediate representation for tag statements.
|
|
8
|
-
* The Peggy parser outputs these, and the interpreter executes them to build a Tag.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Scalar value types
|
|
12
|
-
export type ScalarValue =
|
|
13
|
-
| {kind: 'string'; value: string}
|
|
14
|
-
| {kind: 'number'; value: number}
|
|
15
|
-
| {kind: 'boolean'; value: boolean}
|
|
16
|
-
| {kind: 'date'; value: Date}
|
|
17
|
-
| {kind: 'reference'; ups: number; path: (string | number)[]};
|
|
18
|
-
|
|
19
|
-
// Values that can be assigned
|
|
20
|
-
export type TagValue = ScalarValue | {kind: 'array'; elements: ArrayElement[]};
|
|
21
|
-
|
|
22
|
-
export type ArrayElement = {
|
|
23
|
-
value?: TagValue;
|
|
24
|
-
properties?: TagStatement[];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Operations the parser outputs
|
|
28
|
-
export type TagStatement =
|
|
29
|
-
// name = value { properties } or name = value {...}
|
|
30
|
-
| {
|
|
31
|
-
kind: 'setEq';
|
|
32
|
-
path: string[];
|
|
33
|
-
value: TagValue;
|
|
34
|
-
properties?: TagStatement[];
|
|
35
|
-
preserveProperties?: boolean; // true when {...} is used
|
|
36
|
-
}
|
|
37
|
-
// name = { properties } or name = ... { properties }
|
|
38
|
-
| {
|
|
39
|
-
kind: 'replaceProperties';
|
|
40
|
-
path: string[];
|
|
41
|
-
properties: TagStatement[];
|
|
42
|
-
preserveValue: boolean; // true when ... is used
|
|
43
|
-
}
|
|
44
|
-
// name { properties }
|
|
45
|
-
| {kind: 'updateProperties'; path: string[]; properties: TagStatement[]}
|
|
46
|
-
// name or -name
|
|
47
|
-
| {kind: 'define'; path: string[]; deleted: boolean}
|
|
48
|
-
// -...
|
|
49
|
-
| {kind: 'clearAll'};
|