@shapeshift-labs/frontier-lang-parser 0.3.48 → 0.3.50

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.
@@ -1,5 +1,6 @@
1
1
  import { readFrontierNestedBlocks } from './source-syntax-report.js';
2
2
  import { readActionValue } from './action-expression.js';
3
+ import { readElseHeaderBlock } from './action-else-block.js';
3
4
 
4
5
  export function readActionBodyRecords(body) {
5
6
  const records = [];
@@ -27,10 +28,18 @@ function parseActionBodyRecords(source, state) {
27
28
  const open = offset + ifHeader[0].length - 1;
28
29
  const close = findMatchingBrace(source, open);
29
30
  if (close < 0) break;
31
+ const elseBlock = readElseHeaderBlock(source, close + 1, source.length, { skipWhitespace: skipWhitespaceAndComments, findMatchingBrace });
30
32
  const index = state.index++;
31
- const record = parseActionIfBlock(ifHeader[1].trim(), source.slice(open + 1, close), index, state);
33
+ const record = parseActionIfBlock(ifHeader[1].trim(), source.slice(open + 1, close), index, state, elseBlock);
32
34
  if (record) records.push(record);
33
- offset = close + 1;
35
+ offset = elseBlock ? elseBlock.close + 1 : close + 1;
36
+ continue;
37
+ }
38
+ const elseHeader = /^else\b([^{\n]*)\{/.exec(source.slice(offset));
39
+ if (elseHeader) {
40
+ const open = offset + elseHeader[0].length - 1;
41
+ const close = findMatchingBrace(source, open);
42
+ offset = close < 0 ? source.length : close + 1;
34
43
  continue;
35
44
  }
36
45
  const lineEnd = source.indexOf('\n', offset);
@@ -45,9 +54,10 @@ function parseActionBodyRecords(source, state) {
45
54
  return records;
46
55
  }
47
56
 
48
- function parseActionIfBlock(header, body, index, state) {
57
+ function parseActionIfBlock(header, body, index, state, elseBlock) {
49
58
  const details = parseActionIfHeader(header, index);
50
59
  if (!details.condition) return undefined;
60
+ const elseDetails = elseBlock ? parseActionElseHeader(elseBlock.header, index) : undefined;
51
61
  return compactRecord({
52
62
  kind: 'if',
53
63
  id: details.id,
@@ -55,7 +65,10 @@ function parseActionIfBlock(header, body, index, state) {
55
65
  comparisonType: details.comparisonType,
56
66
  callType: details.callType,
57
67
  condition: details.condition,
58
- body: parseActionBodyRecords(body, state)
68
+ body: parseActionBodyRecords(body, state),
69
+ elseId: elseDetails?.id,
70
+ elseName: elseDetails?.name,
71
+ elseBody: elseBlock ? parseActionBodyRecords(elseBlock.body, state) : undefined
59
72
  });
60
73
  }
61
74
 
@@ -76,6 +89,15 @@ function parseActionIfHeader(header, index) {
76
89
  };
77
90
  }
78
91
 
92
+ function parseActionElseHeader(header, index) {
93
+ const withoutId = header.replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
94
+ const name = firstIdentifier(withoutId) ?? `else_${index}`;
95
+ return {
96
+ id: idFrom(header, `action_body_else_${name}`),
97
+ name
98
+ };
99
+ }
100
+
79
101
  function parseActionBodyLine(line, index) {
80
102
  const statement = /^([A-Za-z_$][\w$-]*)(?:\s+([A-Za-z_$@./:*+-][\w$./@:*+-]*))?(.*)$/.exec(line);
81
103
  if (!statement) return undefined;
@@ -111,14 +133,36 @@ function parseActionBodyLine(line, index) {
111
133
  return compactRecord({ kind: 'let', id: idFrom(rest, `action_body_let_${name}`), name, valueType, comparisonType, callType, value });
112
134
  }
113
135
  if (rowKind === 'return') {
114
- const valueText = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
115
- const value = valueText ? readActionValue(valueText) : undefined;
116
- if (valueText && !value) return undefined;
117
- return compactRecord({ kind: 'return', id: idFrom(rest, `action_body_return_${index}`), value });
136
+ const details = readReturnDetails(rawName, rest);
137
+ const value = details.valueText ? readActionValue(details.valueText, details) : undefined;
138
+ if (details.valueText && !value) return undefined;
139
+ return compactRecord({
140
+ kind: 'return',
141
+ id: idFrom(rest, `action_body_return_${index}`),
142
+ valueType: details.valueType,
143
+ comparisonType: details.comparisonType,
144
+ callType: details.callType,
145
+ value
146
+ });
118
147
  }
119
148
  return undefined;
120
149
  }
121
150
 
151
+ function readReturnDetails(rawName, rest) {
152
+ const text = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
153
+ const explicitValue = /\bvalue\s+/.test(text);
154
+ const valueType = readInlineType(text);
155
+ const comparisonType = readInlineComparisonType(text);
156
+ const callType = readInlineCallType(text);
157
+ const valueText = explicitValue ? readInlineValue('value', text) : text;
158
+ return {
159
+ valueText,
160
+ valueType,
161
+ comparisonType,
162
+ callType
163
+ };
164
+ }
165
+
122
166
  function skipWhitespaceAndComments(source, offset) {
123
167
  let index = offset;
124
168
  while (index < source.length) {
@@ -0,0 +1,18 @@
1
+ export function readElseHeaderBlock(source, offset, endOffset = source.length, helpers = {}) {
2
+ const skipWhitespace = helpers.skipWhitespace;
3
+ const findMatchingBrace = helpers.findMatchingBrace;
4
+ if (typeof skipWhitespace !== 'function' || typeof findMatchingBrace !== 'function') return undefined;
5
+ const elseOffset = skipWhitespace(source, offset, endOffset);
6
+ const elseHeader = /^else\b([^{\n]*)\{/.exec(source.slice(elseOffset, endOffset));
7
+ if (!elseHeader) return undefined;
8
+ const header = elseHeader[1].trim();
9
+ const open = elseOffset + elseHeader[0].length - 1;
10
+ const close = findMatchingBrace(source, open);
11
+ if (close < 0 || close > endOffset) return undefined;
12
+ return { header, start: elseOffset, open, close, body: source.slice(open + 1, close), supported: isSupportedElseHeader(header) };
13
+ }
14
+
15
+ export function isSupportedElseHeader(header) {
16
+ const withoutId = String(header ?? '').replace(/@id\(\s*["'][^"']+["']\s*\)/g, '').trim();
17
+ return !withoutId || /^[A-Za-z_$][\w$-]*$/.test(withoutId);
18
+ }
@@ -1,4 +1,5 @@
1
1
  import { parseActionValue } from './action-expression.js';
2
+ import { readElseHeaderBlock } from './action-else-block.js';
2
3
 
3
4
  const ACTION_BODY_ROWS = new Set(['set', 'insert', 'remove', 'merge', 'callEffect', 'return', 'if', 'let']);
4
5
 
@@ -33,9 +34,31 @@ function readActionSyntaxRows(source, block, options, state, startOffset, endOff
33
34
  }, state, parentActionBodyId);
34
35
  children.push(child);
35
36
  children.push(...readActionSyntaxRows(source, block, options, state, open + 1, close, child.id));
37
+ const elseBlock = readElseHeaderBlock(source, close + 1, endOffset, { skipWhitespace: skipWhitespaceAndLineComments, findMatchingBrace });
38
+ if (elseBlock) {
39
+ const elseChild = actionSyntaxChild(source, block, options, {
40
+ text: source.slice(elseBlock.start, elseBlock.open + 1).trim(),
41
+ startOffset: elseBlock.start,
42
+ endOffset: elseBlock.open + 1
43
+ }, state, child.id, { recognized: elseBlock.supported });
44
+ children.push(elseChild);
45
+ if (elseBlock.supported) children.push(...readActionSyntaxRows(source, block, options, state, elseBlock.open + 1, elseBlock.close, elseChild.id));
46
+ offset = elseBlock.close + 1;
47
+ continue;
48
+ }
36
49
  offset = close + 1;
37
50
  continue;
38
51
  }
52
+ const elseBlock = readElseHeaderBlock(source, offset, endOffset, { skipWhitespace: skipWhitespaceAndLineComments, findMatchingBrace });
53
+ if (elseBlock) {
54
+ children.push(actionSyntaxChild(source, block, options, {
55
+ text: source.slice(elseBlock.start, elseBlock.open + 1).trim(),
56
+ startOffset: elseBlock.start,
57
+ endOffset: elseBlock.open + 1
58
+ }, state, parentActionBodyId));
59
+ offset = elseBlock.close + 1;
60
+ continue;
61
+ }
39
62
  const lineEnd = source.indexOf('\n', offset);
40
63
  const rawEnd = lineEnd < 0 || lineEnd > endOffset ? endOffset : lineEnd;
41
64
  const rawLine = source.slice(offset, rawEnd);
@@ -52,14 +75,14 @@ function readActionSyntaxRows(source, block, options, state, startOffset, endOff
52
75
  return children;
53
76
  }
54
77
 
55
- function actionSyntaxChild(source, block, options, line, state, parentActionBodyId) {
78
+ function actionSyntaxChild(source, block, options, line, state, parentActionBodyId, overrides = {}) {
56
79
  const rowIndex = state.rowIndex++;
57
80
  const row = /^([A-Za-z_$][\w$-]*)(?:\s+([A-Za-z_$@./:*+-][\w$./@:*+-]*))?(.*)$/.exec(line.text);
58
81
  const rowKind = row?.[1] ?? 'unknown';
59
82
  const name = actionRowName(rowKind, row?.[2], rowIndex);
60
83
  const rest = row?.[2]?.startsWith('@') ? ` ${row[2]}${row[3] ?? ''}` : (row?.[3] ?? '');
61
84
  const validation = validateActionRow(rowKind, row?.[2], rest, line.text);
62
- const recognized = ACTION_BODY_ROWS.has(rowKind) && validation.ok;
85
+ const recognized = overrides.recognized ?? (ACTION_BODY_ROWS.has(rowKind) && validation.ok);
63
86
  return cleanRecord({
64
87
  kind: recognized ? 'actionBodyRow' : 'actionUnknownRow',
65
88
  rowKind,
@@ -104,8 +127,12 @@ function validateActionRow(rowKind, rawName, rest, header) {
104
127
  return input ? validateActionExpressionText(input) : { ok: true };
105
128
  }
106
129
  if (rowKind === 'return') {
107
- const value = readReturnValue(rawName, rest);
108
- return value ? validateActionExpressionText(value) : { ok: true };
130
+ const details = readReturnDetails(rawName, rest);
131
+ if (!details.valueText) return { ok: true };
132
+ const parsed = parseActionValue(details.valueText, details);
133
+ if (parsed.ok) return { ok: true };
134
+ if (isActionExpressionAdmissionReason(parsed.reason)) return { ok: false, reason: parsed.reason };
135
+ return { ok: false, reason: 'unsupported-action-return-value' };
109
136
  }
110
137
  if (rowKind === 'let') {
111
138
  if (!rawName || rawName.startsWith('@') || !/^[A-Za-z_$][\w$-]*$/.test(rawName)) {
@@ -148,9 +175,15 @@ function readIfCondition(header) {
148
175
  return explicit ? explicit[1].trim() : text;
149
176
  }
150
177
 
151
- function readReturnValue(rawName, rest) {
152
- if (rawName?.startsWith('@')) return stripIds(`${rawName}${rest ?? ''}`).trim();
153
- return stripIds(`${rawName ?? ''}${rest ?? ''}`).trim();
178
+ function readReturnDetails(rawName, rest) {
179
+ const text = stripIds(rawName?.startsWith('@') ? rest : `${rawName ?? ''}${rest ?? ''}`).trim();
180
+ const explicitValue = /\bvalue\s+/.test(text);
181
+ return {
182
+ valueText: explicitValue ? readInlineValue('value', text) : text,
183
+ valueType: readInlineType(text),
184
+ comparisonType: readInlineComparisonType(text),
185
+ callType: readInlineCallType(text)
186
+ };
154
187
  }
155
188
 
156
189
  function stripIds(text) {
@@ -162,6 +195,20 @@ function validateActionExpressionText(text, options = {}) {
162
195
  return parsed.ok ? { ok: true } : { ok: false, reason: parsed.reason };
163
196
  }
164
197
 
198
+ function isActionExpressionAdmissionReason(reason) {
199
+ return reason === 'missing-action-expression-type'
200
+ || reason === 'unsupported-action-expression-type'
201
+ || reason === 'missing-action-comparison-type'
202
+ || reason === 'unsupported-action-comparison-type'
203
+ || reason === 'missing-action-call-type'
204
+ || reason === 'unsupported-action-call-type'
205
+ || reason === 'unsupported-action-call-callee'
206
+ || reason === 'unsupported-action-call-argument'
207
+ || reason === 'unsupported-action-expression-ref'
208
+ || reason === 'malformed-action-expression'
209
+ || reason === 'missing-action-expression';
210
+ }
211
+
165
212
  function readNestedBodyBlocks(kind, source) {
166
213
  const blocks = [];
167
214
  const header = new RegExp('\\b' + kind.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?:\\s+([^{}\\n]+?))?\\s*\\{', 'g');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.3.48",
3
+ "version": "0.3.50",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  ],
23
23
  "scripts": {
24
24
  "build": "node scripts/build.mjs",
25
- "test": "npm run build && node test/smoke.mjs && node test/action-body-smoke.mjs && node test/action-call-smoke.mjs && node test/target-projection-aggregate-smoke.mjs && node test/conversion-constraint-fields-smoke.mjs && node test/conversion-evidence-smoke.mjs && node test/package-canvas-surface-smoke.mjs && node test/view-render-graph-smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
25
+ "test": "npm run build && node test/smoke.mjs && node test/action-body-smoke.mjs && node test/action-return-smoke.mjs && node test/action-else-smoke.mjs && node test/action-call-smoke.mjs && node test/target-projection-aggregate-smoke.mjs && node test/conversion-constraint-fields-smoke.mjs && node test/conversion-evidence-smoke.mjs && node test/package-canvas-surface-smoke.mjs && node test/view-render-graph-smoke.mjs && node test/resource-graph-smoke.mjs && node test/interlingua-smoke.mjs && node test/dialect-registry-smoke.mjs",
26
26
  "typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
27
27
  "fuzz": "npm run build && node fuzz/smoke.mjs",
28
28
  "bench": "npm run build && node bench/smoke.mjs",