词法结构

本章主要介绍仓颉编程语言的词法结构,完整的词法和语法的 BNF 表示请参见附录 A。

注:为了增加文档的可读性,正文中的语法定义会和附录中的语法定义略有不同,正文中会将符号和关键字替换为它们的字面表示(而非词法结构中的名字)。

标识符和关键字

标识符可以用作变量、函数、类型、packagemodule 等等的名字。将标识符分为普通标识符和原始标识符(raw identifier),普通标识符是除了关键字以外,由字母(大写和小写的 ASCII 编码的拉丁字母 A-Za-z)或下划线(ASCII 编码的 _)开头,后接任意长度的字母、数字(ASCII 编码的数字 0-9)或下划线(ASCII 编码的 _)组合而成的字符串,原始标识符是在普通标识符的外面加上一对反引号(“`..` ”),并且反引号内可以使用关键字。标识符的语法定义为:

identifier
    : Identifier
    | PUBLIC
    | PRIVATE
    | PROTECTED
    | OVERRIDE
    | ABSTRACT
    | SEALED
    | OPEN
    | REDEF
    | GET
    | SET
    ;

Identifier
    : '_'* Letter (Letter | '_' | DecimalDigit)*
    | '`' '_'* Letter (Letter | '_' | DecimalDigit)* '`'
    ;

Letter
    : [a-zA-Z]
    ;
DecimalDigit
    : [0-9]
    ;

关键字是不能作为标识符使用的特殊字符串,仓颉语言的关键字如下表所示:

Keyword
asbreakBool
casecatchclass
constcontinueRune
doelseenum
extendforfrom
funcfalsefinally
foreignFloat16Float32
Float64ifin
isinitinout
importinterfaceInt8
Int16Int32Int64
IntNativeletmut
mainmacromatch
Nothingoperatorprop
packagequotereturn
spawnsuperstatic
structsynchronizedtry
thistruetype
throwThisunsafe
UnitUInt8UInt16
UInt32UInt64UIntNative
varVArraywhere
while

上下文关键字是可以作为标识符使用的特殊字符串,它们在部分语法中作为关键字存在,但也可以作为普通标识符使用。

Contextual Keyword
abstractopenoverride
privateprotectedpublic
redefgetset
sealed

分号和换行符

有两个符号可以表示表达式或声明的结束:分号(;)和换行符。其中,; 的含义是固定的,无论出现在什么位置都表示表达式或声明的结束,可以将多个使用 ; 分隔的表达式或声明写在同一行。但换行符的含义并不固定,根据它出现的位置不同,既可以像空格符一样作为两个 token 的分隔符,也可以像 ; 一样作为表达式或声明的结束符。

换行符可以出现在任意两个 token 之间,但除了下面列出的禁止使用换行符作为两个 token 之间的分隔符的场景、字符串字面值和多行注释之外,其他情况下,将依据“最长匹配”原则(尽可能地将更多的 token 组成一条合法的表达式或声明)确定将换行符处理为 token 间的分隔符拟或表达式或声明的结束符。“最长”匹配结束前遇到的换行符会被处理为 token 间的分隔符,匹配结束后的换行符被处理为表达式或声明的结束符。举例如下:

let width1: Int32 = 32 // The newline character is treated as a terminator.
let length1: Int32 = 1024 // The newline character is treated as a terminator.
let width2: Int32 = 32; let length2: Int32 = 1024 // The newline character is treated as a terminator.
var x = 100 + // The newline character is treated as a connector.
200 * 300 - // The newline character is treated as a connector.
50 // The newline character is treated as a terminator.

