Notist
On this page
  1. 文档管线
  2. 一图总览
  3. 两条入口,一个后端
  4. Checker 会话:名字在这里变成实例
  5. 求值
  6. 对象速查
  7. 宿主装配:Context 从哪来
  8. Vault 内容边界与索引
  9. 三个目录范围
  10. 引用检查
  11. 文档扩展边界

文档管线

一份 .not 文档、一个 .notc 模块,如何变成最终呈现的内容树?本文跟着源码把这条管线走一遍。读完应该能回答四个问题:哪一段不依赖环境、名字在哪里变成函数、两种语言在哪里汇合、求值发生在哪。

一图总览

读法:圆角是数据,矩形是处理,嵌套表示包含关系。左列跟随一份 Markup 文档,右列跟随一个 Code 模块,两列在 Checker 会话汇合。

  • 绿色是 Code 对所选前端的两次借用,每轮往返共用一个编号:① 定界并解析字面量,② 把字面量 Lower 成 IR₁。

  • 蓝色是 Checker 会话内的往返:检查器交出字面量的 IR₁ 和当前词法绑定,收回已完成的 Item 森林。

两条入口,一个后端

Markup 经 Parse 生成前端语法树,Lower 成未解析的 Expr 森林(IR₁)与注解列表。共享后端依次完成 Resolve(名称与契约检查)、Shape(节与内容槽整理)、Materialize(挂载注解并生成 Item)。文档入口添加 Doc 根。阶段契约见 Markup 管线。

Code 将 [ ... ] 的正文交给所选 Markup 前端:ContentParser 解析字面量边界与语法,ContentLowering 将其转为 IR₁,嵌入函数体:

函数体 Expr(Code IR,还不认识任何名字)
└─ Content
   ├─ forest: Vec<Expr>   ← 完整的 Markup IR₁
   ├─ annotations: Vec<Annotation>
   └─ body / span

两种语言的 Parse / Lower 都处理语法结构,名称绑定与类型检查在装配环境后进行。

Checker 会话:名字在这里变成实例

