@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.
Files changed (45) hide show
  1. package/dist/index.d.ts +1 -3
  2. package/dist/index.js +4 -5
  3. package/dist/index.js.map +1 -1
  4. package/dist/{peggy/index.d.ts → parser.d.ts} +13 -4
  5. package/dist/parser.js +181 -0
  6. package/dist/parser.js.map +1 -0
  7. package/package.json +13 -6
  8. package/src/index.ts +1 -3
  9. package/src/parser.ts +203 -0
  10. package/CONTEXT.md +0 -173
  11. package/README.md +0 -0
  12. package/dist/peggy/dist/peg-tag-parser.d.ts +0 -11
  13. package/dist/peggy/dist/peg-tag-parser.js +0 -3130
  14. package/dist/peggy/dist/peg-tag-parser.js.map +0 -1
  15. package/dist/peggy/index.js +0 -117
  16. package/dist/peggy/index.js.map +0 -1
  17. package/dist/peggy/interpreter.d.ts +0 -32
  18. package/dist/peggy/interpreter.js +0 -208
  19. package/dist/peggy/interpreter.js.map +0 -1
  20. package/dist/peggy/statements.d.ts +0 -51
  21. package/dist/peggy/statements.js +0 -7
  22. package/dist/peggy/statements.js.map +0 -1
  23. package/dist/schema.d.ts +0 -41
  24. package/dist/schema.js +0 -573
  25. package/dist/schema.js.map +0 -1
  26. package/dist/schema.spec.d.ts +0 -1
  27. package/dist/schema.spec.js +0 -980
  28. package/dist/schema.spec.js.map +0 -1
  29. package/dist/tags.spec.d.ts +0 -8
  30. package/dist/tags.spec.js +0 -884
  31. package/dist/tags.spec.js.map +0 -1
  32. package/dist/util.spec.d.ts +0 -1
  33. package/dist/util.spec.js +0 -43
  34. package/dist/util.spec.js.map +0 -1
  35. package/src/motly-schema.motly +0 -52
  36. package/src/peggy/dist/peg-tag-parser.js +0 -2790
  37. package/src/peggy/index.ts +0 -89
  38. package/src/peggy/interpreter.ts +0 -265
  39. package/src/peggy/malloy-tag.peggy +0 -224
  40. package/src/peggy/statements.ts +0 -49
  41. package/src/schema.spec.ts +0 -1280
  42. package/src/schema.ts +0 -852
  43. package/src/tags.spec.ts +0 -967
  44. package/src/util.spec.ts +0 -43
  45. package/tsconfig.json +0 -12
@@ -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
- }
@@ -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)*
@@ -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'};