禁止使用换行符作为两个 token 之间的分隔符的场景:

  • 一元操作符和操作数之间禁止使用换行符做分隔符;
  • 调用表达式中,( 和它之前的 token 之间禁止使用换行符做分隔符;
  • 索引访问表达式中,[ 和它之前的 token 之间禁止使用换行符做分隔符;
  • constant pattern 中, $ 和它之后的标识符之间禁止使用换行符做分隔符;

注:上述场景只是禁止了换行符作为两个 token 之间的分隔符的功能,并不代表这些场景中不能使用换行符(如果使用了换行符,会被直接处理为表达式或声明的结束符)。

字符串字面值和多行注释也不适用于“最长匹配”原则:

  • 对于单行字符串,当遇到第一个匹配的非转义双引号,即停止匹配;

  • 对于多行字符串,当遇到第一个匹配的非转义三个双引号,即停止匹配;

  • 对于多行原始字符串,当遇到第一个匹配的非转义双引号以及和开头相同个数的井号(#),即停止匹配;

  • 对于多行注释,当遇到第一个匹配的 */,即停止匹配;

字面量

字面量是一种表达式,它表示的是一个不能被修改的值。

字面量也有类型,仓颉中拥有字面量的类型包括:整数类型、浮点数类型、Rune 类型、布尔类型和字符串类型。字面量的语法为:

literalConstant
    : IntegerLiteral
    | FloatLiteral
    | RuneLiteral
    | booleanLiteral
    | stringLiteral
    ;
    
stringLiteral
    : lineStringLiteral
    | multiLineStringLiteral
    | MultiLineRawStringLiteral
    ;

整数类型字面量

整数类型字面量有 4 种进制表示形式:二进制(使用0b0B前缀)、八进制(使用0o0O前缀)、十进制(没有前缀)、十六进制(使用0x0X前缀)。同时可以加可选的后缀来指定整数类型字面量的具体类型。

整数类型字面量的语法定义为:

IntegerLiteralSuffix
   : 'i8' |'i16' |'i32' |'i64' |'u8' |'u16' |'u32' | 'u64'
   ; 
 
IntegerLiteral
   : BinaryLiteral IntegerLiteralSuffix?
   | OctalLiteral IntegerLiteralSuffix?
   | DecimalLiteral '_'* IntegerLiteralSuffix?
   | HexadecimalLiteral IntegerLiteralSuffix?
   ;

BinaryLiteral
	: '0' [bB] BinDigit (BinDigit | '_')*
	;

BinDigit
	: [01]
	;

OctalLiteral
	: '0' [oO] OctalDigit (OctalDigit | '_')*
	;

OctalDigit
	: [0-7]
	;

DecimalLiteral
	: ([1-9] (DecimalDigit | '_')*) | DecimalDigit
	;

DecimalDigit
	: [0-9]
	;

HexadecimalLiteral
	: '0' [xX] HexadecimalDigits
	;
HexadecimalDigits
   	: HexadecimalDigit (HexadecimalDigit | '_')*
   	;
    
HexadecimalDigit
   	: [0-9a-fA-F]
   	;

其中IntegerLiteralSuffix后缀与类型的对应为:

SuffixTypeSuffixType
i8Int8u8UInt8
i16Int16u16UInt16
i32Int32u32UInt32
i64Int64u64UInt64

浮点数类型字面量

浮点数类型字面量有 2 种进制表示形式:十进制(没有前缀)和十六进制(使用0x0X前缀)。十进制浮点数中,整数部分和小数部分(包含小数点)需要至少包含其一,并且无小数部分时必须包含指数部分(使用eE前缀)。十六进制浮点中,整数部分和小数部分(包含小数点)需要至少包含其一,并且必须包含指数部分(使用pP前缀)。同时可以加可选的后缀来指定浮点数类型字面量的具体类型。

浮点数数类型字面量的语法定义为:

FloatLiteralSuffix
    : 'f16' | 'f32' | 'f64'
    ;
 
FloatLiteral
    : (DecimalLiteral DecimalExponent | DecimalFraction DecimalExponent? | (DecimalLiteral DecimalFraction) DecimalExponent?)  FloatLiteralSuffix? 
    | (Hexadecimalprefix (HexadecimalDigits | HexadecimalFraction | (HexadecimalDigits HexadecimalFraction)) HexadecimalExponent) 

DecimalFraction 
    : '.' DecimalFragment
    ;

DecimalFragment
    : DecimalDigit (DecimalDigit | '_')*
    ;
    
DecimalExponent 
    : FloatE Sign? DecimalFragment
    ;
    
HexadecimalFraction 
    : '.' HexadecimalDigits
    ;

HexadecimalExponent 
    : FloatP Sign? DecimalFragment
    ;
    
FloatE 
    : [eE]
    ;

FloatP 
    : [pP]
    ;

Sign 
    : [-]
    ;

Hexadecimalprefix 
    : '0' [xX]
    ;

其中FloatLiteralSuffix后缀与类型的对应为:

SuffixType
f16Float16
f32Float32
f64Float64

布尔类型字面量

布尔类型字面量只有两个:truefalse

booleanLiteral
    : 'true'
    | 'false'
    ;

字符串类型字面量

字符串字面量可以分为三类:单行字符串字面量,多行字符串字面量,多行原始字符串字面量。

单行字符串字面量使用一对单引号或双引号定义。引号中的内容可以是任意数量的任意字符。如果想要将引号或反斜杠(\)作为字符串本身的字符包括在内,需要在其前面加上(\)。单行字符串字面量不能通过包含换行符来跨越多行。

单行字符串字面量的语法定义为:

lineStringLiteral
    : '"' (lineStringExpression | lineStringContent)* '"'
    ;
    
lineStringExpression
    : '${' SEMI* (expressionOrDeclaration (SEMI+ expressionOrDeclaration?)*) SEMI* '}'
    ;
    
lineStringContent
    : LineStrText
    ;
    
LineStrText
    : ~["\\\r\n]
    | EscapeSeq
    ;

多行字符串字面量开头结尾需各存在三个双引号(""")或三个单引号(''')。引号中的内容可以是任意数量的任意字符。如果要将用于括起字符串的三个引号("')或反斜杠(\)作为字符串本身的字符,则必须在它们前面加上反斜杠(\)。如果在开头的三个双引号后没有换行符,或在当前文件结束之前没有遇到非转义的三个双引号,则编译报错。不同于单行字符串字面量,多行字符串字面量可以跨越多行。

多行字符串字面量的语法定义为:

multiLineStringLiteral
    : '"""' NL (multiLineStringExpression | multiLineStringContent)* '"""'
    ;
    
multiLineStringExpression
    : '${' end* (expressionOrDeclaration (end+ expressionOrDeclaration?)*) end* '}'
    ;
    
multiLineStringContent
    : MultiLineStrText
    ;

MultiLineStrText
    : ~('\\')
    | EscapeSeq
    ;

多行原始字符串字面量以一个或多个井号(#)和一个单引号或双引号('")开头,后跟任意数量的合法字符,直到出现与字符串开头相同的引号和与字符串开头相同数量的井号为止。在当前文件结束之前,如果还没遇到匹配的双引号和相同个数的井号,则编译报错。与多行字符串字面量一样,原始多行字符串字面量可以跨越多行。不同支持在于,转义规则不适用于多行原始字符串字面量,字面量中的内容会维持原样(转义字符不会被转义)。

多行原始字符串字面量的语法定义为:

MultiLineRawStringLiteral
    : MultiLineRawStringContent
    ;

fragment MultiLineRawStringContent
    : '#' MultiLineRawStringContent '#' 
    | '#' '"' .*? '"' '#'
    ;

Rune 类型字面量

一个 Rune 字面量由字符r开头,后跟一个(单引号或双引号)单行字符串字面量。字符串字面量内必须恰好包含一个字符。语法为:

RuneLiteral
    : 'r' '\'' (SingleChar | EscapeSeq) '\''
    : 'r' '"' (SingleChar | EscapeSeq) '"'
    ;

fragment SingleChar
	:	~['\\\r\n]
	;

EscapeSeq
    : UniCharacterLiteral
    | EscapedIdentifier
    ;

fragment UniCharacterLiteral
    : '\\' 'u' '{' HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit '}'
    | '\\' 'u' '{' HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit HexadecimalDigit '}'
    ;

fragment EscapedIdentifier
    : '\\' ('t' | 'b' | 'r' | 'n' | '\'' | '"' | '\\' | 'f' | 'v' | '0' | '$')
    ;

操作符

下表列出了仓颉支持的所有操作符(越靠近表格顶部,操作符的优先级越高),关于每个操作符的详细介绍,请参考[表达式]。

OperatorDescription
@Macro call expression
.Member access
[]Index access
()Function call
++Postfix increment
--Postfix decrement
?Question mark
!Logic NOT
-Unary negative
**Power
*Multiply
/Divide
%Remainder
+Add
-Subtract
<<Bitwise left shift
>>Bitwise right shift
..Range operator
..=
<Less than
<=Less than or equal
>Greater than
>=Greater than or equal
isType test
asType cast
==Equal
!=Not equal
&Bitwise AND
^Bitwise XOR
``
&&Logic AND
`
??coalescing
`>`
~>Composition
=Assignment
**=Compound assignment
*=
/=
%=
+=
-=
<<=
>>=
&=
^=
`=`
&&=
`

注释

仓颉支持如下注释方式:

单行注释,以 // 开头,语法定义为:

LineComment
    : '//' ~[\n\r]*
    ;

多行注释,注释内容写在 /**/ 之内,支持嵌套,语法定义为:

DelimitedComment
    : '/*' ( DelimitedComment | . )*? '*/'
    ;