ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Slang 编译管线第一阶段详解:词法分析与预处理(Lex Preprocess)的 Token 流、宏展开与源码位置保留机制

Slang 编译管线第一阶段详解:词法分析与预处理(Lex  Preprocess)的 Token 流、宏展开与源码位置保留机制 Slang 编译管线第一阶段详解词法分析与预处理Lex Preprocess的 Token 流、宏展开与源码位置保留机制【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang本文围绕 Slang 编译器编译管线的第一阶段展开如何将一份源码缓冲区转化为可供解析器消费的扁平、完全展开的TokenList。你将掌握词法器的扫描与解码分离设计、预处理器宏机制与条件编译规则、#include解析与输入流栈管理、#pragma warning的跨文件状态追踪以及展开期 Token 的三类源码位置保留策略并了解该阶段全部可测试论断在仓库中的对应测试用例与实现位置。阶段总览从源字节流到扁平 TokenList词法分析与预处理Lex and Preprocess是 Slang 编译器流水线的第一阶段。它的输入与输出在设计文档 docs/generated/design/pipeline/01-lex-preprocess.md 中被严格定义输入一个源码缓冲区通常由 include 系统从磁盘加载外加当前的Linkage配置预定义宏、include 搜索路径。原始文件字节经由SourceFile::decodeContentBlobslang-source-loc.h、slang-source-loc.cpp转成源码文本该函数剥离开头的 Unicode BOM并将非 UTF-8 编码解码到一个新的 blob而一个无 BOM 的 UTF-8 文件则原样通过。SourceFile::setContents同样经由该辅助函数因此词法器始终面对的是无 BOM 的 UTF-8 文本。输出一个完全展开的TokenList见 slang-lexer.h即一串Token值其中所有的#include、宏展开和条件预处理指令都已被解析完毕。一个关键的设计约束是预处理器不会向解析器流式输出 token。第一阶段完整运行并产出扁平 token 列表后第二阶段解析才开始。这种解耦正是解析器能够进行任意前瞻lookahead的原因——对应测试包中把它列为未测试声明之一因为该后果由 docs/generated/design/pipeline/02-parse-ast.md 的测试包负责验证。词法器与预处理器分别实现于两个位置词法器在 source/compiler-core/slang-lexer.cppLexer结构及其initialize/ 词法驱动token 数据类型Token、TokenType、TokenFlags、TokenList、TokenSpan、TokenReader在 slang-token.h完整的 token 种类清单以 X-macro 形式列于 slang-token-defs.h预处理器则在 source/slang/slang-preprocessor.cpp公开接口在 slang-preprocessor.h。Token 数据模型与源码位置编码每个 Token 携带四类信息字段说明TokenType一字节的 token 类型通过 X-macro 在 slang-token-defs.h 中声明原始文本charsCount加上一个 union要么指向原始字符数组要么指向驻留interned的Name*SourceLoc一个 32 位整数由SourceManager按需解码回 文件/行/列见 slang-source-loc.hTokenFlags一个小字节标志位AtStartOfLine、AfterWhitespace、ScrubbingNeeded和Name用于区分上述 union源码位置编码的核心思想是每个 token 只携带一个廉价的 32 位uint32_t字段而非三个字段。SourceLoc的拷贝构造函数被显式标记为 default而非用户手写从而使该类型保持平凡可拷贝trivially copyable可以被安全地嵌入到 union 聚合体中。这一设计是 C 表示层面的事实其用户可见的后果诊断信息中的正确文件/行/列则由测试包中所有带脱字符caret锚点的诊断测试来断言。完整的 token 种类目录含分类在 docs/generated/design/syntax-reference/tokens.md该设计文档不重复罗列。词法器标志位与特殊规则LexerFlags声明于 slang-lexer.h目前包含kLexerFlag_SuppressDiagnostics用于在不抱怨非法或不支持的字符的前提下进行词法分析——典型场景是在不活跃的#if块内部做 token 化。该标志通过Lexer::getDiagnosticSink生效标志置位时该访问器返回 null 而非真实 sink因此所有经由该访问器的扫描期诊断点都会被抑制。唯一的例外是malformed-UTF-8 诊断它直接经由lexer-m_sink上报因此在未激活分支内部仍然会触发。词法器处理若干 C 语言风格的特性反斜杠行续接backslash line continuation\紧接换行符会被消费掉使宏定义跨越多个物理行、按一个逻辑行处理结果 token 的源码位置仍映射回原始物理行。测试包中有两个边界探针backslash-line-continuation-in-string.slang字符串字面量内foo\newlinebar折叠为foobar和backslash-line-continuation-in-macro.slang。不区分标识符与关键字每个关键字 token 到达解析器时都是TokenType::Identifier关键字状态由解析器通过查找表lookup解析见 02-parse-ast.md。字面量文本原样保留数字、字符串、字符字面量的文本以原始形式存放在 token 的 raw text 中。词法器只扫描字面量的范围不解码其值值提取推迟到专门的辅助函数。非 ASCII 输入的 UTF-8 折叠字节级_advance辅助函数slang-lexer.cpp将一个有效的多字节序列折叠为单个码点标识符规则接受该码点——因此一个非 ASCII 名称如é能作为一个标识符 token 被词法化对应测试non-ascii-identifier-utf8-folded.slang。畸形序列则被诊断为invalidUtf8ByteSequence违规字节以十六进制格式化并替换为一个空格从而让坏字节终止当前 token 而不至于破坏后续扫描_advance停在第一个不是合法续字节continuation byte的字节处而不消费它使恢复点保持在下一个字符的起始处。字面量扫描与解码的近乎干净分离词法器将字面量的扫描记录其范围作为原始 token 文本与解码求值分开且这种分离几乎彻底扫描只做找到字面量结束位置所需的最小工作外加少量纯语法检查扫描期检查严重级别行为octalLiteral警告W10002继续扫描进入_lexNumber(lexer, 8)017仍产出可用的八进制字面量 tokeninvalidDigitForBase错误E10003字面量基外的数字被拒绝quoteCannotBeDelimiter错误E10010原始字符串分隔符中出现被拒绝其中只有octalLiteral是警告invalidDigitForBase与quoteCannotBeDelimiter都是错误——这正是测试包中octal-literal-scan-warning.slang与invalid-digit-for-base-diagnostic.slang分别验证的内容。扫描发生在词法驱动器中。_lexStringLiteralBody(lexer, char quote)同时处理...与...——闭合引号字符是参数因此不存在独立的字符字面量扫描模式。它的唯一转义处理是跳过\、\、\\使被转义的引号不会误终止 token它不尝试识别八进制、十六进制或 Unicode 转义形式。相应地它只会报两种诊断endOfFileInLiteralE10004与newlineInLiteralE10005——即找不到结束位置的两类失败。原始字符串字面量由_lexRawStringLiteralBody单独扫描它寻找匹配的)delimiter完全不进行转义处理对应测试raw-string-no-escape-processing.slang。按需解码由声明在 slang-lexer.h 中的辅助函数完成getIntegerLiteralValue—— 解析整数字面量可选返回后缀、十进制标志和溢出标志integerLiteralTooLargeForAnyTypeE10012即在此报告。getFloatingPointLiteralValue—— 解析浮点值并且承担后缀分类它将 token 拆为数字部分与后缀部分把后缀映射为FloatingPointLiteralTypeh/hf/fh→Half空后缀或f→Floatl/lf/fl→Double大小写均可再将解析出的值舍入到该类型的精度与范围。同一个枚举还上报两个畸形情况BadSignificand与BadSuffix通过outErrorContent携带违规文本。它的四个 out 参数outLiteralType、outIsOutOfRange、outPrecisionLost、outErrorContent均为非可选引用。outIsOutOfRange表示结果是0下溢或INFINITY溢出——指超出字面量类型的最大值而非double的最大值outPrecisionLost仅对十六进制浮点且值在范围内时上报。getStringLiteralTokenValue(token, sink)—— 将转义解码为结果字节getFileNameTokenValue是#include风格文件名的变体不处理转义因此路径中的\x序列会被原样查找并上报见include-filename-escapes-not-processed.slang。getCharLiteralValue(token, sink)—— 返回 32 位码点失败时返回-1同时上报诊断。该辅助函数拥有单字符规则空体与解码后仍有未消费输入多字符体都会触发illegalCharacterLiteralE10001。空后缀分类为Float而非Double——后缀本身就能决定字面量是否可表示这是该节最反直觉的论断。设计文档给出了对比示例double a 1e300lf; // Double保持有限 float b 1e300; // Float超出范围解码为 INFINITY1e300在double中可表示但远超float最大值因此后缀单独改变了解码值。测试包用边界探针钉死了这一行为float-double-suffix-preserves-double-range.slang1e300lf有限 vs1e300溢出为 INFINITY、float-half-suffix-rounds-to-half-range.slangh后缀使超出 half 最大值的字面量解码为 INFINITY、float-literal-overflow-is-infinity.slang与float-literal-underflow-is-zero.slang。字符串/字符辅助函数接受DiagnosticSink*因为所有字面量值错误都在解码期上报越界的码单元与码点、畸形的转义语法、以及字符数规则。转义处理集中在_decodeStringEscape它接收 sink 与 token 的SourceLoc自行上报invalidStringEscape未知转义字母、无数字或未终止的\x{...}/\u{...}形式、溢出值与invalidUnicodeStringEscape\u不是恰好 4 个十六进制数字、\U不是恰好 8 个失败时返回-1。其语法遵循 docs/language-reference/expressions-literal.md转义形式规则\NNN八进制\xNN十六进制任意位数\uNNNNUnicode恰好 4 个十六进制数字\UNNNNNNNNUnicode恰好 8 个十六进制数字\u{...}花括号形式最高 32 位在字符串字面量中\xNN映射为单个字节而 Unicode 码点转义则编码为 UTF-8 字节序列经encodeUnicodePointToUTF8。getStringLiteralTokenValue将裸的非转义字节原样拷贝、不再检查 UTF-8 良构性因为词法器的_advance在扫描字面量体时已经诊断过畸形序列。在字符字面量中体字节会用getUnicodePointFromUTF8再次解码畸形 UTF-8 会被第二次拒绝——因此在那里\x与\u实际上没有差别。对应诊断invalidUtf8ByteSequence、invalidStringEscape、invalidUnicodeStringEscape、outOfRangeCodeUnit、outOfRangeCodePointForUtf8定义在 slang-lexer-diagnostic-defs.h。这一块被测试包以每个转义形式一个值测试 每个拒绝声明一个诊断测试的方式覆盖\x41→ 65、\101→ 65八进制、\u0041→ 65、\U00000041→ 65、\u{1F600}→ 128512、裸多字节é→ 233以及一系列负例诊断\u少于 4 位 → E10008、\x无数字 → E10007、多字符字面量 → E10001、空字符字面量 → E10001、字符串内裸换行 → E10005、字符串内\x超出单字节码单元范围 → E10009、Unicode 码点无法编码为 UTF-8 → E10013、原始字符串分隔符内引号 → E10010。词法器不做的事不分类关键字延迟到查找阶段不在扫描期求值数字字面量值提取是独立的按需步骤默认不跳过空白与注释lexToken将空白和注释作为它们自己的 token 类型发出过滤由lexAllSemanticTokens和预处理器的ReadAllTokens完成因此解析器永远看不到注释——测试comments-not-in-parser-token-list.slang验证了表达式中间不会出现注释 token。预处理器架构PreprocessorDesc 与输入流栈Preprocessor通过PreprocessorDesc配置包含一个DiagnosticSink*消息输出一个NamePool*标识符文本驻留一个ISlangFileSystemExt*与SourceManager*I/O可选的IncludeSystem*#include解析见 slang-include-system.h可选的DictionaryString, String预定义宏可选的PreprocessorHandler*翻译单元结束、文件依赖等事件的回调——构建系统用它记录 include 依赖可选的PreprocessorContentAssistInfo*语言服务器在预处理时收集代码辅助信息用。预处理器维护一个输入流栈原始源文件在栈底#include的文件与宏展开压入栈顶。token 向上流动时经过指令识别每个被#include的文件拥有自己的InputFile、自己的词法器、以及自己的Conditional状态栈因此条件跳过与指令诊断是逐文件的不会干扰父文件——测试include-conditional-stack-is-per-file.slang验证了在头文件中打开的#if会在该文件末尾被诊断而父文件中的#endif没有可闭合的条件。宏展开Op 重放、参数索引与自抑制宏定义存储已经词法化lexed的 token 序列并预先切分为MacroDefinition::Op条目展开就是重放这些 op。参数引用被编译为携带参数索引的参数 opExpandedParam、UnexpandedParam或StringizedParam。在调用点MacroInvocation按索引重放匹配参数的 token 范围_getArgTokens在ExpandedParam情形下将其包装进ExpansionInputStream使参数 token自身也参与宏展开——不存在按调用创建的伪宏环境。几个关键语义对象式宏#define ANSWER 42存储的词法 token 序列在每次使用时原样重放展开结果就是解析器看到的内容——object-macro-expansion.slang中若宏未展开ANSWER将因未声明而无法解析。字符串化参数#x取参数的原始 token而非其宏展开结果因此对宏名做#x得到该名称的拼写空参数字符串化为空字符串字面量。对应stringize-argument-not-expanded.slang、stringize-operator.slang、stringize-empty-argument.slang。参数 token 自身被展开作为参数传入的宏名会在宏体内展开——function-macro-argument-tokens-are-expanded.slang。参数个数不匹配调用参数数 ≠ 形参数时报WrongNumberOfArgumentsToMacro并跳过该次展开不贡献任何 token变参宏只要求非变参部分接受任意数量的尾部参数。测试macro-wrong-argument-count-diagnostic.slang钉死了这一诊断。展开自抑制而非深度限制每个进行中的调用都在busy列表上_maybeBeginMacroInvocation在MacroInvocation::isBusy发现自己时让标识符保持未展开。因此在自身宏体内命名自身会以普通标识符形式到达解析器——既不重新展开也不诊断同一列表也打破嵌套展开间的相互递归。零参数宏FOO()与空参数零参数函数式宏合法且不需要参数空参数逗号存在、前后 token 列表为空替换为无——对应两个边界探针function-macro-zero-args.slang与function-macro-empty-argument.slang。压力探针8 参数宏正确绑定全部名称function-macro-eight-args.slang。不活跃的#if分支仍然流经词法器保证列/行计数正确但其内容不被展开在不活跃块内只有可能切换激活/未激活状态的指令#if、#ifdef、#ifndef、#else、#elif、#endif会被实际求值其他指令被跳过而不产生效果。对应测试包括inactive-if-branch-not-expanded.slang、inactive-block-skips-non-conditional-directives.slang与边界探针ifndef-nesting-tracked-inside-inactive-block.slang未激活块内的#ifndef仍参与嵌套跟踪其自身的#endif闭合它而非外层的#if。预处理器指令表C/HLSL 集合与 Slang 扩展指令通过预处理器状态上的回调表按名称查找因此新增指令#pragma、自定义扩展就是在 slang-preprocessor.cpp 中注册一个新回调。kDirectives表持有 C/HLSL 风格集合#if及其家族、#include、#define、#undef、#warning、#error、#line、#pragma外加 Slang 语言选择指令#language/#lang与 GLSL 指令#version/#extension。每条指令消费自己的一行因此不向输出列表贡献任何 token。四个非 C 条目中#language/#langHandleLanguageDirective接受可选的slang名称后跟一个版本#lang [slang] version #language [slang] version它们设置preprocessSource通过outDetectedLanguage/outLanguageVersion返回的源语言与版本。版本操作数按名称解析而非按算术解析无论它到达时是标识符还是整数字面量其文本都被交给TypeTextUtil::findLanguageVersion在同一个表中查找——每个版本携带多个可接受拼写legacy/default/20182025/202a2026/202b/latest202c/next。因此#lang 2026、#lang 202b与#lang latest是同一指令本编译器不认识的版本只是表中没有而非数字超范围。注意只有slang被接受为可选语言名该指令已不再识别glsl。测试lang-directive-version-spellings.slang钉死了202b与2026同义这一行为lang-directive-unknown-version-after-language.slang则验证了显式slang之后的未知标识符报 E15207未知语言版本而非 E15208未知语言。无法使用的操作数产生哪种诊断取决于解析器还能推断出什么因此有三种操作数诊断既非标识符也非整数字面量ExpectedIntegralVersionNumber未识别的标识符且未给出slang名称UnknownLanguage——语言未指定孤立的标识符被读作对语言命名的尝试未识别的整数字面量或显式slang之后的未识别标识符UnknownLanguageVersion——语言已确定该 token 只可能是版本#version接受一个整数当它命名合法 GLSL 版本时把检测到的语言切换为 GLSL#extension被接受并丢弃。#pragma通过kPragmaDirectives按第二个名称分派目前有两个条目once与warning。#pragma once将所在文件的唯一标识记录在pragmaOnceUniqueIdentities中后续#include解析到该标识时HandleIncludeDirective提前返回——头文件被包含两次也只处理一次防止重复声明测试pragma-once-prevents-redefinition.slang。未识别的子指令不是错误findPragmaDirective回退到handleUnknownPragmaDirective以UnknownPragmaDirectiveIgnored警告并跳过该行——这与未知#-指令不同后者由HandleInvalidDirective报告为硬错误对应测试pragma-unknown-emits-warning.slang与unknown-directive-diagnostic.slang。#if/#elif条件经由递归下降求值器其值类型是普通有符号intPreprocessorExpressionValue无无符号或 64 位模式任何非零结果选择该分支。未命名任何对象式宏的标识符在UndefinedIdentifierInPreprocessorExpression警告后求值为0EvaluateInfixOp直接应用 C 运算符只有/和%检查操作数零除数被诊断并得0因此、-、*、的溢出既不检测也不诊断——对应测试if-undefined-macro-is-zero.slang未定义标识符视为 0、if-expression-uint32-max-is-true.slang0xFFFFFFFF非零为真、if-expression-signed-overflow-wraps.slangINT_MAX 1回绕为负、if-expression-zero-is-false.slang。条件指令家族的其余覆盖包括#ifdef/#undef对ifdef-selects-branch.slang、undef-makes-ifdef-false.slang、#elif链elif-chain-selects-first-true.slang、压力探针elif-chain-five-arms.slang、五层嵌套if-nested-five-deep.slang、经典头文件守卫模式header-guard-pattern.slang、#error/#warning的带消息与空消息两种形态error-directive-emits-diagnostic.slang、error-directive-empty-message.slang、warning-directive-empty-message.slang。#include 解析IncludeSystem、角度形式与文件依赖回调#include字符串由IncludeSystemslang-include-system.cpp解析它查询Linkage的搜索路径。两种形式的差异角度形式#include ...HandleIncludeDirective将与之间的原始 token 拼接为路径字符串并选择IncludeSystem::Mode::System系统模式。测试include-angle-bracket-mode.slang验证该模式。引号形式路径来自单个StringLiteraltoken。解析返回一个SourceFile预处理器随后为它压入一个新的输入流使该文件的 token 成为外围翻译单元的一部分include-pushes-fresh-stream.slang及其.slang.h辅助头。处理器还会收到handleFileDependency回调供前端为构建系统建立依赖记录。压力探针include-nested-5deep.slang连同include-nested-5deep-1.slang.h至-5.slang.h四个辅助头验证了 5 层嵌套#include链本文件 → d1 → … → d5后最内层声明在根文件仍可见。#pragma warning按绝对源码位置轴追踪的跨文件状态#pragma warning(push/pop/disable/...)状态由WarningStateTracker跟踪它按诊断 id 记录一条以绝对源码位置为轴的时间线。由于每个__include的文件都在自己的preprocessSource趟pass中、以全新的Preprocessor预处理tracker 携带一个persistedAbsoluteSourceLocCounterpreprocessSource在入口处以持久化值播种其absoluteSourceLocCounter退出时把推进后的值交还因此时间线的绝对轴跨文件保持全局单调而不会每次趟都从 0 重启导致冲突。SLANG_RELEASE_ASSERT对交还的计数器做单调性守卫例如防止超大翻译单元上uint32_t回绕因为一旦违反发行版构建中的#pragma warning状态解析会静默出错。测试覆盖了时间线语义的两个面pragma-warning-disable-timeline.slang同一构造在指令之前会警告、之后沉默pragma-warning-push-pop-restores.slangpush保存警告状态、pop恢复它——push/pop 区域内被disable : 30081关闭的警告在 pop 之后重新启用。源码位置保留三类展开期 Token宏展开产出的 token 按源码位置选择方式分为三类这是本阶段诊断质量的核心原始宏体 tokenRaw body tokens——从宏定义中原样拷贝的 token。它们的SourceLoc是宏定义中对应 token 的位置由MacroInvocation::readToken重放不创建新的SourceView因此这类诊断指向宏体内部而非调用点。测试macro-body-diagnostic-points-into-macro-body.slang验证关于宏体的诊断指向宏体。参数 tokenArgument tokens——取自调用点实参列表的 token。_getArgTokens通过PretokenizedInputStream重放记录的 token 范围而不重写任何内容因此每个 token 保留其被物理词法化时的SourceLoc调用点位置。参数被多次使用时会每次重放同一位置给定#define TWICE(x) ((x) (x))关于TWICE(undeclared)中参数的诊断指向调用点处的undeclared而不是宏体中的任一(x)。构造 tokenConstructed tokens——全新合成有三种不同的位置规则内建宏__LINE__、__FILE__由_pushStreamForSourceLocBuiltin压入赋予合成 tokenm_macroInvocationLoc归因于调用点不过其报告的 行/文件 值源自发起的最顶层位置。测试包括file-macro-is-constructed-token.slang、line-macro-is-constructed-token.slang、line-macro-through-nested-expansion.slang嵌套展开中的__LINE__报告发起调用的行与line-macro-after-line-directive.slang#line 1000后__LINE__报大值。字符串化参数#x取宏定义中#token 的位置。粘贴 tokenx##y从全新的PathInfo::makeTokenPaste()源码视图重新词法化其源点origin是##token 的位置。正是这个三分割让诊断系统在宏本身有问题时指向宏体与参数有问题时指向调用点之间正确选择。只有粘贴 token 的情形会构建发起位置链格式化诊断时DiagnosticSink在视图的PathInfo::Type为TokenPaste时反复走getInitiatingSourceLoc每跳发出一个seeTokenPasteLocation提示——测试token-paste-diagnostic-notes-paste-location.slang验证了粘贴产生的诊断报告在新鲜的粘贴视图上并附有指向发起粘贴的##的提示。功能侧的粘贴测试还包括token-paste-operator.slangPASTE(my, value)粘贴为my_value标识符、token-paste-relexed-numeric.slang两个整数字面量粘贴为一个新整数字面量与边界探针token-paste-with-empty-rhs.slang标识符与空序列粘贴仍得该标识符。失败模式扫描期、解码期与预处理器错误扫描期错误经传入词法器initialize的 sink 上报覆盖扫描器无需解释字面量即可看到的问题非法字符、畸形 UTF-8 序列、字面量内 EOF 或换行、对字面量基无效的数字、遗留八进制字面量、用作原始字符串分隔符的引号。解码期错误通过传给值提取辅助函数的 sink 上报getStringLiteralTokenValue与getCharLiteralValue报告畸形转义语法、越界码单元与码点、以及字符字面量的空体或多字符体getIntegerLiteralValue报告integerLiteralTooLargeForAnyTypegetFloatingPointLiteralValue不带 sink——它以BadSignificand或BadSuffix的FloatingPointLiteralType加违规文本返回错误由调用方选择诊断。两个阶段都汇入 slang-lexer.cpp 中文件局部的diagnose辅助函数它在 sink 为空时丢弃、并在 sink 错误计数超过kMaxLexErrorCount100后停止转发从而防止病态畸形文件淹没 sink。设置了kLexerFlag_SuppressDiagnostics时词法器对畸形输入仍发出TokenType::Invalidtoken 但抑制诊断——用于被跳过的预处理块内部这些 token 之后会被不活跃分支过滤器丢弃。由于抑制通过Lexer::getDiagnosticSink分发 null sink 实现它只覆盖使用该访问器的扫描期位置畸形 UTF-8 情形绕过它调用方之后若向值提取辅助函数询问字面量值会传入自己的 sink 并看到解码期诊断。预处理器错误不配对的#if、未知指令、缺失 include、循环 include、无开放条件配对的#else/#elif/#endif同样经 sink 上报且预处理器继续运行畸形指令被跳过至行尾、失败的#include从其处理器返回、坏宏调用可以展开为无 token、未闭合条件在文件末尾按每个仍开放的条件诊断一次EndOfFileInPreprocessorConditional。游离的闭合器是独立诊断DirectiveWithoutIfCyclicInclude在已解析的 include 已位于 include 栈上时触发且该#include被跳过。输出中唯一的EndOfFiletoken 由ReadAllTokens在整个输入栈弹空之后才追加。测试侧对应unbalanced-if-diagnostic.slang、unknown-directive-diagnostic.slang、missing-include-diagnostic.slang、include-self-cycle-diagnostic.slang、integer-literal-too-large-diagnostic.slang。测试包的覆盖策略论断驱动的验证体系测试包 READMEdocs/generated/tests/design/pipeline/01-lex-preprocess/README.md明确说明了覆盖策略每个正向论断一个功能测试、沿文档点名的数字/转义/后缀/嵌套轴布设边界探针、每个被拒绝论断一个带固定诊断码的诊断测试。所有测试与目标无关采用三种指令形态INTERPRET解释执行并检查输出适用于能观察到的数值行为例如printf的%d-target cpp文本发射用于 slangiprintf无法表达的字符串型观察如%s的字符串内容DIAGNOSTIC_TEST锚定诊断码与位置。纯粹的词法表面论断token 种类目录、数字后缀作为类型表面、注释形态已由syntax-reference/tokens覆盖此处刻意不重复。README 还记录了未测试论断及其理由这些是理解测试边界的好材料需要外部工具的BOM/UTF-16 输入、畸形 UTF-8 字节文件——测试文件自身必须以 BOM 字节开头或为 UTF-16会挡在 harness 解析//META块与//TEST指令之前需测试框架之外的字节级文件生成纯内部实现事实Token的 C 结构布局、SourceLoc的平凡可拷贝性、PreprocessorDesc的 C API 表面、kMaxLexErrorCount的编译期常量——没有指令能揭示字节本身只能由 C 单元测试断言需要 CLI 包装脚本的文件中途结束于未终止字面量endOfFileInLiteral无后续行可挂靠脱字符注解出包范围/交给其他包的关键字查找归 02-parse-ast 与 name-resolution 包__include跨文件#pragma warning需要多文件测试基础设施解析器前瞻由 02-parse-ast 包验证。同样有价值的是 README 的Doc gaps observed清单——它指出了设计文档在若干论断上的空白这些空白在 docs/generated/design/pipeline/01-lex-preprocess.md 的最新版本中已部分补齐例如浮点后缀分类示例1e300vs1e300lf、#if表达式的有符号int求值语义、未知#pragma子指令为警告、循环 include 与游离#endif的专门诊断。这展示了测试包反向驱动文档完善的工作流文档的每一条可测试论断都有一个 ID 且只出现在功能表或未测试表之一保证凡文档所述、必有测试可验凡无法验证、必说明缘由。总结01-lex-preprocess测试包与其对应的设计文档共同刻画了 Slang 编译管线第一阶段的完整面貌SourceFile::decodeContentBlob的 BOM 剥离与编码归一、Token的四字段数据模型与 32 位SourceLoc编码、扫描/解码分离的字面量管线含以h/f/l后缀为中心的浮点类型分类、以 Op 重放与参数索引为核心的宏展开、按名称解析的#lang版本表、逐文件隔离的Conditional状态栈、跨文件单调的#pragma warning时间线以及按宏体 / 调用点 / 构造点三分的展开期源码位置保留。对想要修改 token 分类、源码位置编码、字面量值提取或预处理器指令的开发者而言这套设计文档 逐条论断测试的搭配是进入该阶段实现slang-lexer.cpp、slang-preprocessor.cpp最直接的入口诊断系统的完整细节可进一步查阅 docs/generated/design/cross-cutting/diagnostics.md。【免费下载链接】slangMaking it easier to work with shaders项目地址: https://gitcode.com/GitHub_Trending/sl/slang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表