@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.
- package/bench/smoke.mjs +13 -0
- package/benchmarks/package-bench.mjs +1 -0
- package/dist/index.js +92 -2
- package/package.json +9 -3
package/bench/smoke.mjs
ADDED
|
@@ -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({
|
|
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.
|
|
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.
|
|
56
|
+
"@shapeshift-labs/frontier-lang-kernel": "0.3.1"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"typescript": "^5.9.3"
|
|
54
60
|
}
|
|
55
61
|
}
|