@shapeshift-labs/frontier-lang-parser 0.2.0 → 0.3.1

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.
@@ -0,0 +1,13 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { parseFrontierSource } from '../dist/index.js';
3
+
4
+ const blocks = Array.from({ length: 100 }, (_, index) => `entity Todo${index} @id("ent_${index}") {
5
+ title @id("field_title_${index}"): Text { merge conflict }
6
+ tags @id("field_tags_${index}"): Set<Text> { merge union law semilattice }
7
+ }`).join('\n');
8
+ const source = `module Bench @id("mod_bench") {\n${blocks}\n}`;
9
+ const start = performance.now();
10
+ let document;
11
+ for (let index = 0; index < 250; index += 1) document = parseFrontierSource(source);
12
+ const durationMs = performance.now() - start;
13
+ console.log(JSON.stringify({ parses: 250, nodes: Object.keys(document.nodes).length, durationMs: Math.round(durationMs * 100) / 100 }, null, 2));
@@ -0,0 +1 @@
1
+ import '../bench/smoke.mjs';
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  actionNode,
3
+ capabilityNode,
3
4
  createDocument,
4
5
  effectNode,
5
6
  entityNode,
6
7
  externNode,
7
8
  latticeNode,
9
+ nativeSourceNode,
8
10
  stateNode,
9
11
  targetNode,
10
12
  typeNode
@@ -18,10 +20,12 @@ export function parseFrontierSource(source, options = {}) {
18
20
  if (block.kind === 'entity') nodes.push(parseEntity(block));
19
21
  if (block.kind === 'state') nodes.push(parseState(block));
20
22
  if (block.kind === 'action') nodes.push(parseAction(block));
23
+ if (block.kind === 'capability') nodes.push(parseCapability(block));
21
24
  if (block.kind === 'effect') nodes.push(parseEffect(block));
22
25
  if (block.kind === 'type') nodes.push(parseType(block));
23
26
  if (block.kind === 'extern') nodes.push(parseExtern(block));
24
27
  if (block.kind === 'lattice') nodes.push(parseLattice(block));
28
+ if (block.kind === 'nativeSource') nodes.push(parseNativeSource(block));
25
29
  if (block.kind === 'target') nodes.push(parseTarget(block));
26
30
  }
27
31
  return createDocument({ id: documentId, name: documentName, nodes });