分析从一份装配好的 Context 开始(签名注册表、未检查函数体、模块作用域、transform 计划,只读), Checker 会话在其上按需工作。两个方向都会用到它:

  • 文档 Resolve 遇到 Code 调用(如 #diagrams.diagram(...))时,向会话请求具体实例,并按其签名检查调用。

  • 会话根据编译期实参、参数类型与局部绑定,将函数体 Expr 检查为 TypedExpr。遇到 Expr::Content 时,将 IR₁ 与词法绑定交给共享后端,返回的 Item 森林嵌入已检查表达式:

已检查的函数体:TypedExpr
├─ ty: Option<Type>
└─ kind: ExprKind::Content(Vec<Item>)

Item 既是共享内容后端的输出,也可嵌入已检查的 Code 表达式。实例结果保存在 CheckedProgram;泛型分支按编译期条件选择,只检查选中分支。

求值

Analysis 保存求值前 Item 树与实例缓存,Evaluate 在其上工作:按 Item.pending 执行内容实现,replace 扩展在此挑选同签名目标,结果森林替换调用边界内的 children。它保留原语义树、不重新 Shape;失败在内容边界恢复,其余内容继续。契约见 Code 类型与函数系统 与 函数映射。CLI 的 core / json / query 默认看求值前,--eval 看求值后。

对象速查

对象

职责

生命周期

CodeModule

单个 .notc 的 Parse / Lower 结果:签名、函数体、静态 imports

Context 装配前产生

函数体 Expr

未检查表达式树,字面量位置为 Expr::Content

与实例无关,可复用

Markup IR₁

未解析 Expr 森林与注解 side list

Lower 后固定,各实例共享

Environment

宿主装配结果:模块源码、配置位置、Context

宿主持有,不可变

Context

Registry、CodeProgram、模块作用域、ExtensionPlan

装配完成后只读

Checker

当前模块、编译期实参、局部绑定、诊断、CheckedProgram

每次会话创建

TypedExpr

实例的已检查表达式树,内容分支是 Item 森林

存于 InstanceRecord.body

CheckedProgram

ResolvedInstance → 签名、TypedExpr、诊断、状态;伴随源码与定义名称快照

随 Analysis 保留;独立检查只导出报告

Context 保存模块作用域;Checker.scope 保存局部绑定与当前实例的编译期实参。

宿主入口只有几个:analyze / inspect 跑完整文档(inspect 额外保留解析树与中间 IR),compile_module 只出 CodeModule,analyze_content_literal 一次编译单个字面量,evaluate 求值;Vault.analyze_code 是宿主侧的 Code 文件检查入口。

宿主装配:Context 从哪来

装配以当前文件为入口。Markup 文档先经前端拿到 import 列表,宿主沿相对路径递归加载 .notc(读取、Parse / Lower、安装、绑定作用域),得到这份文档的 Environment。配置里每条输出规则带自己的 imports 表,独立加载、独立校验签名,不碰文档和其他规则的绑定;规则语义见 本地模块。纯 Pipeline 不碰文件:读取与缓存都在宿主,内存输入与 Worker 输入走同一加载路径。

Vault 按规范路径与文档源码快照缓存不可变的 Environment:同一文件无论经相对、绝对还是含 . / .. 的写法都共享身份;输入变化时创建新的资源视图与 Vault,缓存不做变更追踪。

LSP 与 CLI 的 Code 检查共用 Vault.analyze_code:用编辑中的源码覆盖资源视图,Environment.load_from 只加载该模块自身的 import 图,再以 check_module 检查——以编译期参数均有默认值的函数为种子,其余实例在调用处按需触发。CodeAnalysis 只导出声明、诊断与来源源码,不读输出配置、不创建 Doc 根、不求值。

宿主负责资源取得、环境装配与文档间关系,以下边界由 Vault 统一维护。

Vault 内容边界与索引

三个目录范围

范围

确定方式

作用

Vault 内容根

Resources.root

确定可纳入正文与本地引用的内容边界

文档输出配置

最近的 Notist.toml,或显式 --config

选择输出规则及其局部 imports;文档名称绑定由源码 import 决定

索引扫描目录

Vault.index 的扫描输入

确定首先扫描哪些文档;随后补载实际链接到的 Vault 内正文

扫描子目录不缩小 Vault 内容边界。Code 模块和组件实现资源可以位于 Vault 外;Resources 的根表示内容边界,资源访问范围由宿主实现决定。导入模块不会自动把其文档加入正文索引。

引用检查

本地 link 与 embed 按文档目录解析并规范化。最终目标越过 Vault 根时,在引用节点产生 Semantic 诊断,并停止补载该目标;../ 在最终目标仍位于 Vault 内时合法。带 scheme 或 // 的外部 URL 保持外部链接,不作为 Vault 正文读取或发布。

边界诊断附加到 Vault.analyze / inspect 的 Analysis,供查询与语言服务等宿主共用。纯 Pipeline 接收源码与 Context;内容边界检查由持有 Vault 根的宿主执行。VaultIndex.from_documents 对显式传入的根执行相同检查,已有诊断不重复添加。

Vault.index 从各文档处理结果派生 VaultIndex,保存索引、诊断、链接与 backlinks。它扫描指定目录,再补载 Vault 内实际链接到的正文;检查结果和 backlinks 的路径均相对于 Vault 根。

文档扩展边界

静态 import 装配后,beforecheck 接收可变 IR₁、节点注解与文档属性;修改结果完整进入 Resolve、Shape 与 Materialize。aftercheck 校验只读语义 Item,after_evaluate 编辑具体输出 Item。ExtensionPlan 调度有序 hooks 与同签名执行映射;内容字面量与 Code 模块不触发文档 hooks。契约与失败处理见 文档扩展。