ARTICLE DETAIL

资讯详情

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

Slang 词法分析器 Token 完全参考:Token 分类、源码映射与特殊词法规则

Slang 词法分析器 Token 完全参考:Token 分类、源码映射与特殊词法规则 编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载本文是 Slang 着色器语言编译器中词法分析器Lexer所产出的全部 Token 的分类目录面向两类读者一是打算扩展slang-lexer.cpp的编译器开发者二是编写消费 Slang 源码的 IDE、LSP、格式化器等工具的开发者。读完本文你将掌握 Slang 词法层 50 余种 Token 的完整清单、每一类 Token 在 source/compiler-core 中的精确源码出处、数字字面量后缀与十六进制浮点等解码期才定语义的设计以及反斜杠续行、原始字符串、#INF等特殊词法规则的确切行为。本文档在仓库中还经过了一轮与词法器源码的差距审查gap-intake修复了 10 处文档与实现不一致的问题因此文中的每一条结论都可以直接对应到具体源码行与测试用例。目录来源与源码映射该 Token 目录是从 Slang 编译器源码反向梳理reverse-engineered而来主要依据以下五个文件读者可按图索骥源码文件提供的信息source/compiler-core/slang-token.hToken结构、TokenType枚举、TokenFlags位掩码定义source/compiler-core/slang-token-defs.h用 X-macro 列出的每一个TokenType值被slang-token.h等多处包含source/compiler-core/slang-token.cppTokenTypeToString把同一份定义列表展开为诊断消息中的 Token 拼写source/compiler-core/slang-lexer.h / source/compiler-core/slang-lexer.cpp真正产出这些 Token 的词法分析器source/slang/slang-preprocessor.cpp读取 Token 标志位的消费者以及HandleIncludeDirective中尖括号 include 路径的组装逻辑其中 slang-token-defs.h 里PUNCTUATION(id, text)宏会展开为TOKEN(id, text)这意味着每一个标点种类既是TokenType枚举值又是诊断消息中使用的字面拼写——例如PUNCTUATION(OpAssign, )展开后描述字符串为。这让词法错误诊断如期望与 Token 类型天然保持一致。Token 种类分类法Slang 词法层把 Token 分为五组结束标记end markers、内容 Tokencontent tokens即字面量与标识符、琐碎 Tokentrivia空白与注释、预处理标记preprocessor markers、标点与运算符。词法分析器核心是一个按字符分发的自由函数_lexTokenImplslang-lexer.cpp 约第 1966 行由成员函数Lexer::lexToken约第 2473 行包装后者负责附加 Token 标志位并返回已经折叠掉行续接符的 Token 文本。行续接的折叠发生在字符级辅助函数_peek越过续接符向后看与_advance消费一个续接符并记录ScrubbingNeeded标志中。结束标记与特殊 TokenTokenKind词法器源码出处说明Unknown默认构造的Token合法输入中不应出现EndOfFile_lexTokenImpl的kEOF分支slang-lexer.cpp 约第 1974 行词法器到达输入末尾时返回Invalid_lexTokenImpl末尾的 fall-through 块slang-lexer.cpp词法器遇到没有任何分发分支匹配、且不是非 ASCII 码点非 ASCII 会被折叠进标识符的字符时产生。它按字符值在illegalCharacterPrint、unexpectedEndOfInput、illegalCharacterHex三种诊断间选择且当设置了kLexerFlag_SuppressDiagnostics时见 slang-lexer.h 中Lexer::getDiagnosticSink直接跳过诊断。无论是否报错InvalidToken 都会被产出内容 Token字面量与标识符TokenKind词法器源码出处说明Identifierslang-lexer.cpp 中的标识符规则包含所有关键字关键字与普通标识符的分类被推迟到解析器通过 syntax-decl 查表完成IntegerLiteral整数字面量规则十进制、0x/0X十六进制、0b/0B二进制或前导零的八进制数字串。任何尾随后缀都属于 Token 原始文本的一部分见下文FloatingPointLiteral浮点字面量规则接受的形态见下方展开同样尾随后缀属于 Token 原始文本StringLiteral_lexStringLiteralBody(lexer, )约第 2172 行/_lexRawStringLiteralBody约第 2166 行Token 原始文本包含开闭引号转义序列的解码与校验推迟到getStringLiteralTokenValueCharLiteral_lexStringLiteralBody(lexer, \)约第 2177 行单引号字符字面量。词法器只负责找到闭合引号必须恰好一个字符的规则由解码期函数getCharLiteralValue第 1660-1725 行强制数字字面量接受形态与词法期无固定后缀集设计词法器对数字后缀采取宽松策略。_maybeLexNumberSuffixslang-lexer.cpp会把数字主体之后的任意ASCII 字母、数字、下划线序列都并入 Token 原始文本含义留给消费者决定——源码注释明确写道be liberal in what we accept here, so that figuring out the semantics of a numeric suffix is left up to the parser。因此词法期不存在固定的后缀集合接受哪些后缀、拒绝哪些后缀并报错都属于解码辅助函数而非词法器本身整数getIntegerLiteralValue第 789-838 行读取基数前缀与数字串把未消费的尾巴作为后缀交还给调用者。它只诊断溢出——LexerDiagnostics::integerLiteralTooLargeForAnyTypeE10012见 slang-lexer-diagnostic-defs.h定义为Error级别——当数字无法放进 64 位时触发。真正接受后缀的是其调用者parseIntegerLiteralExprsource/slang/slang-parser.cpp 第 8614-8719 行后缀循环在 8637-8686 行接受u/U、l/L、ll/LL、z/Z顺序任意但ll的两个字母必须大小写一致其余后缀报告Diagnostics::InvalidIntegerLiteralSuffixinvalid suffix ... on integer literal。这两条解码期诊断分别由 negative-integer-literal-too-large.slang 与 negative-invalid-int-suffix.slang 测试用例验证。浮点getFloatingPointLiteralValue第 1137-1391 行接受空后缀或f/F对应float、h/H/hf/HF/fh/FH对应half、l/L/lf/LF/fl/FL对应double其余作为FloatingPointLiteralType::BadSuffix第 1273-1287 行。单字母后缀大小写不敏感双字母后缀要求两个字母同大小写因此hf、HF合法而Hf非法。对应测试如 numeric-suffix-f-is-float.slang、numeric-suffix-h-is-half.slang、numeric-suffix-lf-is-double.slang、numeric-suffix-ull-is-uint64.slang 逐一验证了这些映射。浮点字面量的接受形态FloatingPointLiteralNotes 列包括1.5普通小数、1.尾随点、.5前导点、1e10/0e-3十进制指数、0x1.8p3十六进制有效数字配二进制p指数以及遗留的1#INF/0#INF无穷大形式。一个值得注意的例外点后紧跟x或r不会开始小数部分——该拼写保留给对标量字面量的 swizzle如1.xxx因此1.xxx会被词法切分为IntegerLiteral、Dot、Identifier三个 Token见 slang-lexer.cpp。琐碎 Token空白与注释词法器把这些作为独立 Token 产出让预处理器与解析器自行决定是否跳过大多数解析层在遍历 Token 流时会将其过滤掉。TokenKind词法器源码出处说明WhiteSpace空白规则空格 / 制表符的连续序列NewLine行尾规则逻辑行终止符在反斜杠续行折叠之后LineComment//规则// ...到行尾BlockComment/* ... */规则注意Slang 不支持嵌套块注释预处理标记TokenKind词法器源码出处说明Pound#标点预处理指令前缀PoundPound##标点预处理 Token 粘贴token pasteCompletionRequest#?分支slang-lexer.cpp 约第 2367 行#?在光标位置发出用于请求补全标点与结构符号按拼写列出每个都经由_lexTokenImpl中的逐字符switch分发TokenKind词法器源码出处说明Semicolon;标点Comma,标点Dot.标点DotDot..标点被词法为独立种类当前提交没有解析器消费者Ellipsis...标点被预处理器用于变参宏参数LBrace/RBrace{/}LBracket/RBracket[/]LParent/RParent(/)Colon:标点Scope::标点命名空间 / 限定名分隔符QuestionMark?标点条件 / 可选RightArrow-标点函数返回类型、通过指针访问成员DoubleRightArrow标点Lambda 语法其唯一解析器消费者At标点slang-lexer.cpp被词法为独立种类当前提交没有解析器消费者与DotDot行的措辞一致Dollar$标点slang-lexer.cpp在spirv_asm块内作为 Slang 值操作数的前缀如$this、$location见 source/slang/hlsl.meta.slang 第 1215 行也引入编译期$for语句DollarDollar$$标点slang-lexer.cpp在spirv_asm块内作为 Slang类型操作数的前缀如result:$$float2同见 hlsl.meta.slang 第 1215 行$与$$的区分示例来自spirv_asm内联汇编的真实使用result:$$float2 OpImageQueryLod $this $location其中$$float2是类型操作数、$this与$location是值操作数。运算符赋值、算术、比较、逻辑与位运算符构成最后一组。词法器采用最长匹配maximal munch且该规则通过嵌套前瞻实现而非查表外层逐字符switch的某个分支先消费其字符再对_peek再次switch只有default:分支才返回较短的种类。前缀家族是教科书式的例子slang-lexer.cpp第二个被消费后若紧跟则产出OpShrAssign所以是单个 Token没有时内层分支回退为OpRsh第一个后直接是产出OpGeq其余情况产出OpGreater。、、、-、|、、!、%、*、^、/、.、:、#各分支都以同样方式解析。这一选择不依赖任何解析上下文唯一的例外是OpLess见下方表格。TokenKind词法器源码出处说明OpAssignOpAdd/OpSub/OpMul/OpDiv/OpMod/-/*///%OpNot!逻辑非OpBitNot~按位非OpLsh/OpRsh/OpEql/OpNeq/!OpGreater/OpGeq/OpLeq//OpLess与泛型实例化的歧义由解析器消除见 docs/generated/design/pipeline/02-parse-ast.mdOpAnd/OpOr/\|\|逻辑与 / 逻辑或OpBitAnd/OpBitOr/OpBitXor/\|/^按位与兼作取地址/ 或 / 异或OpInc/OpDec/--OpAddAssign...OpXorAssign-*/%\|^复合赋值全家族一个仅能从解析器源码看到、刻意未写进文档的补充事实解析器在闭合嵌套泛型参数列表时会把已被词法切为OpRsh的重新拆开slang-parser.cpp 第 2900-2916 行——这正是上面最大吞噬段落的最佳注脚词法层贪心解析层兜底。Token 数据布局Token结构定义于 slang-token.hclass Token { public: TokenType type TokenType::Unknown; TokenFlags flags 0; SourceLoc loc; uint32_t charsCount 0; union CharsNameUnion { const char* chars; Name* name; }; CharsNameUnion charsNameUnion; // ... };charsNameUnion是一个带标签的联合tagged union当Name标志位被设置时Token 文本被驻留intern为Name*用于标识符与关键字否则 Token 持有指向原始源缓冲区中一段文本的裸指针加长度。Token 标志位TokenFlag声明于 slang-token.h是一个位掩码记录词法属性标志位含义AtStartOfLineToken 是某个逻辑行的第一个 Token——在发出NewLineToken 后被设置并跨越中间的空白与注释保持因此被折叠的转义换行不发出NewLine不会开启新行预处理器用它识别指令AfterWhitespaceToken 前面有空白预处理器读取它来在字符串化stringizing时保留空格并区分函数式宏定义NAME(无间隔与对象式宏定义ScrubbingNeeded词法本 Token 时折叠过一个行续接符Lexer::lexToken用该标志把续接符从存储内容中清理掉Name判别chars/name联合AtStartOfLine的直接后果是预处理指令体可以续行到下一物理行指令体一直运行到下一个NewLineTokenIsEndOfLineslang-preprocessor.cpp 第 2700-2712 行而折叠后的续行不会产生NewLineToken所以下面这个宏的宏体是完整的((x) 1)#define BUMP(x) \ ((x) 1)指令识别本身也是该标志位的消费者只有当PoundToken 携带AtStartOfLine时才启动一条指令slang-preprocessor.cpp 第 4833 行。这个双物理行#define的行为由 line-continuation-in-macro-definition.slang 测试验证。特殊词法规则slang-lexer.cpp 实现了若干上下文敏感规则反斜杠行续接紧邻换行前的\被_advance消费并折叠掉但所得 Token 的源码位置仍指向原始物理行。ScrubbingNeeded标志被设置Lexer::lexToken据此把续接符从存储内容中剥离。折叠发生在注释识别之下_lexLineCommentslang-lexer.cpp只在_peek报告换行时结束注释而_peek第 226-305 行在返回前已经越过了续接符。因此以\结尾的//注释会把下一物理行一并吞掉// this comment continues with a backslash \ int swallowed 0;上面的int swallowed 0;会被注释吞掉。该行为由 line-comment-backslash-extends.slang 测试验证测试中注释后的第一行声明被消费只有再下一行的real成为真正的声明并被打印CHECK: real21。#include后的...词法器没有 include 头文件模式它照常发出OpLess、路径、OpGreaterToken。HandleIncludeDirectiveslang-preprocessor.cpp把与之间各 Token 的内容拼接起来重组成路径引号形式则是一个完整的StringLiteral。原始字符串字面量以Rdelimiter(打开的字符串只由)delimiter闭合delimiter任意。内部换行与反斜杠按字面处理不做任何转义处理。实现位于_lexRawStringLiteralBodyslang-lexer.cpp闭合分隔符终止检查在 1802-1812 行由字符串分发处的R分支第 2166 行调用。裸作为分隔符会被拒绝诊断LexerDiagnostics::quoteCannotBeDelimiter。字符字面量引用的字面量主体由与字符串相同的_lexStringLiteralBody辅助函数词法化该函数把闭合引号字符作为第二个参数字符字面量传\第 2177 行字符串传第 2172 行而非单独的单字符标志。词法期间它只做定位闭合引号所需的最小转义处理——越过\、\、\\以防转义引号提前结束 Token——并且只报告两种导致根本找不到结尾的失败LexerDiagnostics::endOfFileInLiteral与LexerDiagnostics::newlineInLiteral。字面量的值语义全部推迟单字符规则由getCharLiteralValue第 1660-1725 行强制它对两种情形发出LexerDiagnostics::illegalCharacterLiteral主体短到装不下字符或解码后仍有未消费输入多字符主体两种情形都返回-1。转义序列——包括\u/\UUnicode 形式位数错误时报invalidUnicodeStringEscape——由共享的_decodeStringEscape解码畸形的 UTF-8 主体报invalidUtf8ByteSequence。在上述所有情况下CharLiteralToken 依然被产出失败只在消费者索取值时浮现。相应测试包括 negative-empty-char-literal.slang、negative-multi-char-literal.slang 与 negative-unicode-escape-digit-count.slang。数字字面量后缀后缀字符u、l、f、h……保留为字面量 Token 原始文本的一部分Token 本身不记录请求的是哪种类型解码是消费者的职责且发生在更晚阶段。前导零的浮点延续单独的0后跟十进制指数0e10、0E5、0e1、0e-3或遗留的 MSVC 无穷大形式0#INF会被词法为FloatingPointLiteral与1e10/1#INF形式一致。_lexTokenImpl中0分支的default:臂slang-lexer.cpp 第 2059 行在回退为IntegerLiteral之前先咨询_maybeLexNumberExponent第 608 行否则指数会被当整数后缀吞掉。同一分支的兄弟臂处理其他基数0x/0X十六进制、0b/0B二进制以及发出LexerDiagnostics::octalLiteral诊断后的前导数字串按 8 进制词法化。八进制诊断是警告而非错误——W100020 prefix indicates octal literal声明于 slang-lexer-diagnostic-defs.hDIAGNOSTIC(10002, Warning, octalLiteral, ...)在 slang-lexer.cpp 中于调用_lexNumber(lexer, 8)之前立即发出因此词法不被它阻断字面量仍按 8 进制词法与解码八进制字面量可以正常编译。这由 negative-octal-literal-warning.slang 验证——测试中的 caret 跨度覆盖整个0777字面量且携带 10002 编号证明该分支确实执行。#INF的匹配先于任何基数检查_maybeLexNumberExponent且getFloatingPointLiteralValue在数字之后的文本一出现#INF就把值设为std::numeric_limitsdouble::infinity()slang-lexer.cpp——#之前的数字从不被解析。因此0#INF与1#INF都解码为正无穷大。相关测试见 float-literal-zero-msvc-infinity.slang。块注释处理BlockCommentToken 覆盖整个/* ... */范围不支持嵌套块注释。标识符 / 关键字分类每个关键字到达解析器时都是TokenType::Identifier。关键字地位由解析器 syntax-decl 表查表决定详见 keywords-and-builtins.md。源码位置Source location每个 Token 的SourceLoc是一个 32 位整数由SourceManager解码slang-source-loc.h、slang-source-loc.cpp。该整数是某一个SourceView的键而非一对位置解释方式由SourceLocType参数选择——Nominal尊重#line指令与 source map、Actual忽略二者、Emit尊重#line但忽略 source map。宏展开或其他派生视图的来源单独记录在SourceView自身上用getInitiatingSourceLoc取回。__LINE__是读取解码位置的用户级表面预处理器展开它时对调用位置的发起位置调用SourceManager::getHumaneLocslang-preprocessor.cpp 第 2318-2348 行而该方法的SourceLocType参数默认为Nominalslang-source-loc.h 第 518 行SourceLocType type SourceLocType::Nominal。因此#line指令——由HandleLineDirective经SourceView::addLineDirective记录第 4233 行——是用户可见的改变 Token 报告值的方式既影响__LINE__也影响诊断行号。相关测试见 source-location-line-directive.slang。本文档未覆盖的内容关键字每个关键字都以TokenType::Identifier到达分类与完整清单见 keywords-and-builtins.md。语法产生式见语法参考文档grammar.md。此外词法-预处理管线的整体流程_lexTokenImpl→Lexer::lexToken→ 预处理器消费在 docs/generated/design/pipeline/01-lex-preprocess.md 中有更宏观的叙述可作为本文的上下游上下文。本文档配套的自动化测试全部位于 docs/generated/tests/design/syntax-reference/tokens/可作为扩展词法器或编写消费工具的回归基线。赞分享编译器图形学编程语言【免费下载链接】slangMaking it easier to work with shaders项目地址https://gitcode.com/GitHub_Trending/sl/slang点击查看免费下载相关推荐Slang 编译器 Token 参考词法分析器 TokenKind 全量清单与 Token 标志位解析Slang 编译器 Token 参考词法分析器 TokenKind 全量清单与 Token 标志位解析 本文是面向 Slang 着色器语言编译器前端的一份 T编译器图形学编程语言Slang 词法分析器 Token 完全参考令牌目录、分词实现与文档验证工作流Slang 词法分析器 Token 完全参考令牌目录、分词实现与文档验证工作流 本篇技术指南围绕 Slang 编译器核心的词法分析Lexer展开完整梳理编译器图形学编程语言CPython 词法分析全解从源码字符到 Token 流的语言参考指南CPython 词法分析全解从源码字符到 Token 流的语言参考指南 Python 程序在被真正解析执行之前首先要经历 词法分析lexical anal编程语言语言运行时解释器标准库创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表