@@ -35,7 +39,7 @@ function readName(source) { return /module\s+([A-Za-z_$][\w$]*)/.exec(source)?.[
35
39
  function readId(source) { return /module\s+[A-Za-z_$][\w$]*\s+@id\(\s*["']([^"']+)["']\s*\)/.exec(source)?.[1]; }
36
40
  function readBlocks(source) {
37
41
  const blocks = [];
38
- const header = /\b(entity|state|action|effect|type|extern|lattice|target)\s+([^{}]+)\{/g;
42
+ const header = /\b(entity|state|action|capability|effect|type|extern|lattice|nativeSource|target)\s+([^{}]+)\{/g;
39
43
  let match;
40
44
  while ((match = header.exec(source))) {
41
45
  let depth = 1; let index = header.lastIndex;
@@ -95,7 +99,29 @@ function parseAction(block) {
95
99
  }
96
100
  function parseEffect(block) {
97
101
  const name = nameFrom(block.header);
98
- return effectNode({ id: idFrom(block.header, `effect_${name}`), name, capability: /capability\s+([^\n]+)/.exec(block.body)?.[1]?.trim() ?? name, resources: readList('resources', block.body) });
102
+ return effectNode({
103
+ id: idFrom(block.header, `effect_${name}`),
104
+ name,
105
+ capability: /capability\s+([^\n]+)/.exec(block.body)?.[1]?.trim() ?? name,
106
+ input: parseOptionalTypeExpression(/input\s*:?\s*([^\n]+)/.exec(block.body)?.[1]),
107
+ returns: parseOptionalTypeExpression(/returns\s+([^\n]+)/.exec(block.body)?.[1]),
108
+ resources: readList('resources', block.body)
109
+ });
110
+ }
111
+ function parseCapability(block) {
112
+ const name = nameFrom(block.header);
113
+ return capabilityNode({
114
+ id: idFrom(block.header, `cap_${name}`),
115
+ name,
116
+ capability: readLine('capability', block.body) ?? name,
117
+ category: readWord('category', block.body),
118
+ input: parseOptionalTypeExpression(/input\s*:?\s*([^\n]+)/.exec(block.body)?.[1]),
119
+ returns: parseOptionalTypeExpression(/returns\s+([^\n]+)/.exec(block.body)?.[1]),
120
+ effects: readList('effects', block.body),
121
+ resources: readList('resources', block.body),
122
+ adapters: readAdapters(block.body),
123
+ unsupportedTargets: readUnsupportedTargets(block.body)
124
+ });
99
125
  }
100
126
  function parseType(block) {
101
127
  const name = nameFrom(block.header);
@@ -140,6 +166,32 @@ function parseLattice(block) {
140
166
  } : undefined
141
167
  });
142
168
  }
169
+ function parseNativeSource(block) {
170
+ const name = nameFrom(block.header);
171
+ const losses = [];
172
+ const lossRe = /^\s*loss\s+([A-Za-z][\w-]*)\s+["']([^"']+)["'](?:\s+severity\s+([A-Za-z][\w-]*))?/gm;
173
+ let match;
174
+ while ((match = lossRe.exec(block.body))) {
175
+ losses.push({
176
+ id: `loss_${name}_${losses.length}`,
177
+ kind: match[1],
178
+ message: match[2],
179
+ severity: match[3] ?? 'warning'
180
+ });
181
+ }
182
+ return nativeSourceNode({
183
+ id: idFrom(block.header, `native_${name}`),
184
+ name,
185
+ language: readWord('language', block.body) ?? name,
186
+ parser: readWord('parser', block.body),
187
+ parserVersion: readWord('parserVersion', block.body),
188
+ sourcePath: readWord('sourcePath', block.body) ?? readWord('path', block.body),
189
+ sourceHash: readWord('sourceHash', block.body),
190
+ symbol: readWord('symbol', block.body),
191
+ frontierNodeIds: readList('frontierNodes', block.body),
192
+ losses: losses.length ? losses : undefined
193
+ });
194
+ }
143
195
  function parseTarget(block) {
144
196
  const name = nameFrom(block.header);
145
197
  return targetNode({
@@ -156,6 +208,44 @@ function parseTarget(block) {
156
208
  function readList(label, body) { const line = new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]; return line ? line.split(',').map((item) => item.trim()).filter(Boolean) : undefined; }
157
209
  function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n]+)', 'm').exec(body)?.[1]?.trim(); }
158
210
  function readWord(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\s,]+)', 'm').exec(body)?.[1]?.trim(); }
211
+ function readInlineWord(label, text) { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
212
+ function readInlineQuoted(label, text) { return new RegExp("(?:^|\\s)" + label + "\\s+[\"']([^\"']+)[\"']").exec(text)?.[1]?.trim(); }
213
+ function readAdapters(body) {
214
+ const adapters = [];
215
+ const re = /^\s*adapter\s+([A-Za-z][\w-]*)\s+symbol\s+([^\s]+)([^\n]*)$/gm;
216
+ let match;
217
+ while ((match = re.exec(body))) {
218
+ const rest = match[3] ?? '';
219
+ adapters.push({
220
+ target: {
221
+ language: match[1],
222
+ platform: readInlineWord('platform', rest),
223
+ framework: readInlineWord('framework', rest),
224
+ packageName: readInlineWord('package', rest) ?? readInlineWord('packageName', rest),
225
+ adapterPackage: readInlineWord('adapterPackage', rest)
226
+ },
227
+ symbol: match[2],
228
+ kind: readInlineWord('kind', rest),
229
+ packageName: readInlineWord('package', rest) ?? readInlineWord('packageName', rest),
230
+ importPath: readInlineWord('import', rest) ?? readInlineWord('importPath', rest),
231
+ requires: readInlineWord('requires', rest)?.split('|').map((item) => item.trim()).filter(Boolean)
232
+ });
233
+ }
234
+ return adapters.length ? adapters : undefined;
235
+ }
236
+ function readUnsupportedTargets(body) {
237
+ const unsupported = [];
238
+ const re = /^\s*unsupported\s+([A-Za-z][\w-]*)([^\n]*)$/gm;
239
+ let match;
240
+ while ((match = re.exec(body))) {
241
+ const rest = match[2] ?? '';
242
+ unsupported.push({
243
+ target: { language: match[1], platform: readInlineWord('platform', rest), framework: readInlineWord('framework', rest) },
244
+ reason: readInlineQuoted('reason', rest) ?? (rest.trim() || 'Unsupported by this target.')
245
+ });
246
+ }
247
+ return unsupported.length ? unsupported : undefined;
248
+ }
159
249
  function parseMerge(text) {
160
250
  const kind = /merge\s+([A-Za-z][\w-]*)/.exec(text)?.[1];
161
251
  if (!kind) return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-parser",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Parser for the first Frontier Lang .frontier syntax slice.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -16,11 +16,14 @@
16
16
  "dist",
17
17
  "examples",
18
18
  "README.md",
19
- "LICENSE"
19
+ "LICENSE",
20
+ "bench",
21
+ "benchmarks/package-bench.mjs"
20
22
  ],
21
23
  "scripts": {
22
24
  "build": "node scripts/build.mjs",
23
25
  "test": "npm run build && node test/smoke.mjs",
26
+ "typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p test/tsconfig.json",
24
27
  "fuzz": "npm run build && node fuzz/smoke.mjs",
25
28
  "bench": "npm run build && node bench/smoke.mjs",
26
29
  "prepare": "npm run build",
@@ -50,6 +53,9 @@
50
53
  "access": "public"
51
54
  },
52
55
  "dependencies": {
53
- "@shapeshift-labs/frontier-lang-kernel": "0.2.0"
56
+ "@shapeshift-labs/frontier-lang-kernel": "0.3.1"
57
+ },
58
+ "devDependencies": {
59
+ "typescript": "^5.9.3"
54
60
  }
55
61
  }