@malloydata/motly-ts-parser 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/ast.d.ts CHANGED
@@ -15,6 +15,11 @@ export type ScalarValue = {
15
15
  kind: "reference";
16
16
  ups: number;
17
17
  path: RefPathSegment[];
18
+ } | {
19
+ kind: "none";
20
+ } | {
21
+ kind: "env";
22
+ name: string;
18
23
  };
19
24
  /** A segment in a reference path: either a named property or an array index. */
20
25
  export type RefPathSegment = {
@@ -43,12 +48,15 @@ export type Statement = {
43
48
  path: string[];
44
49
  value: TagValue;
45
50
  properties: Statement[] | null;
46
- preserveProperties: boolean;
51
+ } | {
52
+ kind: "assignBoth";
53
+ path: string[];
54
+ value: TagValue;
55
+ properties: Statement[] | null;
47
56
  } | {
48
57
  kind: "replaceProperties";
49
58
  path: string[];
50
59
  properties: Statement[];
51
- preserveValue: boolean;
52
60
  } | {
53
61
  kind: "updateProperties";
54
62
  path: string[];
@@ -0,0 +1,3 @@
1
+ import { MOTLYValue } from "motly-ts-interface";
2
+ /** Deep clone a MOTLYValue. */
3
+ export declare function cloneValue(value: MOTLYValue): MOTLYValue;
package/build/clone.js ADDED
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cloneValue = cloneValue;
4
+ const motly_ts_interface_1 = require("motly-ts-interface");
5
+ /** Deep clone a MOTLYValue. */
6
+ function cloneValue(value) {
7
+ const result = {};
8
+ if (value.deleted)
9
+ result.deleted = true;
10
+ if (value.eq !== undefined) {
11
+ if (value.eq instanceof Date) {
12
+ result.eq = new Date(value.eq.getTime());
13
+ }
14
+ else if (Array.isArray(value.eq)) {
15
+ result.eq = value.eq.map(cloneValue);
16
+ }
17
+ else if ((0, motly_ts_interface_1.isRef)(value.eq)) {
18
+ result.eq = { linkTo: value.eq.linkTo };
19
+ }
20
+ else if ((0, motly_ts_interface_1.isEnvRef)(value.eq)) {
21
+ result.eq = { env: value.eq.env };
22
+ }
23
+ else {
24
+ result.eq = value.eq;
25
+ }
26
+ }
27
+ if (value.properties) {
28
+ const props = {};
29
+ for (const key of Object.keys(value.properties)) {
30
+ props[key] = cloneValue(value.properties[key]);
31
+ }
32
+ result.properties = props;
33
+ }
34
+ return result;
35
+ }
package/build/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- export type { MOTLYScalar, MOTLYRef, MOTLYValue, MOTLYNode, MOTLYError, MOTLYSchemaError, MOTLYValidationError, } from "motly-ts-interface";
1
+ export type { MOTLYScalar, MOTLYRef, MOTLYEnvRef, MOTLYValue, MOTLYNode, MOTLYError, MOTLYSchemaError, MOTLYValidationError, } from "motly-ts-interface";
2
+ export { isRef, isEnvRef } from "motly-ts-interface";
2
3
  export { MOTLYSession } from "./session";
package/build/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MOTLYSession = void 0;
3
+ exports.MOTLYSession = exports.isEnvRef = exports.isRef = void 0;
4
+ var motly_ts_interface_1 = require("motly-ts-interface");
5
+ Object.defineProperty(exports, "isRef", { enumerable: true, get: function () { return motly_ts_interface_1.isRef; } });
6
+ Object.defineProperty(exports, "isEnvRef", { enumerable: true, get: function () { return motly_ts_interface_1.isEnvRef; } });
4
7
  var session_1 = require("./session");
5
8
  Object.defineProperty(exports, "MOTLYSession", { enumerable: true, get: function () { return session_1.MOTLYSession; } });
@@ -1,4 +1,4 @@
1
1
  import { Statement } from "./ast";
2
- import { MOTLYValue } from "motly-ts-interface";
2
+ import { MOTLYValue, MOTLYError } from "motly-ts-interface";
3
3
  /** Execute a list of parsed statements against an existing MOTLYValue. */
4
- export declare function execute(statements: Statement[], root: MOTLYValue): MOTLYValue;
4
+ export declare function execute(statements: Statement[], root: MOTLYValue): MOTLYError[];
@@ -1,93 +1,123 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.execute = execute;
4
+ const motly_ts_interface_1 = require("motly-ts-interface");
5
+ const clone_1 = require("./clone");
4
6
  /** Execute a list of parsed statements against an existing MOTLYValue. */
5
7
  function execute(statements, root) {
8
+ const errors = [];
6
9
  for (const stmt of statements) {
7
- executeStatement(stmt, root);
10
+ executeStatement(stmt, root, errors);
8
11
  }
9
- return root;
12
+ return errors;
10
13
  }
11
- function executeStatement(stmt, node) {
14
+ function executeStatement(stmt, node, errors) {
12
15
  switch (stmt.kind) {
13
16
  case "setEq":
14
- executeSetEq(node, stmt.path, stmt.value, stmt.properties, stmt.preserveProperties);
17
+ executeSetEq(node, stmt.path, stmt.value, stmt.properties, errors);
18
+ break;
19
+ case "assignBoth":
20
+ executeAssignBoth(node, stmt.path, stmt.value, stmt.properties, errors);
15
21
  break;
16
22
  case "replaceProperties":
17
- executeReplaceProperties(node, stmt.path, stmt.properties, stmt.preserveValue);
23
+ executeReplaceProperties(node, stmt.path, stmt.properties, errors);
18
24
  break;
19
25
  case "updateProperties":
20
- executeUpdateProperties(node, stmt.path, stmt.properties);
26
+ executeUpdateProperties(node, stmt.path, stmt.properties, errors);
21
27
  break;
22
28
  case "define":
23
29
  executeDefine(node, stmt.path, stmt.deleted);
24
30
  break;
25
31
  case "clearAll":
32
+ delete node.eq;
26
33
  node.properties = {};
27
34
  break;
28
35
  }
29
36
  }
30
- function executeSetEq(node, path, value, properties, preserveProperties) {
31
- // Reference without properties produce a link
32
- if (value.kind === "scalar" &&
33
- value.value.kind === "reference" &&
34
- properties === null &&
35
- !preserveProperties) {
36
- const [writeKey, parent] = buildAccessPath(node, path);
37
- const props = getOrCreateProperties(parent);
38
- props[writeKey] = {
39
- linkTo: formatRefString(value.value.ups, value.value.path),
40
- };
41
- return;
42
- }
37
+ /**
38
+ * `name = value` set eq, preserve existing properties.
39
+ * `name = value { props }` — set eq, then merge properties.
40
+ */
41
+ function executeSetEq(node, path, value, properties, errors) {
43
42
  const [writeKey, parent] = buildAccessPath(node, path);
43
+ const props = getOrCreateProperties(parent);
44
+ // Get or create target (preserves existing node and its properties)
45
+ let target = props[writeKey];
46
+ if (target === undefined) {
47
+ target = {};
48
+ props[writeKey] = target;
49
+ }
50
+ // Set the value slot
51
+ setEqSlot(target, value);
52
+ // If properties block present, MERGE them
44
53
  if (properties !== null) {
45
- // name = value { new_properties } - set value and replace properties
46
- const result = createValueNode(value);
47
54
  for (const s of properties) {
48
- executeStatement(s, result);
49
- }
50
- const props = getOrCreateProperties(parent);
51
- props[writeKey] = result;
52
- }
53
- else if (preserveProperties) {
54
- // name = value { ... } - update value, preserve existing properties
55
- const props = getOrCreateProperties(parent);
56
- const existing = props[writeKey];
57
- if (existing !== undefined && !isRef(existing)) {
58
- const result = createValueNode(value);
59
- if (existing.properties) {
60
- result.properties = existing.properties;
55
+ executeStatement(s, target, errors);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * `name := value` — assign value + clear properties.
61
+ * `name := value { props }` assign value + replace properties.
62
+ * `name := $ref` — clone the referenced subtree.
63
+ * `name := $ref { props }` — clone + replace properties.
64
+ */
65
+ function executeAssignBoth(node, path, value, properties, errors) {
66
+ if (value.kind === "scalar" &&
67
+ value.value.kind === "reference") {
68
+ // CLONE semantics: resolve + deep copy the target
69
+ let cloned;
70
+ try {
71
+ cloned = resolveAndClone(node, path, value.value.ups, value.value.path);
72
+ }
73
+ catch (err) {
74
+ if (err && typeof err === "object" && "code" in err) {
75
+ errors.push(err);
61
76
  }
62
- props[writeKey] = result;
77
+ return;
63
78
  }
64
- else {
65
- props[writeKey] = createValueNode(value);
79
+ // Check for relative references that escape the clone boundary
80
+ sanitizeClonedRefs(cloned, 0, errors);
81
+ if (properties !== null) {
82
+ cloned.properties = {};
83
+ for (const s of properties) {
84
+ executeStatement(s, cloned, errors);
85
+ }
66
86
  }
87
+ const [writeKey, parent] = buildAccessPath(node, path);
88
+ getOrCreateProperties(parent)[writeKey] = cloned;
67
89
  }
68
90
  else {
69
- // name = value - simple assignment
70
- const props = getOrCreateProperties(parent);
71
- props[writeKey] = createValueNode(value);
91
+ // Literal value: create fresh node (replaces everything)
92
+ const result = {};
93
+ setEqSlot(result, value);
94
+ if (properties !== null) {
95
+ for (const s of properties) {
96
+ executeStatement(s, result, errors);
97
+ }
98
+ }
99
+ const [writeKey, parent] = buildAccessPath(node, path);
100
+ getOrCreateProperties(parent)[writeKey] = result;
72
101
  }
73
102
  }
74
- function executeReplaceProperties(node, path, properties, preserveValue) {
103
+ /**
104
+ * `name: { props }` — preserve existing value, replace properties.
105
+ */
106
+ function executeReplaceProperties(node, path, properties, errors) {
75
107
  const [writeKey, parent] = buildAccessPath(node, path);
76
108
  const result = {};
77
- if (preserveValue) {
78
- const props = getOrCreateProperties(parent);
79
- const existing = props[writeKey];
80
- if (existing !== undefined && !isRef(existing)) {
81
- result.eq = existing.eq;
82
- }
109
+ // Always preserve the existing value
110
+ const parentProps = getOrCreateProperties(parent);
111
+ const existing = parentProps[writeKey];
112
+ if (existing !== undefined) {
113
+ result.eq = existing.eq;
83
114
  }
84
115
  for (const stmt of properties) {
85
- executeStatement(stmt, result);
116
+ executeStatement(stmt, result, errors);
86
117
  }
87
- const props = getOrCreateProperties(parent);
88
- props[writeKey] = result;
118
+ parentProps[writeKey] = result;
89
119
  }
90
- function executeUpdateProperties(node, path, properties) {
120
+ function executeUpdateProperties(node, path, properties, errors) {
91
121
  const [writeKey, parent] = buildAccessPath(node, path);
92
122
  const props = getOrCreateProperties(parent);
93
123
  let target = props[writeKey];
@@ -95,17 +125,8 @@ function executeUpdateProperties(node, path, properties) {
95
125
  target = {};
96
126
  props[writeKey] = target;
97
127
  }
98
- if (isRef(target)) {
99
- const newNode = {};
100
- for (const stmt of properties) {
101
- executeStatement(stmt, newNode);
102
- }
103
- props[writeKey] = newNode;
104
- }
105
- else {
106
- for (const stmt of properties) {
107
- executeStatement(stmt, target);
108
- }
128
+ for (const stmt of properties) {
129
+ executeStatement(stmt, target, errors);
109
130
  }
110
131
  }
111
132
  function executeDefine(node, path, deleted) {
@@ -115,7 +136,10 @@ function executeDefine(node, path, deleted) {
115
136
  props[writeKey] = { deleted: true };
116
137
  }
117
138
  else {
118
- props[writeKey] = {};
139
+ // Get-or-create: if node already exists, leave it alone
140
+ if (props[writeKey] === undefined) {
141
+ props[writeKey] = {};
142
+ }
119
143
  }
120
144
  }
121
145
  /** Navigate to the parent of the final path segment, creating intermediate nodes. */
@@ -129,77 +153,54 @@ function buildAccessPath(node, path) {
129
153
  entry = {};
130
154
  props[segment] = entry;
131
155
  }
132
- if (isRef(entry)) {
133
- entry = {};
134
- props[segment] = entry;
135
- }
136
156
  current = entry;
137
157
  }
138
158
  return [path[path.length - 1], current];
139
159
  }
140
- /** Convert an AST TagValue to a MOTLYValue. */
141
- function createValueNode(value) {
160
+ /** Set the eq slot on a target node from a TagValue. */
161
+ function setEqSlot(target, value, errors) {
142
162
  if (value.kind === "array") {
143
- return { eq: resolveArray(value.elements) };
144
- }
145
- const sv = value.value;
146
- switch (sv.kind) {
147
- case "string":
148
- return { eq: sv.value };
149
- case "number":
150
- return { eq: sv.value };
151
- case "boolean":
152
- return { eq: sv.value };
153
- case "date":
154
- return { eq: new Date(sv.value) };
155
- case "reference":
156
- // Should not be reached for simple ref assignments (handled above).
157
- return {};
163
+ target.eq = resolveArray(value.elements, errors ?? []);
164
+ }
165
+ else {
166
+ const sv = value.value;
167
+ switch (sv.kind) {
168
+ case "string":
169
+ target.eq = sv.value;
170
+ break;
171
+ case "number":
172
+ target.eq = sv.value;
173
+ break;
174
+ case "boolean":
175
+ target.eq = sv.value;
176
+ break;
177
+ case "date":
178
+ target.eq = new Date(sv.value);
179
+ break;
180
+ case "reference":
181
+ target.eq = { linkTo: formatRefString(sv.ups, sv.path) };
182
+ break;
183
+ case "env":
184
+ target.eq = { env: sv.name };
185
+ break;
186
+ case "none":
187
+ delete target.eq;
188
+ break;
189
+ }
158
190
  }
159
191
  }
160
192
  /** Resolve an array of AST elements to MOTLYNodes. */
161
- function resolveArray(elements) {
162
- return elements.map(resolveArrayElement);
163
- }
164
- function resolveArrayElement(el) {
165
- // Reference without properties becomes a link
166
- if (el.value !== null &&
167
- el.value.kind === "scalar" &&
168
- el.value.value.kind === "reference" &&
169
- el.properties === null) {
170
- return {
171
- linkTo: formatRefString(el.value.value.ups, el.value.value.path),
172
- };
173
- }
193
+ function resolveArray(elements, errors) {
194
+ return elements.map((el) => resolveArrayElement(el, errors));
195
+ }
196
+ function resolveArrayElement(el, errors) {
174
197
  const node = {};
175
198
  if (el.value !== null) {
176
- if (el.value.kind === "array") {
177
- node.eq = resolveArray(el.value.elements);
178
- }
179
- else {
180
- const sv = el.value.value;
181
- switch (sv.kind) {
182
- case "string":
183
- node.eq = sv.value;
184
- break;
185
- case "number":
186
- node.eq = sv.value;
187
- break;
188
- case "boolean":
189
- node.eq = sv.value;
190
- break;
191
- case "date":
192
- node.eq = new Date(sv.value);
193
- break;
194
- case "reference":
195
- // Reference with properties: ignore the reference value
196
- break;
197
- }
198
- }
199
+ setEqSlot(node, el.value);
199
200
  }
200
201
  if (el.properties !== null) {
201
202
  for (const stmt of el.properties) {
202
- executeStatement(stmt, node);
203
+ executeStatement(stmt, node, errors);
203
204
  }
204
205
  }
205
206
  return node;
@@ -223,9 +224,97 @@ function formatRefString(ups, path) {
223
224
  }
224
225
  return s;
225
226
  }
226
- /** Check if a MOTLYNode is a MOTLYRef. */
227
- function isRef(node) {
228
- return "linkTo" in node;
227
+ /** Resolve a reference path in the tree and return a deep clone. */
228
+ function resolveAndClone(root, stmtPath, ups, refPath) {
229
+ const refStr = formatRefString(ups, refPath);
230
+ let start;
231
+ if (ups === 0) {
232
+ // Absolute reference: start at root
233
+ start = root;
234
+ }
235
+ else {
236
+ // Relative reference: go up from the current context.
237
+ // stmtPath is the full write path (including the key being assigned to).
238
+ // Current context = parent of write target = stmtPath[0..len-2].
239
+ // Going up `ups` levels: stmtPath[0..len-2-ups].
240
+ const contextLen = stmtPath.length - 1 - ups;
241
+ if (contextLen < 0) {
242
+ throw cloneError(`Clone reference ${refStr} goes ${ups} level(s) up but only ${stmtPath.length - 1} ancestor(s) available`);
243
+ }
244
+ start = root;
245
+ for (let i = 0; i < contextLen; i++) {
246
+ if (!start.properties || !start.properties[stmtPath[i]]) {
247
+ throw cloneError(`Clone reference ${refStr} could not be resolved: path segment "${stmtPath[i]}" not found`);
248
+ }
249
+ start = start.properties[stmtPath[i]];
250
+ }
251
+ }
252
+ // Follow refPath segments
253
+ let current = start;
254
+ for (const seg of refPath) {
255
+ if (seg.kind === "name") {
256
+ if (!current.properties || !current.properties[seg.name]) {
257
+ throw cloneError(`Clone reference ${refStr} could not be resolved: property "${seg.name}" not found`);
258
+ }
259
+ current = current.properties[seg.name];
260
+ }
261
+ else {
262
+ if (!current.eq || !Array.isArray(current.eq)) {
263
+ throw cloneError(`Clone reference ${refStr} could not be resolved: index [${seg.index}] used on non-array`);
264
+ }
265
+ if (seg.index >= current.eq.length) {
266
+ throw cloneError(`Clone reference ${refStr} could not be resolved: index [${seg.index}] out of bounds (array length ${current.eq.length})`);
267
+ }
268
+ current = current.eq[seg.index];
269
+ }
270
+ }
271
+ return (0, clone_1.cloneValue)(current);
272
+ }
273
+ function cloneError(message) {
274
+ const zero = { line: 0, column: 0, offset: 0 };
275
+ return { code: "unresolved-clone-reference", message, begin: zero, end: zero };
276
+ }
277
+ /**
278
+ * Walk a cloned subtree and null out any relative (^) references that
279
+ * escape the clone boundary. A reference at depth D with N ups escapes
280
+ * if N > D. Absolute references (ups=0) are left alone.
281
+ */
282
+ function sanitizeClonedRefs(node, depth, errors) {
283
+ if ((0, motly_ts_interface_1.isRef)(node.eq)) {
284
+ const parsed = parseRefUps(node.eq.linkTo);
285
+ if (parsed.ups > 0 && parsed.ups > depth) {
286
+ const zero = { line: 0, column: 0, offset: 0 };
287
+ errors.push({
288
+ code: "clone-reference-out-of-scope",
289
+ message: `Cloned reference "${node.eq.linkTo}" escapes the clone boundary (${parsed.ups} level(s) up from depth ${depth})`,
290
+ begin: zero,
291
+ end: zero,
292
+ });
293
+ delete node.eq;
294
+ }
295
+ }
296
+ if (node.eq !== undefined && Array.isArray(node.eq)) {
297
+ for (const elem of node.eq) {
298
+ sanitizeClonedRefs(elem, depth + 1, errors);
299
+ }
300
+ }
301
+ if (node.properties) {
302
+ for (const key of Object.keys(node.properties)) {
303
+ sanitizeClonedRefs(node.properties[key], depth + 1, errors);
304
+ }
305
+ }
306
+ }
307
+ /** Extract the ups count from a linkTo string like "$^^name". */
308
+ function parseRefUps(linkTo) {
309
+ let i = 0;
310
+ if (i < linkTo.length && linkTo[i] === "$")
311
+ i++;
312
+ let ups = 0;
313
+ while (i < linkTo.length && linkTo[i] === "^") {
314
+ ups++;
315
+ i++;
316
+ }
317
+ return { ups };
229
318
  }
230
319
  /** Get or create the properties object on a MOTLYValue. */
231
320
  function getOrCreateProperties(node) {
package/build/parser.js CHANGED
@@ -7,9 +7,6 @@ class Parser {
7
7
  this.pos = 0;
8
8
  }
9
9
  // ── Helpers ──────────────────────────────────────────────────────
10
- remaining() {
11
- return this.input.substring(this.pos);
12
- }
13
10
  peekChar() {
14
11
  return this.pos < this.input.length ? this.input[this.pos] : undefined;
15
12
  }
@@ -118,87 +115,40 @@ class Parser {
118
115
  const path = this.parsePropName();
119
116
  this.skipWs();
120
117
  const ch = this.peekChar();
118
+ // Check := first (MUST check before : alone)
119
+ if (ch === ":" && this.startsWith(":=")) {
120
+ this.advance(2);
121
+ this.skipWs();
122
+ const value = this.parseEqValue();
123
+ this.skipWs();
124
+ if (this.peekChar() === "{") {
125
+ const props = this.parsePropertiesBlock();
126
+ return { kind: "assignBoth", path, value, properties: props };
127
+ }
128
+ return { kind: "assignBoth", path, value, properties: null };
129
+ }
121
130
  if (ch === "=") {
122
131
  this.advance(1);
123
132
  this.skipWs();
124
- // Check for `= ... {` (replaceProperties with preserveValue)
125
- if (this.startsWith("...")) {
126
- const saved = this.pos;
127
- this.advance(3);
128
- this.skipWs();
129
- if (this.peekChar() === "{") {
130
- const props = this.parsePropertiesBlock();
131
- return {
132
- kind: "replaceProperties",
133
- path,
134
- properties: props,
135
- preserveValue: true,
136
- };
137
- }
138
- this.pos = saved;
139
- }
140
- // Check for `= {` (replaceProperties without preserveValue)
133
+ // = { is now a parse error (= requires a value)
141
134
  if (this.peekChar() === "{") {
142
- const props = this.parsePropertiesBlock();
143
- return {
144
- kind: "replaceProperties",
145
- path,
146
- properties: props,
147
- preserveValue: false,
148
- };
135
+ throw this.errorPoint("'=' requires a value; use ': { ... }' to replace properties");
149
136
  }
150
- // `= value` (setEq)
137
+ // = value
151
138
  const value = this.parseEqValue();
152
139
  this.skipWs();
153
- // Optionally followed by `{ ... }` or `{ statements }`
140
+ // Optional { props } block (MERGE semantics)
154
141
  if (this.peekChar() === "{") {
155
- const saved = this.pos;
156
- this.advance(1);
157
- this.skipWs();
158
- if (this.startsWith("...")) {
159
- const saved2 = this.pos;
160
- this.advance(3);
161
- this.skipWs();
162
- if (this.peekChar() === "}") {
163
- this.advance(1);
164
- return {
165
- kind: "setEq",
166
- path,
167
- value,
168
- properties: null,
169
- preserveProperties: true,
170
- };
171
- }
172
- this.pos = saved2;
173
- }
174
- this.pos = saved;
175
142
  const props = this.parsePropertiesBlock();
176
- return {
177
- kind: "setEq",
178
- path,
179
- value,
180
- properties: props,
181
- preserveProperties: false,
182
- };
143
+ return { kind: "setEq", path, value, properties: props };
183
144
  }
184
- return {
185
- kind: "setEq",
186
- path,
187
- value,
188
- properties: null,
189
- preserveProperties: false,
190
- };
145
+ return { kind: "setEq", path, value, properties: null };
191
146
  }
192
147
  if (ch === ":") {
193
148
  this.advance(1);
194
149
  this.skipWs();
195
150
  const props = this.parsePropertiesBlock();
196
- return {
197
- kind: "replaceProperties",
198
- path,
199
- properties: props,
200
- preserveValue: false,
201
- };
151
+ return { kind: "replaceProperties", path, properties: props };
202
152
  }
203
153
  if (ch === "{") {
204
154
  const props = this.parsePropertiesBlock();
@@ -223,55 +173,16 @@ class Parser {
223
173
  return this.parseBareString();
224
174
  }
225
175
  // ── Values ──────────────────────────────────────────────────────
226
- parseEqValue() {
176
+ parseEqValue(allowArrays = true) {
227
177
  const ch = this.peekChar();
228
- if (ch === "[")
178
+ if (allowArrays && ch === "[")
229
179
  return { kind: "array", elements: this.parseArray() };
230
- if (ch === "@")
231
- return { kind: "scalar", value: this.parseAtValue() };
232
- if (ch === "$")
233
- return { kind: "scalar", value: this.parseReference() };
234
- if (ch === '"') {
235
- if (this.startsWith('"""')) {
236
- return {
237
- kind: "scalar",
238
- value: { kind: "string", value: this.parseTripleString() },
239
- };
240
- }
180
+ if (this.startsWith("<<<")) {
241
181
  return {
242
182
  kind: "scalar",
243
- value: { kind: "string", value: this.parseDoubleQuotedString() },
183
+ value: { kind: "string", value: this.parseHeredoc() },
244
184
  };
245
185
  }
246
- if (ch === "'") {
247
- if (this.startsWith("'''")) {
248
- return {
249
- kind: "scalar",
250
- value: {
251
- kind: "string",
252
- value: this.parseTripleSingleQuotedString(),
253
- },
254
- };
255
- }
256
- return {
257
- kind: "scalar",
258
- value: { kind: "string", value: this.parseSingleQuotedString() },
259
- };
260
- }
261
- if (ch !== undefined &&
262
- (ch === "-" || (ch >= "0" && ch <= "9") || ch === ".")) {
263
- return this.parseNumberOrString();
264
- }
265
- if (ch !== undefined && isBareChar(ch)) {
266
- return {
267
- kind: "scalar",
268
- value: { kind: "string", value: this.parseBareString() },
269
- };
270
- }
271
- throw this.errorPoint("Expected a value");
272
- }
273
- parseScalarValue() {
274
- const ch = this.peekChar();
275
186
  if (ch === "@")
276
187
  return { kind: "scalar", value: this.parseAtValue() };
277
188
  if (ch === "$")
@@ -304,7 +215,7 @@ class Parser {
304
215
  };
305
216
  }
306
217
  if (ch !== undefined &&
307
- ((ch >= "0" && ch <= "9") || ch === "." || ch === "-")) {
218
+ (ch === "-" || (ch >= "0" && ch <= "9") || ch === ".")) {
308
219
  return this.parseNumberOrString();
309
220
  }
310
221
  if (ch !== undefined && isBareChar(ch)) {
@@ -315,7 +226,7 @@ class Parser {
315
226
  }
316
227
  throw this.errorPoint("Expected a value");
317
228
  }
318
- /** Parse `@true`, `@false`, or `@date` */
229
+ /** Parse `@true`, `@false`, `@none`, `@env.NAME`, or `@date` */
319
230
  parseAtValue() {
320
231
  const begin = this.position();
321
232
  this.expectChar("@");
@@ -327,6 +238,15 @@ class Parser {
327
238
  this.advance(5);
328
239
  return { kind: "boolean", value: false };
329
240
  }
241
+ if (this.startsWith("none") && !this.isBareCharAt(4)) {
242
+ this.advance(4);
243
+ return { kind: "none" };
244
+ }
245
+ if (this.startsWith("env.")) {
246
+ this.advance(4);
247
+ const name = this.parseBareString();
248
+ return { kind: "env", name };
249
+ }
330
250
  const ch = this.peekChar();
331
251
  if (ch !== undefined && ch >= "0" && ch <= "9") {
332
252
  return this.parseDate(begin);
@@ -337,11 +257,11 @@ class Parser {
337
257
  this.pos++;
338
258
  }
339
259
  const token = this.pos > tokenStart ? this.input.substring(tokenStart, this.pos) : "";
340
- throw this.errorSpan(`Illegal constant @${token}; expected @true, @false, or @date`, begin);
260
+ throw this.errorSpan(`Illegal constant @${token}; expected @true, @false, @none, @env.NAME, or @date`, begin);
341
261
  }
342
262
  isBareCharAt(offset) {
343
- const rem = this.remaining();
344
- return offset < rem.length && isBareChar(rem[offset]);
263
+ const absPos = this.pos + offset;
264
+ return absPos < this.input.length && isBareChar(this.input[absPos]);
345
265
  }
346
266
  parseDate(begin) {
347
267
  const start = this.pos;
@@ -722,6 +642,79 @@ class Parser {
722
642
  return ch;
723
643
  }
724
644
  }
645
+ // ── Heredoc ─────────────────────────────────────────────────────
646
+ parseHeredoc() {
647
+ const begin = this.position();
648
+ this.advance(3); // past <<<
649
+ // Skip spaces/tabs on the same line
650
+ while (this.pos < this.input.length) {
651
+ const ch = this.input[this.pos];
652
+ if (ch === " " || ch === "\t") {
653
+ this.pos++;
654
+ }
655
+ else {
656
+ break;
657
+ }
658
+ }
659
+ // Allow \r before \n
660
+ if (this.pos < this.input.length && this.input[this.pos] === "\r") {
661
+ this.advance(1);
662
+ }
663
+ // Expect newline
664
+ if (this.pos >= this.input.length || this.input[this.pos] !== "\n") {
665
+ throw this.errorSpan("Expected newline after <<<", begin);
666
+ }
667
+ this.advance(1);
668
+ // Collect lines until we find >>> on its own line
669
+ const lines = [];
670
+ let foundClose = false;
671
+ while (this.pos < this.input.length) {
672
+ // Read a line (break only on \n)
673
+ const lineStart = this.pos;
674
+ while (this.pos < this.input.length && this.input[this.pos] !== "\n") {
675
+ this.pos++;
676
+ }
677
+ // Strip trailing \r for CRLF compatibility
678
+ let lineContent = this.input.substring(lineStart, this.pos);
679
+ if (lineContent.endsWith("\r")) {
680
+ lineContent = lineContent.substring(0, lineContent.length - 1);
681
+ }
682
+ // Consume the \n
683
+ if (this.pos < this.input.length && this.input[this.pos] === "\n") {
684
+ this.advance(1);
685
+ }
686
+ // Check if this is the closing >>> line
687
+ if (lineContent.trim() === ">>>") {
688
+ foundClose = true;
689
+ break;
690
+ }
691
+ lines.push(lineContent);
692
+ }
693
+ if (!foundClose) {
694
+ throw this.errorSpan("Unterminated heredoc (expected >>>)", begin);
695
+ }
696
+ if (lines.length === 0) {
697
+ return "";
698
+ }
699
+ // Determine strip amount from first line containing a non-space character
700
+ let strip = 0;
701
+ for (const line of lines) {
702
+ const trimmed = line.trimStart();
703
+ if (trimmed.length > 0) {
704
+ strip = line.length - trimmed.length;
705
+ break;
706
+ }
707
+ }
708
+ // Strip indentation and join; whitespace-only lines become empty
709
+ const stripped = lines.map((line) => {
710
+ if (line.trimStart().length === 0)
711
+ return "";
712
+ if (strip <= line.length)
713
+ return line.substring(strip);
714
+ return line;
715
+ });
716
+ return stripped.join("\n") + "\n";
717
+ }
725
718
  // ── Arrays ──────────────────────────────────────────────────────
726
719
  parseArray() {
727
720
  const begin = this.position();
@@ -762,7 +755,7 @@ class Parser {
762
755
  const elements = this.parseArray();
763
756
  return { value: { kind: "array", elements }, properties: null };
764
757
  }
765
- const value = this.parseScalarValue();
758
+ const value = this.parseEqValue(false);
766
759
  this.skipWs();
767
760
  if (this.peekChar() === "{") {
768
761
  const props = this.parsePropertiesBlock();
@@ -774,17 +767,6 @@ class Parser {
774
767
  parsePropertiesBlock() {
775
768
  const begin = this.position();
776
769
  this.expectChar("{");
777
- this.skipWs();
778
- if (this.startsWith("...")) {
779
- const saved = this.pos;
780
- this.advance(3);
781
- this.skipWs();
782
- if (this.peekChar() === "}") {
783
- this.advance(1);
784
- return [];
785
- }
786
- this.pos = saved;
787
- }
788
770
  const stmts = [];
789
771
  for (;;) {
790
772
  this.skipWsAndCommas();
package/build/session.js CHANGED
@@ -4,6 +4,7 @@ exports.MOTLYSession = void 0;
4
4
  const parser_1 = require("./parser");
5
5
  const interpreter_1 = require("./interpreter");
6
6
  const validate_1 = require("./validate");
7
+ const clone_1 = require("./clone");
7
8
  /**
8
9
  * A stateful MOTLY parsing session.
9
10
  *
@@ -24,8 +25,8 @@ class MOTLYSession {
24
25
  this.ensureAlive();
25
26
  try {
26
27
  const stmts = (0, parser_1.parse)(source);
27
- this.value = (0, interpreter_1.execute)(stmts, this.value);
28
- return [];
28
+ const errors = (0, interpreter_1.execute)(stmts, this.value);
29
+ return errors;
29
30
  }
30
31
  catch (e) {
31
32
  if (isMotlyError(e))
@@ -41,8 +42,10 @@ class MOTLYSession {
41
42
  this.ensureAlive();
42
43
  try {
43
44
  const stmts = (0, parser_1.parse)(source);
44
- this.schema = (0, interpreter_1.execute)(stmts, {});
45
- return [];
45
+ const fresh = {};
46
+ const errors = (0, interpreter_1.execute)(stmts, fresh);
47
+ this.schema = fresh;
48
+ return errors;
46
49
  }
47
50
  catch (e) {
48
51
  if (isMotlyError(e))
@@ -62,7 +65,7 @@ class MOTLYSession {
62
65
  */
63
66
  getValue() {
64
67
  this.ensureAlive();
65
- return deepClone(this.value);
68
+ return (0, clone_1.cloneValue)(this.value);
66
69
  }
67
70
  /**
68
71
  * Validate the session's value against its stored schema.
@@ -103,33 +106,3 @@ function isMotlyError(e) {
103
106
  "begin" in e &&
104
107
  "end" in e);
105
108
  }
106
- function deepClone(value) {
107
- const result = {};
108
- if (value.deleted)
109
- result.deleted = true;
110
- if (value.eq !== undefined) {
111
- if (value.eq instanceof Date) {
112
- result.eq = new Date(value.eq.getTime());
113
- }
114
- else if (Array.isArray(value.eq)) {
115
- result.eq = value.eq.map(cloneNode);
116
- }
117
- else {
118
- result.eq = value.eq;
119
- }
120
- }
121
- if (value.properties) {
122
- const props = {};
123
- for (const key of Object.keys(value.properties)) {
124
- props[key] = cloneNode(value.properties[key]);
125
- }
126
- result.properties = props;
127
- }
128
- return result;
129
- }
130
- function cloneNode(node) {
131
- if ("linkTo" in node) {
132
- return { linkTo: node.linkTo };
133
- }
134
- return deepClone(node);
135
- }
package/build/validate.js CHANGED
@@ -2,15 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateReferences = validateReferences;
4
4
  exports.validateSchema = validateSchema;
5
- // ── Helpers ─────────────────────────────────────────────────────
6
- function isRef(node) {
7
- return "linkTo" in node;
8
- }
5
+ const motly_ts_interface_1 = require("motly-ts-interface");
9
6
  function getEqString(node) {
10
7
  return typeof node.eq === "string" ? node.eq : undefined;
11
8
  }
12
9
  function valueEqString(node) {
13
- if (isRef(node))
10
+ if ((0, motly_ts_interface_1.isRef)(node.eq) || (0, motly_ts_interface_1.isEnvRef)(node.eq))
14
11
  return undefined;
15
12
  return getEqString(node);
16
13
  }
@@ -18,7 +15,7 @@ function extractSection(node, name) {
18
15
  if (!node.properties)
19
16
  return undefined;
20
17
  const section = node.properties[name];
21
- if (section === undefined || isRef(section))
18
+ if (section === undefined || (0, motly_ts_interface_1.isRef)(section.eq) || (0, motly_ts_interface_1.isEnvRef)(section.eq))
22
19
  return undefined;
23
20
  return section.properties;
24
21
  }
@@ -37,10 +34,12 @@ function walkRefs(node, path, ancestors, root, errors) {
37
34
  }
38
35
  if (node.properties) {
39
36
  for (const key of Object.keys(node.properties)) {
40
- const value = node.properties[key];
37
+ const child = node.properties[key];
41
38
  path.push(key);
42
- if (isRef(value)) {
43
- const errMsg = checkLink(value, ancestors, root);
39
+ // Check if child's eq is a reference (checked at property level
40
+ // to maintain correct ancestor depth for reference resolution)
41
+ if ((0, motly_ts_interface_1.isRef)(child.eq)) {
42
+ const errMsg = checkLink(child.eq, ancestors, root);
44
43
  if (errMsg !== null) {
45
44
  errors.push({
46
45
  message: errMsg,
@@ -49,11 +48,10 @@ function walkRefs(node, path, ancestors, root, errors) {
49
48
  });
50
49
  }
51
50
  }
52
- else {
53
- ancestors.push(node);
54
- walkRefs(value, path, ancestors, root, errors);
55
- ancestors.pop();
56
- }
51
+ // Recurse into child
52
+ ancestors.push(node);
53
+ walkRefs(child, path, ancestors, root, errors);
54
+ ancestors.pop();
57
55
  path.pop();
58
56
  }
59
57
  }
@@ -63,8 +61,9 @@ function walkArrayRefs(arr, path, ancestors, parentNode, root, errors) {
63
61
  const elem = arr[i];
64
62
  const idxKey = `[${i}]`;
65
63
  path.push(idxKey);
66
- if (isRef(elem)) {
67
- const errMsg = checkLink(elem, ancestors, root);
64
+ // Check if element's eq is a reference
65
+ if ((0, motly_ts_interface_1.isRef)(elem.eq)) {
66
+ const errMsg = checkLink(elem.eq, ancestors, root);
68
67
  if (errMsg !== null) {
69
68
  errors.push({
70
69
  message: errMsg,
@@ -73,16 +72,17 @@ function walkArrayRefs(arr, path, ancestors, parentNode, root, errors) {
73
72
  });
74
73
  }
75
74
  }
76
- else {
77
- ancestors.push(parentNode);
78
- walkRefs(elem, path, ancestors, root, errors);
79
- ancestors.pop();
80
- }
75
+ // Recurse into element
76
+ ancestors.push(parentNode);
77
+ walkRefs(elem, path, ancestors, root, errors);
78
+ ancestors.pop();
81
79
  path.pop();
82
80
  }
83
81
  }
84
82
  function checkLink(link, ancestors, root) {
85
- const { ups, segments } = parseLinkString(link.linkTo);
83
+ const { ups, segments, error } = parseLinkString(link.linkTo);
84
+ if (error !== null)
85
+ return error;
86
86
  let start;
87
87
  if (ups === 0) {
88
88
  start = root;
@@ -130,9 +130,10 @@ function parseLinkString(s) {
130
130
  if (i < s.length)
131
131
  i++; // skip ']'
132
132
  const idx = parseInt(idxBuf, 10);
133
- if (!isNaN(idx) && idx >= 0) {
134
- segments.push({ kind: "index", index: idx });
133
+ if (isNaN(idx) || idx < 0) {
134
+ return { ups, segments, error: `Reference "${s}" has invalid array index [${idxBuf}]` };
135
135
  }
136
+ segments.push({ kind: "index", index: idx });
136
137
  }
137
138
  else {
138
139
  nameBuf += ch;
@@ -142,44 +143,29 @@ function parseLinkString(s) {
142
143
  if (nameBuf.length > 0) {
143
144
  segments.push({ kind: "name", name: nameBuf });
144
145
  }
145
- return { ups, segments };
146
+ return { ups, segments, error: null };
146
147
  }
147
148
  function resolvePath(start, segments, linkStr) {
148
- let current = { kind: "node", node: start };
149
+ let current = start;
149
150
  for (const seg of segments) {
150
- if (current.kind === "terminal") {
151
- return `Reference "${linkStr}" could not be resolved: cannot follow path through a link`;
152
- }
153
- const node = current.node;
154
151
  if (seg.kind === "name") {
155
- if (!node.properties) {
152
+ if (!current.properties) {
156
153
  return `Reference "${linkStr}" could not be resolved: property "${seg.name}" not found (node has no properties)`;
157
154
  }
158
- const child = node.properties[seg.name];
155
+ const child = current.properties[seg.name];
159
156
  if (child === undefined) {
160
157
  return `Reference "${linkStr}" could not be resolved: property "${seg.name}" not found`;
161
158
  }
162
- if (isRef(child)) {
163
- current = { kind: "terminal" };
164
- }
165
- else {
166
- current = { kind: "node", node: child };
167
- }
159
+ current = child;
168
160
  }
169
161
  else {
170
- if (node.eq === undefined || !Array.isArray(node.eq)) {
162
+ if (current.eq === undefined || !Array.isArray(current.eq)) {
171
163
  return `Reference "${linkStr}" could not be resolved: index [${seg.index}] used on non-array`;
172
164
  }
173
- if (seg.index >= node.eq.length) {
174
- return `Reference "${linkStr}" could not be resolved: index [${seg.index}] out of bounds (array length ${node.eq.length})`;
175
- }
176
- const elem = node.eq[seg.index];
177
- if (isRef(elem)) {
178
- current = { kind: "terminal" };
179
- }
180
- else {
181
- current = { kind: "node", node: elem };
165
+ if (seg.index >= current.eq.length) {
166
+ return `Reference "${linkStr}" could not be resolved: index [${seg.index}] out of bounds (array length ${current.eq.length})`;
182
167
  }
168
+ current = current.eq[seg.index];
183
169
  }
184
170
  }
185
171
  return null;
@@ -197,7 +183,7 @@ function getAdditionalPolicy(schema) {
197
183
  const additional = schema.properties["Additional"];
198
184
  if (additional === undefined)
199
185
  return { kind: "reject" };
200
- if (isRef(additional))
186
+ if ((0, motly_ts_interface_1.isRef)(additional.eq))
201
187
  return { kind: "reject" };
202
188
  const eqStr = getEqString(additional);
203
189
  if (eqStr !== undefined) {
@@ -276,12 +262,12 @@ function makeTypeSpecNode(typeName) {
276
262
  return { eq: typeName };
277
263
  }
278
264
  function validateValueType(value, typeSpec, types, path, errors) {
279
- if (isRef(typeSpec))
265
+ if ((0, motly_ts_interface_1.isRef)(typeSpec.eq))
280
266
  return;
281
267
  // Check for union type (oneOf)
282
268
  if (typeSpec.properties) {
283
269
  const oneOf = typeSpec.properties["oneOf"];
284
- if (oneOf !== undefined && !isRef(oneOf)) {
270
+ if (oneOf !== undefined && !(0, motly_ts_interface_1.isRef)(oneOf.eq)) {
285
271
  validateUnion(value, oneOf, types, path, errors);
286
272
  return;
287
273
  }
@@ -289,14 +275,14 @@ function validateValueType(value, typeSpec, types, path, errors) {
289
275
  // Check for enum (eq) or pattern (matches)
290
276
  if (typeSpec.properties) {
291
277
  const eqProp = typeSpec.properties["eq"];
292
- if (eqProp !== undefined && !isRef(eqProp)) {
278
+ if (eqProp !== undefined && !(0, motly_ts_interface_1.isRef)(eqProp.eq)) {
293
279
  if (Array.isArray(eqProp.eq)) {
294
280
  validateEnum(value, eqProp.eq, path, errors);
295
281
  return;
296
282
  }
297
283
  }
298
284
  const matchesProp = typeSpec.properties["matches"];
299
- if (matchesProp !== undefined && !isRef(matchesProp)) {
285
+ if (matchesProp !== undefined && !(0, motly_ts_interface_1.isRef)(matchesProp.eq)) {
300
286
  const baseType = getEqString(typeSpec);
301
287
  if (baseType !== undefined) {
302
288
  validateBaseType(value, baseType, types, path, errors);
@@ -313,7 +299,7 @@ function validateValueType(value, typeSpec, types, path, errors) {
313
299
  ("Required" in typeSpec.properties ||
314
300
  "Optional" in typeSpec.properties ||
315
301
  "Additional" in typeSpec.properties)) {
316
- if (isRef(value)) {
302
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
317
303
  errors.push({
318
304
  message: "Expected a tag but found a link",
319
305
  path: [...path],
@@ -382,7 +368,7 @@ function validateBaseType(value, typeName, types, path, errors) {
382
368
  }
383
369
  }
384
370
  function validateTypeString(value, path, errors) {
385
- if (isRef(value)) {
371
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
386
372
  errors.push({
387
373
  message: 'Expected type "string" but found a link',
388
374
  path: [...path],
@@ -399,7 +385,7 @@ function validateTypeString(value, path, errors) {
399
385
  }
400
386
  }
401
387
  function validateTypeNumber(value, path, errors) {
402
- if (isRef(value)) {
388
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
403
389
  errors.push({
404
390
  message: 'Expected type "number" but found a link',
405
391
  path: [...path],
@@ -416,7 +402,7 @@ function validateTypeNumber(value, path, errors) {
416
402
  }
417
403
  }
418
404
  function validateTypeBoolean(value, path, errors) {
419
- if (isRef(value)) {
405
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
420
406
  errors.push({
421
407
  message: 'Expected type "boolean" but found a link',
422
408
  path: [...path],
@@ -433,7 +419,7 @@ function validateTypeBoolean(value, path, errors) {
433
419
  }
434
420
  }
435
421
  function validateTypeDate(value, path, errors) {
436
- if (isRef(value)) {
422
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
437
423
  errors.push({
438
424
  message: 'Expected type "date" but found a link',
439
425
  path: [...path],
@@ -450,7 +436,7 @@ function validateTypeDate(value, path, errors) {
450
436
  }
451
437
  }
452
438
  function validateTypeTag(value, path, errors) {
453
- if (isRef(value)) {
439
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
454
440
  errors.push({
455
441
  message: 'Expected type "tag" but found a link',
456
442
  path: [...path],
@@ -459,7 +445,7 @@ function validateTypeTag(value, path, errors) {
459
445
  }
460
446
  }
461
447
  function validateTypeFlag(value, path, errors) {
462
- if (isRef(value)) {
448
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
463
449
  errors.push({
464
450
  message: 'Expected type "flag" but found a link',
465
451
  path: [...path],
@@ -468,7 +454,7 @@ function validateTypeFlag(value, path, errors) {
468
454
  }
469
455
  }
470
456
  function validateArrayType(value, innerType, types, path, errors) {
471
- if (isRef(value)) {
457
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
472
458
  errors.push({
473
459
  message: `Expected type "${innerType}[]" but found a link`,
474
460
  path: [...path],
@@ -490,7 +476,7 @@ function validateArrayType(value, innerType, types, path, errors) {
490
476
  }
491
477
  }
492
478
  function validateEnum(value, allowed, path, errors) {
493
- if (isRef(value)) {
479
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
494
480
  errors.push({
495
481
  message: "Expected an enum value but found a link",
496
482
  path: [...path],
@@ -512,7 +498,7 @@ function validateEnum(value, allowed, path, errors) {
512
498
  return;
513
499
  }
514
500
  const matches = allowed.some((a) => {
515
- if (isRef(a))
501
+ if ((0, motly_ts_interface_1.isRef)(a.eq))
516
502
  return false;
517
503
  const aeq = a.eq;
518
504
  if (aeq instanceof Date && nodeEq instanceof Date) {
@@ -522,7 +508,7 @@ function validateEnum(value, allowed, path, errors) {
522
508
  });
523
509
  if (!matches) {
524
510
  const allowedStrs = allowed
525
- .filter((a) => !isRef(a))
511
+ .filter((a) => !(0, motly_ts_interface_1.isRef)(a.eq))
526
512
  .map((a) => {
527
513
  const aeq = a.eq;
528
514
  return JSON.stringify(String(aeq));
@@ -538,7 +524,7 @@ function validatePattern(value, matchesNode, path, errors) {
538
524
  const pattern = getEqString(matchesNode);
539
525
  if (pattern === undefined)
540
526
  return;
541
- if (isRef(value)) {
527
+ if ((0, motly_ts_interface_1.isRef)(value.eq)) {
542
528
  errors.push({
543
529
  message: "Expected a value matching a pattern but found a link",
544
530
  path: [...path],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/motly-ts-parser",
3
- "version": "0.0.2",
3
+ "version": "0.2.0",
4
4
  "description": "MOTLY text to wire format parser — pure TypeScript",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",