18 KiB
TP 1.0 API Semantics
本文记录 TP 1.0 当前公共 API 的 ownership、presence、响应式、retained surface、事务与失败合同。它描述已经实现的行为;目标背景与设计理由见 retained runtime architecture。
完整的公共符号、参数形状、返回值和示例见 API reference。
1. 产品边界
TP 负责最终 Emacs text property policy/contribution、signals/bindings、stable objects、marker-backed mounts、surface diff、transaction、rollback 和 Buffer publication。
TP 不实现 selector、stylesheet、specificity、origin/importance、CSS cascade layers、CSS-wide values、custom properties 或 Box/Flex/Grid。需要 CSS cascade 的调用者先通过独立 ECSS 获得 final declarations,再交给 TP。
2. 普通值、显式 nil 与 computed source
普通 Elisp value 始终是 literal,包括 function object、keymap command、help-echo callback 和 list。TP 不会因为一个值可调用就执行它。
需要求值的 declaration 必须显式使用:
(tp-computed (lambda () ...))
tp-resolve-value 只执行这种 tagged source。compute result 只 normalize/project 一次,不会被隐式二次调用。compute error 直接终止 candidate transaction。
TP 严格区分:
- absent:property plist 中没有该 key;
- present nil:property key 存在,value 为
nil。
tp-member 和底层 contribution ledger 使用 presence-aware 语义。properties mount 的显式 nil contribution 可以覆盖 baseline,但 unmount 仍只撤销 TP 自己拥有的 contribution。
3. Property policy 与 direct declarations
tp-define-property-policy 为一个 canonical native property id 注册:
- normalizer;
- validator;
- equality;
- merge;
- projector。
注册是原子的:无效 options 或 function 会报 tp-invalid-property-policy,旧 definition 保持不变。
tp-register-text-property 为原生 Emacs property 建立默认 policy,并返回 policy record。tp-text-property-id 把原生 property 映射到 canonical text/PROPERTY id;tp-text-declarations 把普通 property plist 转换为 canonical declarations。
tp-merge-declarations 按输入顺序合并 direct declaration groups,并防御性复制 caller-owned value。它只做 TP contribution composition,不实现 CSS winner selection。
tp-define-style、tp-style-declarations 和 tp-undefine-style 管理 named direct declarations。registry getter 返回防御性副本。
4. Declaration recipes
define-tp 定义一个返回 native property plist 的 recipe;define-tps 定义一组有序 recipe elements。tp-define-layer、define-tp-group 与 tp-define-group 是同一静态 declaration workflow 的命名入口。
Recipe 可以是静态或参数化的,可以组合其他 recipes。展开结果经过同一 direct property policy/projector。Recipe application 不建立 live identity,不写 tp-name、tp-layers 或 tp-meta。
旧 $variable syntax 会报 tp-invalid-layer-definition。响应式值必须使用 tp-computed 加 signal/binding,不能建立第二套 watcher runtime。
Recipe/group definition 与 redefinition 是原子的:definition body、generated named elements 或 compiled style 任一步失败时,不留下半个新 definition,已有 definition 保持可用。
5. 一次性 façade
5.1 tp-propertize
(tp-propertize STRING DECLARATIONS)
返回新的 propertized string,不修改输入 STRING,不创建 object、binding、anchor、mount 或 surface。
5.2 tp-apply
(tp-apply BUFFER START END DECLARATIONS)
只修改 BUFFER 的 [START, END) 文本属性,不替换文字,成功返回 (START . END)。无效 buffer 不会退回 current buffer;无效 range 直接报错。
5.3 Direct operations
tp-set、tp-reset、tp-add、tp-remove、tp-clear、tp-get、tp-at 与 tp-member 保留既有 string/buffer 调用形状,但统一经过 direct property resolution。
- whole-string
tp-set/tp-reset/tp-add返回新 string; - 带 range 的 string 操作按各函数 docstring 的 mutation contract 执行;
- buffer 坐标使用 Emacs 原生 1-based position;
- string 坐标使用 0-based index;
- direct buffer write 遵循 Emacs read-only、undo 和 modified semantics。
Search/match/regexp/navigation/query API 继续委托 Emacs 原生 text-property interval 语义,不创建 retained runtime。
6. Signals 与 bindings
tp-signal-create 返回 global 或 buffer-scoped signal。tp-signal-read 在当前 binding computation 中登记依赖;tp-signal-peek 只读值而不登记依赖;tp-signal-set 在 transaction 中设置 candidate value。
相等写入按 signal equality 返回 no-op,不 invalidates subscribers。buffer-scoped signal 随 buffer kill 自动 dispose;global signal 使用 tp-signal-dispose 显式释放。
tp-bind 的 identity 是 owner 加 caller-namespaced key。相同 owner/key 幂等复用 binding。binding 保存 last successful value、dynamic dependencies、dirty/revision state 和 lifecycle policy。
tp-binding-read 读取 memoized binding value并登记 binding-to-binding dependency。每次 recompute 成功后,本次实际读取集合替换旧 dependencies;条件分支因此自动断开旧 source。cycle 报 tp-binding-cycle 并包含 dependency path。
删除 owner 会释放 bindings、subscriptions 和下游 edges。tp-reactive-counters 只读报告 invalidated、recomputed、skipped、subscription-added 和 subscription-removed,用于结构性性能验收,不暴露内部 hash tables。
7. Surface plan 与 object identity
tp-surface-plan-create 的公共字段是:
| Field | Contract |
|---|---|
key |
sibling-local stable key;同一 parent 下不可重复 |
kind |
opaque comparable discriminator |
text |
optional plain/propertized string leaf |
props |
final direct Emacs properties |
children |
ordered child plans |
tags |
opaque indexed metadata,TP 不解释其业务含义 |
capability |
content 或 properties |
Plan 不允许 marker、buffer position、patch op、producer closure 或 binding closure。Constructor 防御性复制 caller 提供的 key、kind、text、props、children list 与 tags,使 caller 后续 mutation 不改变 committed plan。tp-surface-result-create 只携带 plan 与 opaque client state;TP 在 prepare transaction 内从该 plan 生成唯一的 authoritative rendered output,不接受第二份 caller-provided text 或 traversal records。Result 与 producer normalization 会在各自边界建立防御性 plan snapshot。Producer 如果为当前 candidate freshly allocate 了完整 plan tree,并且之后不再 mutation 或 expose,可使用 tp-surface-plan-create-owned 与 tp-surface-result-create-owned 将 plan 所有权转给 TP,跳过重复 snapshot;owned result 必须绑定当前 active prepare context,并且只允许被 prepare normalization 消费一次;普通 caller-owned plan 必须使用复制入口。
producer 在 prepare 阶段接收 context,并在产生 plan 前调用:
(tp-object-ensure CONTEXT PARENT KEY KIND)
Object identity 只在所属 surface 中有效。显式 key 按 parent/key/kind reconcile;unkeyed object 按 position/kind reconcile。duplicate sibling key、stale parent、orphan object 或 cross-surface handle 在 prepare 中失败。
Candidate object 只有成功 publication 后才变为 live。失败 candidate handle 必须不可解析。tp-object-resolve 按 surface/key path 查询 live handle,不创建 identity。
无可见字符但需要保留的 logical object 使用 tp-object-retain。一个 logical object 可以通过 tp-object-attach-fragment 关联多个离散 plan fragments;attachment 保存在 prepare side state,不进入 plan。
当 producer 已经证明一个现有 logical subtree 在本次 candidate 中保持不变,可以使用 tp-object-retain-subtree 复用当前 live root 下已知的 object path。该 API 会把 subtree objects 标记为 touched/retained,并保留其 live bindings;它不创建缺失对象,也不替 producer 验证 candidate plan、输出范围或布局不变性。调用方仍必须提交完整 candidate plan,并在不满足证明时回退到普通 tp-object-ensure 路径。若完整 candidate 仍会重新提交这些对象的 output attachment,但已证明它们的 identity、lifecycle 和 output 不变,可使用 tp-object-reuse-subtree:对象保留在 candidate 中但不进入 touched/reconcile 集合。
Content range ownership 有两个入口:tp-object-attach-content-ranges 对每个 :tags 建立防御性快照;只有当 producer 为当前 candidate 新建了 tags、之后不会再修改或暴露它们时,才可使用 tp-object-attach-content-ranges-owned 转移这些值的所有权并跳过复制。转移入口不改变失败 candidate 的 rollback 语义;未成功 publication 的值会随 candidate 一起丢弃。普通 caller-owned tags 必须使用复制入口。
tp-surface-report 始终返回完整的防御性 report snapshot。只需要 transaction、operation、scope 或 object reconciliation 计数等提交标量的 integration caller 应使用 tp-surface-report-summary,它不暴露 nested report payload,也不为读取这些标量复制整份 report。
8. Surface lifecycle
8.1 Materialize
(tp-surface-materialize-string PLAN-OR-PRODUCER)
以 ephemeral prepare context 生成 propertized string,不建立 live surface。函数返回前释放 candidate objects、bindings、subscriptions 和 anchors。
8.2 Mount/update
(tp-surface-mount BUFFER PLAN-OR-PRODUCER OPTIONS)
(tp-surface-update SURFACE PLAN-OR-PRODUCER)
(tp-surface-update-scoped SURFACE OBJECTS PLAN-OR-PRODUCER OPTIONS)
Mount options 支持 :capability、:start、:end、:inhibit-read-only、:client-state 和 :observers。
content surface 拥有其 span 的 text 和 properties,可以插入、删除、替换或移动输出。properties surface 只能贡献声明的 properties,不能替换 host text。
tp-surface-update-scoped 仍接收完整 candidate。TP 从 object-to-mount index 得到授权范围,验证 candidate 没有改变范围外输出,再在同一 transaction 发布。默认 mismatch 报 tp-scope-mismatch;(:on-mismatch root) 显式允许 full-root fallback。默认返回 defensive report snapshot;调用方如果只通过 tp-surface-report-summary 读取标量提交指标,可传 (:return-report nil) 避免构造完整 report snapshot。
相等 candidate 不产生 publication,surface revision 和 buffer modified state 保持不变。
8.3 Unmount
tp-surface-unmount 释放 surface、objects、bindings、mounts、markers、subscriptions、indexes 和 opaque client state,并返回 generic report。content surface 删除自己拥有的 span;properties surface 只撤销仍由 TP 拥有的 contributions。
kill-buffer cleanup 以 buffer 死亡为权威结果,释放 runtime state,不尝试复活 buffer。
9. Range anchors 与 property conflicts
tp-range-anchor-create 接受 buffer、start/end、marker insertion policy 与 stale/shorten/remove boundary policy,返回 opaque handle。raw marker 和 position 不进入 surface plan。
producer 使用 tp-object-attach-range 把 object 绑定到 anchor。一个 properties surface 的重叠 mounts 通过 property policy 合成 contributions。
TP 为每个 interval 保存:
- host baseline presence/value;
- ordered TP contributions;
- last published presence/value。
如果当前 host value 与 TP last published value 不同,update 报 tp-property-conflict,不会覆盖外部值。tp-range-rebase 显式把当前 host state 接受为新 baseline。Unmount 只在当前值仍等于 last published value 时恢复 baseline,否则保留 host value并在 report 中列出 conflict。
10. tp-watch
(tp-watch BUFFER START END COMPUTE)
COMPUTE 返回 native direct declarations。tp-watch 组合 range anchor、properties surface、stable object 和 binding,返回 underlying surface handle。COMPUTE 中读取的 signals/bindings进入正常 dependency graph;更新与 unmount 使用同一 conflict和rollback合同。
11. Transactions
tp-with-transaction 的顺序是:
- 保存 candidate signal writes 并去重 dirty bindings;
- 为所有实际受影响 surfaces 建立 prepare contexts;
- 运行 binding graph 与 producers;
- 校验 object、plan、capability、range、conflict 与 lifecycle;
- 为所有 surfaces 准备 text/property operations 与 inverse journals;
- 默认从 publication batch 的 exact entry bindings 按稳定 surface id publish;
- 从 batch 绑定的同一 participant vector 按声明顺序 stage participant,再执行 declared precommit;
- commit signal journal;
- 在 candidate 绑定的 single final accept 内按顺序 apply bounded opaque markers,再 accept
change group;marker 只能使用 closed
tp-vector-slots/v1fixed-write primitive,不能注册 callback;partial apply 或 accept failure 先逆序 restore markers; - final accept 成功后固定写入 tagged success,再运行 contained committed/observer work。
嵌套 transaction 加入最外层。一个 global signal 可以原子触达多个 buffers;任一 surface 失败时,已发布 surfaces 和 source/binding state 全部回滚。
tp-transaction-participate 允许 client side state 在 surfaces 发布后、source commit 前加入同一 rollback boundary。它保留公共 v1 调用形状,但在默认 structured route 中适配成同一个 participant record 的 stage capability,只执行一次。participant key 在一个 outer transaction 中必须唯一。它不是 observer;失败会回滚 transaction。Observer failure 只记录,不回滚已提交结果。
跨包 consumer 使用公开 tp-transaction-participate-v2 直接注册 structured
stage/rollback pair;返回值仍是 key,内部 participant identity、state 与
journal 不暴露。完整 v1 execution route 也能执行该同一 v2 participant object,
因此 Ebox 可以独立 cutover,而不要求同步改变 TP live route。
publication batch、structured participant、final marker 与 tagged outcome 共享
现有 journal/change-group,不复制第二份 live state。默认 structured route 是
唯一 live writer:surface 从 candidate entries 执行,participant 从 candidate
绑定的同一 identity vector 执行,final accept 从 candidate binding 执行;任何
binding/order 漂移都会 fail-fast 并回滚。把
tp-transaction-execution-route 在 outer transaction 前设为 v1 会立即恢复
完整 legacy prepared-list、registration-list 与 final-accept route。route 在 outer
transaction 入口冻结,中途修改 option 只影响下一次 outer transaction;
structured/v1 分支始终互斥,不双写 Buffer
或 participant state。tp--transaction-artifact-mode 只控制 v1/shadow 兼容证据,
不能关闭 structured candidate。tp-with-transaction 的返回值仍是 body result;
success/failure outcome 只走内部 side channel。zero-surface 与 output-equal
operation 不创建 publication batch。
ETAF uses this API with one opaque participant for its immutable generation and Ebox client state. The participant is published only after candidate preparation, and its paired rollback is still required when TP final accept fails. This keeps ETAF generation visibility, Ebox publication, signals, and client state on one commit boundary; ETAF's own flush guard is responsible for detecting repeated input/version tuples and bounded non-convergence.
12. Diagnostics 与 reports
tp-surface-report 返回最近一次成功 publication 的防御性 report;equal/no-op update 不替换 report。字段包括 transaction/surface/revision、candidate source writes、binding counters、object reconcile counts、text/property operation counts、touched characters、scope/full-root 和 failure-related slots。
tp-surface-inspect 返回 surface id、buffer、capability、revision、object/mount count、opaque client state 与 report。tp-surface-at-point 从 side index 返回 mounted objects;它不扫描显示文本寻找 identity。tp-object-mounts 返回 defensive numeric range/tag snapshots,不暴露 live markers。
13. Error ownership
Surface publication 还会使用 tp-invalid-content-range、
tp-producer-buffer-mutation、tp-publication-mismatch 和
tp-owned-result-error;它们分别归属于 content range 校验、prepare
buffer guard、publication journal 和 owned producer result 合同。
主要错误类型:
- property/declaration:
tp-property-error、tp-invalid-property-policy、tp-invalid-declaration、tp-invalid-layer-definition、tp-unresolved-layer; - reactive:
tp-reactive-error、tp-invalid-signal-scope、tp-disposed-signal、tp-disposed-binding、tp-binding-cycle; - retained surface:
tp-surface-error、tp-invalid-surface-plan、tp-duplicate-object-key、tp-invalid-prepare-context、tp-stale-object、tp-cross-surface-object、tp-orphan-object、tp-capability-error、tp-stale-mount、tp-property-conflict、tp-dead-surface、tp-invalid-range-anchor、tp-invalid-content-range、tp-producer-buffer-mutation、tp-publication-mismatch、tp-owned-result-error、tp-scope-mismatch。
内部 computation 不吞错或返回貌似合理的 fallback。只有用户入口和 batch test runner等外层边界负责把错误转换为展示信息。
14. 1.0 删除项
TP 1.0 删除了不能诚实映射到统一 retained runtime 的 0.3 managed behavior:
tp-render.el和tp-stack.el;- stack push/pop/move/hide/show/merge/flatten workflow;
tp-text双向内容替换;$variabledeclaration syntax;- layer-to-buffer registry、buffer-list/identity scan refresh;
- managed attach/detach/diagnostics/transaction;
- 以
tp-name、tp-layers、tp-meta作为字符上权威 runtime database 的机制。
TP 不提供 hidden compatibility engine,也不根据文本是否含旧 metadata 自动切换执行语义。静态 recipe、tp-watch 和 retained surface 分别承担复用声明、已有文本响应式属性与 retained content 的清晰职责。