openpuppet-language 2.0__py3-none-any.whl
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.
- openpuppet_language-2.0.data/data/share/puppet/conformance/README.md +274 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/binding.json +238 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/capabilities.json +155 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/grammar.json +248 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/interaction.json +220 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/listen.json +133 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/platform.json +274 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/probes.json +117 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/render.json +180 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/tabs.json +39 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/templates.json +143 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_bad_result.py +11 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_basic.py +24 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_deps_mismatch.py +11 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_no_doc.py +10 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/runner.py +721 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/01-grammar.md +283 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/02-ir.md +77 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/03-semantics.md +167 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/04-vocabulary.md +227 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/05-render-contract.md +126 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/06-diagnostics.md +151 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/README.md +73 -0
- openpuppet_language-2.0.dist-info/METADATA +50 -0
- openpuppet_language-2.0.dist-info/RECORD +40 -0
- openpuppet_language-2.0.dist-info/WHEEL +5 -0
- openpuppet_language-2.0.dist-info/entry_points.txt +2 -0
- openpuppet_language-2.0.dist-info/top_level.txt +1 -0
- puppet/__init__.py +19 -0
- puppet/adapter.py +112 -0
- puppet/capabilities.py +172 -0
- puppet/cli.py +132 -0
- puppet/diag.py +42 -0
- puppet/engine.py +1038 -0
- puppet/eval.py +243 -0
- puppet/ir.py +538 -0
- puppet/lang.py +755 -0
- puppet/raster_adapter.py +348 -0
- puppet/tk_adapter.py +374 -0
- puppet/vocab.py +147 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# 01 · 词法与语法
|
|
2
|
+
|
|
3
|
+
## 1. 源文本形状
|
|
4
|
+
|
|
5
|
+
- 源文件是一串**行**。一行是**一条语句**或**一条注释**。
|
|
6
|
+
- **一条语句默认写在一行内**;字符串中的换行必须转义。
|
|
7
|
+
- **唯一例外是行为头**:以 `on … :` 结尾的行,其**动作体可以写在紧随其后、缩进更深**的行上;遇到第一行缩进不深于该行为头,即结束。空行与注释行被忽略,不参与缩进判定。
|
|
8
|
+
|
|
9
|
+
```puppet
|
|
10
|
+
on #add click when #new.value != "":
|
|
11
|
+
append #todos item={text: #new.value} ; set #new value=""
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
多行写法与单行写法**完全等价**(动作体各行以 `;` 连接)。允许这一例外的原因很实际:行为体单独成行是普遍排版习惯,拒绝它只会制造一整类系统性语法错误。
|
|
15
|
+
- 语句之间不共享上下文;**每条语句必须自足**。
|
|
16
|
+
- 源文件是 UTF-8。
|
|
17
|
+
|
|
18
|
+
## 2. 注释
|
|
19
|
+
|
|
20
|
+
- **注释**:`//` 之后直到行尾。`//` **无其他含义**,因此与地址记号彻底消歧。
|
|
21
|
+
- **地址**:`#` 之后**紧跟标识符首字符**(字母或下划线)。`#` **不是**注释记号——`#` 后必须
|
|
22
|
+
是地址或颜色;`#` 之后无标识符即报错(`SYNTAX`)。
|
|
23
|
+
- 实现**应该**在无法判定时报告诊断,而不是猜测。
|
|
24
|
+
|
|
25
|
+
> 历史:`#` 曾同时充当注释与地址,靠"后随空白"区分。本版起注释记号为 `//`。
|
|
26
|
+
|
|
27
|
+
## 3. 记号
|
|
28
|
+
|
|
29
|
+
| 记号 | 形式 | 说明 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| 地址 | `#` + 标识符 | 节点的全局唯一 ID |
|
|
32
|
+
| 注释 | `//` 到行尾 | 行内任意位置起效;`//` 无其他含义,故与地址彻底消歧 |
|
|
33
|
+
| 局部引用 | 标识符 `.` 字段 | 数据项绑定(如模板行绑定) |
|
|
34
|
+
| 属性 | `key=value` | 空格分隔,一行内可多个 |
|
|
35
|
+
| 字符串 | `"…"` | 转义 `\"` 与 `\\` |
|
|
36
|
+
| 数字 | 整数或小数 | 前导 `-` 表示负数 |
|
|
37
|
+
| 布尔 | `true` / `false` | 小写 |
|
|
38
|
+
| 颜色 | `#` + 6/8 位十六进制 | 与地址按**长度**消歧。**不采用 3 位简写**:它会与 `#bad` / `#dad` / `#abc` 这类短地址撞车 |
|
|
39
|
+
| 列表 | `[a, b, c]` | — |
|
|
40
|
+
| 字典 | `{k: v, …}` | — |
|
|
41
|
+
| 动作分隔 | `;` | 仅用于 `on` 的动作序列 |
|
|
42
|
+
| 绑定关键字 | `as` | 数据项绑定,仅此用途 |
|
|
43
|
+
| 参数名 | `item=` | 数据操作参数,仅此用途 |
|
|
44
|
+
|
|
45
|
+
**标识符**:首字符为字母或下划线,后续为字母、数字、下划线。
|
|
46
|
+
|
|
47
|
+
**地址必须全局唯一**。重复即**错误**(`ID_DUP`),带精确行号。
|
|
48
|
+
|
|
49
|
+
## 4. 表达式
|
|
50
|
+
|
|
51
|
+
表达式**只**出现在以下位置:属性值、`when` 守卫、动作参数、`data` 初值、字典/列表字面量内部。
|
|
52
|
+
|
|
53
|
+
**允许**:
|
|
54
|
+
|
|
55
|
+
- 字面量;
|
|
56
|
+
- 引用:地址属性 `#id.prop`、局部绑定字段 `t.field`;
|
|
57
|
+
- 算术 `+ - * /`;
|
|
58
|
+
- 比较 `== != < <= > >=`;
|
|
59
|
+
- 逻辑 `and or not`(操作数必须为布尔,不隐式取值);
|
|
60
|
+
- 函数调用:`name(arg, …)`;
|
|
61
|
+
- 列表与字典字面量。
|
|
62
|
+
|
|
63
|
+
**禁止**:循环、自定义函数、条件分支表达式、赋值、变量声明。
|
|
64
|
+
|
|
65
|
+
**隐式类型转换**(白名单,其余组合**错误**,不猜测):
|
|
66
|
+
|
|
67
|
+
- 数字与字符串相加 → 字符串拼接;
|
|
68
|
+
- 布尔转字符串 → `"true"` / `"false"`;
|
|
69
|
+
- 颜色转字符串 → 颜色字面量的源文本。
|
|
70
|
+
|
|
71
|
+
**内置函数**:函数集由 `04-vocabulary.md` 定义并**可扩充**(属兼容改动)。计数函数 `count(source)` 是普通函数,不再限定于数据源。
|
|
72
|
+
|
|
73
|
+
## 5. 命令的形状
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
<动词> <必需位置参数…> [属性=值…] [修饰符…]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- 位置参数的定义由各动词给出。
|
|
80
|
+
- 属性顺序无意义。
|
|
81
|
+
- 未识别属性名 → **警告**(`UNKNOWN_ATTR`),带行号与近似建议;**不得静默丢弃**。
|
|
82
|
+
- 未识别控件类型 → **警告**(`UNKNOWN_TYPE`);**不得**渲染成空白。
|
|
83
|
+
|
|
84
|
+
## 6. 属性与值
|
|
85
|
+
|
|
86
|
+
- **值类属性**(文本、初始值、提示、工具提示等)**可以**写表达式;表达式的求值见 `03-semantics.md` 第 2 节(活绑定)。
|
|
87
|
+
- **引用类属性**(数据源、模板、绑定名、图标、选项来源等)**必须**是引用或字面量,**禁止**在渲染期持续求值的表达式。
|
|
88
|
+
- 哪些属性属于哪一类,由 `04-vocabulary.md` 逐个标注;未标注者**默认**为值类。
|
|
89
|
+
- **绑定不设额外白名单**(派生决定,已确认):**任何值类属性**都可以写表达式;
|
|
90
|
+
引用类属性一律**禁止**表达式(`REF_ATTR_EXPR`);加白名单只会让“能不能绑”变成实现自由。
|
|
91
|
+
|
|
92
|
+
## 7. 动词
|
|
93
|
+
|
|
94
|
+
动词共 **12** 个。`template` 不是动词,是节点类型(见第 8 节)。
|
|
95
|
+
|
|
96
|
+
此外,第 7.7 节的**动作**(`append` / `remove` / `remove_where` / `update_where` / `clear` / `sort`)**也可以直接作为顶层语句**:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
append #todos item={text: "买牛奶"}
|
|
100
|
+
remove_where #todos as t where t.done == true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
理由:动作改的是**状态**,与"谁来触发"无关。写进 `on` 之下是"由事件触发",写在顶层是"由驱动者触发";两者语法一致、语义同一。
|
|
104
|
+
|
|
105
|
+
### 7.1 `add`
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
add <父地址> <类型> <自身地址> [属性=值…] [before|after <锚地址>]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
- 父必须已存在;地址必须未被占用。违者**错误**(`PARENT_MISSING` / `ID_DUP`)。
|
|
112
|
+
- 锚地址不存在 → **错误**(`ANCHOR_MISSING`), **禁止**静默追加到末尾。
|
|
113
|
+
|
|
114
|
+
### 7.2 `set`
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
set <地址> [属性=值…]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
- 地址不存在 → **错误**(`TARGET_MISSING`)。
|
|
121
|
+
- 作用于**已绑定属性**时:**解除该属性的绑定**并写入字面量,且**必须**产生诊断(`BIND_OVERRIDDEN`)。
|
|
122
|
+
- 天然幂等。
|
|
123
|
+
|
|
124
|
+
### 7.3 `upsert`
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
upsert <父地址> <类型> <自身地址> [属性=值…]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- 显式幂等:不存在则新建,存在则更新给定属性(不解除未提及属性的绑定)。
|
|
131
|
+
|
|
132
|
+
### 7.4 `del`
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
del <地址>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
- 地址不存在 → **警告**(`DEL_MISSING`),不中断本批。
|
|
139
|
+
|
|
140
|
+
### 7.5 `move`
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
move <地址> <新父地址> [before|after <锚地址>]
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- 新父必须存在;自环与成环 → **错误**(`MOVE_CYCLE`)。
|
|
147
|
+
- 锚地址不存在 → **错误**(`ANCHOR_MISSING`)。
|
|
148
|
+
|
|
149
|
+
### 7.6 `data`
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
data <地址> [persist=true] = <初值> of {字段: 类型 [= 默认值], …}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- `<初值>` **必须**是列表字面量(数据源是行集合)。
|
|
156
|
+
- `of {…}` **必须**给出显式结构;**禁止**无结构的隐式结构。
|
|
157
|
+
- `persist=true` 表示该数据源的**数据项**进入状态文件的持久分区(见 `03-semantics.md` 第 5 节)。
|
|
158
|
+
|
|
159
|
+
### 7.7 `on`
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
on <地址|数据源地址> <事件> [as <绑定名>] [when <条件>]: <动作>[; <动作>…]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- 事件名集合由 `04-vocabulary.md` 定义;未识别事件 → **警告**(`UNKNOWN_EVENT`)。
|
|
166
|
+
- `when` 必须求值为布尔;非布尔 → **错误**(`WHEN_NOT_BOOL`)。
|
|
167
|
+
- 处理器的 `as <名>` 绑定该事件的**载荷**:`change` 是 `{value: <新值>}`;`click` 是空;槽事件是 `{status, value}`;数据源的 `change` 是 `{source}`。
|
|
168
|
+
- **行上下文与事件载荷互不相干**:行上下文的绑定名来自**模板声明的** `as`(见第 8.1 节),不是处理器的 `as`。两者可以同时出现在同一个表达式里。
|
|
169
|
+
- 目标静态 `disabled=true` 且绑定了交互事件 → **警告**(`DISABLED_HANDLER`)。
|
|
170
|
+
- 控件的**主交互事件**(见 `04-vocabulary.md` 第 5.1 节)没有任何处理器 → **警告**(`UNCOVERED_INTERACTION`)。这是"杜绝静默失败"在界面侧的直接要求:用户点了没反应,必须被说出来。
|
|
171
|
+
- **绑定可叠加**:处理器声明的 `as t` 与动作内部声明的 `as r`(`remove_where` / `update_where`)在同一个表达式里**同时可见**,内层**不**覆盖外层。例:`on #del click as t: remove_where #todos as r where r.text == t.text`。
|
|
172
|
+
|
|
173
|
+
### 7.8 `listen`
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
listen <地址> <事件>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- 表示"把该事件回推给外部驱动者"。
|
|
180
|
+
- **不区分模式**:无论运行时是否带 LLM,语法与语义一致。没有驱动者连接时,事件进入观察流但不产生额外降级。
|
|
181
|
+
- **观察独立于反应**:被订阅的事件进入观察流,**与有没有处理器无关**。单纯"看"不需要先"做"。
|
|
182
|
+
- 只有被订阅的 `(地址, 事件)` 才推送;其余事件不进观察流。
|
|
183
|
+
- 事件的载荷是 `{目标地址, 事件名}`;**行内事件另带行序号**。
|
|
184
|
+
- 目标不存在 → **错误**(`TARGET_MISSING`);事件名未识别 → **警告**(`UNKNOWN_EVENT`)。
|
|
185
|
+
- 禁用节点上的交互**不产生事件**——它根本没有被触发(见 `03-semantics.md` 第 3.4 节)。
|
|
186
|
+
|
|
187
|
+
### 7.9 `call`
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
call <函数名> with {参数: 值, …} into <地址>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- `into` 指向的地址**必须**是数据源(槽契约见 `03-semantics.md` 第 4 节)。
|
|
194
|
+
- 参数必须与函数契约匹配;缺失必需参数或类型不符 → **错误**(`CALL_CONTRACT`)。
|
|
195
|
+
- 目标函数不存在 → **错误**(`CALL_UNKNOWN`),**不得**静默返回空值。
|
|
196
|
+
|
|
197
|
+
### 7.10 探针
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
tree [<地址>]
|
|
201
|
+
get <地址>
|
|
202
|
+
where <地址>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
- 探针是**只读查询**,不改变程序或状态。
|
|
206
|
+
- **输出进入观察流**的 `probes` 通道(自上次请求以来),形状 `{verb, target, result}`。
|
|
207
|
+
- **位置决定时机**:写在**源文件**里的探针在**装载完成后**执行(此时程序已完整);写在**批次**里的探针立即执行。
|
|
208
|
+
- `tree` / `get` 的目标不存在 → **错误**(`TARGET_MISSING`),且 `result` 为 `null`——不得静默返回空结构。
|
|
209
|
+
- `where` 依赖渲染器提供几何信息。不可用时**必须**产生 `DEGRADED_FEATURE`(信息级)并把 `result.geometry` 记为 `null`:降级必须可见,驱动者据此改走视觉自检。
|
|
210
|
+
- "有没有消费者"只有**宿主**知道,因此"探针的输出当前无人消费"这一提示由**宿主**(运行 app 的软件)发出,而不是由语言实现发出。
|
|
211
|
+
- **语言实现不发编译期警告**(派生决定,已确认):编译期无从知道有没有驱动者连接;
|
|
212
|
+
探针是无副作用的只读查询,“无人消费”不构成失败,因此没有需要升级的语义。
|
|
213
|
+
|
|
214
|
+
## 8. 模板(节点类型)
|
|
215
|
+
|
|
216
|
+
模板是**一等节点**:有地址、住节点表、有自己的子节点。
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
add <父地址> template <模板地址> as <绑定名>
|
|
220
|
+
add <模板地址> <类型> <子地址> [属性=值…]
|
|
221
|
+
set <列表地址> source=<数据源地址> template=<模板地址>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
- 模板的 `as <绑定名>` 声明该模板内各行的**数据项绑定名**(与 `on … as` 同一套绑定语义)。
|
|
225
|
+
- 模板的子节点是普通节点,受**同一套**校验与诊断规则约束;不存在"模板专用的节点表"。
|
|
226
|
+
- 列表渲染:每行实例化一次模板子树,行内**值类属性**按行求值(见 `03-semantics.md` 第 2 节)。
|
|
227
|
+
- 模板未绑定到任何列表 → **警告**(`TEMPLATE_UNUSED`)。
|
|
228
|
+
|
|
229
|
+
### 8.1 行内事件的上下文
|
|
230
|
+
|
|
231
|
+
行内节点的交互事件**必须携带行上下文**——即"这是第几行":
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
on #del click as t: remove_where #todos as r where r.text == t.text
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- 派发者(渲染器或驱动者)**必须**给出行序号;只有它知道用户点的是哪一行。
|
|
238
|
+
- 引擎在派发时把**该模板声明的绑定名**(`as <名>`)指向该行记录。它与处理器自己的 `as` **互不相干**——后者绑定的是事件载荷(见第 7.7 节)。
|
|
239
|
+
- 无法确定行上下文(目标不在模板内、行序号越界、模板未被任何列表使用)→ **错误** `ROW_CONTEXT`,**禁止**静默地按"无绑定"派发。
|
|
240
|
+
|
|
241
|
+
## 9. 状态与状态外观
|
|
242
|
+
|
|
243
|
+
- **状态标志**(运行期属性,可读可写):`hover` / `focus` / `pressed` / `error` / `visible` / `disabled`。读取形式 `#id.<标志>`;写入形式 `set #id <标志>=<布尔>`。
|
|
244
|
+
- **状态外观**:单个属性 `states`,其值为字典:状态名 → 外观属性表。
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
add #root col #card pad=16 states={hover: {bgcolor: "#f8fafc"}, error: {border: "1 #dc2626"}}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- `states` 中**禁止**出现布局类属性(`pad` / `margin` / `gap` / `flex` / `w` / `h` / `x` / `y` / `scroll` / `justify` / `wrap`)→ **警告**(`STATE_LAYOUT_ATTR`)。
|
|
251
|
+
- `states` 中**禁止**出现非外观属性 → **警告**(`STATE_UNKNOWN_ATTR`)。
|
|
252
|
+
- 状态外观**不触发事件、不改写程序**。
|
|
253
|
+
|
|
254
|
+
## 10. 动画
|
|
255
|
+
|
|
256
|
+
- `animate=<属性名>[ <属性名>…]` 声明对该节点哪些属性做过渡;`duration=<毫秒>`(默认 200),`curve=<缓动名>`(默认 `ease_out`)。
|
|
257
|
+
- 可动画属性按**语义**定义(值、位置、尺寸、透明度、颜色)。`animate` 中出现非语义可动画属性 → **警告**(`ANIMATE_UNKNOWN`)。
|
|
258
|
+
- 渲染器**可以**不支持其中某些属性,但**必须**在能力声明中列出并在缺失时产生可见降级说明。
|
|
259
|
+
|
|
260
|
+
## 11. 示例
|
|
261
|
+
|
|
262
|
+
```puppet
|
|
263
|
+
add #root window #win title="待办" w=420 h=640
|
|
264
|
+
add #win col #main pad=16 gap=12
|
|
265
|
+
add #main row #entry gap=8
|
|
266
|
+
add #entry input #new placeholder="添加一项…" flex=1
|
|
267
|
+
add #entry button #add text="添加"
|
|
268
|
+
add #main list #items flex=1 source=#todos template=#tpl
|
|
269
|
+
add #main text #count text="共 " + str(count(#todos)) + " 条"
|
|
270
|
+
add #root template #tpl as t
|
|
271
|
+
add #tpl row #row gap=8
|
|
272
|
+
add #row checkbox #done value=t.done
|
|
273
|
+
add #row text #label text=t.text flex=1
|
|
274
|
+
|
|
275
|
+
data #todos persist=true = [] of {text: str, done: bool = false}
|
|
276
|
+
|
|
277
|
+
on #add click when #new.value != "":
|
|
278
|
+
append #todos item={text: #new.value} ; set #new value=""
|
|
279
|
+
on #done change as t:
|
|
280
|
+
update_where #todos as r set done=#done.value where r.text == t.text
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
注意最后两行:`text="共 " + str(count(#todos)) + " 条"` 与 `text=t.text` 是**活绑定**(见 `03-semantics.md` 第 2 节),随数据源变化自动重算——不再需要手写 `on #todos change: set …`。
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 02 · 中间表示(IR)
|
|
2
|
+
|
|
3
|
+
## 1. 两本账
|
|
4
|
+
|
|
5
|
+
本规范把"应用"拆成两份**互不混存**的数据:
|
|
6
|
+
|
|
7
|
+
| | **程序 IR** | **状态 IR** |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 内容 | 声明:节点、结构、数据源结构、处理器、订阅 | 事实:数据项、运行期状态、槽、修订号 |
|
|
10
|
+
| 来源 | 由 `.puppet` 文本产生,**只由命令批次改变** | 由引擎在运行期产生 |
|
|
11
|
+
| 写回 | 批次结束时一次性写回 `.puppet` | 原子写回状态文件 |
|
|
12
|
+
| 是否可丢 | 否(它是只有一份的真源) | **是**(缺失时从声明默认值重建,不报错) |
|
|
13
|
+
|
|
14
|
+
**不变式**:引擎**绝不**用运行期产生的内容改写程序 IR。反向序列化只读程序 IR。
|
|
15
|
+
|
|
16
|
+
## 2. 程序 IR
|
|
17
|
+
|
|
18
|
+
### 2.1 节点树
|
|
19
|
+
|
|
20
|
+
每个节点:地址(唯一)、类型、父地址、子序(有序表)、属性表。
|
|
21
|
+
|
|
22
|
+
- 属性值以**规范源文本**存储:表达式**原样保留**,不求值。这样程序可以被逐行读、diff、再编辑。
|
|
23
|
+
- 模板是普通节点(类型为 `template`),带一个**绑定名**(`as` 声明的名字)。模板的子节点是普通子节点。
|
|
24
|
+
- 根节点是约定的内置节点,所有窗口挂在它下面。
|
|
25
|
+
|
|
26
|
+
**结构不变式**(违反即错误):
|
|
27
|
+
|
|
28
|
+
1. 地址全局唯一(`ID_DUP`)。
|
|
29
|
+
2. 除根外,每个节点**恰好**有一个父(`PARENT_MISSING`)。
|
|
30
|
+
3. 父子关系无环(`MOVE_CYCLE`)。
|
|
31
|
+
4. 属性名必须属于该类型的词汇表(`UNKNOWN_ATTR`,警告级)。
|
|
32
|
+
|
|
33
|
+
### 2.2 数据源声明
|
|
34
|
+
|
|
35
|
+
每个数据源:地址、`persist` 标志、结构(字段名 → 类型 → 默认值表达式)、初始值表达式。
|
|
36
|
+
|
|
37
|
+
**数据源声明属于程序**;它的**数据项属于状态**(见第 3 节)。
|
|
38
|
+
|
|
39
|
+
### 2.3 处理器与订阅
|
|
40
|
+
|
|
41
|
+
- **处理器**:目标、事件名、绑定名、`when` 守卫、动作序列。
|
|
42
|
+
- **订阅**:目标、事件名。
|
|
43
|
+
- 两者都是程序的一部分;它们**不随状态变化**。
|
|
44
|
+
|
|
45
|
+
### 2.4 元信息
|
|
46
|
+
|
|
47
|
+
- 规范版本(程序声明其面向的规范版本)。
|
|
48
|
+
- 批次外独立调用的记录(若实现允许)。
|
|
49
|
+
- 实现**可以**附加自有元信息,但**禁止**将其用于改变本规范定义的语义。
|
|
50
|
+
|
|
51
|
+
## 3. 状态 IR
|
|
52
|
+
|
|
53
|
+
- **数据项**:每个数据源 → 行列表(有序)。
|
|
54
|
+
- **节点运行期状态**:
|
|
55
|
+
- 状态标志:`hover` / `focus` / `pressed` / `error` / `visible` / `disabled`;
|
|
56
|
+
- 交互产生的值:输入内容、选中项、开关状态、滑块位置等。
|
|
57
|
+
- **槽**:状态(进行中 / 成功 / 失败 / 超时 / 取消)、值、序号、时间戳。
|
|
58
|
+
- **修订号**:每个 app 实例一条序列,每批命令递增。用途只有一个:让驱动者在断线重连后判断是否漏了事件。
|
|
59
|
+
|
|
60
|
+
**状态可丢**:状态文件缺失、损坏或结构漂移时,实现必须能从程序 IR 的默认值重建应用,并且**不得**因此拒绝启动。
|
|
61
|
+
|
|
62
|
+
## 4. 绑定图
|
|
63
|
+
|
|
64
|
+
- 程序 IR 中每个**值类属性**,若其值不是字面量而是表达式,则产生一条**绑定**:`(节点, 属性) → 引用集合`。
|
|
65
|
+
- 引用来源有二:地址属性引用(`#id.prop`)与模板行绑定字段(`t.field`)。
|
|
66
|
+
- **绑定图必须是有向无环图**。出现环 → **错误** `BIND_CYCLE`,在编译/校验期报告,带精确行号。
|
|
67
|
+
- 引用不存在的地址或字段 → **错误** `BIND_UNKNOWN_REF`。
|
|
68
|
+
- 求值过程中的类型不符、函数错误 → 运行期诊断 `BIND_EVAL`。
|
|
69
|
+
|
|
70
|
+
**绑定图是静态可分析的**:它只由程序决定,不受状态影响。这是"传播可证明收敛"的依据(见 `03-semantics.md` 第 2 节)。
|
|
71
|
+
|
|
72
|
+
## 5. 序列化与写回
|
|
73
|
+
|
|
74
|
+
- **程序写回**:从程序 IR 反向生成 `.puppet` 文本;**在批次结束时一次性完成**,不允许留下"半批程序"。
|
|
75
|
+
- **状态写回**:**原子替换**(写临时文件后重命名)。半截写入视为缺陷。
|
|
76
|
+
- **结构漂移**:状态文件中出现程序里不存在的字段 → **不自动迁移**,**不删除数据**,产生显式诊断(`PERSIST_SCHEMA_DRIFT`)。
|
|
77
|
+
- 反向序列化的输出**应该**与输入文本在语义上等价,且**应该**保持人类可读、可 diff。
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# 03 · 执行语义
|
|
2
|
+
|
|
3
|
+
## 1. 命令批的应用
|
|
4
|
+
|
|
5
|
+
- 一批命令 = 驱动者一次下发的一组语句。
|
|
6
|
+
- **逐条应用,错误不阻断**:一条语句失败**不**阻止后续语句;这就是"部分应用"。
|
|
7
|
+
- **必须收集全部诊断**,每条带**精确行号**与列位置。
|
|
8
|
+
- **程序写回在批次结束时一次性完成**。批中失败的程序变更**不写回**,但已成功应用的变更会被写回。
|
|
9
|
+
- 批结束时修订号 +1。
|
|
10
|
+
- 批内**禁止**静默跳过任何语句:每条语句要么应用、要么产生诊断。
|
|
11
|
+
|
|
12
|
+
## 2. 值传播(活绑定)
|
|
13
|
+
|
|
14
|
+
### 2.1 定义
|
|
15
|
+
|
|
16
|
+
**值类属性**(见 `01-grammar.md` 第 6 节)的值若为表达式,即构成绑定。绑定的求值时机为:
|
|
17
|
+
|
|
18
|
+
1. 初次渲染;
|
|
19
|
+
2. 其**引用集合**中的任何值发生变化之后。
|
|
20
|
+
|
|
21
|
+
**引用类属性禁止**绑定(校验期错误 `REF_ATTR_EXPR`,见 `06-diagnostics.md` 第 4.2 节)。
|
|
22
|
+
|
|
23
|
+
### 2.2 求值顺序
|
|
24
|
+
|
|
25
|
+
- 按**绑定图的拓扑序**求值。同一节点内属性之间的书写顺序**无意义**。
|
|
26
|
+
- 求值只读取程序 IR 与状态 IR 的当前值,不产生副作用。**禁止**在求值过程中写入状态。
|
|
27
|
+
|
|
28
|
+
### 2.3 收敛(可证明)
|
|
29
|
+
|
|
30
|
+
- 绑定图是 DAG(`02-ir.md` 第 4 节)→ 拓扑序存在且唯一 → **一次求值即可到达终态**。
|
|
31
|
+
- **禁止**"迭代若干轮后放弃"的实现方式。绑定传播不存在"轮次上限"这个概念。
|
|
32
|
+
|
|
33
|
+
### 2.4 循环
|
|
34
|
+
|
|
35
|
+
- 静态环 → **错误** `BIND_CYCLE`(编译/校验期)。程序**不得**进入运行期。
|
|
36
|
+
- 实现**禁止**用运行期深度计数来兜住循环依赖。
|
|
37
|
+
|
|
38
|
+
### 2.5 求值失败
|
|
39
|
+
|
|
40
|
+
- 求值失败(类型不符、除零、未知字段、函数错误)**必须**产生诊断(`BIND_EVAL`)**并**产生一条观察事件。
|
|
41
|
+
- 渲染表现:若该属性此前有成功的值,**可以**继续显示该值,但**必须**同时暴露错误标记(如错误态置位);若无成功值,**必须**显示明确的不可用占位。
|
|
42
|
+
- **禁止**静默显示旧值、**禁止**静默显示空白、**禁止**显示表达式源码文本(这是旧规范的缺陷)。
|
|
43
|
+
|
|
44
|
+
### 2.6 模板行
|
|
45
|
+
|
|
46
|
+
- 模板按行实例化:每行独立求值一次,行绑定字段取自该行的数据项。
|
|
47
|
+
- 行的增删由数据源变化驱动;行内绑定的重算范围**应该**限于受影响的行。
|
|
48
|
+
|
|
49
|
+
### 2.7 `set` 与绑定的优先级
|
|
50
|
+
|
|
51
|
+
- `set` 作用于**已绑定**的属性 = **显式覆盖**:解除该属性的绑定,写入字面量,并且**必须**产生诊断 `BIND_OVERRIDDEN`。
|
|
52
|
+
- `upsert` 对**其提及**的属性同理;未提及的属性保持原有绑定。
|
|
53
|
+
- **禁止**"写入成功但被绑定覆盖、因而看不出效果"这种静默无效。
|
|
54
|
+
|
|
55
|
+
### 2.8 控件的交互值与绑定
|
|
56
|
+
|
|
57
|
+
- 若控件的 `value` / `selected` 是**绑定**(表达式),它就是**派生值**:**控件不保存用户输入**。
|
|
58
|
+
- 用户交互产生 `change` 事件,**载荷携带新值**(`{value: <新值>}`);处理器的 `as <名>` 绑定到该载荷。
|
|
59
|
+
- 因此"受控控件"的标准写法是 **绑定读 + 处理器回写数据源**:
|
|
60
|
+
|
|
61
|
+
```puppet
|
|
62
|
+
add #tpl checkbox #done value=t.done
|
|
63
|
+
on #done change as e:
|
|
64
|
+
update_where #todos as r set done=e.value where r.text == t.text
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
处理器写回数据源 → 触发传播 → 绑定重算 → 界面随之更新。
|
|
68
|
+
- **若不回写**,绑定重算会把用户改动覆盖掉(控件弹回)。这是派生值的必然结果,**不是缺陷**;但它确实是"操作了却没有持久效果",所以:
|
|
69
|
+
- 被 `listen` 订阅时不算静默失败——外部驱动者看得到事件(载荷含新值),会自己下发命令;
|
|
70
|
+
- **有处理器但没有回写**它绑定所读的数据源 → **警告** `BOUND_VALUE_NOT_WRITTEN`(见 `06-diagnostics.md` 第 4.8 节);
|
|
71
|
+
- 既没处理器也没被订阅 → **警告** `UNCOVERED_INTERACTION`(见 `04-vocabulary.md` 第 5.1 节)。
|
|
72
|
+
- **两个绑定是正交的**:事件载荷的名字来自处理器的 `as`,行记录的名字来自**模板声明的** `as`(`01-grammar.md` 第 8.1 节)。
|
|
73
|
+
|
|
74
|
+
## 3. 事件
|
|
75
|
+
|
|
76
|
+
### 3.1 事件来源
|
|
77
|
+
|
|
78
|
+
用户交互、数据源**既有内容**的变化、槽状态变化、订阅。
|
|
79
|
+
|
|
80
|
+
**数据源的声明与初始化不是"变化"**:`data … = …` 建立数据源、写入初值,**不得**触发 `on <数据源> change`。否则装载期就会跑一遍级联,级联上限之类的诊断会在应用尚未运行时就消耗掉。
|
|
81
|
+
|
|
82
|
+
### 3.2 一个变更的处理顺序
|
|
83
|
+
|
|
84
|
+
1. 应用状态变更(数据源、节点运行期状态、槽);
|
|
85
|
+
2. 按拓扑序重算受影响的绑定;
|
|
86
|
+
3. 分派匹配的处理器(`on`);
|
|
87
|
+
4. 处理器产生的新变更**重新从第 1 步开始**;
|
|
88
|
+
5. 推送观察事件给订阅者与驱动者。
|
|
89
|
+
|
|
90
|
+
### 3.3 处理器链必须有界(且上限可见)
|
|
91
|
+
|
|
92
|
+
- 绑定传播本身可证明收敛(第 2.3 节),**不需要**上限。
|
|
93
|
+
- 但**处理器可以写数据源**,从而再次触发 `change`,这类级联**无法静态证明收敛**。
|
|
94
|
+
- 因此实现**必须**为处理器级联设置一个轮次上限;**达到上限时必须是可见错误** `CONVERGENCE_LIMIT`(带触发链的快照),**禁止**静默停止传播。
|
|
95
|
+
- 旧规范在此处静默截断(跑到上限就不再传播、不报任何东西),**本规范禁止**该行为。
|
|
96
|
+
|
|
97
|
+
### 3.4 禁用与不可见
|
|
98
|
+
|
|
99
|
+
- 状态标志 `disabled=true` 的节点**不触发**交互事件。拦截必须在**引擎层**生效(不只是界面层),否则程序化派发会绕过它。
|
|
100
|
+
- 若节点的静态声明的 `disabled` 恒为真,却又绑定了交互处理器 → **警告** `DISABLED_HANDLER`。
|
|
101
|
+
- `visible=false` 的节点**不占位**、不接收用户交互。
|
|
102
|
+
|
|
103
|
+
### 3.5 事件名
|
|
104
|
+
|
|
105
|
+
- 事件名集合由 `04-vocabulary.md` 定义;未识别的事件名 → **警告** `UNKNOWN_EVENT`。
|
|
106
|
+
|
|
107
|
+
## 4. 能力与槽
|
|
108
|
+
|
|
109
|
+
### 4.1 调用契约
|
|
110
|
+
|
|
111
|
+
- 函数必须有**非空说明文本**(供驱动者发现);为空 → **错误** `CAP_NO_DOC`,该能力**不予注册**。
|
|
112
|
+
- 契约从签名与类型提示提取:参数名、类型、是否必需、默认值、返回类型、是否异步。
|
|
113
|
+
- 参数缺失、多余、类型不符 → **失败**(`CALL_CONTRACT`),**禁止**静默补默认值。
|
|
114
|
+
- 返回值**必须**可序列化,且符合声明结构;否则 **失败**(`CALL_RESULT`)。
|
|
115
|
+
- 目标函数不存在 → **失败**(`CALL_UNKNOWN`)。**禁止**静默返回空值。
|
|
116
|
+
- **依赖必须显式声明 + 扫描兜底 + 不一致必须报警**:能力模块声明其依赖(如 `REQUIRES`),
|
|
117
|
+
实现**必须**扫描源码实际 import 作为兜底;两者不一致 → **警告** `CAP_DEPS_MISMATCH`。
|
|
118
|
+
仅靠扫描 import 是启发式的:动态 / 条件 import 会被静默漏掉,直到部署机上才缺包。
|
|
119
|
+
- 能力模块**加载失败**(文件缺失、导入抛错)→ **错误** `CAP_IMPORT`,**禁止**静默忽略;
|
|
120
|
+
能力体是否安全不在本规范承诺范围内(`CAP_IMPORT` 是**功能性**要求:agent 不能瞎)。
|
|
121
|
+
|
|
122
|
+
### 4.2 槽状态(五态)
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
进行中 → 成功 | 失败 | 超时 | 取消
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **超时必须支持**。一次永不返回的调用若只停留在"进行中",对驱动者等于**永久失明**;这是不可接受的。
|
|
129
|
+
- 超时的判定来源可以是调用处声明的时限,或实现的默认时限;**必须**在能力声明与诊断中可见。
|
|
130
|
+
- 取消:同一槽上的新调用**可以**取消进行中的旧调用;取消**必须**产生可见观察。
|
|
131
|
+
|
|
132
|
+
### 4.3 并发与陈旧结果
|
|
133
|
+
|
|
134
|
+
- 同一数据源上的多次调用**可以**并行。
|
|
135
|
+
- 每次调用递增槽序号;**旧序号的结果到达时被丢弃**。
|
|
136
|
+
- **丢弃必须可见**(观察流中的信息级事件,含被丢弃的序号);**禁止**静默丢弃。
|
|
137
|
+
|
|
138
|
+
### 4.4 槽值的读取
|
|
139
|
+
|
|
140
|
+
- 槽是**程序可读的状态**:`#槽.status`、`#槽.value`(以及实现定义的错误/超时原因)。
|
|
141
|
+
- 因槽值是可读状态,**值类属性可以直接绑定它**——"三段式"(某人被触发时去改某处文本)退化为可选的写法,而非唯一写法。
|
|
142
|
+
|
|
143
|
+
### 4.5 能力体与安全
|
|
144
|
+
|
|
145
|
+
- 能力体是**宿主提供的工具**,本规范不约束它做什么,也**不承诺**任何隔离。
|
|
146
|
+
- 但"能力缺失、加载失败、超时、异常"**必须**表现为可见观察——这是功能性要求,不因安全议题出局而豁免。
|
|
147
|
+
|
|
148
|
+
## 5. 持久化
|
|
149
|
+
|
|
150
|
+
- `persist=true` 的数据源,其**数据项**进入状态文件的**持久分区**;其余数据项进入**瞬态分区**(进程内,可丢)。
|
|
151
|
+
- 状态文件**必须原子写**。
|
|
152
|
+
- **结构漂移**(状态里出现程序未声明的字段、或类型不符):**不自动迁移**、**不删数据**,产生诊断 `PERSIST_SCHEMA_DRIFT`。
|
|
153
|
+
- 状态文件缺失或不可读:从程序声明的默认值重建,并产生信息级事件。**禁止**因此拒绝启动。
|
|
154
|
+
|
|
155
|
+
## 6. 观察流
|
|
156
|
+
|
|
157
|
+
- 观察流承载:诊断、**订阅事件**、**探针结果**、状态变更摘要、槽变化。
|
|
158
|
+
- **订阅事件**的载荷为 `{目标地址, 事件名}`,行内事件另带行序号;只有被 `listen` 订阅的 `(地址, 事件)` 才推送(见 `01-grammar.md` 第 7.8 节)。
|
|
159
|
+
- **不得静默丢弃**:
|
|
160
|
+
- 队列满 → **必须**背压;无法背压时**必须**声明断连或**必须**推送一条"事件丢失"标记(含丢失区间)。
|
|
161
|
+
- 旧规范在队列满时直接丢事件且不告知任何人,**本规范禁止**。
|
|
162
|
+
|
|
163
|
+
## 7. 单写者与生命周期
|
|
164
|
+
|
|
165
|
+
- **每个 app 实例**同一时刻只接受**一个**驱动者连接。第二个连接**必须**被拒绝,并明确告知原因。
|
|
166
|
+
- 修订号是**per-app** 的序列(见 `02-ir.md` 第 3 节)。
|
|
167
|
+
- 生命周期:**程序真源存在即"活着"**。驱动者断开或窗口关闭只表示"暂时无人驱动",程序与状态都不因此丢失。
|