@shapeshift-labs/frontier-lang-parser 0.3.24 → 0.3.25
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/README.md +40 -0
- package/dist/view.js +28 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -220,6 +220,46 @@ npm install @shapeshift-labs/frontier-lang-parser
|
|
|
220
220
|
|
|
221
221
|
The parser projects text into `@shapeshift-labs/frontier-lang-kernel` documents. The syntax is intentionally small and experimental.
|
|
222
222
|
|
|
223
|
+
## Authored view render graph syntax
|
|
224
|
+
|
|
225
|
+
`.frontier` view blocks can describe UI render graphs directly. Nested `render`
|
|
226
|
+
blocks are flattened into `view.renders`, and each parent stores stable child
|
|
227
|
+
render IDs in `children`. This keeps authored UI structural and target-neutral:
|
|
228
|
+
HTML, JSX, SwiftUI, or another target can lower the same semantic graph without
|
|
229
|
+
the parser claiming browser or runtime equivalence.
|
|
230
|
+
|
|
231
|
+
```frontier
|
|
232
|
+
view TodoList @id("view_todo_list") {
|
|
233
|
+
reads TodoDb.todos
|
|
234
|
+
dispatches action_add
|
|
235
|
+
prop disabled @id("view_prop_disabled"): Boolean
|
|
236
|
+
event save @id("view_event_save") action action_add input TodoInput
|
|
237
|
+
|
|
238
|
+
render Article @id("render_todo_root") {
|
|
239
|
+
key todo-list-root
|
|
240
|
+
|
|
241
|
+
render Button @id("render_save_button") {
|
|
242
|
+
identity save
|
|
243
|
+
text "Save"
|
|
244
|
+
prop disabled disabled
|
|
245
|
+
on press save
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
render SaveIcon kind component @id("render_save_icon") {
|
|
249
|
+
component Icon
|
|
250
|
+
key save-icon
|
|
251
|
+
prop name "check"
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The root render becomes a graph node whose `children` reference
|
|
258
|
+
`render_save_button` and `render_save_icon` in source order. Child props, text,
|
|
259
|
+
and events stay attached to the child render node instead of leaking onto the
|
|
260
|
+
parent. `kind component` records `component` instead of a literal HTML tag, so
|
|
261
|
+
target adapters can decide how to project it.
|
|
262
|
+
|
|
223
263
|
## Authored target projection syntax
|
|
224
264
|
|
|
225
265
|
`.frontier` target blocks can carry projection contracts next to their emit settings. These rows describe what a target lowering claims to represent, what it still needs proof for, and which losses or missing evidence must stay visible to merge and translation tooling.
|
package/dist/view.js
CHANGED
|
@@ -34,20 +34,32 @@ function readViewEvents(body) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function readRenderNodes(body) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
37
|
+
const renders = [];
|
|
38
|
+
for (const block of readNestedBlocks('render', body)) pushRenderNode(block, renders);
|
|
39
|
+
return renders;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pushRenderNode(block, renders) {
|
|
43
|
+
const headerName = nameFrom(block.header);
|
|
44
|
+
const ownBody = stripNestedBlocks('render', block.body);
|
|
45
|
+
const kind = readInlineWord('kind', block.header) ?? readLine('kind', ownBody) ?? 'element';
|
|
46
|
+
const props = readRenderProps(ownBody);
|
|
47
|
+
const events = readRenderEvents(ownBody);
|
|
48
|
+
const childBlocks = readNestedBlocks('render', block.body);
|
|
49
|
+
const explicitChildren = readList('children', ownBody) ?? readList('child', ownBody);
|
|
50
|
+
const nestedChildren = childBlocks.map((child) => idFrom(child.header, `render_${nameFrom(child.header)}`));
|
|
51
|
+
renders.push(compactRecord({
|
|
52
|
+
id: idFrom(block.header, `render_${headerName}`),
|
|
53
|
+
kind,
|
|
54
|
+
tagName: readLine('tag', ownBody) ?? readLine('tagName', ownBody) ?? (kind === 'component' || kind === 'text' ? undefined : headerName),
|
|
55
|
+
component: readLine('component', ownBody) ?? (kind === 'component' ? headerName : undefined),
|
|
56
|
+
identityKey: readLine('identity', ownBody) ?? readLine('key', ownBody),
|
|
57
|
+
text: readQuotedLine('text', ownBody),
|
|
58
|
+
props: props.length ? props : undefined,
|
|
59
|
+
events: events.length ? events : undefined,
|
|
60
|
+
children: uniqueStrings([...(explicitChildren ?? []), ...nestedChildren])
|
|
61
|
+
}));
|
|
62
|
+
for (const child of childBlocks) pushRenderNode(child, renders);
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
function readRenderProps(body) {
|
|
@@ -92,6 +104,8 @@ function readLine(label, body) { return new RegExp('^\\s*' + label + '\\s+([^\\n
|
|
|
92
104
|
function readQuotedLine(label, body) { return new RegExp(`^\\s*${label}\\s+["']([^"']+)["']`, 'm').exec(body)?.[1]; }
|
|
93
105
|
function readInlineWord(label, text = '') { return new RegExp('(?:^|\\s)' + label + '\\s+([^\\s,]+)').exec(text)?.[1]?.trim(); }
|
|
94
106
|
function readRenderValue(value) { const quoted = /^["']([^"']+)["']$/.exec(value.trim()); return quoted ? { value: quoted[1] } : { expression: value.trim() }; }
|
|
107
|
+
function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
|
|
108
|
+
function uniqueStrings(values) { const result = []; for (const value of values) if (value && !result.includes(value)) result.push(value); return result.length ? result : undefined; }
|
|
95
109
|
function parseOptionalTypeExpression(value) { return value ? parseTypeExpression(value.trim()) : undefined; }
|
|
96
110
|
function parseTypeExpression(value) {
|
|
97
111
|
const text = value.trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shapeshift-labs/frontier-lang-parser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.25",
|
|
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/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/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",
|