openpuppet-language 2.0__tar.gz

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 (45) hide show
  1. openpuppet_language-2.0/PKG-INFO +50 -0
  2. openpuppet_language-2.0/README.md +42 -0
  3. openpuppet_language-2.0/conformance/README.md +274 -0
  4. openpuppet_language-2.0/conformance/cases/binding.json +238 -0
  5. openpuppet_language-2.0/conformance/cases/capabilities.json +155 -0
  6. openpuppet_language-2.0/conformance/cases/grammar.json +248 -0
  7. openpuppet_language-2.0/conformance/cases/interaction.json +220 -0
  8. openpuppet_language-2.0/conformance/cases/listen.json +133 -0
  9. openpuppet_language-2.0/conformance/cases/platform.json +274 -0
  10. openpuppet_language-2.0/conformance/cases/probes.json +117 -0
  11. openpuppet_language-2.0/conformance/cases/render.json +180 -0
  12. openpuppet_language-2.0/conformance/cases/tabs.json +39 -0
  13. openpuppet_language-2.0/conformance/cases/templates.json +143 -0
  14. openpuppet_language-2.0/conformance/fixtures/capabilities_bad_result.py +11 -0
  15. openpuppet_language-2.0/conformance/fixtures/capabilities_basic.py +24 -0
  16. openpuppet_language-2.0/conformance/fixtures/capabilities_deps_mismatch.py +11 -0
  17. openpuppet_language-2.0/conformance/fixtures/capabilities_no_doc.py +10 -0
  18. openpuppet_language-2.0/conformance/runner.py +721 -0
  19. openpuppet_language-2.0/openpuppet_language.egg-info/PKG-INFO +50 -0
  20. openpuppet_language-2.0/openpuppet_language.egg-info/SOURCES.txt +43 -0
  21. openpuppet_language-2.0/openpuppet_language.egg-info/dependency_links.txt +1 -0
  22. openpuppet_language-2.0/openpuppet_language.egg-info/entry_points.txt +2 -0
  23. openpuppet_language-2.0/openpuppet_language.egg-info/requires.txt +2 -0
  24. openpuppet_language-2.0/openpuppet_language.egg-info/top_level.txt +1 -0
  25. openpuppet_language-2.0/puppet/__init__.py +19 -0
  26. openpuppet_language-2.0/puppet/adapter.py +112 -0
  27. openpuppet_language-2.0/puppet/capabilities.py +172 -0
  28. openpuppet_language-2.0/puppet/cli.py +132 -0
  29. openpuppet_language-2.0/puppet/diag.py +42 -0
  30. openpuppet_language-2.0/puppet/engine.py +1038 -0
  31. openpuppet_language-2.0/puppet/eval.py +243 -0
  32. openpuppet_language-2.0/puppet/ir.py +538 -0
  33. openpuppet_language-2.0/puppet/lang.py +755 -0
  34. openpuppet_language-2.0/puppet/raster_adapter.py +348 -0
  35. openpuppet_language-2.0/puppet/tk_adapter.py +374 -0
  36. openpuppet_language-2.0/puppet/vocab.py +147 -0
  37. openpuppet_language-2.0/pyproject.toml +32 -0
  38. openpuppet_language-2.0/setup.cfg +4 -0
  39. openpuppet_language-2.0/spec/01-grammar.md +283 -0
  40. openpuppet_language-2.0/spec/02-ir.md +77 -0
  41. openpuppet_language-2.0/spec/03-semantics.md +167 -0
  42. openpuppet_language-2.0/spec/04-vocabulary.md +227 -0
  43. openpuppet_language-2.0/spec/05-render-contract.md +126 -0
  44. openpuppet_language-2.0/spec/06-diagnostics.md +151 -0
  45. openpuppet_language-2.0/spec/README.md +73 -0
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: openpuppet-language
3
+ Version: 2.0
4
+ Summary: Puppet 语言标准实现(设计 + 语言规范 + 参考实现)。零第三方依赖。
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Provides-Extra: tk
8
+
9
+ # Puppet 语言标准(发行名 `openpuppet-language`)
10
+
11
+ **An app is an agent.** 本仓是 **Puppet 语言标准**的规范与参考实现:不含任何具体渲染实现,
12
+ 不含 app 宿主。
13
+
14
+ | 目录 | 内容 |
15
+ |---|---|
16
+ | `spec/` | **人读规范**(`2.0`,**已冻结**):01 词法语法 / 02 IR / 03 执行语义 / 04 词汇 / 05 渲染契约 / 06 诊断 |
17
+ | `conformance/` | **可执行用例集**:随发行包分发,任意实现用它自证合规 |
18
+ | `puppet/` | **参考实现**(零第三方依赖):词法、IR、引擎、协议适配器、Tk 渲染器 |
19
+ | `docs/design-v2-draft.md` | 设计草案:决定"为什么" |
20
+ | `examples/` | 真实示例程序(`puppet validate examples --strict` 零诊断) |
21
+
22
+ ## 安装
23
+
24
+ ```bash
25
+ pip install openpuppet-language
26
+ ```
27
+
28
+ Tk 参考渲染器(`puppet conformance --tk`,真实几何)需要 Tcl/Tk:
29
+ Debian/Ubuntu 需 `apt install python3-tk`。
30
+
31
+ ## 命令
32
+
33
+ | 命令 | 作用 |
34
+ |---|---|
35
+ | `puppet spec` | 规范版本与位置 |
36
+ | `puppet validate <路径>` | 静态校验 `.puppet` 文件或目录(`--strict`:有任何诊断即失败) |
37
+ | `puppet conformance` | 跑合规用例集(`--tk` 改用 Tk 渲染器;`--filter <子串>` 过滤) |
38
+
39
+ 只想校验用例文件本身、不需要任何实现:
40
+
41
+ ```bash
42
+ python conformance/runner.py --check
43
+ ```
44
+
45
+ ## 参与
46
+
47
+ - **用例与实现都以 `spec/` 为准**:conformance 用例**从规范写**,不移植旧测试。
48
+ - **杜绝静默失败**:任何降级、丢弃、未实现都必须产生诊断或观察事件。
49
+ - **渲染器按能力声明行事**:启动握手(`hello`)声明支持范围,未声明支持的词汇被使用时
50
+ 必须产生 `DEGRADED_FEATURE`——标准不靠信任,靠用例抓。
@@ -0,0 +1,42 @@
1
+ # Puppet 语言标准(发行名 `openpuppet-language`)
2
+
3
+ **An app is an agent.** 本仓是 **Puppet 语言标准**的规范与参考实现:不含任何具体渲染实现,
4
+ 不含 app 宿主。
5
+
6
+ | 目录 | 内容 |
7
+ |---|---|
8
+ | `spec/` | **人读规范**(`2.0`,**已冻结**):01 词法语法 / 02 IR / 03 执行语义 / 04 词汇 / 05 渲染契约 / 06 诊断 |
9
+ | `conformance/` | **可执行用例集**:随发行包分发,任意实现用它自证合规 |
10
+ | `puppet/` | **参考实现**(零第三方依赖):词法、IR、引擎、协议适配器、Tk 渲染器 |
11
+ | `docs/design-v2-draft.md` | 设计草案:决定"为什么" |
12
+ | `examples/` | 真实示例程序(`puppet validate examples --strict` 零诊断) |
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ pip install openpuppet-language
18
+ ```
19
+
20
+ Tk 参考渲染器(`puppet conformance --tk`,真实几何)需要 Tcl/Tk:
21
+ Debian/Ubuntu 需 `apt install python3-tk`。
22
+
23
+ ## 命令
24
+
25
+ | 命令 | 作用 |
26
+ |---|---|
27
+ | `puppet spec` | 规范版本与位置 |
28
+ | `puppet validate <路径>` | 静态校验 `.puppet` 文件或目录(`--strict`:有任何诊断即失败) |
29
+ | `puppet conformance` | 跑合规用例集(`--tk` 改用 Tk 渲染器;`--filter <子串>` 过滤) |
30
+
31
+ 只想校验用例文件本身、不需要任何实现:
32
+
33
+ ```bash
34
+ python conformance/runner.py --check
35
+ ```
36
+
37
+ ## 参与
38
+
39
+ - **用例与实现都以 `spec/` 为准**:conformance 用例**从规范写**,不移植旧测试。
40
+ - **杜绝静默失败**:任何降级、丢弃、未实现都必须产生诊断或观察事件。
41
+ - **渲染器按能力声明行事**:启动握手(`hello`)声明支持范围,未声明支持的词汇被使用时
42
+ 必须产生 `DEGRADED_FEATURE`——标准不靠信任,靠用例抓。
@@ -0,0 +1,274 @@
1
+ # Conformance 用例集
2
+
3
+ ## 1. 目的
4
+
5
+ 让**任何**实现自证符合 `spec/` 下的语言规范。本目录随 `puppet` 发行包分发;`puppethub` 的 CI 必须运行它。
6
+
7
+ **用例来自规范,不从实现反推。** 每条用例在 `spec` 字段登记它断言的规范位置。
8
+
9
+ ## 2. 目录
10
+
11
+ ```
12
+ conformance/
13
+ ├── README.md
14
+ ├── runner.py # 零依赖运行器
15
+ └── cases/
16
+ ├── grammar.json # 词法与语法
17
+ ├── binding.json # 校验、绑定、传播
18
+ ├── platform.json # 能力、槽、持久化、收敛
19
+ ├── probes.json # 探针(tree / get / where)
20
+ ├── templates.json # 模板与行上下文
21
+ ├── listen.json # 订阅事件
22
+ ├── interaction.json # 交互覆盖
23
+ └── render.json # 渲染:几何关系 / 能力声明 oracle / 可见降级
24
+ ```
25
+
26
+ ## 3. 运行
27
+
28
+ ```bash
29
+ python conformance/runner.py --check
30
+ ```
31
+
32
+ 只校验用例文件本身是否符合本文件的模式,**不需要任何实现**。
33
+
34
+ ```bash
35
+ python conformance/runner.py --impl "<启动实现的命令>"
36
+ python conformance/runner.py --impl "..." --filter b-cycle
37
+ ```
38
+
39
+ 驱动一个真实实现跑全部用例。
40
+
41
+ 运行器在每步之后会轮询 `observe`,直到该步的期望满足或达到等待上限(默认 3 秒)。因此像"槽超时"这类异步用例**不需要**额外的同步手段。
42
+
43
+ 每步的期望只针对**该步**(`send` / `fire` / `restart` / `snapshot`)产生的诊断;装载期诊断用 `loadExpect` 断言。
44
+
45
+ ## 4. 实现协议
46
+
47
+ 实现是一个**子进程**,通过标准输入/输出交换**行分隔 JSON**(每行一个 JSON 对象,UTF-8,无嵌套换行)。
48
+
49
+ ### 4.1 请求
50
+
51
+ | 请求 | 字段 | 期望响应 |
52
+ |---|---|---|
53
+ | `hello` | — | `{"protocol": "...", "rendering": {…}}`(见 4.1.1) |
54
+ | `load` | `program`(字符串数组)、`capabilities`(数组,可选)、`capabilityModules`(字符串数组,可选)、`limits`(对象,可选)、`seedState`(对象,可选)、`renderGeometry`(对象,可选) | `{"diagnostics": [...]}` |
55
+ | `send` | `batch`(字符串数组) | `{"diagnostics": [...]}` |
56
+ | `fire` | `target`(地址)、`event`(事件名)、`row`(行序号,可选)、`value`(新值,可选) | `{"diagnostics": [...]}` |
57
+ | `observe` | — | 见 4.3 |
58
+ | `interact` | `target`(地址)、`action`(`click`/`change`/`submit`/`focus`/`blur`)、`value`(新值,可选) | `{"delivered": 布尔, "diagnostics": [...]}` |
59
+ | `snapshot` | — | `{"image": <base64 或 null>, "format": "png"}`(见 4.1.3) |
60
+ | `restart` | — | `{"diagnostics": [...]}` |
61
+ | `quit` | — | 进程退出 |
62
+
63
+ - `capabilities` 描述**测试替身**(实现按描述注册假能力,不得调用真实业务):`{"name", "params":[{"name","type","required"}], "returns", "behavior"}`。`behavior` 取值:`return`(附 `value`)、`error`、`hang`(永不返回)、`nonjson`(返回不可序列化的值)、`delay`(附 `delayMs`)。
64
+ - `limits`:`{"callTimeoutMs": <整数>, "eventQueue": <整数>}`。后者限定观察流缓冲上限,用于验证"溢出不得静默丢弃"。
65
+ - `seedState`:预置的状态文件内容,形状与实现写出的状态一致:`{"revision": <整数>, "items": {"#数据源地址": [行, …]}}`。实现应**先**把它当作既有状态,再执行 `load`(用于持久化漂移类用例)。
66
+ - `capabilityModules` 描述**真实能力模块**(`.py` 文件路径,相对路径按 `conformance/` 解析)。
67
+ 实现必须从签名与类型注解提取契约,并履行三条义务:说明文本为空 → 错误 `CAP_NO_DOC`
68
+ 且**不予注册**;依赖声明与实际 import 不一致 → 警告 `CAP_DEPS_MISMATCH`;模块加载失败 →
69
+ 错误 `CAP_IMPORT`。真实能力与替身走**同一份契约形状**,因此行为用例对二者同样适用
70
+ (`conformance/fixtures/` 内置四个测试用模块)。
71
+ - `restart`:模拟进程重启——内存状态丢弃,持久分区从状态文件恢复。
72
+ - `fire.row`:**派发行内事件时必须给出**。行内事件的绑定名只能由"第几行"确定(规范 01 第 8.1 节);省略 `row` 而目标又在模板内,属于驱动者的错误用法。
73
+ - `fire.value`:**`change` 事件必须给出**——它是"用户改成了什么"。事件载荷 `{value: …}` 由此而来(规范 03 第 2.8 节);不给,处理器就无从知道新值。
74
+
75
+ ### 4.1.1 能力声明握手(`hello`)
76
+
77
+ 运行器在 spawn 出实现之后、跑任何用例之前,先发一次 `hello`,读回实现声明的**渲染能力**。
78
+ 这是"**声明即 oracle**"的输入:conformance 用声明决定每条渲染期望是**硬断言**,还是退化为
79
+ **要求可见降级**(见第 6 节)。
80
+
81
+ ```json
82
+ → {"op": "hello"}
83
+ ← {"protocol": "1", "rendering": {
84
+ "controls": ["window", "col", "row", "button"],
85
+ "attributes": ["gap", "flex", "bgcolor"],
86
+ "animations": ["opacity", "bgcolor"],
87
+ "icons": ["add", "delete"],
88
+ "geometry": false, "snapshot": false, "headless": true,
89
+ "notes": "……可选的人读说明……"
90
+ }}
91
+ ```
92
+
93
+ | 字段 | 类型 | 含义 |
94
+ |---|---|---|
95
+ | `controls` | 数组或 `null` | 支持的节点 / 控件类型;`null` = 支持全部标准类型 |
96
+ | `attributes` | 数组或 `null` | 支持的属性名;`null` = 全部标准属性 |
97
+ | `animations` | 数组或 `null` | 支持过渡的属性;`null` = 全部标准可动画属性 |
98
+ | `icons` | 数组或 `null` | 支持的图标语义名;`null` = 核心子集 |
99
+ | `geometry` | 布尔 | 是否提供几何观察(`observe.geometry`) |
100
+ | `snapshot` | 布尔 | 是否支持 `snapshot` 请求 |
101
+ | `headless` | 布尔 | 是否可**无 GUI** 驱动(`puppethub` 对 `puppetOS` 的唯一义务) |
102
+
103
+ - 未实现 `hello` 的实现会回一条 `error`;运行器将其视为**未声明**(`rendering = {}`),不影响
104
+ 非渲染用例;渲染用例随后会要求它对一切被请求的特性**可见降级**。
105
+ - **谎报**(声明支持却做不到)由行为用例抓出——标准不靠信任。
106
+
107
+ ### 4.1.2 测试替身:几何与能力声明
108
+
109
+ - `observe` 的响应**可以**含可选字段 `geometry`(见 4.3):地址 → `{x, y, width, height}`,以
110
+ 窗口内容区左上角为原点、**浮点像素**。**仅当 `rendering.geometry` 为真时该字段出现**;否则
111
+ 字段缺席——实现必须在被 `where` 询问时产生 `DEGRADED_FEATURE(feature=geometry)`,**禁止**静默。
112
+ - **观测替身**:为了在没有真实渲染器时**确定性地**验证关系断言,`load` **可以**带
113
+ `renderGeometry`(对象:地址 → 矩形)。存在时它**就是真值**:实现照它返回 `observe.geometry`
114
+ 与 `where` 结果,且**不**报几何降级——即使实现自己有真实几何(替身优先,与 `capabilities`
115
+ 同一待遇)。这是"观测面测试替身":一个替身业务能力,一个替身观测能力。
116
+ - **声明替身**:`load` 还**可以**带 `rendering`(形状同 4.1.1 的 `rendering`),**覆盖**实现的
117
+ 真实声明。实现必须**双向按声明行事**:
118
+ - 声明里没有的词汇被程序使用 → 必须 `DEGRADED_FEATURE`(哪怕实现其实支持);
119
+ - 声明 `geometry: false` → 必须表现为无几何(哪怕实现其实量得到)。
120
+ 这让"未声明 → 必须降级"的 oracle 路径可以**确定性**地用例化,而不依赖某个实现恰好缺什么。
121
+ - 几何来源优先级:**观测替身 > 声明替身(geometry:false 即无) > 真实测量**。
122
+ - 参考实现三枚:`python -m puppet.adapter`(无渲染器,词汇全声明、geometry/snapshot=false、
123
+ headless=true)与 `python -m puppet.tk_adapter`(Tk 渲染器,geometry=true、词汇子集声明——
124
+ 未声明词汇真实降级)与 `python -m puppet.raster_adapter`(软件光栅渲染器:确定性堆叠布局 + **纯标准库 PNG 截图** + **命中测试**交互,geometry/snapshot/interaction 全 true、headless=true,**不需要显示器**)。同一套用例必须三者全绿,这正是"渲染器无关"的自证。
125
+
126
+ ### 4.1.3 视觉快照(`snapshot`)
127
+
128
+ - `snapshot` 是**可选**请求,**仅供驱动者自检**;其结果**不进断言**(自检归驱动者,见设计草案第 5 节)。
129
+ - 支持 → `{"image": "<base64 PNG>", "format": "png"}`;不支持 → `{"image": null, …}` **并**产生
130
+ `DEGRADED_FEATURE(feature=snapshot)`。
131
+ - 用例以 `expect.degraded: ["snapshot"]` 表达"若不支持就必须可见降级"(见第 5 节)。
132
+
133
+ ### 4.1.4 用户动作投递(`interact`)
134
+
135
+ - `fire`(4.1)**直接派发进引擎**——它验证语义,**不验证渲染器**。`interact` 才是"用户动作":
136
+ 实现**必须**把它投递给**真实部件**,由渲染器自己的事件绑定翻译成引擎事件
137
+ (用户动作 → 部件事件 → 渲染器 → `fire` → 处理器)。规范 05 第 4 节要求这条路径可观察。
138
+ - 投递不了(如没有渲染层)**必须**返回 `{"delivered": false}` **并**产生
139
+ `DEGRADED_FEATURE(feature=interaction)`;**禁止**假装送达。
140
+ - 用例用 `requires: ["interaction"]` 表达"这条用例要求实现能投递用户动作";实现未声明该能力时
141
+ 运行器**可见跳过**(打印 `skip`),**不是**静默通过。
142
+
143
+ ### 4.2 诊断
144
+
145
+ 每条诊断是一个对象,**必须**含 `code` 与 `level`(`error` / `warning` / `info`),**应该**含 `line`、`message`。
146
+
147
+ ### 4.3 观察面
148
+
149
+ ```json
150
+ {
151
+ "events": [{"target": "#b", "event": "click"}],
152
+ "nodes": ["#root", "#win"],
153
+ "attrs": {"#cnt.text": "共 1 条"},
154
+ "data": {"#todos": [{"text": "a", "done": false}]},
155
+ "flags": {"#b.error": true},
156
+ "slots": {"#slot": {"status": "done", "value": {"ok": true}, "seq": 1}},
157
+ "rows": {"#items": [{"#label": {"text": "a"}}]},
158
+ "geometry": {"#a": {"x": 16, "y": 16, "width": 80, "height": 40}}
159
+ }
160
+ ```
161
+
162
+ | 字段 | 含义 |
163
+ |---|---|
164
+ | `diagnostics` | **自上一次请求以来**新产生的诊断(异步观察的主要通道) |
165
+ | `events` | **自上一次请求以来**的订阅事件(只有被 `listen` 订阅的才推送);载荷 `{target, event, row?}` |
166
+ | `probes` | **自上一次请求以来**的探针结果;形状 `{verb, target, result}`(`result` 为 `null` 表示目标不存在) |
167
+ | `nodes` | 当前存在的节点地址 |
168
+ | `attrs` | **求值后**的属性值,键为 `#地址.属性名` |
169
+ | `data` | 数据源的数据项 |
170
+ | `flags` | 运行期状态标志,键为 `#地址.标志名` |
171
+ | `slots` | 槽的状态、值、序号 |
172
+ | `rows` | 列表行:列表地址 → 各行(行内以模板子地址为键) |
173
+ | `geometry` | **可选**几何快照:地址 → `{x, y, width, height}`。仅当 `rendering.geometry` 为真时出现 |
174
+
175
+ ## 5. 用例模式
176
+
177
+ ```json
178
+ {
179
+ "id": "b-recompute",
180
+ "title": "值类属性随数据源变化自动重算",
181
+ "spec": "03-semantics.md 第 2 节",
182
+ "program": ["add #root window #win", "..."],
183
+ "capabilities": [],
184
+ "limits": {},
185
+ "seedState": {},
186
+ "loadExpect": { "diagnostics": [], "forbid": [] },
187
+ "steps": [
188
+ { "send": ["append #todos item={text: \"a\"}"],
189
+ "expect": { "diagnostics": [], "attrs": {"#cnt.text": "共 1 条"} } },
190
+ { "restart": true, "expect": { "data": {"#todos": []} } }
191
+ ]
192
+ }
193
+ ```
194
+
195
+ | 字段 | 必需 | 说明 |
196
+ |---|---|---|
197
+ | `id` | 是 | 全局唯一 |
198
+ | `title` | 是 | 人读标题 |
199
+ | `spec` | 是 | 断言的规范位置 |
200
+ | `program` | 是 | 程序文本(行数组) |
201
+ | `loadExpect` | 否 | 装载后的期望 |
202
+ | `steps` | 是 | 至少一步;每步**必须**含 `send` / `fire` / `restart` / `snapshot` / `expect` 之一 |
203
+ | `capabilities` / `limits` / `seedState` / `renderGeometry` | 否 | 见第 4 节 |
204
+ | `capabilityModules` | 否 | 真实能力模块(`.py` 路径,相对 `conformance/`);见第 4.1 节 |
205
+ | `rendering` | 否 | 能力声明**替身**,覆盖实现自身的声明(见 4.1.2) |
206
+ | `requires` | 否 | 本用例要求的能力(如 `["interaction"]`);实现未声明时**可见跳过** |
207
+
208
+ **期望对象**(`loadExpect` 与每步的 `expect` 同构)。运行器**只在用例声明了 `loadExpect` 时**才在装载后额外观察一次——没人监听时不消费观察流,否则装载后立刻完成的异步诊断会被吸进装载期,步骤断言就看不到它了:(`loadExpect` 与每步的 `expect` 同构):
209
+
210
+ | 键 | 语义 |
211
+ |---|---|
212
+ | `diagnostics` | **必须包含**:列表中的每一项都是某条实际诊断的子集(按 `code` 等键比对) |
213
+ | `forbid` | **必须不含**:这些码不得出现在实际诊断中 |
214
+ | `events` | **必须包含**:列表中的每一项都是某个实际订阅事件的子集 |
215
+ | `noEvents` | 期望**没有**订阅事件 |
216
+ | `probes` | **必须包含**:列表中的每一项都是某个实际探针结果的子集(按 `verb` 比对) |
217
+ | `nodes` / `attrs` / `data` / `flags` / `slots` / `rows` | 实际观察结果的**子集**必须匹配(递归子集:字典按键子集,列表要求等长逐项匹配) |
218
+ | `geometry` | `observe.geometry` 的**子集**必须匹配(仅在几何可用时比对;见第 6 节) |
219
+ | `relations` | **必须成立**的关系断言列表:`{"rel", "a", "b", "tol?", "value?"}`;词汇见表(第 6 节)。几何可用 → 硬判定;不可用 → 退化为要求 `DEGRADED_FEATURE(feature=geometry)` |
220
+ | `degraded` | **声明即 oracle**:列出的特性若**未被实现声明支持**,实际诊断中必须出现对应的 `DEGRADED_FEATURE(feature=…)`;已声明支持则不要求 |
221
+ | `note` | 说明文字,不参与比对 |
222
+
223
+ ## 6. 声明即 oracle(渲染 / 几何分档)
224
+
225
+ 渲染契约(`spec/05-render-contract.md` 第 1 节)把"支持范围"交给实现**声明**,把"缺失必须可见"
226
+ 交给 `DEGRADED_FEATURE`。conformance 把两者闭环——用例**从规范写**,一套用例同时约束
227
+ "能做"与"不能做却不说"两种实现:
228
+
229
+ | 实现声明 | 用例断言 | 运行器行为 |
230
+ |---|---|---|
231
+ | `geometry: true`(或用例带 `renderGeometry`) | `relations` / `geometry` | **硬断言**:按 `observe.geometry` 判定,不成立即 FAIL |
232
+ | `geometry: false` | `relations` | 退化为**诚实性检查**:运行器先发一次 `where` 探针,要求出现 `DEGRADED_FEATURE(feature=geometry)`;**静默即 FAIL** |
233
+ | 未声明支持 `<特性>` | `degraded: ["<特性>"]` | 要求出现对应的 `DEGRADED_FEATURE(feature=…)` |
234
+ | 已声明支持 `<特性>` | `degraded: ["<特性>"]` | **不**要求降级(谎报"支持"由行为用例抓出) |
235
+ | `interaction: false` | `requires: ["interaction"]` | **可见跳过**(打印 `skip`),不是静默通过 |
236
+
237
+ 两条配套规则,缺一不可:
238
+
239
+ - **`noDiagnostics` 豁免**:未声明支持的特性产生的 `DEGRADED_FEATURE` 是**规范要求的行为**,
240
+ 不是缺陷——期望"零诊断"的用例对它豁免。否则任何一个子集渲染器都过不了写死"零诊断"的
241
+ 用例,套件就不再渲染器无关。
242
+ - **声明替身**(`load.rendering`):让"未声明 → 必须降级"的路径**确定性**用例化(见 4.1.2),
243
+ 不必依赖某个实现恰好缺什么词汇。
244
+
245
+ 几何关系词汇(与规范 05 第 8 节一一对应)。每项形如
246
+ `{"rel": "…", "a": "#x", "b": "#y", "tol": 1, "value": 8}`;`tol` 缺省 1(像素),
247
+ 比例关系缺省 0.05。全部为**定性 / 相对**断言,**不比较绝对坐标**——字号、DPI、窗口尺寸
248
+ 不同不应导致断言不可移植:
249
+
250
+ | 类别 | 关系 |
251
+ |---|---|
252
+ | 方位 | `left_of` `right_of` `above` `below` |
253
+ | 包含 | `contains` `inside` |
254
+ | 重叠 | `overlaps` `disjoint` |
255
+ | 对齐 | `aligned_x` `aligned_y` `aligned_center_x` `aligned_center_y` |
256
+ | 尺寸 | `same_width` `same_height` `same_size` |
257
+ | 间距 | `gap_h` `gap_v`(`value` = 期望间隙 ± `tol`) |
258
+ | 比例 | `width_ratio` `height_ratio`(`value` = 期望比值) |
259
+
260
+ ## 7. 覆盖范围
261
+
262
+ 覆盖:词法、语法、校验、IR 结构、绑定与传播、事件、能力与槽、持久化、收敛、诊断码,
263
+ 以及**渲染的可观察行为**——几何关系(无真实渲染器时经 `renderGeometry` 替身确定性验证;
264
+ 有真实渲染器时直接走上真实布局)、能力声明 oracle、词汇 / 几何 / 截图的可见降级
265
+ (`cases/render.json`)、观察流溢出标记(`p-observation-dropped`,靠 `limits.eventQueue`
266
+ 把时序问题转化为确定性的批量溢出)、**真实能力层**(加载 / docstring 强制 / 依赖声明核对 /
267
+ 超时 / 取消 / 返回值与参数契约,见 `cases/capabilities.json`)、`tabs` 的页结构与 `selected`
268
+ 语义(`cases/tabs.json`)、经渲染器的用户动作投递(`cases/interaction.json` 的 `ir-*`)。
269
+
270
+ **不覆盖**:像素级视觉呈现(字体度量、抗锯齿、阴影质量、动效曲线的精确形状)。这些属实现自由
271
+ (规范 05 第 9 节),同一实现在等价输入下一致即可;按规范纪律,本套件**不**对像素做黄金图像比对。
272
+
273
+ **已知未覆盖项**:`tabs` 的"非选中页不占位"尚未用例化——它需要真实渲染器的几何(声明替身给不出
274
+ "随 `selected` 切换而变化的矩形");两个真实渲染器(Tk 与软件光栅)目前都未声明 `tabs`,故它仍未覆盖;交叉验证已落地——同一套用例在三个适配器上全绿。
@@ -0,0 +1,238 @@
1
+ [
2
+ {
3
+ "id": "b-recompute",
4
+ "title": "值类属性随数据源变化自动重算,不需要手写 change 处理器",
5
+ "spec": "03-semantics.md 第 2.1、2.2 节",
6
+ "program": [
7
+ "add #root window #win",
8
+ "add #win col #main",
9
+ "add #main text #cnt text=str(count(#todos))",
10
+ "data #todos = [] of {text: str = \"\"}"
11
+ ],
12
+ "loadExpect": {
13
+ "noDiagnostics": true,
14
+ "attrs": { "#cnt.text": "0" }
15
+ },
16
+ "steps": [
17
+ {
18
+ "send": ["append #todos item={text: \"a\"}"],
19
+ "expect": { "attrs": { "#cnt.text": "1" } }
20
+ },
21
+ {
22
+ "send": ["append #todos item={text: \"b\"}"],
23
+ "expect": { "attrs": { "#cnt.text": "2" } }
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "id": "b-template-rows",
29
+ "title": "模板按行实例化求值,行绑定取自该行数据项",
30
+ "spec": "03-semantics.md 第 2.6 节;01-grammar.md 第 8 节",
31
+ "program": [
32
+ "add #root window #win",
33
+ "add #win col #main",
34
+ "add #main list #items source=#todos template=#tpl",
35
+ "add #root template #tpl as t",
36
+ "add #tpl row #r",
37
+ "add #r text #label text=t.text",
38
+ "data #todos = [] of {text: str = \"\"}"
39
+ ],
40
+ "steps": [
41
+ {
42
+ "send": [
43
+ "append #todos item={text: \"a\"}",
44
+ "append #todos item={text: \"b\"}"
45
+ ],
46
+ "expect": {
47
+ "rows": {
48
+ "#items": [
49
+ { "#label": { "text": "a" } },
50
+ { "#label": { "text": "b" } }
51
+ ]
52
+ }
53
+ }
54
+ }
55
+ ]
56
+ },
57
+ {
58
+ "id": "b-cycle",
59
+ "title": "绑定成环在编译期报错",
60
+ "spec": "02-ir.md 第 4 节;03-semantics.md 第 2.4 节",
61
+ "program": [
62
+ "add #root window #win",
63
+ "add #win col #main",
64
+ "add #main text #a text=#b.text",
65
+ "add #main text #b text=#a.text"
66
+ ],
67
+ "loadExpect": {
68
+ "diagnostics": [{ "code": "BIND_CYCLE", "level": "error" }]
69
+ },
70
+ "steps": [
71
+ { "expect": { "nodes": ["#a", "#b"] } }
72
+ ]
73
+ },
74
+ {
75
+ "id": "b-unknown-ref",
76
+ "title": "绑定引用了不存在的地址或字段是错误",
77
+ "spec": "02-ir.md 第 4 节",
78
+ "program": [
79
+ "add #root window #win",
80
+ "add #win col #main",
81
+ "add #main text #a text=#ghost.text"
82
+ ],
83
+ "loadExpect": {
84
+ "diagnostics": [{ "code": "BIND_UNKNOWN_REF", "level": "error" }]
85
+ },
86
+ "steps": [
87
+ { "expect": { "nodes": ["#a"] } }
88
+ ]
89
+ },
90
+ {
91
+ "id": "b-set-override",
92
+ "title": "set 覆盖已绑定属性:解除绑定、写入字面量,且必须可见",
93
+ "spec": "03-semantics.md 第 2.7 节;06-diagnostics.md 第 4.4 节",
94
+ "program": [
95
+ "add #root window #win",
96
+ "add #win col #main",
97
+ "add #main text #cnt text=str(count(#todos))",
98
+ "data #todos = [] of {text: str = \"\"}"
99
+ ],
100
+ "steps": [
101
+ {
102
+ "send": ["set #cnt text=\"fixed\""],
103
+ "expect": {
104
+ "diagnostics": [{ "code": "BIND_OVERRIDDEN", "level": "info" }],
105
+ "attrs": { "#cnt.text": "fixed" }
106
+ }
107
+ },
108
+ {
109
+ "send": ["append #todos item={text: \"a\"}"],
110
+ "expect": { "attrs": { "#cnt.text": "fixed" } }
111
+ }
112
+ ]
113
+ },
114
+ {
115
+ "id": "b-ref-attr-expr",
116
+ "title": "引用类属性写表达式是错误",
117
+ "spec": "04-vocabulary.md 第 1 节;06-diagnostics.md 第 4.2 节",
118
+ "program": [
119
+ "add #root window #win",
120
+ "add #win col #main",
121
+ "add #main list #items source=#todos template=#tpl",
122
+ "add #root template #tpl as t",
123
+ "add #tpl text #label text=t.text",
124
+ "data #todos = [] of {text: str = \"\"}"
125
+ ],
126
+ "steps": [
127
+ {
128
+ "send": ["set #items source=\"x\" + \"y\""],
129
+ "expect": { "diagnostics": [{ "code": "REF_ATTR_EXPR", "level": "error" }] }
130
+ }
131
+ ]
132
+ },
133
+ {
134
+ "id": "b-eval-failure-visible",
135
+ "title": "绑定求值失败必须可见:诊断 + 错误标记,不得静默显示旧值或源码",
136
+ "spec": "03-semantics.md 第 2.5 节",
137
+ "program": [
138
+ "add #root window #win",
139
+ "add #win col #main",
140
+ "add #main text #bad text=str(10 / #d.n)",
141
+ "data #d = [{n: 0}] of {n: int = 1}"
142
+ ],
143
+ "loadExpect": {
144
+ "minErrors": 1,
145
+ "flags": { "#bad.error": true }
146
+ },
147
+ "steps": [
148
+ { "expect": { "flags": { "#bad.error": true } } }
149
+ ]
150
+ },
151
+ {
152
+ "id": "b-when-not-bool",
153
+ "title": "when 守卫不是布尔是错误",
154
+ "spec": "01-grammar.md 第 7.7 节",
155
+ "program": [
156
+ "add #root window #win",
157
+ "add #win button #b text=\"x\"",
158
+ "on #b click when \"x\": set #b visible=false"
159
+ ],
160
+ "loadExpect": {
161
+ "diagnostics": [{ "code": "WHEN_NOT_BOOL", "level": "error" }]
162
+ },
163
+ "steps": [
164
+ { "expect": { "noDiagnostics": true } }
165
+ ]
166
+ },
167
+ {
168
+ "id": "b-disabled-handler",
169
+ "title": "静态禁用的节点绑定了交互处理器是警告",
170
+ "spec": "01-grammar.md 第 7.7 节;06-diagnostics.md 第 4.3 节",
171
+ "program": [
172
+ "add #root window #win",
173
+ "add #win button #b text=\"x\" disabled=true",
174
+ "on #b click: set #b visible=false"
175
+ ],
176
+ "loadExpect": {
177
+ "diagnostics": [{ "code": "DISABLED_HANDLER", "level": "warning" }]
178
+ },
179
+ "steps": [
180
+ { "expect": { "noDiagnostics": true } }
181
+ ]
182
+ },
183
+ {
184
+ "id": "b-disabled-blocks-dispatch",
185
+ "title": "引擎层拦截:禁用节点上派发的交互不执行,解禁后恢复",
186
+ "spec": "03-semantics.md 第 3.4 节",
187
+ "program": [
188
+ "add #root window #win",
189
+ "add #win button #b text=\"x\" disabled=true",
190
+ "add #win text #t text=\"on\"",
191
+ "on #b click: set #t text=\"clicked\""
192
+ ],
193
+ "steps": [
194
+ {
195
+ "fire": { "target": "#b", "event": "click" },
196
+ "expect": { "attrs": { "#t.text": "on" } }
197
+ },
198
+ {
199
+ "send": ["set #b disabled=false"],
200
+ "expect": { "flags": { "#b.disabled": false } }
201
+ },
202
+ {
203
+ "fire": { "target": "#b", "event": "click" },
204
+ "expect": { "attrs": { "#t.text": "clicked" } }
205
+ }
206
+ ]
207
+ },
208
+ {
209
+ "id": "b-animate-unknown",
210
+ "title": "animate 指向非语义可动画属性是警告",
211
+ "spec": "04-vocabulary.md 第 6 节;06-diagnostics.md 第 4.3 节",
212
+ "program": [
213
+ "add #root window #win",
214
+ "add #win col #c animate=text"
215
+ ],
216
+ "loadExpect": {
217
+ "diagnostics": [{ "code": "ANIMATE_UNKNOWN", "level": "warning" }]
218
+ },
219
+ "steps": [
220
+ { "expect": { "noDiagnostics": true } }
221
+ ]
222
+ },
223
+ {
224
+ "id": "b-state-unknown-attr",
225
+ "title": "states 里出现外观白名单外的属性是警告",
226
+ "spec": "04-vocabulary.md 第 3.6 节",
227
+ "program": [
228
+ "add #root window #win",
229
+ "add #win col #c states={hover: {foo: 1}}"
230
+ ],
231
+ "loadExpect": {
232
+ "diagnostics": [{ "code": "STATE_UNKNOWN_ATTR", "level": "warning" }]
233
+ },
234
+ "steps": [
235
+ { "expect": { "noDiagnostics": true } }
236
+ ]
237
+ }
238
+ ]