joule-ui takes in OpenUI Lang 1.0 the way it takes in A2UI: a model writes a surface as a program, one widget a line, and joule-ui-lang builds a widget tree against the host's registry as the program streams. After every chunk a snapshot holds the tree as far as it can be built, each widget checked by joule-ui's own validate, and every error with its source line and reason code. State, queries, operators, conditionals, built-in functions and sequences of actions are parsed and refused not-declarative. A canonical writer turns a tree back into the language; it parses back to the same tree. - joule-ui-core: WidgetSchema::positional, the order a positional call passes props in (SPEC §3); absent from the wire form when undeclared. - joule-ui-interaction: begin_interaction and Pending::finish, the lifecycle's three phases in two steps for a host whose handler is asynchronous (SPEC §6); run_interaction is built on them and seals what it sealed before. - conformance/lang/v1: L1 statements, L2 ingest, L3 streaming, L4 the canonical form, L5 refusals, with an independent Python verifier written from §11. - docs/create.md: the design of Create. Co-Authored-By: Claude Opus 5.5 <[email protected]>
985 lines
23 KiB
JSON
985 lines
23 KiB
JSON
{
|
|
"cases": [
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "card",
|
|
"line": 2,
|
|
"statement": "card"
|
|
},
|
|
{
|
|
"key": "t1",
|
|
"line": 3,
|
|
"statement": "t1"
|
|
},
|
|
{
|
|
"key": "t2",
|
|
"line": 4,
|
|
"statement": "t2"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "one"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"size": {
|
|
"type": "number",
|
|
"value": 14.0
|
|
},
|
|
"text": {
|
|
"type": "text",
|
|
"value": "two"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Card",
|
|
"props": {
|
|
"subtitle": {
|
|
"type": "text",
|
|
"value": "sub"
|
|
},
|
|
"title": {
|
|
"type": "text",
|
|
"value": "Hello"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {
|
|
"title": {
|
|
"type": "text",
|
|
"value": "Home"
|
|
}
|
|
}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([card], \"Home\")\ncard = Card(\"Hello\", [t1, t2], \"sub\")\nt1 = Text(\"one\")\nt2 = Text(\"two\", 14)\n",
|
|
"name": "hoisting"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "a",
|
|
"line": 2,
|
|
"statement": "a"
|
|
},
|
|
{
|
|
"key": "b",
|
|
"line": 3,
|
|
"statement": "b"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "a"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "b"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": [
|
|
"missing"
|
|
]
|
|
},
|
|
"input": "root = Page([a, missing, b])\na = Text(\"a\")\nb = Text(\"b\")\n",
|
|
"name": "unresolved-left-out"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "a",
|
|
"line": 2,
|
|
"statement": "a"
|
|
}
|
|
],
|
|
"orphans": [
|
|
"old"
|
|
],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "a"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([a])\na = Text(\"a\")\nold = Text(\"old\")\n",
|
|
"name": "orphans"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "root/0",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "root/0/0",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "root/0/1",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "root/1",
|
|
"line": 1,
|
|
"statement": "root"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "x"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "y"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Card",
|
|
"props": {
|
|
"title": {
|
|
"type": "text",
|
|
"value": "c"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "z"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([Card(\"c\", [Text(\"x\"), Text(\"y\")]), Text(\"z\")])\n",
|
|
"name": "inline-calls-and-keys"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "t",
|
|
"line": 2,
|
|
"statement": "t"
|
|
},
|
|
{
|
|
"key": "t#2",
|
|
"line": 2,
|
|
"statement": "t"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "again"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "again"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([t, t])\nt = Text(\"again\")\n",
|
|
"name": "used-twice"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "c",
|
|
"line": 2,
|
|
"statement": "c"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Card",
|
|
"props": {
|
|
"subtitle": {
|
|
"type": "text",
|
|
"value": "sub"
|
|
},
|
|
"title": {
|
|
"type": "text",
|
|
"value": "t"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([c])\nc = Card(\"t\", null, \"sub\")\n",
|
|
"name": "null-skips-an-optional"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "ch",
|
|
"line": 2,
|
|
"statement": "ch"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Chart",
|
|
"props": {
|
|
"labels": {
|
|
"type": "list",
|
|
"value": [
|
|
{
|
|
"type": "text",
|
|
"value": "a"
|
|
},
|
|
{
|
|
"type": "text",
|
|
"value": "b"
|
|
}
|
|
]
|
|
},
|
|
"options": {
|
|
"type": "object",
|
|
"value": {
|
|
"colours": {
|
|
"type": "list",
|
|
"value": [
|
|
{
|
|
"type": "text",
|
|
"value": "red"
|
|
},
|
|
{
|
|
"type": "text",
|
|
"value": "blue"
|
|
}
|
|
]
|
|
},
|
|
"max value": {
|
|
"type": "null"
|
|
},
|
|
"stacked": {
|
|
"type": "bool",
|
|
"value": true
|
|
}
|
|
}
|
|
},
|
|
"values": {
|
|
"type": "list",
|
|
"value": [
|
|
{
|
|
"type": "number",
|
|
"value": 1.0
|
|
},
|
|
{
|
|
"type": "number",
|
|
"value": -2.5
|
|
},
|
|
{
|
|
"type": "number",
|
|
"value": 300.0
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([ch])\nch = Chart([\"a\", \"b\"], [1, -2.5, 3e2], {stacked: true, \"max value\": null, colours: ['red', \"blue\"]})\n",
|
|
"name": "data-props"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "b1",
|
|
"line": 2,
|
|
"statement": "b1"
|
|
},
|
|
{
|
|
"key": "b2",
|
|
"line": 3,
|
|
"statement": "b2"
|
|
},
|
|
{
|
|
"key": "b3",
|
|
"line": 4,
|
|
"statement": "b3"
|
|
},
|
|
{
|
|
"key": "b4",
|
|
"line": 5,
|
|
"statement": "b4"
|
|
},
|
|
{
|
|
"key": "b5",
|
|
"line": 6,
|
|
"statement": "b5"
|
|
},
|
|
{
|
|
"key": "b6",
|
|
"line": 7,
|
|
"statement": "b6"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Button",
|
|
"props": {
|
|
"action": {
|
|
"type": "action",
|
|
"value": {
|
|
"id": "Go",
|
|
"payload": null
|
|
}
|
|
},
|
|
"label": {
|
|
"type": "text",
|
|
"value": "none"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Button",
|
|
"props": {
|
|
"action": {
|
|
"type": "action",
|
|
"value": {
|
|
"id": "Open",
|
|
"payload": {
|
|
"type": "text",
|
|
"value": "x"
|
|
}
|
|
}
|
|
},
|
|
"label": {
|
|
"type": "text",
|
|
"value": "one"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Button",
|
|
"props": {
|
|
"action": {
|
|
"type": "action",
|
|
"value": {
|
|
"id": "Send",
|
|
"payload": {
|
|
"type": "list",
|
|
"value": [
|
|
{
|
|
"type": "text",
|
|
"value": "a"
|
|
},
|
|
{
|
|
"type": "number",
|
|
"value": 2.0
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"label": {
|
|
"type": "text",
|
|
"value": "two"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Button",
|
|
"props": {
|
|
"action": {
|
|
"type": "action",
|
|
"value": {
|
|
"id": "Pick",
|
|
"payload": {
|
|
"type": "list",
|
|
"value": [
|
|
{
|
|
"type": "number",
|
|
"value": 1.0
|
|
},
|
|
{
|
|
"type": "number",
|
|
"value": 2.0
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"label": {
|
|
"type": "text",
|
|
"value": "list"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Button",
|
|
"props": {
|
|
"action": {
|
|
"type": "action",
|
|
"value": {
|
|
"id": "Go",
|
|
"payload": null
|
|
}
|
|
},
|
|
"label": {
|
|
"type": "text",
|
|
"value": "wrapped"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Button",
|
|
"props": {
|
|
"action": {
|
|
"type": "action",
|
|
"value": {
|
|
"id": "ToAssistant",
|
|
"payload": {
|
|
"type": "list",
|
|
"value": [
|
|
{
|
|
"type": "text",
|
|
"value": "hi"
|
|
},
|
|
{
|
|
"type": "object",
|
|
"value": {
|
|
"id": {
|
|
"type": "number",
|
|
"value": 1.0
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
"label": {
|
|
"type": "text",
|
|
"value": "by name"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([b1, b2, b3, b4, b5, b6])\nb1 = Button(\"none\", @Go())\nb2 = Button(\"one\", @Open(\"x\"))\nb3 = Button(\"two\", @Send(\"a\", 2))\nb4 = Button(\"list\", @Pick([1, 2]))\nb5 = Button(\"wrapped\", Action([@Go()]))\nb6 = Button(\"by name\", go)\ngo = @ToAssistant(\"hi\", {id: 1})\n",
|
|
"name": "actions"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "tabs",
|
|
"line": 2,
|
|
"statement": "tabs"
|
|
},
|
|
{
|
|
"key": "t1",
|
|
"line": 3,
|
|
"statement": "t1"
|
|
},
|
|
{
|
|
"key": "t1/0",
|
|
"line": 3,
|
|
"statement": "t1"
|
|
},
|
|
{
|
|
"key": "t2",
|
|
"line": 4,
|
|
"statement": "t2"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "1"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Tab",
|
|
"props": {
|
|
"title": {
|
|
"type": "text",
|
|
"value": "One"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Tab",
|
|
"props": {
|
|
"title": {
|
|
"type": "text",
|
|
"value": "Two"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Tabs",
|
|
"props": {}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([tabs])\ntabs = Tabs([t1, t2])\nt1 = Tab(\"One\", [Text(\"1\")])\nt2 = Tab(\"Two\")\n",
|
|
"name": "only-of-children"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "n",
|
|
"line": 2,
|
|
"statement": "n"
|
|
},
|
|
{
|
|
"key": "n/0",
|
|
"line": 2,
|
|
"statement": "n"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "inside"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Note",
|
|
"props": {
|
|
"author": {
|
|
"type": "text",
|
|
"value": "by"
|
|
},
|
|
"body": {
|
|
"type": "text",
|
|
"value": "the body"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([n])\nn = Note(\"by\", \"the body\", [Text(\"inside\")])\n",
|
|
"name": "derived-order"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "a",
|
|
"line": 3,
|
|
"statement": "a"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "second"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([a])\na = Text(\"first\")\na = Text(\"second\")\n",
|
|
"name": "duplicate-later-wins"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "a",
|
|
"line": 2,
|
|
"statement": "a"
|
|
},
|
|
{
|
|
"key": "b",
|
|
"line": 3,
|
|
"statement": "b"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"children": [
|
|
{
|
|
"name": "Card",
|
|
"props": {
|
|
"title": {
|
|
"type": "text",
|
|
"value": "b"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Card",
|
|
"props": {
|
|
"title": {
|
|
"type": "text",
|
|
"value": "a"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": [
|
|
"a"
|
|
]
|
|
},
|
|
"input": "root = Page([a])\na = Card(\"a\", [b])\nb = Card(\"b\", [a])\n",
|
|
"name": "cycle"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [
|
|
{
|
|
"code": "no-root",
|
|
"line": 2,
|
|
"statement": "page",
|
|
"warning": true
|
|
}
|
|
],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "page",
|
|
"line": 2,
|
|
"statement": "page"
|
|
},
|
|
{
|
|
"key": "t",
|
|
"line": 1,
|
|
"statement": "t"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "x"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "t = Text(\"x\")\npage = Page([t])\n",
|
|
"name": "fallback-entry"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [],
|
|
"incomplete": false,
|
|
"nodes": [
|
|
{
|
|
"key": "root",
|
|
"line": 1,
|
|
"statement": "root"
|
|
},
|
|
{
|
|
"key": "a",
|
|
"line": 2,
|
|
"statement": "a"
|
|
},
|
|
{
|
|
"key": "b",
|
|
"line": 3,
|
|
"statement": "b"
|
|
},
|
|
{
|
|
"key": "c",
|
|
"line": 4,
|
|
"statement": "c"
|
|
}
|
|
],
|
|
"orphans": [],
|
|
"tree": {
|
|
"children": [
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "tab\there é"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "it's"
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"name": "Text",
|
|
"props": {
|
|
"text": {
|
|
"type": "text",
|
|
"value": "bad \\q escape"
|
|
}
|
|
}
|
|
}
|
|
],
|
|
"name": "Page",
|
|
"props": {}
|
|
},
|
|
"unresolved": []
|
|
},
|
|
"input": "root = Page([a, b, c])\na = Text(\"tab\\there \\u00e9\")\nb = Text('it\\'s')\nc = Text(\"bad \\q escape\")\n",
|
|
"name": "escapes"
|
|
},
|
|
{
|
|
"expected": {
|
|
"errors": [
|
|
{
|
|
"code": "no-root",
|
|
"line": 0,
|
|
"statement": ""
|
|
}
|
|
],
|
|
"incomplete": false,
|
|
"nodes": [],
|
|
"orphans": [],
|
|
"tree": null,
|
|
"unresolved": []
|
|
},
|
|
"input": "",
|
|
"name": "empty"
|
|
}
|
|
],
|
|
"class": "L2"
|
|
}
|