资讯详情

Carp 动态语义指南:作用域、求值规则与 VM 化动态求值器

📅 2026/10/10 5:33:47 | 华诺云谱 👁 阅读
Carp 动态语义指南:作用域、求值规则与 VM 化动态求值器
编程语言编译器【免费下载链接】CarpA statically typed lisp, without a GC, for real-time applications.项目地址https://gitcode.com/gh_mirrors/ca/Carp点击查看免费下载导读本文以 Carp 仓库中 docs/DynamicSemantics.md 为核心骨架系统讲解 Carp 的“动态语言”部分即解释执行、带垃圾回收、用于宏展开与编译期计算的那一层。文章覆盖动态/静态语义的术语体系、词法作用域与命名空间规则、全局与局部变量的可变性、各类定义形式defndynamic/defdynamic/defmacro等、求值规则、宏与准引用quasiquotation、REPL 的上下文切换以及当前实现迁移到 VM 架构后的源码级现状。读完本文你将掌握 Carp 动态层与静态层的边界、符号查找与set!赋值规则、闭包语义并能理解src/Eval.hs、src/EvalVM.hs等核心模块的求值管线。文档定位历史目标与当前实现docs/DynamicSemantics.md最初描述的是 Carp 动态求值器“重写”的历史目标修复动态求值中发现的各类 bug、补齐一个动态 Lisp 应有的缺失特性并让求值器在未来易于扩展。该目标已实现——Carp 的动态求值器如今是VM 后端VM-backed架构。对于当前实现的架构总览请阅读 docs/EvaluatorVM.md本文在讲解语义规则的同时也会同步标注当前实现的源码位置使历史语义目标与实际代码一一对应。0. 本文使用的基本术语docs/DynamicSemantics.md定义了四个贯穿全文的基本术语术语含义form任何以文本形式表示的合法 Carp 数据结构top level未被嵌入其他 form 中的任何 formStatic CarpCarp 语言被编译生成 C 代码的那一部分Dynamic CarpCarp 被解释执行、函数式、带 GC 的那一部分理解“两层”的划分是读懂 Carp 的关键静态层最终被编译为 C 并在运行时执行动态层则运行在编译器中宏展开、REPL、构建系统配置均发生在此层。动态层与静态层共享模块但动态查找只会找到动态函数静态查找只会找到静态函数。1. 作用域规则Scoping Rules1.1 符号如何取值词法作用域Carp 采用词法作用域lexical scoping查找符号 X 的值时先查当前作用域再逐层向外直到全局作用域。能够创建作用域的结构包括函数定义defn、defndynamic、fnlet绑定模块defmodule这意味着符号的可见性完全由源码的词法结构决定不存在任何动态作用域。1.2 如何给符号赋值set!赋值使用统一的set!形式(set! symbol value)在源码层面set!由specialCommandSet实现见 src/Eval.hs它会先在内部环境局部中查找符号再回退到全局环境并根据绑定是动态还是静态类型决定是否做类型检查动态或无类型的绑定走handleUnTyped分支不检查类型静态绑定则会对新值做类型核对。1.3 保留名Reserved NamesCarp 存在一组保留名详见 Parsing 模块defn、def、do、while、fn、let、break、if、match、true、false、address、set!、the、ref、deref、with此外defmacro中的:resttoken 也是保留的。文档同时指出“更多东西应该被移入保留列表”——即这份清单本身是演进中的。1.4 关键字Keywords动态语义的原始设计中**没有关键字keyword**这一概念文档提及一种可能的宏实现方案类似带冒号前缀的 token但这属于历史探讨当前仓库的宏系统并未引入关键字。1.5 动态与静态是否使用不同的命名空间动态 Carp 与静态 Carp共享同一套模块结构区别只在于查找范围动态查找只命中动态函数defndynamic、宏展开期的命令等静态查找只命中静态函数defn等。1.6 全局变量关于全局变量docs/DynamicSemantics.md明确回答全局变量可变可以。如何修改、何时生效用set!修改内部基于 IORef立即生效。作用域类型词法作用域任何东西都没有动态作用域。在测试中可以看到这一语义的直接验证test/dynamic-closures.carp 中先(defdynamic x 400)闭包closure-one捕获x随后顶层(set! x 1000)断言闭包读取到的是1000“closures over global variables get the global variables latest state”印证了全局变量在闭包中读到的是最新状态。1.7 局部变量与闭包局部变量可变可以。进入/离开作用域时机遵循词法作用域规则函数与let创建新变量。闭包的重要规则被捕获的变量不可变动态 lambda 在(fn ...)形式被求值的时刻捕获整个环境。test/dynamic-closures.carp 对闭包语义给出了系统化验证闭包可以捕获全局变量、函数参数、let绑定动态函数可以在其引用的全局变量尚未定义时先创建闭包closure-four捕获后面才定义的y最终读到500说明闭包保存的是环境而非立即求值嵌套闭包优先使用闭合的内部环境closure-five中let的x5优先于全局x闭包可以通过set!更新全局环境closure-six连续三次(set! z (inc z))后读到3。这些测试位于 test/dynamic-closures.carp是理解动态闭包语义最直接的样例。1.8 命名空间规则两个模块中出现同名符号aFoo与Bar用点号限定引用Foo.a、Bar.a用(use module name)可免去限定。多个模块被导入且含同名符号时查找该符号会得到运行时错误——因为无法解析为单一值。若Foo.a与Bar.a都存在裸符号a除非有且仅有一个模块被use否则不指向任何一个两个都被use时查找即报错。函数与变量位于同一命名空间类型位于不同的命名空间。注意这与静态类型的多态推断有所不同在静态上下文中多个use的模块出现同名符号时类型推断器会尝试依据类型消歧见 docs/LanguageGuide.md 的“Modules and Name Lookup”一节而动态语义文档描述的规则是动态查找的基线行为。1.9 定义形式Definitionsdocs/DynamicSemantics.md将定义形式按上下文分为三类动态上下文defndynamic定义动态函数编译期/REPL 可调用defdynamic定义动态全局变量defmacro定义宏静态上下文defn定义静态函数def定义静态全局变量deftype定义积类型struct与和类型sumtyperegister注册外部 C 函数/变量所有上下文defmodule组织模块在源码中这些形式分别由 src/Eval.hs 的primitiveDefdynamic、primitiveDefndynamic、primitiveDefmacro等 primitive 实现例如defndynamic与defmacro都通过dynamicOrMacro构造形如(dynamic|macro name params body)的绑定。2. 求值规则Evaluation Rulesdocs/DynamicSemantics.md以问题清单的形式提出了求值规则的核心问题宏何时求值、符号何时求值、form 何时求值、求值顺序是从左到右还是从右到左、错误报告如何工作。其中部分问题在当时尚无定论对应章节留空但当前的 VM 实现已经给出了明确答案可对照 docs/EvaluatorVM.md 阅读。2.1 三类可求值实体当前实现将动态求值器面对的构造分为三类详见 docs/Macros.md 的“Inner Workings”特殊形式special forms在 AST 中有独立表示、由求值器直接处理的构造如fn、the不能被当作值传递原语primitives不求值其参数的普通 Carp 形式类似用 Haskell 实现的内置宏如defmacro、defn、quote主要定义在 src/Primitives.hs命令commands求值其参数、行为类似内置函数的普通 Carp 形式如Project.config、car、cons定义在 src/Commands.hs。2.2 求值管线的当前实现docs/DynamicSemantics.md提出的“易于扩展”“修复动态求值 bug”的目标最终落地为一条清晰的 VM 管线docs/EvaluatorVM.mdXObj降级为EvalIRlowerExpr见 src/EvalIR.hsEvalIR编译为字节码EvalCodecompileEvalIR见 src/EvalVM.hs执行字节码runEvalCode。可调用对象要么被预编译VMPrecompiled要么首次调用时编译并缓存VMCompileOnCall。公开入口集中在 src/Eval.hsevalDynamic :: Context - XObj - IO (Context, Either EvalError XObj) evalStatic :: Context - XObj - IO (Context, Either EvalError XObj) eval :: Context - XObj - LookupPreference - IO (Context, Either EvalError XObj)其中evalDynamic以PreferDynamic模式执行evalStatic以PreferGlobal模式执行eval则接受显式的LookupPreference定义见 src/EvalTypes.hsdata LookupPreference PreferDynamic | PreferGlobal | PreferLocal (Set.Set String) (Map.Map Int XObj) (Map.Map String Int) EvalExecMode模式决定符号查找与可调用对象的编译策略函数模式PreferLocal ... ExecFunction要求局部槽位存在并快速失败动态与宏模式ExecDynamic/ExecMacro保持对动态友好的查找行为。字节码指令集包括栈/值操作IPushConst、IMakeArray、IMakeStaticArray、IDrop、控制流IJumpIfFalseRel、IJumpRel、IHalt、ITrap、符号/调用分发IResolveSymbol、IExecCallSymbol、IExecCall以及特殊形式执行指令IExecLet、IExecFn、IExecWhile、IExecWith、IExecSet。2.3 求值顺序Carp 的调用参数遵循从左到右的求值顺序Lisp 惯例宏则不求值其参数宏参数以原始 form 传入宏体。在 VM 中IExecCallSymbol/IExecCall同时携带原始参数 IR 与预编译的参数代码使非宏参数求值在热路径上避免递归重新进入 IR从而兼顾顺序语义与性能。2.4 符号解析模型编译阶段为每个符号分配一个ResolverHandleRHLocalSlot局部槽位、RHGlobal全局、RHDynamic动态、RHQualified限定、RHUnqualified未限定。opcode 循环先按 handle 形状解析仅在语义需要时才回退到更宽的查找。符号解析缓存以(contextBindingEpoch, symbolId)为键环境状态变化时通过contextBindingEpoch使缓存失效。2.5 错误报告EvalError在动态求值器遇到错误时产生由错误消息与元信息traceback 与源码位置组成Context中维护了求值帧历史用于生成 traceback。reportExecutionErrorsrc/Eval.hs会根据contextExecModeCheck/Repl/Build/BuildAndRun/Install决定是打印错误还是抛出异常终止。3. 宏系统从“缺失特性”到现行实现docs/DynamicSemantics.md的“Desired features (currently missing)”清单包含动态语言与宏系统的使用文档、完整的宏设施准引用、拼接、完整错误报告等、动态堆栈跟踪、用户定义名的自动补全。其中的宏设施与文档需求已经实现详见 docs/Macros.md 与 docs/Quasiquotation.md。3.1 defmacro 与动态函数宏用defmacro定义参数在调用时不求值而是以原始 form 传入(defmacro apply [f args] (cons f args)) (apply (1 2)) ; ( 1 2) (apply Array.replicate (5 hello)) ; (Array.replicate 5 hello)对应的动态函数版本用defndynamic参数会被求值调用时需要手动 quote(defndynamic apply [f args] (cons f args)) (apply (1 2)) ; ( 1 2) (apply Array.replicate (5 hello)) ; (Array.replicate 5 hello)宏还支持可变参数rest arguments以:rest声明“catch-all”尾参(defmacro apply-or-sym [head :rest tail] (if ( (length tail) 0) head (cons head tail))) (apply-or-sym *global*) ; *global* (apply-or-sym 1 2) ; ( 1 2)3.2 准引用Quasiquotationdocs/Quasiquotation.md说明准引用只在动态部分可用它把列表的大部分保持为未求值同时允许用unquote插入求值结果(defdynamic x 2) (quasiquote ( (unquote x) 1)) ; ( 2 1) ; 字面量简写quasiquote 为 unquote 为 % ( %x 1) ; ( 2 1)拼接splicing用unquote-splicing简写%将一个列表扁平展开(defdynamic x (1 2)) (quasiquote ( (unquote-splicing x))) ; ( 1 2) ( %x) ; ( 1 2) ; unquote 变体内部可以使用任意表达式 ( %(map inc [1 2])) ; ( 2 3)准引用可以嵌套每个quasiquote使嵌套层级 1每个unquote/unquote-splicing使层级 −1只有降到 0 层的表达式被求值(x %%(* 2 2)) ; (x %4) (x %(* 2 2)) ; (x %(* 2 2))在unquote之外使用unquote会在宏展开期报错。注意docs/Macros.md的历史说明曾称“未实现 quasiquoting”该功能随后在 core/Macros.carp 及求值器中落地(info Dynamic)可以查看动态模块中的全部可用函数。4. REPL动态与静态上下文的切换docs/DynamicSemantics.md提出的问题——REPL 如何知道在动态还是静态上下文求值、何时决定运行给定代码——由 src/Eval.hs 的executeStringAtLine/executeCommand回答REPL 逐条解析输入并执行命令普通表达式默认走evalDynamic动态求值因此defndynamic/defdynamic/defmacro与car、cons等动态操作在 REPL 中直接可用对于顶层的defn/def静态绑定executeStringAtLine会先识别静态绑定集合staticBindingName若存在则走processFormsWithForwardRefs分支以支持文件内跨定义顺序的正向引用与相互递归参见 docs/LanguageGuide.md求值main定义或静态函数调用时executeCommand会构造“build run”序列withBuildAndRun触发编译与运行HasStaticCall时走callFromRepl若求值结果非UnitTyREPL 以 value的形式打印prettyDynamic还会识别动态 map 的桶列表并按{k v ...}格式化。动态/静态两层因此不是两套独立 REPL而是同一 REPL 依据 form 形态自动分发的两条执行路径。5. 类型Typesdocs/DynamicSemantics.md第 3 节围绕类型提出问题可用类型有哪些、form 何时被类型检查、类型是否是一等公民。回答可归纳为静态类型由类型推断得出可用the显式标注(the Int x)详见 docs/LanguageGuide.md可用的类型字面量包括Int、Long、Float、Double、Byte、Bool、String、Pattern、Char、(Array t)、(Map k v)、(Fn [args...] ret)等动态层独有的数据字面量是列表(1 2 3)与符号foo——它们只存在于非编译代码中静态层没有类型检查的时机动态求值时set!对静态绑定会调用typeCheckValueAgainstBinder做类型核对对defn/def等静态绑定则经过annotateWithinContext的完整标注流程含宏展开、限定与推断。也就是说动态层本身不做类型推断但负责把需要静态处理的 form 交给标注/推断管线类型与值分属不同命名空间函数与变量同在一个值命名空间类型在独立的类型环境contextTypeEnv中因此类型名称与值名称可以共存。docs/DynamicSemantics.md以“一等公民”提问从当前实现看类型作为编译期实体由deftype/类型环境管理而非动态值。6. 维护者视角如何安全地改动求值器docs/EvaluatorVM.md给出了与动态语义改动配套的工程准则可作为实践补充先判定改动性质是纯实现细节还是有意变更语言语义保持外部形态若是实现性改动保留外部 form 形态与错误表面动态/宏定义保持(dynamic|macro name params body)外层形态同步测试扩展 test/TestEvalIR.hs、test/TestEvalSlotLowering.hs、test/TestEvalVM.hs、test/TestEvalVMCore.hs回归宏与闭包场景运行 test/macros.carp 与 test/dynamic-closures.carp基准对比运行 bench/run-evaluator-bench.sh比较多次运行的中位数而非单次结果。此外docs/EvaluatorVM.md 明确指出求值器 VM 是编译器的内部基础设施不是用户运行时 VM它不改变 Carp 已编译代码的执行模型——这与“Dynamic Carp 是编译期解释层”的历史定位完全一致。小结Carp 的动态语义可以浓缩为几条核心规则词法作用域、无动态作用域全局与局部变量均可变set!立即生效闭包捕获创建时的整个环境且被捕获变量不可变函数与变量同一命名空间、类型独立命名空间动态查找与静态查找共享模块但互不越界defndynamic/defdynamic/defmacro构成动态上下文defn/def/deftype/register构成静态上下文。这些规则在 docs/DynamicSemantics.md 中以问答形式确立并通过 VM 求值器src/EvalVM.hs、src/EvalIR.hs、src/Eval.hs落地为可运行的实现辅以 test/dynamic-closures.carp 等测试对闭包、全局变量最新状态等语义做了精确验证。对编译期编程感兴趣的用户可继续阅读 docs/Macros.md、docs/Quasiquotation.md 与 docs/EvaluatorVM.md。赞分享编程语言编译器【免费下载链接】CarpA statically typed lisp, without a GC, for real-time applications.项目地址https://gitcode.com/gh_mirrors/ca/Carp点击查看免费下载相关推荐Agent Governance Toolkit 策略目录实战指南从示例 YAML 到生产级 Agent 治理Agent Governance Toolkit 策略目录实战指南从示例 YAML 到生产级 Agent 治理 导读本文以仓库 examples/polic编程语言编译器Carp 准引用Quasiquotation完全指南在宏与动态代码中混合求值与引用Carp 准引用Quasiquotation完全指南在宏与动态代码中混合求值与引用 准引用quasiquotation是 Carp 宏编程的核心机制编程语言编译器DuckDB 内嵌 cpp-httplib 依赖的维护与定制指南transform 脚本 语义补丁全解析DuckDB 内嵌 cpp httplib 依赖的维护与定制指南transform 脚本 语义补丁全解析 本指南以 DuckDB 仓库中 third_pa编程语言编译器上一篇LaserGRBL固件升级教程保持你的雕刻机始终最新下一篇Texture 之 ASCellNode 完全指南三种实现方式、占位符控制与 UITableViewCell 属性桥接创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