设计与实现解读:`@` 前缀语法与 `@dirname`/`@filename`/`@app`/`@target` 实战)
编程语言编译器云原生语言运行时【免费下载链接】wingA programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡项目地址https://gitcode.com/gh_mirrors/wi/wing点击查看免费下载本文以 Wing 官方 RFC《Intrinsic Functions》docs/contributing/999-rfcs/2024-05-07-intrinsics.md为骨架结合当前仓库中 wingc 编译器、tree-sitter-wing 语法定义、语言参考文档与测试用例系统讲解 Wing 如何通过前缀内建函数把编译器才有的超能力显式化包括设计动机、语法取舍、内建函数全集、从语法解析到 JS 代码生成的完整编译管线以及迁移与未来扩展方向。读完本文你将理解dirname在构建时如何解析为相对路径、target为何能拿到目标平台字符串并能在自己的 Wing 代码中正确使用这些内建函数。一、背景内置全局变量的隐式魔法与三大问题在 Wing 语言中编译器在进入每个编译单元时都会注入一批内置全局符号built-in globals它们各自带有宏macro级别的特殊功能。RFC 指出这些全局符号可以有意地表现出用 Wing 自身无法实现的行为有的只是简单宏负责向生成的代码中发射一段代码有的则会与编译器发生更深层的交互例如lift显式提升 preflight 对象这类机制即使是简单宏也能访问用户自定义代码通常拿不到的信息——类型信息、源码位置source position等。这些能力极其有用但也带来三个现实问题行为惊喜对用户来说这些全局符号看起来和任何普通符号/变量没有区别但其行为却不是普通变量——例如lift会改变对象的传递语义。可被遮蔽shadowing用户可以在局部作用域里声明同名变量把内置全局遮住从而意外破坏其功能。可被赋值用户可以把这些全局符号赋值给变量、传入函数使其脱离编译器内建的语义约束。RFC 的结论是这些特殊行为应当用一种显式的语法形态表达出来让这是编译器内建物这件事在代码里一目了然。二、RFC 方案x与x()编译器内建函数2.1 语法形态RFC 提议引入x与x()两种形态的编译器内建函数compiler intrinsic并附带两条设计约束内建名与符号解耦存在一个名为x的内建函数不意味着存在一个名为x的符号。内建函数是独立于符号体系的一类概念天然免疫变量遮蔽问题——你无法用一个普通变量去盖住x。参数有静态限制虽然内建函数的参数类型与返回类型都必须是可表示的 Wing 类型但允许传给参数的那些表达式种类可能受到限制。例如某些内建函数可能只接受非插值字符串non-interpolated string以确保所有信息在编译期静态可用而不是在运行时才求值。这一设计在今天仓库中已经落地为可查证的语法在 tree-sitter 语法规则 中intrinsic的规则为intrinsic: ($) prec.right( seq( field(name, $.intrinsic_identifier), optional(field(args, $.argument_list)) ) ), intrinsic_identifier: ($) /[A-Za-z_$0-9]*/,即后跟字母/下划线/$/数字构成内建名x无参数与x(...)带参数列表两种形态都合法。同一个文件中还定义了type_intrinsictype(...)用来接收一个类型注解作为参数这是参数必须静态可解析约束的直接体现。2.2 为什么偏偏选RFC 给出了选择的完整理由链候选方案被否定的原因x!(...)仿 Rust!在 Wing 中已经被用作后缀解包optional unwrap运算符让identifier!同时表示两种含义对用户和编译器都是歧义无前缀普通标识符与现有全局变量无异遮蔽问题无法解决也看不出特殊前缀最终选择不是合法标识符字符天然无歧义输入即可触发 IDE 补全成本最低Zig 语言对内置函数Builtin Functions使用同类写法有先例在 JavaScript 中用于装饰器气质相似未来 Wing 若引入装饰器也能保持一致的编译器特殊标记风格最后一点尤其值得注意RFC 认为未来用表达某种装饰器语义并非不合理前缀将成为一个统一的编译器视角下的特殊构造标记。2.3 解决的实际问题将现有全局变量转换为内建函数后log、assert这类广泛使用的全局函数建议软弃用soft-deprecate旧语法继续可用但编译器在使用时给出警告引导用户切换到log(...)、assert(...)lift等与编译器深度交互的构造获得显式形态遮蔽/赋值导致的功能静默损坏从机制上被杜绝新增内建函数有了标准化的落点RFC 点名了两个待实现项file对应 issue #5449与inflight对应 issue #6045其中file最终以filename的形态落地见下文。三、当前 Wing 的内建函数全集RFC 是 2024-05-07 提交的设计提案其设想在当前仓库中已有相当一部分落地。语言参考文档 docs/api/05-language-reference.md 在 Intrinsic Functions 一节给出了官方全集表格名称说明log()输出日志接收 strassert()检查条件求值为 false 时抛出异常filename当前源文件的绝对路径dirname当前源文件所在目录的绝对路径target标识当前目标平台的字符串app构造树construct tree的根节点unsafeCast()将值强制转换为另一种类型nodeof()获取 preflight 对象的树节点lift()显式限定 preflight 对象的提升lift语言参考文档还给出了两个带参内建的典型用法log(Hello {name}); assert(x 0); assert(x 0, x should be positive); // 第二个参数为自定义错误信息其中filename、dirname、app、target这四个无参内建已完整落地到编译器 AST 中。在 wingc 的 AST 定义 里IntrinsicKind枚举明确列出pub enum IntrinsicKind { Unknown, Dirname, Filename, App, Target, }from_str负责把dirname、filename、app、target字符串映射到对应枚举变体未知的xxx一律归入Unknown由解析器报出 Unknown intrinsic 错误。而is_valid_phase则规定dirname、filename、app只能在preflight阶段使用target在 preflight/inflight 均可使用——这正是语言参考文档所说返回类型可能在 inflight 与 preflight 之间变化的底层实现。四、从语法到代码生成内建函数的完整编译管线4.1 语法解析Parser在 wingc 解析器 中表达式分派逻辑显式区分了两类节点intrinsic self.build_intrinsic_expression(expression_node, phase), type_intrinsic self.build_type_intrinsic_expression(expression_node, phase),build_intrinsic_expressionparser.rs从语法树中取出name字段通过IntrinsicKind::from_str解析内建名并可选解析参数列表若解析结果为Unknown立即抛出Unknown intrinsic: xxx编译错误。这样任何未注册的表达式在语法树构建阶段就会被拒绝用户无法用造出一个看起来像内建的普通变量。4.2 类型检查Type Check内建函数的类型信息以符号环境SymbolEnv的形式注册在类型系统中。在 type_check.rs 的内建注册段 中四个内建被逐一定义为StaticMember类型的变量并附带文档说明与阶段约束dirname返回strpreflight 专用文档注明解析出的路径仅代表 preflight 期间的值不保证 inflight 时依然有效filename返回strpreflight 专用语义同上app返回std.IApp根应用节点preflight 专用文档强调它是构造树的根所有资源都是它的子/孙节点target返回str阶段为Independent前后置通用文档注明该值由 CLI 在编译期设置可用于按目标平台条件编译。类型检查器在 type_check_intrinsic 中执行三条规则阶段校验调用is_valid_phase若当前编译阶段不允许该内建报错dirname cannot be used in Inflight之类信息参数数量校验若内建被声明为函数签名缺参数时报dirname requires arguments反之若是纯值内建却带上了参数列表则报dirname does not expect arguments签名匹配参数按函数签名逐一对齐检查。同时还有独立的type_check_type_intrinsictype_check.rs负责type(...)它会解析其中的类型注解并返回std.Reflect类型阶段固定为 preflight——这与反射/类型内省必须在编译期完成的语义一致。4.3 JS 代码生成Jsify内建函数最终如何变成运行时行为答案在 wingc 的 jsify.rs 的ExprKind::Intrinsic分支dirname/filename代码生成器会先拿到当前编译单元对应的源文件路径计算出从输出目录到源目录/源文件的相对路径make_relative_path然后发射$helpers.resolve(__dirname, 相对路径)。代码注释说明了这一设计动机把相对路径写进生成的 JS 文件比写绝对路径在不同用户的机器之间更稳定运行时再由$helpers.resolve统一规范化 Windows/Unix 路径并转换为绝对路径。这就是为什么dirname是preflight 专用——它依赖编译期源文件位置信息而 inflight 环境中没有source_file上下文。app发射$helpers.nodeof(this).app通过构造树节点的app字段拿到根应用节点。target发射process.env.WING_TARGET即目标平台字符串由 CLI 在编译/运行时通过环境变量注入这也印证了注册文档中由 CLI 在编译期设置的描述。4.4 编辑器体验LSP 补全RFC 提到特殊字符前缀对 IDE 补全很有用输入就能看到所有可用选项——这一设想在 LSP 中真实落地。completions.rs 中的get_intrinsic_list直接遍历注册在types.intrinsics符号环境中的所有内建函数并生成补全条目当光标所在位置的最近非引用节点是intrinsic_identifier时就会返回这份内建列表completions.rs。也就是说在 VSCode Wing 插件中敲下一个所有可用内建会立即弹出附带各自的文档说明。五、测试用例行为如何被验证仓库中用一对测试文件完整覆盖了内建函数的正反两面5.1 有效用例 tests/valid/intrinsics.test.w该测试组合了fs、expect、cloud、util库与一个本地bar模块验证语义一致性let currentFile fs.join(dirname, filename); expect.equal(filename, fs.basename(currentFile)); expect.equal(dirname, fs.dirname(currentFile)); expect.equal(bar.Bar.getSubdir(), fs.join(dirname, subdir)); expect.equal(filename, currentFile); expect.equal(bar.Bar.getSubfile(), fs.join(dirname, subdir, bar.w)); expect.ok([sim, tf-aws, tf-azure, tf-gcp, awscdk].contains(target));dirname与fs.dirname求出的目录一致且能定位到被bring的本地模块子目录filename与fs.join(dirname, 文件名)结果一致target的值必定落在仓库 CI 所使用的目标平台集合中——测试注释也说明任何字符串都合法这里只是校验其存在性。仓库文档中还有大量实际用法可参考例如读取文件示例 docs/by-example/30-reading-writing-files.md 中的let filename: str fs.join(dirname, test.txt)以及执行子进程示例 docs/by-example/35-exec-processes.md 中直接用dirname定位工作目录。5.2 无效用例 tests/invalid/intrinsics.test.w该测试反向验证两条错误路径行内注释直接标注了期望的诊断位置inflight () { let x dirname; // dirname cannot be used inflight let y filename; // filename cannot be used inflight }; let path dirname(); // Unexpected arguments在 inflight 闭包中使用dirname/filename→ 阶段错误对应is_valid_phase对纯值内建dirname施加调用语法 → Unexpected arguments对应type_check_intrinsic的参数校验。六、target实战按目标平台条件编译target是四个内建中唯一前后置通用、且最适合演示条件编译场景的成员。平台概念文档 给出了一段典型代码在本地模拟器sim上只睡 1 秒在真实云目标上睡 30 秒既省资源又不改变业务逻辑bring cloud; bring util; let invocationCounter new cloud.Counter(); let queue new cloud.Queue(); queue.setConsumer(inflight (msg: str) { invocationCounter.inc(); }); new cloud.Function(inflight () { queue.push(m); if target sim { log(Running on Simulator, sleeping for 1s); util.sleep(1s); } else { log(Running on the cloud, sleeping for 30s); util.sleep(30s); } log(Function invoked {invocationCounter.peek()} times); });在 编译目标文档 中也有同样模式if target sim分支在模拟器上跳过云端专属行为。结合 4.3 节的代码生成可知target的值最终来自process.env.WING_TARGET因此在运行时也可以安全地在 inflight 代码中读取。七、迁移计划与未来方向RFC 给出了清晰的演进路径存量全局软弃用对log、assert这类使用面极广的全局函数保留旧语法但发出警告引导社区逐步迁移到log()、assert()避免一次性破坏既有代码。新内建持续落地在体系下继续实现fileissue #5449、inflightissue #6045等新能力。从当前仓库看file的诉求已被filename承担——它返回当前 Wing 源文件的规范化绝对路径。用户空间内建远期构想RFC 认为通过某种编译器 APIcompiler API让用户自定义内建是合理方向这会是极好的能力但设计和实现是一项巨大的工程。即便未来开放了自定义能力调用语法仍可保持一致只是那时这些构造可能不再适合被称为intrinsic。这一远期构想对应着仓库中的类型内建type(...)grammar.js 的 type_intrinsic 规则其注释里就写着 TODO是否有办法将其泛化以支持定义其他接受类型参数的内建函数或普通函数——说明编译器团队已经为内建函数接受任意类型参数留出了扩展口。八、小结Wing 的内建函数intrinsics设计本质上是把编译器注入的隐式全局魔法升级为显式、不可遮蔽、语义自明的前缀构造。从本次 RFC 的动机消除遮蔽与赋值导致的静默破坏、语法选型的无歧义与补全友好到当前仓库中语法规则、AST 枚举、类型检查、JS 代码生成与 LSP 补全的完整落地再到dirname/filename/app/target四个无参内建及log/assert/unsafeCast/nodeof/lift/type的扩展家族整个过程都有明确的源码与测试佐证。对 Wing 开发者而言最实用的三条结论是优先使用形态新代码直接写log(...)、assert(...)避免触发旧全局的软弃用警告记住阶段限制dirname、filename、app只在 preflight 可用inflight 代码需要把值在 preflight 提前取出善用target做平台分支在 sim 与云端目标间编写差异化逻辑是官方文档反复演示的标准姿势。对语言设计者而言这份 RFC 则提供了一个如何把编译器特权能力显式化、同时保持向前兼容的完整案例语法选择要无歧义且补全友好行为差异要用文档与测试锚定旧语法用警告过渡新能力统一收敛到同一套可扩展语法之下。赞分享编程语言编译器云原生语言运行时【免费下载链接】wingA programming language for the cloud ☁️ A unified programming model, combining infrastructure and runtime code into one language ⚡项目地址https://gitcode.com/gh_mirrors/wi/wing点击查看免费下载相关推荐Twig iterable 测试判断数组与可遍历对象让模板循环更健壮Twig iterable 测试判断数组与可遍历对象让模板循环更健壮 iterable 是 Twig 内置的核心测试test用于判断一个变量是否为可编程语言编译器云原生语言运行时AI_NovelGenerator 提速10章长篇小说生成从4小时压到2小时AI_NovelGenerator 提速10章长篇小说生成从4小时压到2小时 你设定 10 章、每章 3000 字点完生成去泡了杯茶。回来发现还卡在第 3编程语言编译器云原生语言运行时TanStack Table 内置过滤函数 filterFn_startsWith 深度解析前缀匹配的源码实现与实战用法TanStack Table 内置过滤函数 filterFn_startsWith 深度解析前缀匹配的源码实现与实战用法 filterFn_startsWit前端UI组件上一篇一道过滤闸为何拖垮所有消费者MyContext渠道数据平面v4分层修复设计笔记下一篇如何把EasyXMen移植到全新MCU基于Portable三层架构的移植教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考