ARTICLE DETAIL

资讯详情

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

Carbon 语言字符串字面量完全指南:简单、块与原始字符串的语法、转义与词法实现解析

Carbon 语言字符串字面量完全指南:简单、块与原始字符串的语法、转义与词法实现解析 Carbon 语言字符串字面量完全指南简单、块与原始字符串的语法、转义与词法实现解析【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang本文以 docs/design/lexical_conventions/string_literals.md 为骨架结合 Carbon 词法分析器源码与词法测试数据系统讲解 Carbon 字符串字面量的三种形态单引号包围的简单字符串、包围的块字符串、以#定界的原始字符串、完整的转义序列集合、UTF-8 编码约定以及编译器在词法阶段的实际处理逻辑。读完本文你将能够准确书写各类 Carbon 字符串字面量理解块字符串缩进剥离、文件类型指示符与原始字符串哈希定界的工作机制并掌握词法层面的错误诊断行为。概述Carbon 字符串字面量的三种形态Carbon 同时支持单行与多行两类字符串字面量并各自拥有普通与原始raw两种形态简单字符串字面量simple string literal使用一个双引号定界单行书写。块字符串字面量block string literal使用三个单引号定界可跨多行在起始之后还可以附带可选的文件类型指示符file type indicator该指示符不影响字符串本身的内容仅供外部工具如语法高亮器、格式化器识别内容结构。原始字符串字面量raw string literal通过为定界符添加一个或多个#前缀来定制定界方式便于在字符串中直接书写字面意义的\与。简单与块字符串均有原始形态。以下是一个三种形态的直观对比示例摘自设计文档// 简单字符串字面量 var simple: String example; // 块字符串字面量 var block: String The winds grow high; so do your stomachs, lords. How irksome is this music to my heart! When such strings jar, what hope of harmony? I pray, my lords, let me compound this strife. -- History of Henry VI, Part II, Act II, Scene 1, W. Shakespeare ; // 带文件类型指示符的块字符串字面量 var code_block: String cpp #include iostream int main() { std::cout Hello world!; return 0; } 块字符串字面量会将终止行closing line的前导缩进从所有前导行中移除。因此在上面的code_block示例中最终字符串里只有std::cout与return两行各保留了 4 个空格的缩进。转义序列由反斜杠\引入用于表达特殊字符或代码单元序列例如\n表示换行。原始字符串字面量则额外以若干#定界其中的转义序列需要\后跟等量的#才会被识别。例如// 含换行转义序列的简单字符串 var newline: String line one\nline two; // 字面意义的 \n两个字符不是换行 var raw: String #line one\nstill line one#; // 原始字符串中的换行转义序列\ 后跟一个 # var raw_newline: String #line one\#nline two#;简单字符串字面量一个简单字符串字面量由以下序列构成并被包围除\与之外的字符。字符串字面量中合法的空白只有空格字符U0020。其他水平空白包括制表符 tab都是不允许的但出于错误恢复error recovery的目的它们仍会作为字符串的一部分被解析。垂直空白如换行不会被解析为简单字符串字面量的一部分。转义序列。每个转义序列都会被替换为对应的字符序列或代码单元序列。与非法空白类似\z这类非法转义序列也会作为字符串的一部分被解析用于错误恢复。例如var String: lucius The strings, my lord, are false.;相邻字符串字面量是被禁止的Carbon 与 C/C 不同不会将相邻的字符串字面量隐式拼接。例如下面的写法是无效的// 三个相邻的简单字符串字面量 、abc 与 是无效的。 var String: block abc;以三个双引号开头的字符串字面量属于相邻字符串字面量必须被拒绝并给出诊断。从词法实现看toolchain/lex/string_literal.cpp中的Introducer::Lex会识别以开头的输入并标记为MultiLineWithDoubleQuotesKind枚举之一见 string_literal.h随后在计算字符串值时发出错误诊断usedelimiters for a multi-line string literal, not多行字符串字面量应使用定界符而不是对应实现见 string_literal.cpp 与词法测试 toolchain/lex/testdata/multiline_string_literals.carbon 中的fail_quotes用例。块字符串字面量块字符串字面量以开始。同一行之后、换行之前的文本是可选的文件类型指示符。字面量在下一个其首个不属于\转义序列的三单引号处结束。需要注意闭合的必须是该行第一个非空白字符。起始行与闭合行之间的各行称为内容行content lines。内容行中不允许出现不构成转义序列组成部分的\字符。缩进剥离规则块字符串字面量的**缩进indentation**定义为闭合之前的那段水平空白。每条非空内容行必须以该缩进开头。字面量的内容按如下方式形成将闭合行的缩进从每条非空内容行中移除。每一行末尾的所有尾随空白包括行终止符都被替换为一个换行符U000A。将得到的各行拼接起来。每个转义序列被替换为对应的字符序列或代码单元序列。其中空内容行指仅包含空白字符的行。// 所有块字符串字面量默认都以一个换行结尾。 var String: newline_example This is a block string literal. Its first character is T and its last character is a newline. It contains another newline character between is and a. ; // 可以用转义字符 \ 抑制换行。 var String: suppressed_newlines This is another block string literal. The newline character here \ is suppressed, along with the trailing newline here.\ ; // 下面的块字符串字面量是无效的因为 closing 后的 虽然终止了 // 字面量却不在行首。 var String: invalid error: closing is not on its own line. ;实现层面缩进检测与剥离由 string_literal.cpp 中的ExpandEscapeSequencesAndRemoveIndent完成它对每一行尝试contents.consume_front(indent)若某行的缩进与闭合行不一致会发出MismatchedIndentInString诊断indentation does not match that of the closingin multi-line string literal对应测试用例见 multiline_string_literals.carbon 中的fail_indent_mismatch。同时lex.cpp 会更新行信息块字符串字面量所跨越的每一行其缩进列都被设为闭合定界符的列宽以保证后续令牌如尾随注释的行列定位正确。文件类型指示符与尾随注释文件类型指示符是之后、引入行上的文本去除两侧空白后得到它不得包含、#或。在文件类型指示符之后可以跟一个尾随注释它是一条普通注释工具将其视作普通注释处理不属于指示符本身因此可以包含指示符所不允许的字符如、#、。文件类型指示符对 Carbon 编译器没有语义意义但部分指示符会被语言工具链如语法高亮器、代码格式化器理解用于识别字符串内容的结构。例如// 这是一个块字符串字面量。其前两个字符是空格最后一个字符是换行。 // 它的文件类型是 c。 var String: starts_with_whitespace c int x 1; // This line starts with two spaces. int y 2; // This line starts with two spaces. ;文件类型指示符还可以携带文件类型之外的语义信息例如向代码格式化器发出对这段代码块禁用格式化的指令。开放问题目前还没有一个具体的、被公认的文件类型指示符集合。设计文档指出正式地给出一个知名指示符集合将有助于工具对指示符含义达成共识例如写在最佳实践指南中。词法实现印证Introducer::Lex在解析引入行时会查找//并剥离开头的尾随注释要求//后跟空白或行尾见 string_literal.cpp再对剩余文本去除空白并检查是否含#\三者之一。该行为有专门的测试用例introducer_trailing_comment见 multiline_string_literals.carbon。与字符字面量的消歧由于字符字面量永不空只能作为块字符串字面量的起始或结束的开头出现。形如foo的单行写法是错误绝不会被解释为一串字符字面量。词法器会对这种引入行形式不合法的情况返回一个覆盖整个引入行的无效字面量并发出MultiLineStringInvalidIntroducer诊断invalid multi-line string literal introducer; a file type indicator may not contain,#, or, and the content must begin on a new line见 lex.cpp。转义序列详解在字符串字面量内部以下转义序列是被识别的转义序列含义\tU0009 CHARACTER TABULATION水平制表\nU000A LINE FEED换行\rU000D CARRIAGE RETURN回车\U0022 QUOTATION MARK\U0027 APOSTROPHE\\U005C REVERSE SOLIDUS\\0值为 0 的代码单元\0D非法为将来演进保留\xHH值为 HH₁₆ 的代码单元\u{HHHH...}Unicode 码点 UHHHH...\newline不产生任何字符串内容仅限块字符串字面量十六进制字符H必须大写\xAA合法\xaa非法。与 C 转义序列的差异这套转义序列覆盖了除以下情形之外的所有 C 转义序列\?历史上用于在字符串字面量中转义三字符组trigraphs现已无用途故不支持。\ooo八进制转义被移除因为 Carbon 不支持八进制字面量\0作为特例保留预期对 C 互操作很重要。\uABCD被\u{ABCD}取代。\U0010FFFF被\u{10FFFF}取代。\a响铃、\b退格、\v垂直制表、\f换页\a与\b已过时\f与\v基本废弃。如有需要可以分别用\x07、\x08、\x0B、\x0C表达。值得注意的是这套转义序列与 Swift 和 Rust 支持的集合相同唯一的区别是 Carbon 额外支持\xHHSwift 不支持。八进制转义预计将长期不被允许即便\0D被保留而不支持\1..\7、更一般地不支持\DDDD这一决定目前标记为实验性experimental。\x与\u的数字规则上表中H代表任意十六进制字符0-9或A-F区分大小写。与 C 不同、与 Python 相同\x恰好要求两个十六进制数字。与 JavaScript、Rust、Swift 相同Unicode 码点可以用\u{10FFFF}记法按数字表达它接受1 到 8 个十六进制字符这一限制由提案 p002040-unicode-escape-code-length.md 确立理由是对齐 Swift 的 8 位限制、简化解析器并避免\u{000...000E9}这类可写任意前导零的混乱情况。可表达的码点数值范围为 0₁₆–D7FF₁₆ 与 E000₁₆–10FFFF₁₆即排除了代理区 D800–DFFF。开放问题部分语言尤其是 Python支持\N{unicode character name}语法。Carbon 未来可能增加该转义序列设计文档提醒相关提案应关注 C Unicode 研究小组在此领域的工作。\0与后续十进制数字\0转义序列后面不得紧跟十进制数字。当需要在空字节后跟一个十进制数字时应改用\x00例如foo\x00123。这样做的意图是为将来允许十进制转义序列保留可能性。词法实现会在\0后紧跟十进制数字时发出DecimalEscapeSequence诊断提示改用\x00见 string_literal.cpp测试用例fail_decimal_escape.carbon输入\01验证了这一点error: decimal digit follows \0 escape sequence. Use \x00 instead of \0 if the next character is a digit [DecimalEscapeSequence]转义换行\newline反斜杠后跟换行符是一种不产生任何字符串内容的转义序列。该特性是实验性的且只能出现在块字符串字面量中。处理时机在尾随空白被替换为换行符之后因此\ 水平空白 行终止符会移除直到并包括该行终止符在内的空白。与 Rust 不同、与 Swift 相同被转义换行之后那一行的前导空白不会被移除除非这些空白与终止的缩进匹配。非法转义与空白限制以反斜杠开头、但不匹配任何已知转义序列的字符序列是非法的。词法器会发出UnknownEscapeSequence诊断例如\w→ unrecognized escape sequencew并在错误恢复时将该转义展开为自身即去掉引入符\q→q见 string_literal.cpp。除空格以及块字符串字面量中可选的回车换行之外的其他空白字符均被禁止。具体而言词法器在检测到 tab 或其它水平空白时会发出InvalidHorizontalWhitespaceInString诊断whitespace other than plain space must be expressed with an escape sequence in a string literal见 string_literal.cpp测试用例fail_literal_tab_in_string.carbon验证了这一点。其余所有字符包括不可打印字符原样保留。由于所有 Carbon 源文件都必须是合法的 Unicode 字符序列非合法 UTF-8 的代码单元序列只能通过\x转义序列产生。不允许字符串字面量中出现字面 tab 字符这一决定同样是实验性的。以下示例综合展示了上述规则var String: fret I would twere something that would fret the string,\n The master-cord ons \u{2764}\u{FE0F}!; // 该字符串包含两个字符在编码为 UTF-8 之前 // U1F3F9 (BOW AND ARROW) 后跟 U0032 (DIGIT TWO) var String: password \u{1F3F9}2; // 该字符串不包含任何换行字符。 var String: type_mismatch Shall I compare thee to a summers day? Thou art \ more lovely and more temperate.\ ; var String: trailing_whitespace This line ends in a space followed by a newline. \n\ This line starts with four spaces. ;词法层的转义校验与诊断汇总从源码实现看\x与\u的处理位于ExpandAndConsumeEscapeSequencestring_literal.cpp\x要求紧跟两个大写十六进制数字否则发出HexadecimalEscapeMissingDigits错误信息示例escape sequence\xmust be followed by two uppercase hexadecimal digits, for example\x0F。\u要求形如\u{HHHH...}否则发出UnicodeEscapeMissingBracedDigits。\u内的数值超过 0x10FFFF 时发出UnicodeEscapeTooLarge落在代理区0xD800–0xDFFF时发出UnicodeEscapeSurrogate。这些诊断均有对应的词法测试见 toolchain/lex/testdata/string_literals.carbonfail_unknown_escape、fail_unicode_escape_missing_braced_digits、fail_hex_escape_missing_digits、fail_unicode_surrogate、fail_unicode_length等用例。原始字符串字面量为了让字符串内容可以自由包含\与字符串字面量的定界符可以通过在起始定界符前加N个#字符来定制。规则如下只有在后跟N个#时才被识别为闭合定界符。同理只有\后跟N个#时才被识别为转义序列。未后跟N个#的\、或均没有特殊含义。起始定界符转义序列引入符闭合定界符/\例如\n/#/#\#例如\#n#/###/##\##例如\##n##/#####/###\###例如\###n###/###.........示例var String: x # This is the content of the string. The T is the first character of the string. -- This is not the end of the string. #; // 但上一行确实结束了字符串。 // OK最后一个字符是 \ var String: y #Hello\#; var String: z ##Raw strings #nesting###; var String: w #Tab is expressed as \t. Example: \#t#;从上例可见原始字符串可以逐级嵌套#...#、##...##、###...###且可以通过\#n这类写法在原始字符串中按需使用转义序列。该机制源于 Swift 的 raw strings 设计被提案 p000199-string-literals.md 采纳——普通字符串可视为N 0 个#的特例从而普通/原始与单行/多行两对正交特性可以自由组合出全部 4 种字面量形式。在词法实现中StringLiteral::Lex会先统计开头的#数量作为hash_levelstring_literal.cpp随后据此构造带等量#的终止符与转义引入符string_literal.cpp并在扫描内容时仅匹配带#的闭合符与转义序列string_literal.cpp。编码一个字符串字面量最终产生一串 8 位字节byte。与 Carbon 源文件一样字符串字面量以UTF-8编码。然而由于可以通过\xHH转义插入任意字节序列无法保证字符串一定是合法的 UTF-8。这一决定是实验性的如果未来出现直接在字符串字面量中表达其他编码的充分动机应当重新评估。同样地随着库对字符串类型的支持不断演进应当考虑引入一种或许作为默认的保证字符串内容为合法 UTF-8 编码的字符串字面量语法以便在类型系统中区分合法 UTF-8 与任意字符串对于此类字面量还应考虑像 Rust 那样拒绝\xHH中 HH 大于 7F₁₆ 的转义。编译器实现视角词法阶段如何识别字符串字面量在 Carbon 工具链中字符串字面量的词法分析集中在 toolchain/lex/string_literal.h 与 toolchain/lex/string_literal.cpp 两个文件中核心类为StringLiteralKind枚举区分四种形态string_literal.hChar字符字面量仍经由字符串字面量词法路径处理、SingleLinecontent、MultiLinecontent、MultiLineWithDoubleQuotes错误的content写法用于错误恢复。StringLiteral::Lexstring_literal.cpp负责从源码文本中提取字面量先统计#前缀得到hash_level再通过Introducer::Lex解析引入符最后用查找表快速扫描有趣字符\、换行、引号、tab以定位终止符与转义序列。返回std::nullopt表示未发现字面量返回未终止is_terminated_ false的字面量则表示发现了前缀但形式有误用于辅助构造错误。ComputeStringValuestring_literal.cpp在BumpPtrAllocator上展开转义序列并剥离缩进当内容无需校验且无缩进时可直接返回原始内容引用。展开过程保证只会缩短内容CARBON_CHECK(result.size() content_.size())因此无需重分配。调用入口Lexer::LexStringLiteral位于 lex.cpp字符字面量由ComputeCharLiteralValue计算码点值字符串字面量则把计算出的值存入string_literal_values存储并生成StringLiteral令牌遇到未终止字面量时发出UnterminatedString诊断。用词法测试验证行为上述诊断与行为均由基于文件测试file test框架的测试数据锁定可通过以下命令单独运行以简单字符串为例见 string_literals.carbon 文件头部的 TIPbazel test //toolchain/testing:file_test --test_arg--file_teststoolchain/lex/testdata/string_literals.carbon或转储输出bazel run //toolchain/testing:file_test -- --dump_output --file_teststoolchain/lex/testdata/string_literals.carbon多行字符串的对应测试位于 multiline_string_literals.carbon覆盖缩进不匹配fail_indent_mismatch、误用fail_quotes以及引入行尾随注释introducer_trailing_comment等场景。设计演进与相关提案字符串字面量的设计演进可以沿以下提案追溯基础设计提案 p000199-string-literals.md 确立了简单/块字符串、转义序列集合、原始字符串与编码规则其Alternatives considered部分详细记录了块字符串的前导空白移除与终止换行取舍、\x位数、\u{NNNN}记法而非保留\uNNNN/\U00NNNNNN、基于 Swift 而非 Rust 的原始字符串机制#定界而非r前缀以及内部空白拒绝字面 tab等设计决策。Unicode 转义长度提案 p002040-unicode-escape-code-length.md 将\u{...}的位数限定为 1 到 8以简化解析器并尽早失败于明显非法的输入。尾随注释文件类型指示符后可跟尾随注释的规则与提案 p007441-trailing-comments.md 相关。字符字面量字符串字面量与字符字面量共享同一套转义序列字符字面量中\xHH被限制为 ≤0x7F以避免字节值与码点的歧义且永不构成字符字面量、只作为的开头出现从而保证不会被误读。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表