tp/docs/API-SEMANTICS.md
2026-08-22 06:18:51 +08:00

16 KiB
Raw Blame History

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 严格区分:

  • absentproperty plist 中没有该 key
  • present nilproperty 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 idtp-text-declarations 把普通 property plist 转换为 canonical declarations。

tp-merge-declarations 按输入顺序合并 direct declaration groups并防御性复制 caller-owned value。它只做 TP contribution composition不实现 CSS winner selection。

tp-define-styletp-style-declarationstp-undefine-style 管理 named direct declarations。registry getter 返回防御性副本。

4. Declaration recipes

define-tp 定义一个返回 native property plist 的 recipedefine-tps 定义一组有序 recipe elements。tp-define-layerdefine-tp-grouptp-define-group 是同一静态 declaration workflow 的命名入口。

Recipe 可以是静态或参数化的,可以组合其他 recipes。展开结果经过同一 direct property policy/projector。Recipe application 不建立 live identity不写 tp-nametp-layerstp-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-settp-resettp-addtp-removetp-cleartp-gettp-attp-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 自动 disposeglobal 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 metadataTP 不解释其业务含义
capability contentproperties

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 stateTP 在 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-ownedtp-surface-result-create-owned 将 plan 所有权转给 TP跳过重复 snapshotowned 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 reconcileunkeyed 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 fragmentsattachment 保存在 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 不产生 publicationsurface revision 和 buffer modified state 保持不变。

8.3 Unmount

tp-surface-unmount 释放 surface、objects、bindings、mounts、markers、subscriptions、indexes 和 opaque client state并返回 generic report。content surface 删除自己拥有的 spanproperties 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 的顺序是:

  1. 保存 candidate signal writes 并去重 dirty bindings
  2. 为所有实际受影响 surfaces 建立 prepare contexts
  3. 运行 binding graph 与 producers
  4. 校验 object、plan、capability、range、conflict 与 lifecycle
  5. 为所有 surfaces 准备 text/property operations 与 inverse journals
  6. 按稳定 surface id publish
  7. 原子切换 signals、bindings、plans、mount/index、client state 和 revisions
  8. 全部成功后运行 observers。

嵌套 transaction 加入最外层。一个 global signal 可以原子触达多个 buffers任一 surface 失败时,已发布 surfaces 和 source/binding state 全部回滚。

tp-transaction-participate 允许 client side state 在 surfaces 发布后、source commit 前加入同一 rollback boundary。participant key 在一个 outer transaction 中必须唯一。它不是 observer失败会回滚 transaction。Observer failure 只记录,不回滚已提交结果。

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 的防御性 reportequal/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-rangetp-producer-buffer-mutationtp-publication-mismatchtp-owned-result-error;它们分别归属于 content range 校验、prepare buffer guard、publication journal 和 owned producer result 合同。

主要错误类型:

  • property/declarationtp-property-errortp-invalid-property-policytp-invalid-declarationtp-invalid-layer-definitiontp-unresolved-layer
  • reactivetp-reactive-errortp-invalid-signal-scopetp-disposed-signaltp-disposed-bindingtp-binding-cycle
  • retained surfacetp-surface-errortp-invalid-surface-plantp-duplicate-object-keytp-invalid-prepare-contexttp-stale-objecttp-cross-surface-objecttp-orphan-objecttp-capability-errortp-stale-mounttp-property-conflicttp-dead-surfacetp-invalid-range-anchortp-invalid-content-rangetp-producer-buffer-mutationtp-publication-mismatchtp-owned-result-errortp-scope-mismatch

内部 computation 不吞错或返回貌似合理的 fallback。只有用户入口和 batch test runner等外层边界负责把错误转换为展示信息。

14. 1.0 删除项

TP 1.0 删除了不能诚实映射到统一 retained runtime 的 0.3 managed behavior

  • tp-render.eltp-stack.el
  • stack push/pop/move/hide/show/merge/flatten workflow
  • tp-text 双向内容替换;
  • $variable declaration syntax
  • layer-to-buffer registry、buffer-list/identity scan refresh
  • managed attach/detach/diagnostics/transaction
  • tp-nametp-layerstp-meta 作为字符上权威 runtime database 的机制。

TP 不提供 hidden compatibility engine也不根据文本是否含旧 metadata 自动切换执行语义。静态 recipe、tp-watch 和 retained surface 分别承担复用声明、已有文本响应式属性与 retained content 的清晰职责。