@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.
Files changed (3) hide show
  1. package/README.md +40 -0
  2. package/dist/view.js +28 -14
  3. 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
- return readNestedBlocks('render', body).map((block) => {
38
- const tagName = nameFrom(block.header);
39
- const props = readRenderProps(block.body);
40
- const events = readRenderEvents(block.body);
41
- return {
42
- id: idFrom(block.header, `render_${tagName}`),
43
- kind: readInlineWord('kind', block.header) ?? 'element',
44
- tagName,
45
- identityKey: readLine('identity', block.body) ?? readLine('key', block.body),
46
- text: readQuotedLine('text', block.body),
47
- props: props.length ? props : undefined,
48
- events: events.length ? events : undefined
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.24",
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",