资讯动态

Slang 编译器解析与 AST 构建深入解析:从 Token 流到强类型语法树

发布时间:2026/9/20 13:19:07 来源:尧图企业网站定制
编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载本指南以 Slang 着色器语言编译器Slang Shading Language Compiler的docs/generated/design/pipeline/02-parse-ast.md为核心骨架系统讲解编译流水线中解析Parse与 AST 构建阶段的完整设计两阶段解析如何化解歧义、语法即声明的关键字注册机制、错误恢复策略、AST 六大节点族的数据模型、ASTBuilder的分配与类型 hash-consing以及泛型约束、修饰符与字面量解析等失败模式的诊断细节。读完本文你将掌握 Slang 源码中解析器与 AST 的代码组织方式、关键入口函数与数据结构的实际形态能够在 source/slang/slang-parser.cpp 与 source/slang/slang-ast-base.h 中准确定位并理解解析相关的每一处实现。一、阶段定位解析器处于编译流水线的什么位置Slang 编译器的前端遵循经典的词法 → 语法 → 语义流水线。解析阶段承接由词法分析与预处理阶段产生的扁平 Token 列表将其转换为强类型strongly-typed的 AST抽象语法树。本阶段的目标读者是要添加新语法、修改 AST 节点、或排查解析错误的开发者。本文所讲的解析阶段输出将直接作为语义检查阶段的输入——语义检查负责名字解析、类型附着、约束求解与泛型特化最终交由 AST → IR 下降见 05-ir-passes.md。需要特别说明的是函数与方法体在这一阶段并不会被解析而是以原始 Token 形式延迟保存这一两阶段解析设计是全文最重要的主线之一。二、输入与输出解析器的两个入口2.1 主入口parseSourceFile解析一个源文件的入口声明在 slang-parser.hvoid parseSourceFile( ASTBuilder* astBuilder, TranslationUnitRequest* translationUnit, SourceLanguage sourceLanguage, TokenSpan const tokens, DiagnosticSink* sink, Scope* outerScope, ContainerDecl* parentDecl);各参数语义如下参数含义astBuilderAST 节点分配器解析产生的所有节点都由它负责 arena 分配详见ASTBuilder一节translationUnit翻译单元请求携带本次编译的单元级信息sourceLanguage源语言Slang / HLSL / GLSL 等影响解析行为分支tokens扁平 Token 列表TokenSpan由上一阶段词法分析产生sink诊断收集器解析错误与警告统一汇入outerScope外层作用域环境parentDecl接收 AST 的父容器声明命名空间 / 模块声明输出挂接到parentDecl上的 AST 节点全部通过传入的ASTBuilder分配。函数与方法体在此阶段仍是未解析状态——详见下文两阶段解析。2.2 辅助入口parseUnparsedStmt当语义检查遇到延迟保存的函数体UnparsedStmt时会以 body 模式重新初始化一个Parser并调用第二个入口同样声明在 slang-parser.hStmt* parseUnparsedStmt( ASTBuilder* astBuilder, SemanticsVisitor* semantics, TranslationUnitRequest* translationUnit, SourceLanguage sourceLanguage, TokenSpan const tokens, DiagnosticSink* sink, Scope* currentScope, Scope* outerScope);两个入口最主要的区别在于记录到ParserOptions中的ParsingStageparseSourceFile选择ParsingStage::DeclparseUnparsedStmt选择ParsingStage::Body。凡需要在两个阶段表现不同的代码均通过Parser::getStage()查询当前阶段。三、Parser 与 TokenReader递归下降的骨架解析器实现于 slang-parser.cpp设计为递归下降解析器recursive-descent parser。由于 Token 已被预收集为扁平列表见词法预处理文档解析器可以使用任意长度的向前看lookahead尽管实际中很少超过一个 Token。Token 流通过TokenReader消费其定义在 source/compiler-core/slang-lexer.h只读访问peekToken、peekTokenType、peekLoc前进advanceToken回溯ParsingCursor的保存getCursor与恢复setCursorTokenReader内部维护一个预读 Tokenm_nextTokenParsingCursor同时记录游标指针与预读 Token从而支持任意深度的推测性解析。这一机制是后面tryParseGenericApp歧义消解的基础。四、两阶段解析化解歧义的关键设计4.1 动机的双重身份Xab(5)这样的表达式在缺乏语义信息时无法判定它可能是对泛型函数X的调用实参为ab也可能是条件表达式X a b 5。经典的教科书式前端在解析阶段没有语义信息难以有效消解此类歧义。Slang 采用两阶段解析Decl 解析阶段正常解析顶层声明struct、变量、函数等但当解析到函数体{ ... }时不递归进入。parseOptBodyslang-parser.cpp 约第 2219 行跟踪花括号嵌套深度将包围的 Token 原样复制进一个UnparsedStmtAST 节点同时记录该函数体书写时所在的作用域currentScope、outerScope。Body 解析阶段语义检查遇到UnparsedStmt时派生一个新的Parser并以 body 模式重新解析这些 Token同时传入SemanticsVisitor指针——使解析器在消解时能够调用检查器的CheckTerm查询名字所指。两个阶段的差异体现在ParserOptions的ParsingStage上并通过Parser::getStage()供各解析函数查询。4.2 语义驱动的歧义消解规则在 body 解析阶段遇到时tryParseGenericAppslang-parser.cpp 第 2985 行附近会把之前的表达式交给检查器CheckTerm依据其结果决定走向基准表达式解析为GenericDecl、FunctionDeclBase或AggTypeDeclBase时一律按泛型应用解析——因为函数名或类型名之后不可能合法地出现比较运算符且按泛型读取能给出更好的诊断重载的基准OverloadedExpr只要任一候选属于上述三类之一也提交为泛型应用只有基准解析为普通变量等其他实体时才强制采用比较运算读法。这一错误方向优先的策略其历史叙事与完整推导记录在 docs/design/parsing.md本文不重复展开。4.3 局部变量的作用域问题两阶段解析还带来了局部变量作用域的难题消歧时需要尽早把变量注册进作用域但尽早注册会让块内后声明的变量提前可见从而无法报出使用未声明变量的错误例如块内int input input 1;会错误地引用局部input而非全局input。Slang 的取舍是所有局部变量仍注册到同一个ScopeDecl但每个VarDecl携带一个hiddenFromLookup状态标记。解析期间全部默认可见供消歧查询当语义检查真正检查一个BlockStmt时先遍历块内所有DeclStmt将其标记为不可见再检查子语句遇到DeclStmt时再恢复可见。这样既尊重了局部变量的语义作用域又避免了为每条语句建立深层的嵌套作用域链深度嵌套会拖慢查找并可能栈溢出。hiddenFromLookup相关的更细致讨论见 docs/design/parsing.md 的 Scope of Local Variables 一节。五、语法即声明关键字如何被注册为语法5.1 核心思想Slang 不把大多数关键字当作词法层的保留字而是将其视为绑定到当前环境中语法syntax的标识符。解析器维护一张SyntaxParseInfo表将关键字名映射到解析回调类型与访问器声明于 slang-parser.h约第 45–53 行struct SyntaxParseInfo { const char* keywordName; // 与本次解析关联的关键字 SyntaxParseCallback callback; // 应用于解析的回调 SyntaxClassNodeBase classInfo; // 关联的语法类信息 }; ConstArrayViewSyntaxParseInfo getSyntaxParseInfos();表本身是 slang-parser.cpp 中的静态数组g_parseSyntaxEntries[]约第 10831 行其行由三个小助手构造_makeParseDecl、_makeParseModifier、_makeParseExpr约第 10802–10722 行区间getSyntaxParseInfos()约第 10894 行以ConstArrayView形式对外提供。5.2 注册与调用链路解析器遇到标识符时沿活动作用域链查询tryLookUpSyntaxDecl约第 1129 行若查到通过该表注册的SyntaxDecltryParseUsingSyntaxDecl约第 1216 行便调用关联回调这是ParseDeclWithModifiers约第 5911 行对以标识符起始的声明尝试的第一条路径同样的机制从ParseModifiers约第 1243 行分派修饰符关键字大多数语言修饰符关键字在启动时由populateBaseLanguageModule约第 10874 行填充核心模块的*.meta.slang文件还会贡献额外条目。从g_parseSyntaxEntries[]的实际内容可以看到典型条目typedef、associatedtype、__constraint、__associatedfunc、cbuffer、tbuffer、__generic、__extension、extension、property、semantic等。完整关键字清单见关键字与内建文档。实际后果在 Slang 中新增一个修饰符关键字通常只需在语法表中注册而无需改动词法器或解析器核心——这是该架构最直接的工程收益。六、尖括号注解两种被整体丢弃的 ... 子句两处相互独立的代码会丢弃 ... 子句而不解释其中任何内容。两者均继承自遗留的 D3D effect 语法且都整体抛弃该子句——尖括号内的内容不会进入 AST。6.1 声明符级跳过需要开关位于parseDirectAbstractDeclaratorslang-parser.cpp 约第 2582–2681 行区间选择性启用仅当ParserOptions::enableEffectAnnotations被设置时运行-enable-effect-annotations命令行选项会设置它两个解析入口各设一次。由于声明符后的可能是泛型实参列表它先做消歧let与 X :前缀仍视为泛型实参列表否则在临时TokenReader上向前扫描寻找下一个之前的;。找到;即判定该子句为注解提交临时 reader 并消费找不到则把 Token 留给泛型实参路径。// 需要 -enable-effect-annotations Texture2D tex string uiName Tex; int uiOrder 3;;6.2 语义级跳过无条件位于_parseOptSemantics约第 4067 行无条件执行。语义如: SV_Position之后的永远被当作注解从当前位置一直跳过到下一个之前的所有 Token不设标志、不做消歧。struct VSOut { float4 pos : SV_Position string ann hello;; };6.3 失败时的迷惑行为尖括号内的;正是将第一种写法与泛型实参列表区分开的关键也是不带开关时失败显得令人困惑的原因去掉-enable-effect-annotations后声明符级子句永远不会被识别为注解落入泛型实参读法其内部的第一个;会被报告为意外的 Token——诊断为Diagnostics::UnexpectedTokenExpectedTokenTypeE20001unexpected ;, expected 而不是任何提及注解被禁用的信息。七、错误恢复避免级联报错的艺术7.1 单点抑制isRecovering解析器遇到意外 Token 时Unexpectedslang-parser.cpp 约第 336–365 行通过DiagnosticSink参见诊断文档发出诊断并设置Parser::isRecovering。该标志正是抑制级联报错的开关置位期间不再报告任何新的意外 Token诊断一旦等到解析器等待的 Token 出现即清除。7.2 重新同步TryRecover与平衡组跳跃TryRecover约第 489 行接受两类 Token 集合recover-before 集合停在这些 Token 之前不消费它们recover-after 集合消费这些 Token 并继续。{ ... }块内使用的默认策略约第 634 行是recover-before}、recover-after;。跳跃以**平衡组balanced group**为单位由SkipBalancedToken约第 387 行完成——括号或花括号区域整体跳过而非逐 Token 跳过且TryRecover拒绝跳过闭合 Token)、]、}、文件结尾见IsClosingToken约第 426 行除非闭合 Token 本身就是它正在寻找的对象。即便出错AST 仍会尽力构建使下游工具可以在部分树上继续工作。7.3 块策略是唯一使用 recover-after 的地方块内策略是唯一使用 recover-after 集合的位置。其余所有恢复点都走TryRecoverBefore约第 627 行只传单个 recover-before Token、不带 recover-after 集合——因此块外解析器在闭合 Token 上重新同步而非在分隔符上。共有两个这样的站点Parser::readTokenImpl约第 643 行仅在isRecovering已置位、或期望的 Token 是经由ReadMatchingToken读取的}/)/]时才在期望的 Token 之前恢复ReadToken调用中第一个意外 Token 会被报告并留在原位。AdvanceIfMatch约第 811 行驱动所有( ... )、[ ... ]、{ ... }与文件级列表的消费在该区域闭合 Token 之前恢复。若下一个 Token 反而在该区域的bail 集合中——( ... )与[ ... ]为}或文件结尾{ ... }与文件级为仅文件结尾见kMatchedTokenInfos约第 789 行——则放弃搜索让外层构造自行闭合。因此参数列表、初始化列表与声明位置没有自己的恢复集合它们继承各自所在匹配区域的闭合 Token。八、AST 数据模型强类型类层次8.1 根类NodeBase与 FIDDLE 机制AST 是根植于NodeBase的强类型 C 类层次声明于 slang-ast-base.hFIDDLE(abstract) class NodeBase { FIDDLE(...) // ... ASTNodeType astNodeType ASTNodeType(-1); ASTBuilder* getASTBuilder(); };FIDDLE(...)宏实例由构建期工具slang-fiddle处理生成对应定义于构建产物目录例如slang-ast-base.h.fiddle。生成代码提供访问者visitor分发表、asT类型转换所用的SyntaxClass反射元数据、以及序列化支持。切勿直接编辑生成文件请编辑带有 FIDDLE 标记的源文件。8.2 六大节点族每个族在 slang-ast-base.h 中声明其基类具体子类则分布在各自的slang-ast-*.h头文件中全部清单见模块地图族基类具体子类所在头文件关键成员举例Decl声明NodeBase约第 763 行slang-ast-decl.hContainerDecl、FunctionDeclBase、VarDecl、AggTypeDecl、SimpleTypeDecl、GenericDecl、ExtensionDecl、InterfaceDecl、SyntaxDeclExpr表达式约第 815 行slang-ast-expr.hInvokeExpr、BuiltinOperatorExpr等Stmt语句约第 825 行slang-ast-stmt.h含UnparsedStmt延迟函数体Type类型约第 569 行注意Type派生自Val即类型本身就是编译期值slang-ast-type.hVectorExpressionType、ArrayExpressionType等Modifier修饰符约第 707 行slang-ast-modifier.hAttributeBase、NoDiscardAttribute等Val编译期值约第 380 行slang-ast-val.hIntVal等Val族的用户级面貌是泛型的值实参vectorfloat, 3中的3就是VectorExpressionType::getElementCount返回的IntValslang-ast-type.h 约第 583 行int a[4]中的4则是ArrayExpressionType::getElementCount返回的那个IntVal约第 751 行。8.3 解析期与检查期的表达差异新解析出的 AST 中类型与表达式都使用Expr表示——因为解析时A(B)可能是函数调用也可能是类型构造由语义检查阶段负责重新分类。检查器还可能把已解析的节点改写成更特化的节点例如InvokeExpr的 callee 若是对内建标量/向量/矩阵类型上的内建算术、比较、位运算、移位、一元运算符则被convertToBuiltinArithmeticOpslang-check-expr.cpp转换为BuiltinOperatorExprslang-ast-expr.h并记录解析出的BuiltinOperationKind——使运算符名 → kind的映射只做一次而非被每个消费者常量折叠、IR 下降、for 循环迭代次数推断各自重复解析。该节点是检查期合成的不是解析器产生的。8.4 节点转换asTAST 节点转换使用 slang-ast-base.h 中的模板助手asTtemplatetypename T T* as(NodeBase* node);它基于 FIDDLE 生成的SyntaxClass元数据分派而非 C RTTI因此类型转换开销低且不依赖运行时类型信息。九、ASTBuilder分配与类型 internASTBuilderslang-ast-builder.h / slang-ast-builder.cpp承担两项职责分配AST 节点通过 builder 进行 arena 分配生命周期跟随所属模块 / session。类型的 hash-consing两个结构相同的类型用同一个Type*指针表示builder 维护 hash-cons 表。这种分工在 API 上清晰可见ASTBuilder::createT()分配全新节点是解析器为声明、表达式、语句调用入口Val类型在此编译失败由static_assert拒绝。Val/DeclRef世界中的节点则走getOrCreateT()以操作数为键查询m_cachedNodes字典命中即返回既有节点。部分getOrCreate的带类型包装还会在 intern 前规范化使一个逻辑值只有一种表示getTypeCastIntVal会解开嵌套的TypeCastIntVal、尝试常量折叠该转换、并在操作数已具目标类型时完全丢弃转换包装而不是总生成一个新的TypeCastIntVal。intern 的可观测效果核心模块声明了typedef vectorfloat,3 float3;core.meta.slang 约第 2594 行。因为两种拼写 intern 到同一个Type*int probe(float3 v)与int probe(vectorfloat, 3 v)是同一个签名后者是前者的重声明而非重载。由于构造任何 AST 节点都需要ASTBuilder*builder 指针被贯穿进每个产生节点的解析助手。十、泛型歧义tryParseGenericApp的推测性解析10.1 FOLLOW 集判定标识符后的裸在语法上是有歧义的可能是泛型实参列表fooT也可能是小于运算符foo bar。tryParseGenericApp通过在Parser的副本上推测解决使用一次性DiagnosticSink真正的 Token reader 从不移动。只有当推测性解析无错、且闭合之后的下一个 Token 属于泛型应用的 FOLLOW 集合时才在真正的解析器上重放泛型应用否则保持未读交由普通中缀解析接管——不存在单 Token 前看的启发式能在所有情况下成立。FOLLOW 集合完整列表slang-parser.cpp 约第 3033–3052 行::、.、(、)、[、]、:、,、?、;、、!、、、文件结尾。同一列表也记录于语法参考的消歧章节。10.2 泛型声明无歧义泛型声明不存在歧义因为声明上下文告诉解析器开启的是参数列表无论是关键字引导的声明func、struct、interface等还是 C 风格函数声明符如float fT(T x)声明符后的本身就是选择函数分支的信号最终都产生GenericDecl。解析器把参数收集进GenericDecl后继续解析内部声明。10.3where约束子句参数列表后可选地跟where子句以附加类型约束。解析只记录语法形式约束求解与替换发生在检查期slang-check-constraint.cpp与 IR 特化期见 05-ir-passes.md。约束由两个函数解析parseOptionalGenericConstraints继承子句形式: Base1, Base2maybeParseGenericConstraintswhere形式。两者都产生GenericTypeConstraintDecl成员slang-ast-decl.h。对泛型而言这些约束声明是泛型参数在GenericDecl下的兄弟节点。parseOptionalGenericConstraints接受可选的constraintTarget参数把被约束主体派生自正在解析的声明与约束声明加入的容器解耦。maybeParseGenericConstraints在where后接受多种拼写每种映射到各自的约束声明类slang-ast-decl.hwhere后的拼写约束声明类说明T : IBar、T UGenericTypeConstraintDecl拼写设置isEqualityConstraint:拼写接受逗号分隔的边界列表每个边界产生一个约束声明nonempty(Pack)NonEmptyPackConstraintDecl变参包非空约束countof(Pack) IntExprGenericVariadicPackCountConstraintDecl由_parsePackCountConstraintCountOfExpr加_readPackCountConstraintOperator解析刻意单向反向形式IntExpr countof(Pack)由_hasCountOfOnRightOfPackCountComparison检出并报Diagnostics::VariadicPackCountConstraintRequiresCountofOnLeft而非误读为无关类型约束__hasDiffTypeInfo(T)HasDiffTypeInfoConstraintDecl微分类型信息检查To(From)TypeCoercionConstraintDecl强转能力约束尾部implicit关键字附加ImplicitConversionModifier类型/见证、nonempty、countof形式可前缀optional附加OptionalConstraintModifieroptional加在__hasDiffTypeInfo子句上会被拒绝Diagnostics::OptionalHasDiffTypeInfoConstraintIsInvalid。FuncConstraintDeclslang-ast-decl.h是GenericTypeConstraintDecl的子类解析器从不产生它头文件检查header checking在接口的可调用需求自身需要被约束为类型时例如[Differentiable]方法将其合成为兄弟需求并携带一个已检查的callableRequirementDeclRef命名被约束的可调用体。10.4 接口关联类型与__constraint接口内声明的associatedtype其约束——无论写成继承子句associatedtype A : IBar还是where子句associatedtype A where A : IBar——都会被搬迁到外层InterfaceDecl成为接口级需求、与关联类型平级。parseAssocTypeslang-parser.cpp 约第 4423 行通过parser-currentScope检测外层接口并将其作为constraintTarget传入。这产生了与__constraint语法即声明形式相同的表示interface IDerived : IBase { __constraint DataType This; }__constraint关键字注册于语法解析表g_parseSyntaxEntries约第 10831 行第 10836 行条目由parseInterfaceConstraintDecl约第 4465 行处理构建GenericTypeConstraintDecl解析主体类型然后解析设置isEqualityConstraint或:子类型需求再解析边界类型。__constraint在何处合法由isDeclAllowed约第 5476 行集中强制只允许GenericTypeConstraintDecl作为InterfaceDecl或GenericDecl的成员。十一、修饰符解析语法与语义的分界线修饰符in、out、static、const等与属性[unroll]、[shader(compute)]等通过根植于ModifiableSyntaxNode的Modifier链挂到Decl上。ParseModifiers约第 1243 行在声明关键字之前收集修饰符 Token 并作为Modifier节点附加——关键字修饰符走前述语法声明查找[...]组走ParseSquareBracketAttributes约第 1002 行。语义检查随后依据被修饰声明的种类校验它们见语义检查文档。这条分界线正是 parse 阶段与 check 阶段的分界把[unroll]写在函数前而不是循环前解析不会报任何意外 Token——唯一的诊断来自检查器的Diagnostics::AttributeNotApplicableE31002attribute unroll is not valid here。11.1 解析器自己强制的一条放置规则Parser::ParseStruct约第 5819 行附近仍会解析写在struct关键字之后的属性列表struct [attr] Name但以模块的语言版本门控2025 之前静默接受2025报Diagnostics::DeprecatedBracketAttributesPlacementW31204警告2026 起报Diagnostics::InvalidBracketAttributesPlacementE31205错误。关键字之前的属性不受影响。门控读取的版本来自currentModule-languageVersion其设置途径有二命令行-std language-versionslang-options.cpp 约第 577 行-std 2018、-std 2025、-std 2026分别选中静默接受、弃用警告、拒绝三种分支逐文件#lang/#language指令其可接受版本拼写列于词法预处理文档的预处理指令章节。11.2 属性即修饰符修饰符类清单见 slang-ast-modifier.h属性解析只是修饰符解析的伪装——[name(args)]Token 变成一个AttributeBase修饰符。因此新增一个需要自身语义的属性通常就是给它一个专用的Attribute子类例如NoDiscardAttribute[NoDiscard]属性它使检查器在标记函数的调用出现在丢弃结果上下文如表达式语句时报错。十二、失败模式解析期诊断全景以下是解析阶段最值得关注的失败模式与诊断行为词法层遗漏的错误在此上浮词法器未报告的 Token 级错误如声明内部的未识别标点在这里以解析错误形式通过DiagnosticSink呈现。启发式消歧可能出错此时解析器倾向于产出某种AST把更精确的错误留给检查器发出或由检查器成功接受而不是中止解析。关键字被当作名字maybeDiagnoseKeywordUsedAsName约第 2571 行在声明符名是保留类型关键字struct、class、enum、typealias、typedef见isReservedKeywordName约第 2552 行时警告Diagnostics::KeywordUsedAsName。该检查与运算符名规则一样从共享的声明符路径运行因此统一覆盖var、let、参数、字段与typedef声明。运算符名只对函数合法规则在UnwrapDeclarator约第 2786 行强制——每个 C 风格声明符都必经此咽喉点parseDirectAbstractDeclarator记录的isOperatorName标志除非调用方以allowOperatorName显式同意否则报Diagnostics::OperatorNameOnNonFunctionE20020只有ParseDeclaratorDecl约第 3598 行的函数分支一旦参数列表或泛型确认声明符是函数才同意约第 3818 行。因此V operator(V a, V b) { ... }被接受而int operator 3;——同名但用于变量声明符——报 E20020。由于检查位于咽喉点变量、参数、typedef、属性property情形无需逐个测试即可被统一拒绝。畸形operator garbage不会重复标记——它已产生Diagnostics::InvalidOperator。语句解析器只接受声明形式的一个子集Parser::parseVarDeclrStatement约第 7279 行通过普通声明路径解析声明然后仅当它是变量VarDeclBase、DeclGroup、聚合类型AggTypeDecl覆盖struct、class、enum、interface——测试在基类上解析器不做区分、typedef/typealiasTypeDefDecl或using时才保留。被保留不等于被整体接受实践中只有struct能在函数体内使用语义检查期还有第二道独立的嵌套校验validateDeclNestingslang-check-decl.cpp 约第 304 行它查阅自己的父子规则表对禁止的组合报Diagnostics::DeclNotAllowedInContextE31400declaration not allowed in this context。两层相互感知检查器在decl-nestingAlreadyDiagnosed已设置即解析器已抱怨过时跳过诊断。函数体内写其他任何东西——如namespace——报Diagnostics::DeclNotAllowedE30102namespace is not allowed here.。这是与isDeclAllowed对声明写在另一个声明内部的容器嵌套检查相互独立的机制。浮点字面量在解析期解码字面量表达式是 Token 文本最终变成值的地方因此部分字面量诊断发生在解析期而非词法期。parseFloatingPointLiteralExpr约第 8740 行向getFloatingPointLiteralValue请求值加FloatingPointLiteralType分类slang-lexer.h并把分类映射到FloatingPointLiteralExpr的suffixTypeHalf/Float/Double或诊断InvalidFloatingPointLiteralNumber针对坏的有效数字InvalidFloatingPointLiteralSuffix针对未识别后缀。范围与精度问题由同一助手的outIsOutOfRange/outPrecisionLost标志上报解析器转为FloatLiteralTooSmall、FloatLiteralUnrepresentable或FloatHexLiteralPrecisionLost。每个字面量至多报一个单个diagnosed标志守卫后续分支——分类报告优先再是范围对最后是精度报告。两个分类分支互斥对FloatingPointLiteralType的单个switch唯一能同时触发两个条件的情形是分类失败且同时越界——如1e400q未识别后缀叠加溢出的有效数字——此时后缀报告胜出、范围报告被抑制。解析器只存储助手返回的值自身不做截断或钳制。词法器侧的另一半扫描 vs 解码见词法预处理文档。整数字面量同样在解析期解码parseIntegerLiteralExpr约第 8633 行把后缀拆成宽度部分l/L、ll/LL、z/Z与无符号部分u/U——重复任一部分报Diagnostics::InvalidIntegerLiteralSuffix——_determineIntegerLiteralType约第 8511 行把该对加上数值大小映射到基础类型。指针宽度后缀z选择intptr_t加u则uintptr_t对非十进制字面量完全不参考数值大小因此0xFFz就是intptr_t。对不带u的十进制z或ll字面量不大于INT64_MAX为有符号恰好INT64_MAX 1类型为无符号但标记signedMinimumIntException允许parsePrefixExpr约第 9860 行中包围的一元-把类型改回intptr_t/int64_tINT64_MAX 2及以上警告Diagnostics::IntegerLiteralTooLargeW40004并保持无符号。十三、延伸阅读与源码导航解析器两阶段设计的历史动机与局部变量作用域讨论docs/design/parsing.md解析器所接受语法的逆向工程文档语法参考 grammar.md关键字清单keywords-and-builtins.md诊断机制总览cross-cutting/diagnostics.md模块地图slang-ast-*.h家族位置architecture/module-map.md解析后语义检查如何消费 AST03-semantic-check.md泛型约束求解在检查期的实现slang-check-constraint.cppIR 特化阶段如何应用约束05-ir-passes.md解析器主实现slang-parser.cpp入口与SyntaxParseInfo声明slang-parser.hTokenReader 与浮点字面量分类slang-lexer.hAST 基类与asT转换slang-ast-base.hASTBuilderslang-ast-builder.h 与 slang-ast-builder.cpp对想在解析层做贡献新增关键字、调整语法、修复解析错误的开发者本文的核心行动建议是新关键字优先走g_parseSyntaxEntries语法表注册而非改动词法器涉及的新语法务必同时考虑两阶段解析与tryParseGenericApp的 FOLLOW 集判定涉及函数体内声明合法性的改动要同时考虑parseVarDeclrStatement与检查期validateDeclNesting两层机制。赞分享编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载相关推荐Slang 编译器解析与 AST 构建全解析从 Token 流到语法树的两阶段设计Slang 编译器解析与 AST 构建全解析从 Token 流到语法树的两阶段设计 本指南深入讲解 Slang 着色器编译器前端中解析与 AST 构建这一编译器图形学编程语言Slang 编译器解析阶段深度解析从扁平 Token 流到强类型 AST 的构建原理与实践Slang 编译器解析阶段深度解析从扁平 Token 流到强类型 AST 的构建原理与实践 导读 本文围绕 Slang 开源着色器语言编译器的第二个编译阶段展编译器图形学编程语言Slang 编译器 Parse/AST 阶段测试套件详解从 Token 流到强类型 ASTSlang 编译器 Parse/AST 阶段测试套件详解从 Token 流到强类型 AST 导读 本文围绕 Slang 着色器语言的编译器前端中“解析Par编译器图形学编程语言创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价