@malloydata/malloy-tag 0.0.335 → 0.0.336
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/CONTEXT.md +83 -9
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/peggy/dist/peg-tag-parser.d.ts +11 -0
- package/dist/peggy/dist/peg-tag-parser.js +3130 -0
- package/dist/peggy/dist/peg-tag-parser.js.map +1 -0
- package/dist/peggy/index.d.ts +13 -0
- package/dist/peggy/index.js +117 -0
- package/dist/peggy/index.js.map +1 -0
- package/dist/peggy/interpreter.d.ts +32 -0
- package/dist/peggy/interpreter.js +208 -0
- package/dist/peggy/interpreter.js.map +1 -0
- package/dist/peggy/statements.d.ts +51 -0
- package/dist/peggy/statements.js +7 -0
- package/dist/peggy/statements.js.map +1 -0
- package/dist/schema.d.ts +41 -0
- package/dist/schema.js +573 -0
- package/dist/schema.js.map +1 -0
- package/dist/schema.spec.d.ts +1 -0
- package/dist/schema.spec.js +980 -0
- package/dist/schema.spec.js.map +1 -0
- package/dist/tags.d.ts +144 -37
- package/dist/tags.js +535 -344
- package/dist/tags.js.map +1 -1
- package/dist/tags.spec.js +524 -45
- package/dist/tags.spec.js.map +1 -1
- package/package.json +6 -8
- package/src/index.ts +3 -0
- package/src/motly-schema.motly +52 -0
- package/src/peggy/dist/peg-tag-parser.js +2790 -0
- package/src/peggy/index.ts +89 -0
- package/src/peggy/interpreter.ts +265 -0
- package/src/peggy/malloy-tag.peggy +224 -0
- package/src/peggy/statements.ts +49 -0
- package/src/schema.spec.ts +1280 -0
- package/src/schema.ts +852 -0
- package/src/tags.spec.ts +591 -46
- package/src/tags.ts +597 -398
- package/tsconfig.json +3 -2
- package/dist/lib/Malloy/MalloyTagLexer.d.ts +0 -42
- package/dist/lib/Malloy/MalloyTagLexer.js +0 -395
- package/dist/lib/Malloy/MalloyTagLexer.js.map +0 -1
- package/dist/lib/Malloy/MalloyTagParser.d.ts +0 -180
- package/dist/lib/Malloy/MalloyTagParser.js +0 -1077
- package/dist/lib/Malloy/MalloyTagParser.js.map +0 -1
- package/dist/lib/Malloy/MalloyTagVisitor.d.ts +0 -120
- package/dist/lib/Malloy/MalloyTagVisitor.js +0 -4
- package/dist/lib/Malloy/MalloyTagVisitor.js.map +0 -1
- package/scripts/build_parser.js +0 -98
- package/src/MalloyTag.g4 +0 -104
- package/src/lib/Malloy/MalloyTag.interp +0 -61
- package/src/lib/Malloy/MalloyTag.tokens +0 -32
- package/src/lib/Malloy/MalloyTagLexer.interp +0 -85
- package/src/lib/Malloy/MalloyTagLexer.tokens +0 -32
- package/src/lib/Malloy/MalloyTagLexer.ts +0 -386
- package/src/lib/Malloy/MalloyTagParser.ts +0 -1065
- package/src/lib/Malloy/MalloyTagVisitor.ts +0 -141
- package/src/lib/Malloy/_BUILD_DIGEST_ +0 -1
package/dist/tags.js
CHANGED
|
@@ -22,34 +22,48 @@
|
|
|
22
22
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.Tag = void 0;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const MalloyTagParser_1 = require("./lib/Malloy/MalloyTagParser");
|
|
29
|
-
const antlr4ts_1 = require("antlr4ts");
|
|
30
|
-
const util_1 = require("./util");
|
|
25
|
+
exports.RefTag = exports.Tag = void 0;
|
|
26
|
+
exports.interfaceFromTag = interfaceFromTag;
|
|
27
|
+
exports.interfaceFromDict = interfaceFromDict;
|
|
31
28
|
/**
|
|
32
29
|
* Class for interacting with the parsed output of an annotation
|
|
33
30
|
* containing the Malloy tag language. Used by the parser to
|
|
34
31
|
* generate parsed data, and as an API to that data.
|
|
35
32
|
* ```
|
|
36
33
|
* tag.text(p?) => string value of tag.p or undefined
|
|
37
|
-
* tag.array(p?) => Tag[] value of tag.p or undefined
|
|
38
34
|
* tag.numeric(p?) => numeric value of tag.p or undefined
|
|
39
|
-
* tag.
|
|
40
|
-
* tag.
|
|
41
|
-
* tag.
|
|
42
|
-
* tag.
|
|
43
|
-
* tag.
|
|
35
|
+
* tag.boolean(p?) => boolean value of tag.p or undefined
|
|
36
|
+
* tag.isTrue(p?) => true if tag.p is boolean true
|
|
37
|
+
* tag.isFalse(p?) => true if tag.p is boolean false
|
|
38
|
+
* tag.date(p?) => Date value of tag.p or undefined
|
|
39
|
+
* tag.array(p?) => Tag[] value of tag.p or undefined
|
|
40
|
+
* tag.textArray(p?) => string[] value of elements in tag.p or undefined
|
|
41
|
+
* tag.numericArray(p?) => number[] value of elements in tag.p or undefined
|
|
42
|
+
* tag.tag(p?) => Tag value of tag.p
|
|
43
|
+
* tag.has(p?) => boolean "tag contains tag.p"
|
|
44
|
+
* tag.bare(p?) => tag.p exists and has no properties
|
|
44
45
|
* tag.dict => Record<string,Tag> of tag properties
|
|
46
|
+
* tag.toObject() => Plain JS object representation
|
|
45
47
|
* ```
|
|
46
48
|
*/
|
|
47
49
|
class Tag {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Get the parent tag, if this tag is part of a tree.
|
|
52
|
+
*/
|
|
53
|
+
get parent() {
|
|
54
|
+
return this._parent;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get the root tag by traversing up the parent chain.
|
|
58
|
+
* Returns this tag if it has no parent.
|
|
59
|
+
*/
|
|
60
|
+
get root() {
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
62
|
+
let current = this;
|
|
63
|
+
while (current._parent !== undefined) {
|
|
64
|
+
current = current._parent;
|
|
51
65
|
}
|
|
52
|
-
return
|
|
66
|
+
return current;
|
|
53
67
|
}
|
|
54
68
|
static id(t) {
|
|
55
69
|
let thisTagId = Tag.ids.get(t);
|
|
@@ -69,62 +83,43 @@ class Tag {
|
|
|
69
83
|
return str + `=${this.eq}`;
|
|
70
84
|
}
|
|
71
85
|
str += ' {';
|
|
72
|
-
if (this.eq) {
|
|
73
|
-
if (
|
|
74
|
-
str += `\n${spaces} =: ${this.eq}`;
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
86
|
+
if (this.eq !== undefined) {
|
|
87
|
+
if (Array.isArray(this.eq)) {
|
|
77
88
|
str += `\n${spaces} =: [\n${spaces} ${this.eq
|
|
78
|
-
.map(el =>
|
|
89
|
+
.map(el => el.peek(indent + 4))
|
|
79
90
|
.join(`\n${spaces} `)}\n${spaces} ]`;
|
|
80
91
|
}
|
|
92
|
+
else {
|
|
93
|
+
str += `\n${spaces} =: ${this.eq}`;
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
96
|
if (this.properties) {
|
|
83
97
|
for (const k in this.properties) {
|
|
84
|
-
|
|
85
|
-
str += `\n${spaces} ${k}: ${val.peek(indent + 2)}`;
|
|
98
|
+
str += `\n${spaces} ${k}: ${this.properties[k].peek(indent + 2)}`;
|
|
86
99
|
}
|
|
87
100
|
}
|
|
88
101
|
str += `\n${spaces}}`;
|
|
89
102
|
return str;
|
|
90
103
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* all characters up to the first space.
|
|
95
|
-
* @param lineNumber -- A line number to be associated with the parse errors.
|
|
96
|
-
* @param extending A tag which this line will be extending
|
|
97
|
-
* @param importing Outer "scopes" for $() references
|
|
98
|
-
* @returns Something shaped like { tag: Tag, log: ParseErrors[] }
|
|
99
|
-
*/
|
|
100
|
-
static fromTagLine(source, lineNumber = 0, extending, ...importing) {
|
|
101
|
-
return parseTagLine(source, extending, importing, lineNumber);
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Parse multiple lines of Malloy tag language, merging them into a single Tag
|
|
105
|
-
* @param lines -- The source line to be parsed. If the string starts with #, then it skips
|
|
106
|
-
* all characters up to the first space.
|
|
107
|
-
* @param extending A tag which this line will be extending
|
|
108
|
-
* @param importing Outer "scopes" for $() references
|
|
109
|
-
* @returns Something shaped like { tag: Tag, log: ParseErrors[] }
|
|
110
|
-
*/
|
|
111
|
-
static fromTagLines(lines, extending, ...importing) {
|
|
112
|
-
const allErrs = [];
|
|
113
|
-
let current = extending;
|
|
114
|
-
for (let i = 0; i < lines.length; i++) {
|
|
115
|
-
const text = lines[i];
|
|
116
|
-
const noteParse = parseTagLine(text, current, importing, i);
|
|
117
|
-
current = noteParse.tag;
|
|
118
|
-
allErrs.push(...noteParse.log);
|
|
104
|
+
constructor(from = {}, parent) {
|
|
105
|
+
if (parent !== undefined) {
|
|
106
|
+
this._parent = parent;
|
|
119
107
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
108
|
+
if (from.eq !== undefined) {
|
|
109
|
+
if (Array.isArray(from.eq)) {
|
|
110
|
+
// Convert array elements to Tags
|
|
111
|
+
this.eq = from.eq.map(el => el instanceof Tag ? el : new Tag(el, this));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
this.eq = from.eq;
|
|
115
|
+
}
|
|
125
116
|
}
|
|
126
117
|
if (from.properties) {
|
|
127
|
-
|
|
118
|
+
// Convert property values to Tags
|
|
119
|
+
this.properties = {};
|
|
120
|
+
for (const [key, val] of Object.entries(from.properties)) {
|
|
121
|
+
this.properties[key] = val instanceof Tag ? val : new Tag(val, this);
|
|
122
|
+
}
|
|
128
123
|
}
|
|
129
124
|
if (from.deleted) {
|
|
130
125
|
this.deleted = from.deleted;
|
|
@@ -141,62 +136,149 @@ class Tag {
|
|
|
141
136
|
}
|
|
142
137
|
text(...at) {
|
|
143
138
|
var _a;
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
return
|
|
139
|
+
const val = (_a = this.find(at)) === null || _a === void 0 ? void 0 : _a.getEq();
|
|
140
|
+
if (val === undefined || Array.isArray(val)) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
if (val instanceof Date) {
|
|
144
|
+
return val.toISOString();
|
|
147
145
|
}
|
|
146
|
+
return String(val);
|
|
148
147
|
}
|
|
149
148
|
numeric(...at) {
|
|
150
149
|
var _a;
|
|
151
|
-
const
|
|
152
|
-
if (typeof
|
|
153
|
-
|
|
150
|
+
const val = (_a = this.find(at)) === null || _a === void 0 ? void 0 : _a.getEq();
|
|
151
|
+
if (typeof val === 'number') {
|
|
152
|
+
return val;
|
|
153
|
+
}
|
|
154
|
+
if (typeof val === 'string') {
|
|
155
|
+
const num = Number.parseFloat(val);
|
|
154
156
|
if (!Number.isNaN(num)) {
|
|
155
157
|
return num;
|
|
156
158
|
}
|
|
157
159
|
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
boolean(...at) {
|
|
163
|
+
var _a;
|
|
164
|
+
const val = (_a = this.find(at)) === null || _a === void 0 ? void 0 : _a.getEq();
|
|
165
|
+
if (typeof val === 'boolean') {
|
|
166
|
+
return val;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}
|
|
170
|
+
isTrue(...at) {
|
|
171
|
+
var _a;
|
|
172
|
+
return ((_a = this.find(at)) === null || _a === void 0 ? void 0 : _a.getEq()) === true;
|
|
173
|
+
}
|
|
174
|
+
isFalse(...at) {
|
|
175
|
+
var _a;
|
|
176
|
+
return ((_a = this.find(at)) === null || _a === void 0 ? void 0 : _a.getEq()) === false;
|
|
177
|
+
}
|
|
178
|
+
date(...at) {
|
|
179
|
+
var _a;
|
|
180
|
+
const val = (_a = this.find(at)) === null || _a === void 0 ? void 0 : _a.getEq();
|
|
181
|
+
if (val instanceof Date) {
|
|
182
|
+
return val;
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
158
185
|
}
|
|
159
186
|
bare(...at) {
|
|
160
187
|
const p = this.find(at);
|
|
161
188
|
if (p === undefined) {
|
|
162
189
|
return;
|
|
163
190
|
}
|
|
164
|
-
return
|
|
191
|
+
return !p.hasProperties();
|
|
192
|
+
}
|
|
193
|
+
/** Virtual accessor for eq - overridden in RefTag to resolve */
|
|
194
|
+
getEq() {
|
|
195
|
+
return this.eq;
|
|
196
|
+
}
|
|
197
|
+
/** Virtual accessor for a property - overridden in RefTag to resolve */
|
|
198
|
+
getProperty(name) {
|
|
199
|
+
var _a;
|
|
200
|
+
return (_a = this.properties) === null || _a === void 0 ? void 0 : _a[name];
|
|
201
|
+
}
|
|
202
|
+
/** Virtual accessor for an array element - overridden in RefTag to resolve */
|
|
203
|
+
getArrayElement(index) {
|
|
204
|
+
if (Array.isArray(this.eq) && index < this.eq.length) {
|
|
205
|
+
return this.eq[index];
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
165
208
|
}
|
|
166
209
|
get dict() {
|
|
167
|
-
|
|
210
|
+
var _a;
|
|
211
|
+
return (_a = this.properties) !== null && _a !== void 0 ? _a : {};
|
|
212
|
+
}
|
|
213
|
+
/** Iterate over [name, Tag] pairs for each property */
|
|
214
|
+
*entries() {
|
|
215
|
+
if (this.properties) {
|
|
216
|
+
for (const key in this.properties) {
|
|
217
|
+
yield [key, this.properties[key]];
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Iterate over property names */
|
|
222
|
+
*keys() {
|
|
168
223
|
if (this.properties) {
|
|
169
224
|
for (const key in this.properties) {
|
|
170
|
-
|
|
225
|
+
yield key;
|
|
171
226
|
}
|
|
172
227
|
}
|
|
173
|
-
|
|
228
|
+
}
|
|
229
|
+
/** Check if this tag has any properties */
|
|
230
|
+
hasProperties() {
|
|
231
|
+
return (this.properties !== undefined && Object.keys(this.properties).length > 0);
|
|
174
232
|
}
|
|
175
233
|
array(...at) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
234
|
+
const found = this.find(at);
|
|
235
|
+
if (found === undefined) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
const arr = found.getEq();
|
|
239
|
+
if (!Array.isArray(arr)) {
|
|
179
240
|
return undefined;
|
|
180
241
|
}
|
|
181
|
-
return
|
|
242
|
+
return arr.map((_, i) => found.getArrayElement(i));
|
|
182
243
|
}
|
|
183
244
|
textArray(...at) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
245
|
+
const found = this.find(at);
|
|
246
|
+
if (found === undefined) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
const arr = found.getEq();
|
|
250
|
+
if (!Array.isArray(arr)) {
|
|
187
251
|
return undefined;
|
|
188
252
|
}
|
|
189
|
-
return
|
|
253
|
+
return arr.reduce((allStrs, _, i) => {
|
|
254
|
+
const el = found.getArrayElement(i);
|
|
255
|
+
const val = el === null || el === void 0 ? void 0 : el.getEq();
|
|
256
|
+
if (val === undefined || Array.isArray(val)) {
|
|
257
|
+
return allStrs;
|
|
258
|
+
}
|
|
259
|
+
if (val instanceof Date) {
|
|
260
|
+
return allStrs.concat(val.toISOString());
|
|
261
|
+
}
|
|
262
|
+
return allStrs.concat(String(val));
|
|
263
|
+
}, []);
|
|
190
264
|
}
|
|
191
265
|
numericArray(...at) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
266
|
+
const found = this.find(at);
|
|
267
|
+
if (found === undefined) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
const arr = found.getEq();
|
|
271
|
+
if (!Array.isArray(arr)) {
|
|
195
272
|
return undefined;
|
|
196
273
|
}
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
|
|
274
|
+
return arr.reduce((allNums, _, i) => {
|
|
275
|
+
const el = found.getArrayElement(i);
|
|
276
|
+
const val = el === null || el === void 0 ? void 0 : el.getEq();
|
|
277
|
+
if (typeof val === 'number') {
|
|
278
|
+
return allNums.concat(val);
|
|
279
|
+
}
|
|
280
|
+
if (typeof val === 'string') {
|
|
281
|
+
const num = Number.parseFloat(val);
|
|
200
282
|
if (!Number.isNaN(num)) {
|
|
201
283
|
return allNums.concat(num);
|
|
202
284
|
}
|
|
@@ -204,15 +286,178 @@ class Tag {
|
|
|
204
286
|
return allNums;
|
|
205
287
|
}, []);
|
|
206
288
|
}
|
|
207
|
-
// Has the sometimes
|
|
289
|
+
// Has the sometimes desirable side effect of initializing properties
|
|
208
290
|
getProperties() {
|
|
209
291
|
if (this.properties === undefined) {
|
|
210
292
|
this.properties = {};
|
|
211
293
|
}
|
|
212
294
|
return this.properties;
|
|
213
295
|
}
|
|
214
|
-
clone() {
|
|
215
|
-
|
|
296
|
+
clone(newParent) {
|
|
297
|
+
const cloned = new Tag({}, newParent);
|
|
298
|
+
cloned.prefix = this.prefix;
|
|
299
|
+
cloned.deleted = this.deleted;
|
|
300
|
+
if (this.eq !== undefined) {
|
|
301
|
+
if (Array.isArray(this.eq)) {
|
|
302
|
+
cloned.eq = this.eq.map(el => el.clone(cloned));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// Scalar value - copy directly (Date needs special handling)
|
|
306
|
+
cloned.eq = this.eq instanceof Date ? new Date(this.eq) : this.eq;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (this.properties) {
|
|
310
|
+
cloned.properties = {};
|
|
311
|
+
for (const [key, val] of Object.entries(this.properties)) {
|
|
312
|
+
cloned.properties[key] = val.clone(cloned);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return cloned;
|
|
316
|
+
}
|
|
317
|
+
static scalarToObject(val) {
|
|
318
|
+
return val;
|
|
319
|
+
}
|
|
320
|
+
static tagToObject(tag) {
|
|
321
|
+
const hasProps = tag.properties !== undefined && Object.keys(tag.properties).length > 0;
|
|
322
|
+
const hasValue = tag.eq !== undefined;
|
|
323
|
+
// Bare tag (no value, no properties)
|
|
324
|
+
if (!hasValue && !hasProps) {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
// Properties only
|
|
328
|
+
if (!hasValue && hasProps) {
|
|
329
|
+
const result = {};
|
|
330
|
+
for (const [key, val] of Object.entries(tag.properties)) {
|
|
331
|
+
if (!val.deleted) {
|
|
332
|
+
result[key] = Tag.tagToObject(val);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
// Value only
|
|
338
|
+
if (hasValue && !hasProps) {
|
|
339
|
+
if (Array.isArray(tag.eq)) {
|
|
340
|
+
return tag.eq.map(el => Tag.tagToObject(el));
|
|
341
|
+
}
|
|
342
|
+
return Tag.scalarToObject(tag.eq);
|
|
343
|
+
}
|
|
344
|
+
// Both value and properties
|
|
345
|
+
const result = {};
|
|
346
|
+
if (Array.isArray(tag.eq)) {
|
|
347
|
+
result['='] = tag.eq.map(el => Tag.tagToObject(el));
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
result['='] = Tag.scalarToObject(tag.eq);
|
|
351
|
+
}
|
|
352
|
+
for (const [key, val] of Object.entries(tag.properties)) {
|
|
353
|
+
if (!val.deleted) {
|
|
354
|
+
result[key] = Tag.tagToObject(val);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Convert to a plain JS object. References are resolved to actual
|
|
361
|
+
* object pointers (which may be circular in JS - that's fine).
|
|
362
|
+
* @param resolving - RefTags currently being resolved (for cycle detection)
|
|
363
|
+
*/
|
|
364
|
+
toObject(resolving = new Set()) {
|
|
365
|
+
const result = {};
|
|
366
|
+
if (this.properties) {
|
|
367
|
+
for (const [key, prop] of Object.entries(this.properties)) {
|
|
368
|
+
if (!prop.deleted) {
|
|
369
|
+
result[key] = prop.toObjectValue(resolving);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Convert this tag's value to a plain JS object.
|
|
377
|
+
* Override in RefTag to resolve references.
|
|
378
|
+
* @param resolving - RefTags currently being resolved (for cycle detection)
|
|
379
|
+
*/
|
|
380
|
+
toObjectValue(resolving) {
|
|
381
|
+
const hasProps = this.properties !== undefined && Object.keys(this.properties).length > 0;
|
|
382
|
+
const hasValue = this.eq !== undefined;
|
|
383
|
+
// Bare tag (no value, no properties)
|
|
384
|
+
if (!hasValue && !hasProps) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
// Properties only
|
|
388
|
+
if (!hasValue && hasProps) {
|
|
389
|
+
return this.toObject(resolving);
|
|
390
|
+
}
|
|
391
|
+
// Value only
|
|
392
|
+
if (hasValue && !hasProps) {
|
|
393
|
+
if (Array.isArray(this.eq)) {
|
|
394
|
+
return this.eq.map(el => el.toObjectValue(resolving));
|
|
395
|
+
}
|
|
396
|
+
return this.eq;
|
|
397
|
+
}
|
|
398
|
+
// Both value and properties
|
|
399
|
+
const result = this.toObject(resolving);
|
|
400
|
+
if (Array.isArray(this.eq)) {
|
|
401
|
+
result['='] = this.eq.map(el => el.toObjectValue(resolving));
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
result['='] = this.eq;
|
|
405
|
+
}
|
|
406
|
+
return result;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Custom JSON serialization that excludes _parent to avoid circular references.
|
|
410
|
+
* This is called automatically by JSON.stringify().
|
|
411
|
+
*/
|
|
412
|
+
toJSON() {
|
|
413
|
+
const result = {};
|
|
414
|
+
if (this.eq !== undefined) {
|
|
415
|
+
if (Array.isArray(this.eq)) {
|
|
416
|
+
result.eq = this.eq.map(el => el.toJSON());
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
result.eq = this.eq;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (this.properties !== undefined) {
|
|
423
|
+
result.properties = {};
|
|
424
|
+
for (const [key, val] of Object.entries(this.properties)) {
|
|
425
|
+
result.properties[key] = val.toJSON();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (this.deleted) {
|
|
429
|
+
result.deleted = true;
|
|
430
|
+
}
|
|
431
|
+
if (this.prefix) {
|
|
432
|
+
result.prefix = this.prefix;
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Validate all references in this tag tree.
|
|
438
|
+
* Returns an array of error messages for unresolved references.
|
|
439
|
+
*/
|
|
440
|
+
validateReferences() {
|
|
441
|
+
const errors = [];
|
|
442
|
+
this.collectReferenceErrors(errors, []);
|
|
443
|
+
return errors;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Recursively collect reference errors.
|
|
447
|
+
*/
|
|
448
|
+
collectReferenceErrors(errors, path) {
|
|
449
|
+
if (this.properties) {
|
|
450
|
+
for (const [key, prop] of Object.entries(this.properties)) {
|
|
451
|
+
if (!prop.deleted) {
|
|
452
|
+
prop.collectReferenceErrors(errors, [...path, key]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (Array.isArray(this.eq)) {
|
|
457
|
+
this.eq.forEach((el, i) => {
|
|
458
|
+
el.collectReferenceErrors(errors, [...path, `[${i}]`]);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
216
461
|
}
|
|
217
462
|
static escapeString(str) {
|
|
218
463
|
return str
|
|
@@ -231,6 +476,18 @@ class Tag {
|
|
|
231
476
|
// TODO consider choosing the quote character based on which quotes appear in the string
|
|
232
477
|
return `"${Tag.escapeString(str)}"`;
|
|
233
478
|
}
|
|
479
|
+
static serializeScalar(val) {
|
|
480
|
+
if (typeof val === 'boolean') {
|
|
481
|
+
return val ? '@true' : '@false';
|
|
482
|
+
}
|
|
483
|
+
if (val instanceof Date) {
|
|
484
|
+
return `@${val.toISOString()}`;
|
|
485
|
+
}
|
|
486
|
+
if (typeof val === 'number') {
|
|
487
|
+
return String(val);
|
|
488
|
+
}
|
|
489
|
+
return Tag.quoteAndEscape(val);
|
|
490
|
+
}
|
|
234
491
|
toString() {
|
|
235
492
|
var _a;
|
|
236
493
|
let annotation = (_a = this.prefix) !== null && _a !== void 0 ? _a : '# ';
|
|
@@ -258,7 +515,7 @@ class Tag {
|
|
|
258
515
|
annotation += ']';
|
|
259
516
|
}
|
|
260
517
|
else {
|
|
261
|
-
annotation += Tag.
|
|
518
|
+
annotation += Tag.serializeScalar(child.eq);
|
|
262
519
|
}
|
|
263
520
|
}
|
|
264
521
|
if (child.properties) {
|
|
@@ -292,26 +549,20 @@ class Tag {
|
|
|
292
549
|
return annotation;
|
|
293
550
|
}
|
|
294
551
|
find(path) {
|
|
295
|
-
|
|
296
|
-
let currentTag =
|
|
552
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
553
|
+
let currentTag = this;
|
|
297
554
|
for (const segment of path) {
|
|
555
|
+
let next;
|
|
298
556
|
if (typeof segment === 'number') {
|
|
299
|
-
|
|
300
|
-
!Array.isArray(currentTag.eq) ||
|
|
301
|
-
currentTag.eq.length <= segment) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
currentTag = Tag.tagFrom(currentTag.eq[segment]);
|
|
557
|
+
next = currentTag.getArrayElement(segment);
|
|
305
558
|
}
|
|
306
559
|
else {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
else {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
560
|
+
next = currentTag.getProperty(segment);
|
|
561
|
+
}
|
|
562
|
+
if (next === undefined) {
|
|
563
|
+
return undefined;
|
|
314
564
|
}
|
|
565
|
+
currentTag = next;
|
|
315
566
|
}
|
|
316
567
|
return currentTag.deleted ? undefined : currentTag;
|
|
317
568
|
}
|
|
@@ -319,16 +570,20 @@ class Tag {
|
|
|
319
570
|
return this.find(path) !== undefined;
|
|
320
571
|
}
|
|
321
572
|
set(path, value = null) {
|
|
322
|
-
|
|
323
|
-
let currentTag =
|
|
573
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
574
|
+
let currentTag = this;
|
|
575
|
+
let parentTag;
|
|
576
|
+
let lastSegment;
|
|
324
577
|
for (const segment of path) {
|
|
578
|
+
parentTag = currentTag;
|
|
579
|
+
lastSegment = segment;
|
|
325
580
|
if (typeof segment === 'number') {
|
|
326
581
|
if (currentTag.eq === undefined || !Array.isArray(currentTag.eq)) {
|
|
327
|
-
currentTag.eq = Array.from({ length: segment + 1 }).map(
|
|
582
|
+
currentTag.eq = Array.from({ length: segment + 1 }).map(() => new Tag({}, currentTag));
|
|
328
583
|
}
|
|
329
584
|
else if (currentTag.eq.length <= segment) {
|
|
330
585
|
const values = currentTag.eq;
|
|
331
|
-
const newVal = Array.from({ length: segment + 1 }).map((_, i) => i < values.length ? values[i] : {});
|
|
586
|
+
const newVal = Array.from({ length: segment + 1 }).map((_, i) => i < values.length ? values[i] : new Tag({}, currentTag));
|
|
332
587
|
currentTag.eq = newVal;
|
|
333
588
|
}
|
|
334
589
|
currentTag = currentTag.eq[segment];
|
|
@@ -336,7 +591,7 @@ class Tag {
|
|
|
336
591
|
else {
|
|
337
592
|
const properties = currentTag.properties;
|
|
338
593
|
if (properties === undefined) {
|
|
339
|
-
currentTag.properties = { [segment]: {} };
|
|
594
|
+
currentTag.properties = { [segment]: new Tag({}, currentTag) };
|
|
340
595
|
currentTag = currentTag.properties[segment];
|
|
341
596
|
}
|
|
342
597
|
else if (segment in properties) {
|
|
@@ -346,7 +601,7 @@ class Tag {
|
|
|
346
601
|
}
|
|
347
602
|
}
|
|
348
603
|
else {
|
|
349
|
-
properties[segment] = {};
|
|
604
|
+
properties[segment] = new Tag({}, currentTag);
|
|
350
605
|
currentTag = properties[segment];
|
|
351
606
|
}
|
|
352
607
|
}
|
|
@@ -354,18 +609,30 @@ class Tag {
|
|
|
354
609
|
if (value === null) {
|
|
355
610
|
currentTag.eq = undefined;
|
|
356
611
|
}
|
|
357
|
-
else if (
|
|
358
|
-
|
|
612
|
+
else if (value instanceof Tag) {
|
|
613
|
+
// Clone the tag with correct parent and replace in parent's slot
|
|
614
|
+
const cloned = value.clone(parentTag);
|
|
615
|
+
if (parentTag && lastSegment !== undefined) {
|
|
616
|
+
if (typeof lastSegment === 'number' && Array.isArray(parentTag.eq)) {
|
|
617
|
+
parentTag.eq[lastSegment] = cloned;
|
|
618
|
+
}
|
|
619
|
+
else if (typeof lastSegment === 'string' && parentTag.properties) {
|
|
620
|
+
parentTag.properties[lastSegment] = cloned;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
359
623
|
}
|
|
360
|
-
else if (typeof value === '
|
|
361
|
-
|
|
624
|
+
else if (typeof value === 'string' ||
|
|
625
|
+
typeof value === 'number' ||
|
|
626
|
+
typeof value === 'boolean' ||
|
|
627
|
+
value instanceof Date) {
|
|
628
|
+
currentTag.eq = value;
|
|
362
629
|
}
|
|
363
630
|
else if (Array.isArray(value)) {
|
|
364
631
|
currentTag.eq = value.map((v) => {
|
|
365
|
-
return { eq:
|
|
632
|
+
return new Tag({ eq: v }, currentTag);
|
|
366
633
|
});
|
|
367
634
|
}
|
|
368
|
-
return
|
|
635
|
+
return this;
|
|
369
636
|
}
|
|
370
637
|
delete(...path) {
|
|
371
638
|
return this.remove(path, false);
|
|
@@ -375,20 +642,20 @@ class Tag {
|
|
|
375
642
|
}
|
|
376
643
|
remove(path, hard = false) {
|
|
377
644
|
var _a;
|
|
378
|
-
|
|
379
|
-
let currentTag =
|
|
645
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
646
|
+
let currentTag = this;
|
|
380
647
|
for (const segment of path.slice(0, path.length - 1)) {
|
|
381
648
|
if (typeof segment === 'number') {
|
|
382
649
|
if (currentTag.eq === undefined || !Array.isArray(currentTag.eq)) {
|
|
383
650
|
if (!hard)
|
|
384
|
-
return
|
|
385
|
-
currentTag.eq = Array.from({ length: segment }).map(
|
|
651
|
+
return this;
|
|
652
|
+
currentTag.eq = Array.from({ length: segment }).map(() => new Tag({}, currentTag));
|
|
386
653
|
}
|
|
387
654
|
else if (currentTag.eq.length <= segment) {
|
|
388
655
|
if (!hard)
|
|
389
|
-
return
|
|
656
|
+
return this;
|
|
390
657
|
const values = currentTag.eq;
|
|
391
|
-
const newVal = Array.from({ length: segment }).map((_, i) => i < values.length ? values[i] : {});
|
|
658
|
+
const newVal = Array.from({ length: segment }).map((_, i) => i < values.length ? values[i] : new Tag({}, currentTag));
|
|
392
659
|
currentTag.eq = newVal;
|
|
393
660
|
}
|
|
394
661
|
currentTag = currentTag.eq[segment];
|
|
@@ -397,8 +664,8 @@ class Tag {
|
|
|
397
664
|
const properties = currentTag.properties;
|
|
398
665
|
if (properties === undefined) {
|
|
399
666
|
if (!hard)
|
|
400
|
-
return
|
|
401
|
-
currentTag.properties = { [segment]: {} };
|
|
667
|
+
return this;
|
|
668
|
+
currentTag.properties = { [segment]: new Tag({}, currentTag) };
|
|
402
669
|
currentTag = currentTag.properties[segment];
|
|
403
670
|
}
|
|
404
671
|
else if (segment in properties) {
|
|
@@ -406,8 +673,8 @@ class Tag {
|
|
|
406
673
|
}
|
|
407
674
|
else {
|
|
408
675
|
if (!hard)
|
|
409
|
-
return
|
|
410
|
-
properties[segment] = {};
|
|
676
|
+
return this;
|
|
677
|
+
properties[segment] = new Tag({}, currentTag);
|
|
411
678
|
currentTag = properties[segment];
|
|
412
679
|
}
|
|
413
680
|
}
|
|
@@ -419,7 +686,7 @@ class Tag {
|
|
|
419
686
|
}
|
|
420
687
|
else if (hard) {
|
|
421
688
|
(_a = currentTag.properties) !== null && _a !== void 0 ? _a : (currentTag.properties = {});
|
|
422
|
-
currentTag.properties[segment] = { deleted: true };
|
|
689
|
+
currentTag.properties[segment] = new Tag({ deleted: true }, currentTag);
|
|
423
690
|
}
|
|
424
691
|
}
|
|
425
692
|
else {
|
|
@@ -427,249 +694,173 @@ class Tag {
|
|
|
427
694
|
currentTag.eq.splice(segment, 1);
|
|
428
695
|
}
|
|
429
696
|
}
|
|
430
|
-
return
|
|
697
|
+
return this;
|
|
431
698
|
}
|
|
432
699
|
}
|
|
433
700
|
exports.Tag = Tag;
|
|
434
701
|
// --- Just for debugging ---
|
|
435
702
|
Tag.ids = new Map();
|
|
436
703
|
Tag.nextTagId = 1000;
|
|
437
|
-
class TagErrorListener {
|
|
438
|
-
constructor(line) {
|
|
439
|
-
this.line = line;
|
|
440
|
-
this.log = [];
|
|
441
|
-
}
|
|
442
|
-
syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, _e) {
|
|
443
|
-
const logMsg = {
|
|
444
|
-
code: 'tag-parse-syntax-error',
|
|
445
|
-
message: msg,
|
|
446
|
-
line: this.line,
|
|
447
|
-
offset: charPositionInLine,
|
|
448
|
-
};
|
|
449
|
-
this.log.push(logMsg);
|
|
450
|
-
}
|
|
451
|
-
semanticError(cx, code, msg) {
|
|
452
|
-
const logMsg = {
|
|
453
|
-
code,
|
|
454
|
-
message: msg,
|
|
455
|
-
line: this.line,
|
|
456
|
-
offset: cx.start.charPositionInLine,
|
|
457
|
-
};
|
|
458
|
-
this.log.push(logMsg);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
function getBuildOn(ctx) {
|
|
462
|
-
const buildOn = ctx['buildOn'];
|
|
463
|
-
if (buildOn instanceof Tag) {
|
|
464
|
-
return buildOn;
|
|
465
|
-
}
|
|
466
|
-
return new Tag();
|
|
467
|
-
}
|
|
468
704
|
/**
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
705
|
+
* A tag that references another location in the tag tree.
|
|
706
|
+
* When accessed, it dereferences to the target tag.
|
|
707
|
+
*
|
|
708
|
+
* Reference syntax:
|
|
709
|
+
* $path.to.thing - absolute from root
|
|
710
|
+
* $^thing - up one level, then 'thing'
|
|
711
|
+
* $^^thing - up two levels, then 'thing'
|
|
712
|
+
* $items[0].name - with array indexing
|
|
474
713
|
*/
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
let next;
|
|
481
|
-
if (parentPropertyObject[p] === undefined) {
|
|
482
|
-
next = new Tag({});
|
|
483
|
-
parentPropertyObject[p] = next;
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
// The access that we are performing requires that `.properties` be the
|
|
487
|
-
// same JS object (not equal, but identical), and `Tag.tagFrom` only copies
|
|
488
|
-
// the exact object in if it is actually present.
|
|
489
|
-
(_a = (_b = parentPropertyObject[p]).properties) !== null && _a !== void 0 ? _a : (_b.properties = {});
|
|
490
|
-
next = Tag.tagFrom(parentPropertyObject[p]);
|
|
491
|
-
}
|
|
492
|
-
parentPropertyObject = next.getProperties();
|
|
493
|
-
}
|
|
494
|
-
return [path[path.length - 1], parentPropertyObject];
|
|
495
|
-
}
|
|
496
|
-
function getString(ctx) {
|
|
497
|
-
if (ctx.SQ_STRING() || ctx.DQ_STRING()) {
|
|
498
|
-
return (0, util_1.parseString)(ctx.text, ctx.text[0]);
|
|
714
|
+
class RefTag extends Tag {
|
|
715
|
+
constructor(ups, refPath, parent) {
|
|
716
|
+
super({}, parent);
|
|
717
|
+
this.ups = ups;
|
|
718
|
+
this.refPath = refPath;
|
|
499
719
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
720
|
+
/**
|
|
721
|
+
* Resolve this reference to the target tag.
|
|
722
|
+
* Returns undefined if the reference cannot be resolved.
|
|
723
|
+
*/
|
|
724
|
+
resolve() {
|
|
725
|
+
// Start from the appropriate point based on ups
|
|
726
|
+
let current;
|
|
727
|
+
if (this.ups === 0) {
|
|
728
|
+
// Absolute reference from root
|
|
729
|
+
current = this.root;
|
|
507
730
|
}
|
|
508
731
|
else {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const tokenStream = new antlr4ts_1.CommonTokenStream(lexer);
|
|
515
|
-
const pLog = new TagErrorListener(onLine);
|
|
516
|
-
const taglineParser = new MalloyTagParser_1.MalloyTagParser(tokenStream);
|
|
517
|
-
taglineParser.removeErrorListeners();
|
|
518
|
-
taglineParser.addErrorListener(pLog);
|
|
519
|
-
const tagTree = taglineParser.tagLine();
|
|
520
|
-
const treeWalker = new TagLineParser(outerScope, pLog);
|
|
521
|
-
const tag = treeWalker.tagLineToTag(tagTree, extending);
|
|
522
|
-
return { tag, log: pLog.log };
|
|
523
|
-
}
|
|
524
|
-
class TagLineParser extends tree_1.AbstractParseTreeVisitor {
|
|
525
|
-
constructor(outerScopes = [], msgLog) {
|
|
526
|
-
super();
|
|
527
|
-
this.scopes = [];
|
|
528
|
-
this.msgLog = msgLog;
|
|
529
|
-
this.scopes.unshift(...outerScopes);
|
|
530
|
-
}
|
|
531
|
-
defaultResult() {
|
|
532
|
-
return new Tag();
|
|
533
|
-
}
|
|
534
|
-
visitString(ctx) {
|
|
535
|
-
return new Tag({ eq: getString(ctx) });
|
|
536
|
-
}
|
|
537
|
-
getPropName(ctx) {
|
|
538
|
-
return ctx
|
|
539
|
-
.identifier()
|
|
540
|
-
.map(cx => cx.BARE_STRING() ? cx.text : (0, util_1.parseString)(cx.text, cx.text[0]));
|
|
541
|
-
}
|
|
542
|
-
getTags(tags, tagLine) {
|
|
543
|
-
for (const tagSpec of tags) {
|
|
544
|
-
// Stash the current state of this tag in the context and then visit it
|
|
545
|
-
// visit functions should alter the tagLine
|
|
546
|
-
tagSpec['buildOn'] = tagLine;
|
|
547
|
-
this.visit(tagSpec);
|
|
548
|
-
}
|
|
549
|
-
return tagLine;
|
|
550
|
-
}
|
|
551
|
-
tagLineToTag(ctx, extending) {
|
|
552
|
-
extending = (extending === null || extending === void 0 ? void 0 : extending.clone()) || new Tag({});
|
|
553
|
-
this.scopes.unshift(extending);
|
|
554
|
-
this.getTags(ctx.tagSpec(), extending);
|
|
555
|
-
return extending;
|
|
556
|
-
}
|
|
557
|
-
visitTagLine(_ctx) {
|
|
558
|
-
throw new Error('INTERNAL: ERROR: Call tagLineToTag, not vistTagLine');
|
|
559
|
-
return this.defaultResult();
|
|
560
|
-
}
|
|
561
|
-
visitProperties(ctx) {
|
|
562
|
-
return this.getTags(ctx.tagSpec(), getBuildOn(ctx));
|
|
563
|
-
}
|
|
564
|
-
visitArrayValue(ctx) {
|
|
565
|
-
return new Tag({ eq: this.getArray(ctx) });
|
|
566
|
-
}
|
|
567
|
-
getArray(ctx) {
|
|
568
|
-
return ctx.arrayElement().map(v => this.visit(v));
|
|
569
|
-
}
|
|
570
|
-
visitArrayElement(ctx) {
|
|
571
|
-
const propCx = ctx.properties();
|
|
572
|
-
const properties = propCx ? this.visitProperties(propCx) : undefined;
|
|
573
|
-
const strCx = ctx.string();
|
|
574
|
-
let value = strCx ? getString(strCx) : undefined;
|
|
575
|
-
const arrayCx = ctx.arrayValue();
|
|
576
|
-
if (arrayCx) {
|
|
577
|
-
value = this.getArray(arrayCx);
|
|
578
|
-
}
|
|
579
|
-
if (properties) {
|
|
580
|
-
if (value !== undefined) {
|
|
581
|
-
properties.eq = value;
|
|
582
|
-
}
|
|
583
|
-
return properties;
|
|
584
|
-
}
|
|
585
|
-
const refCx = ctx.reference();
|
|
586
|
-
if (refCx) {
|
|
587
|
-
return this.visitReference(refCx);
|
|
588
|
-
}
|
|
589
|
-
return new Tag({ eq: value });
|
|
590
|
-
}
|
|
591
|
-
visitReference(ctx) {
|
|
592
|
-
const path = this.getPropName(ctx.propName());
|
|
593
|
-
for (const scope of this.scopes) {
|
|
594
|
-
// first scope which has the first component gets to resolve the whole path
|
|
595
|
-
if (scope.has(path[0])) {
|
|
596
|
-
const refTo = scope.tag(...path);
|
|
597
|
-
if (refTo) {
|
|
598
|
-
return refTo.clone();
|
|
599
|
-
}
|
|
600
|
-
break;
|
|
732
|
+
// Relative reference - go up 'ups' levels from parent
|
|
733
|
+
// $^ means go up 1 level from the containing scope
|
|
734
|
+
current = this.parent;
|
|
735
|
+
for (let i = 0; i < this.ups && current !== undefined; i++) {
|
|
736
|
+
current = current.parent;
|
|
601
737
|
}
|
|
602
738
|
}
|
|
603
|
-
|
|
604
|
-
|
|
739
|
+
if (current === undefined) {
|
|
740
|
+
return undefined;
|
|
741
|
+
}
|
|
742
|
+
// Follow the path
|
|
743
|
+
return current.find(this.refPath);
|
|
605
744
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
745
|
+
/**
|
|
746
|
+
* Quote an identifier if it contains special characters.
|
|
747
|
+
*/
|
|
748
|
+
quoteSegment(seg) {
|
|
749
|
+
// Match bareString from peggy grammar: [0-9A-Za-z_\u00C0-\u024F\u1E00-\u1EFF]+
|
|
750
|
+
if (/^[0-9A-Za-z_\u00C0-\u024F\u1E00-\u1EFF]+$/.test(seg)) {
|
|
751
|
+
return seg;
|
|
752
|
+
}
|
|
753
|
+
// Otherwise, backquote it (escaping backslashes and backquotes)
|
|
754
|
+
return '`' + seg.replace(/\\/g, '\\\\').replace(/`/g, '\\`') + '`';
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Convert this reference to its string representation.
|
|
758
|
+
*/
|
|
759
|
+
toRefString() {
|
|
760
|
+
const prefix = '$' + '^'.repeat(this.ups);
|
|
761
|
+
const pathStr = this.refPath
|
|
762
|
+
.map((seg, i) => {
|
|
763
|
+
if (typeof seg === 'number') {
|
|
764
|
+
return `[${seg}]`;
|
|
622
765
|
}
|
|
766
|
+
const quoted = this.quoteSegment(seg);
|
|
767
|
+
return i === 0 ? quoted : `.${quoted}`;
|
|
768
|
+
})
|
|
769
|
+
.join('');
|
|
770
|
+
return prefix + pathStr;
|
|
771
|
+
}
|
|
772
|
+
// Override virtual accessors to resolve the reference
|
|
773
|
+
getEq() {
|
|
774
|
+
var _a;
|
|
775
|
+
return (_a = this.resolve()) === null || _a === void 0 ? void 0 : _a.getEq();
|
|
776
|
+
}
|
|
777
|
+
getProperty(name) {
|
|
778
|
+
var _a;
|
|
779
|
+
return (_a = this.resolve()) === null || _a === void 0 ? void 0 : _a.getProperty(name);
|
|
780
|
+
}
|
|
781
|
+
getArrayElement(index) {
|
|
782
|
+
var _a;
|
|
783
|
+
return (_a = this.resolve()) === null || _a === void 0 ? void 0 : _a.getArrayElement(index);
|
|
784
|
+
}
|
|
785
|
+
hasProperties() {
|
|
786
|
+
var _a, _b;
|
|
787
|
+
return (_b = (_a = this.resolve()) === null || _a === void 0 ? void 0 : _a.hasProperties()) !== null && _b !== void 0 ? _b : false;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* For toObject, resolve the reference and return the target's object value.
|
|
791
|
+
* This creates actual object pointers (circular references are allowed).
|
|
792
|
+
* Detects cycles in the reference chain to prevent infinite recursion.
|
|
793
|
+
*/
|
|
794
|
+
toObjectValue(resolving) {
|
|
795
|
+
// Check for cycle in reference chain
|
|
796
|
+
if (resolving.has(this)) {
|
|
797
|
+
// We're in a cycle - return undefined to break it
|
|
798
|
+
// (The cycle will be completed when the outer resolution finishes)
|
|
799
|
+
return undefined;
|
|
623
800
|
}
|
|
624
|
-
|
|
625
|
-
|
|
801
|
+
const resolved = this.resolve();
|
|
802
|
+
if (resolved === undefined) {
|
|
803
|
+
return undefined;
|
|
626
804
|
}
|
|
627
|
-
|
|
805
|
+
// Track that we're resolving this RefTag
|
|
806
|
+
resolving.add(this);
|
|
807
|
+
const result = resolved.toObjectValue(resolving);
|
|
808
|
+
resolving.delete(this);
|
|
809
|
+
return result;
|
|
628
810
|
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
811
|
+
/**
|
|
812
|
+
* For JSON serialization, return a marker object instead of resolving.
|
|
813
|
+
*/
|
|
814
|
+
toJSON() {
|
|
815
|
+
return { linkTo: this.toRefString() };
|
|
816
|
+
}
|
|
817
|
+
/**
|
|
818
|
+
* Check if this reference resolves, add error if not.
|
|
819
|
+
*/
|
|
820
|
+
collectReferenceErrors(errors, path) {
|
|
821
|
+
if (this.resolve() === undefined) {
|
|
822
|
+
const location = path.length > 0 ? path.join('.') : 'root';
|
|
823
|
+
errors.push(`Unresolved reference at ${location}: ${this.toRefString()}`);
|
|
638
824
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
visitTagDef(ctx) {
|
|
658
|
-
const buildOn = getBuildOn(ctx);
|
|
659
|
-
const path = this.getPropName(ctx.propName());
|
|
660
|
-
const [writeKey, writeInto] = buildAccessPath(buildOn, path);
|
|
661
|
-
if (ctx.MINUS()) {
|
|
662
|
-
writeInto[writeKey] = { deleted: true };
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Clone this RefTag, preserving the reference information.
|
|
828
|
+
*/
|
|
829
|
+
clone(newParent) {
|
|
830
|
+
return new RefTag(this.ups, [...this.refPath], newParent);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
exports.RefTag = RefTag;
|
|
834
|
+
/**
|
|
835
|
+
* Convert a Tag to a plain TagInterface without internal fields like _parent.
|
|
836
|
+
* Useful for test comparisons.
|
|
837
|
+
*/
|
|
838
|
+
function interfaceFromTag(tag) {
|
|
839
|
+
const result = {};
|
|
840
|
+
if (tag.eq !== undefined) {
|
|
841
|
+
if (Array.isArray(tag.eq)) {
|
|
842
|
+
result.eq = tag.eq.map(el => interfaceFromTag(el));
|
|
663
843
|
}
|
|
664
844
|
else {
|
|
665
|
-
|
|
845
|
+
result.eq = tag.eq;
|
|
666
846
|
}
|
|
667
|
-
return buildOn;
|
|
668
847
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
848
|
+
if (tag.properties !== undefined) {
|
|
849
|
+
result.properties = interfaceFromDict(tag.properties);
|
|
850
|
+
}
|
|
851
|
+
if (tag.deleted) {
|
|
852
|
+
result.deleted = true;
|
|
853
|
+
}
|
|
854
|
+
return result;
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Convert a TagDict to a plain TagDict without internal fields.
|
|
858
|
+
*/
|
|
859
|
+
function interfaceFromDict(dict) {
|
|
860
|
+
const result = {};
|
|
861
|
+
for (const [key, val] of Object.entries(dict)) {
|
|
862
|
+
result[key] = interfaceFromTag(val);
|
|
673
863
|
}
|
|
864
|
+
return result;
|
|
674
865
|
}
|
|
675
866
|
//# sourceMappingURL=tags.js.map
|