NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.3
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置Canjie-SDK
WARNING博主在此之前, 基本只接触过C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与C/C++中的相似概念作类比, 见谅
此样式内容, 表示文档原文内容
标识符和关键字
如果已经接触过其他编程语言, 对”标识符”和”关键词”应该并不陌生, 不过每种语言的标识符和关键字规则大概都会有一些差异
仓颉的文档中对这两个概念在仓颉语言中的应用做了说明
标识符
词法结构中 关于标识符描述是:
标识符可以用作变量、函数、类型、
package和module等等的名字将标识符分为普通标识符和原始标识符(raw identifier)
普通标识符是除了关键字以外, 由字母 (大写和小写的 ASCII 编码的拉丁字母
A-Z和a-z) 或下划线 (ASCII 编码的_) 开头, 后接任意长度的字母、数字 (ASCII 编码的数字0-9) 或下划线 (ASCII 编码的_) 组合而成的字符串原始标识符是在普通标识符的外面加上一对反引号
(``), 并且反引号内可以使用关键字
而开发指南中, 关于标识符做了一些更准确的补充:
仓颉语言使用 Unicode 标准 15.0.0, 在 Unicode 标准中,
XID_Start和XID_Continue属性用于标记可以作为 Unicode 标识符 (Identifier) 的起始字符和后续字符, 其详细定义请参见 Unicode 标准文档其中,
XID_Start包含中文和英文等字符,XID_Continue包含中文、英文和阿拉伯数字等字符仓颉编程语言的标识符分为普通标识符和原始标识符两类, 它们遵从不同的命名规则.
普通标识符不能和仓颉关键字相同, 其取自以下两类字符序列:
- 由
XID_Start字符开头, 后接任意长度的XID_Continue字符.- 由一个
_开头, 后接至少一个XID_Continue字符.
从描述中, 可以提取出一些结论:
-
标识符, 实际是开发者 为 变量、函数、类型、
package和module等东西 起的名字 -
仓颉语言中存在两种标识符类型: 普通标识符和原始标识符
-
普通标识符 不能以数字开头、不能用仓颉的关键字作为标识符、不能只有一个
_、不能包含非XID_Continue和非XID_Start中的字符// 合法普通标识符abc_abcabc_a1b2c3a_b_ca1_b2_c3仓颉__こんにちは// 不合法普通标识符ab&c // & 不是 XID_Continue 字符3abc // 阿拉伯数字不是 XID_Start 字符, 因此, 数字不能作为起始字符_ // _ 后至少需要有一个 XID_Continue 字符while // while 是仓颉关键字, 普通标识符不能使用仓颉关键字 -
原始标识符 用```包裹`, 但不能以数字开头, 可以用仓颉的关键字
// 合法原始标识符`abc``_abc``a1b2c3``if``while``à֮̅̕b`// 不合法原始标识符`ab&c` // & 不是 XID_Continue 字符`3abc` // 阿拉伯数字不是 XID_Start 字符, 因此, 数字不能作为起始字符
关键字
关键字是不能作为标识符使用的特殊字符串, 仓颉语言的关键字如下表所示:
| Keyword | ||
|---|---|---|
as | break | Bool |
case | catch | class |
const | continue | Rune |
do | else | enum |
extend | for | from |
func | false | finally |
foreign | Float16 | Float32 |
Float64 | if | in |
is | init | inout |
import | interface | Int8 |
Int16 | Int32 | Int64 |
IntNative | let | mut |
main | macro | match |
Nothing | operator | prop |
package | quote | return |
spawn | super | static |
struct | synchronized | try |
this | true | type |
throw | This | unsafe |
Unit | UInt8 | UInt16 |
UInt32 | UInt64 | UIntNative |
var | VArray | where |
while |
上面的关键字中, 一大部分是数据类型和修饰符, 具体之后再了解吧
上下文关键字是可以作为标识符使用的特殊字符串, 它们在部分语法中作为关键字存在, 但也可以作为普通标识符使用.
| Contextual Keyword | ||
|---|---|---|
abstract | open | override |
private | protected | public |
redef | get | set |
sealed |
从文档中看, 这部分是关键字, 但是可以作为普通标识符用
分号和换行符
仓颉语言中 有两个符号可以表示表达式或声明的结束: 分号
;和换行符其中,
;的含义是固定的, 无论出现在什么位置都表示表达式或声明的结束, 可以将多个使用;分隔的表达式或声明写在同一行但换行符的含义并不固定, 根据它出现的位置不同, 既可以像空格符一样作为两个
token(词法单元)的分隔符, 也可以像;一样作为表达式或声明的结束符
意思是, 仓颉中表达式或声明的结束, 可以用;, 也可以为换行(不是空)
比如:
let val1 = 20let val2 = 30;在仓颉中, 这两种方式都可以看作表达式或声明的结束
且, 以;作为表达式或声明结束时, 同一行可以有多个表达式或声明
let val1 = 20; let val2 = 30;以换行为表达式或声明结束时, 因为换行, 当然只能有一个
但, 换行并不总是表示表达式或声明的结束, 还可以被当作空格作为两个token的分隔符
举个例子:
let val1 =30 +20// 这样定义的val1值会是50, 是成立的文档中还提到:
换行符可以出现在任意两个
token之间但除了下面列出的禁止使用换行符作为两个
token之间的分隔符的场景、字符串字面值和多行注释之外, 其他情况下, 将依据”最长匹配”原则 (尽可能地将更多的token组成一条合法的表达式或声明) 确定将换行符处理为token间的分隔符拟或表达式或声明的结束符“最长”匹配结束前遇到的换行符会被处理为
token间的分隔符, 匹配结束后的换行符被处理为表达式或声明的结束符禁止使用换行符作为两个
token之间的分隔符的场景:
一元操作符和操作数之间禁止使用换行符做分隔符;
调用表达式中,
(和它之前的token之间禁止使用换行符做分隔符;索引访问表达式中,
[和它之前的token之间禁止使用换行符做分隔符;
constant pattern中,$和它之后的标识符之间禁止使用换行符做分隔符;
意思为, 除了提到的四种换行符不能 像空格一样做为分割符的情况, 其他情况 换行符按照最长匹配原则 可像空格一样作为分隔符使用
禁止使用换行符作为两个token之间的分隔符的场景:
-
一元操作符和操作数之间禁止使用换行符做分隔符;
let val = -10// - 是一元操作符, 与操作数之间不能使用换行做分隔符 -
调用表达式中,
(和它之前的token之间禁止使用换行符做分隔符;func name() {return "55"}main() {let test: String = name()// 试图将()与name 用换行符做为分隔符 调用, 这样是禁止的// 正确的为 name()println(test)return 0} -
索引访问表达式中,
[和它之前的token之间禁止使用换行符做分隔符;main() {var arr: Array<Int64> = [1, 2, 3, 4]println(arr[0])// 试图将[]与arr 用换行符做为分隔符 调用, 这样是禁止的// 正确的为 arr[]return 0} -
constant pattern中,$和它之后的标识符之间禁止使用换行符做分隔符;main() {let name = "humid1ch"println("hello ${name}")// 试图将$与{name} 用换行符做为分隔符 调用, 这样是禁止的// 正确的为 ${name}return 0}
字面量
字面量是一种表达式, 它表示的是一个不能被修改的值.
字面量也有类型, 仓颉中拥有字面量的类型包括: 整数类型、浮点数类型、Rune 类型、布尔类型和字符串类型.
实际上字面量就是类似: 1234 12.34 '1' "1234" 这样的常量值
整数类型字面量
整数类型字面量有 4 种进制表示形式: 二进制 (使用
0b或0B前缀) 、八进制 (使用0o或0O前缀) 、十进制 (没有前缀) 、十六进制 (使用0x或0X前缀)同时可以加可选的后缀来指定整数类型字面量的具体类型
具体语法定义可查看具体文档
即, 仓颉中的整型字面量, 支持: 二进制、八进制、十进制和十六进制
// 二进制 需要加前缀 0b/0B0b000011110b00001111i80b00001111_i80b0000_1111_i80b0000_1111_u8
// 八进制 需要加前缀 0o/0O0o765430o76543i160o76543_i160o76_543_i160o76_543_u16
// 十进制 无前缀1234567812345678i3212345678_i3212_345_678_i3212_345_678_u32
// 十六进制 需要加前缀 0x/0X0xFE0xFEi80xFE_i80xFE_DC_i160xFE_DC_u16整形字面量可以用_分割, 没有实际意义, 同时可以加后缀, 指定目标类型
| Suffix | Type | Suffix | Type |
|---|---|---|---|
i8 | Int8 | u8 | UInt8 |
i16 | Int16 | u16 | UInt16 |
i32 | Int32 | u32 | UInt32 |
i64 | Int64 | u64 | UInt64 |
浮点数类型字面量
浮点数类型字面量有 2 种进制表示形式: 十进制 (没有前缀) 和十六进制 (使用
0x或0X前缀)十进制浮点数中, 整数部分和小数部分 (包含小数点) 需要至少包含其一, 并且无小数部分时必须包含指数部分 (使用
e或E前缀)十六进制浮点中, 整数部分和小数部分 (包含小数点) 需要至少包含其一, 并且必须包含指数部分 (使用
p或P前缀)同时十进制浮点数可以加可选的后缀来指定浮点数类型字面量的具体类型
仓颉语言中, 浮点数字面量, 稍微复杂一点e表示 *10的指数 p表示 *2的指数
// 十进制100.0100.0f16100.0_f16100_.0_f161e2f161e2_f161.2e2_f161_.2e2_f161_.2_e2_f16
// 十六进制0x1p0 // 1.0 * 2^0 = 1.00x1.1p0 // (1 + 1/16) * 2^0 = 1.06250x1.1p2 // (1 + 1/16) * 2^2 = 1.0625 * 4 = 4.25000x1_.1p2 // (1 + 1/16) * 2^2 = 1.0625 * 4 = 4.25000x1_.1_p2 // (1 + 1/16) * 2^2 = 1.0625 * 4 = 4.2500如果存在小数点, 要把整数部分和小数部分 分开看, 但乘指数时, 要当作整体计算
十六进制必须加指数部分p或P
布尔类型字面量
布尔类型字面量只有两个:
true和false
即, 仓颉中存在布尔类型字面量true和false
字符串类型字面量
字符串字面量可以分为三类: 单行字符串字面量, 多行字符串字面量, 多行原始字符串字面量
单行字符串字面量
单行字符串字面量使用一对单引号或双引号定义
引号中的内容可以是任意数量的任意字符, 如果想要将引号或反斜杠(
\)作为字符串本身的字符包括在内, 需要在其前面加上(\)单行字符串字面量不能通过包含换行符来跨越多行
很简单, 即:
// 合法的单行字符串字面量"humid1ch"'humid1ch'"'humid1ch'"'"humid1ch"'"humi\\d1ch"'humi\\d1ch'"\"humid1ch\""'\'humid1ch\''
// 不合法的"humid1ch"""humid1ch""多行字符串字面量
多行字符串字面量开头结尾需各存在三个双引号 (""") 或三个单引号 (''') , 引号中的内容可以是任意数量的任意字符
如果要将用于括起字符串的三个引号 (
"或') 或反斜杠 (\) 作为字符串本身的字符, 则必须在它们前面加上反斜杠 (\)如果在开头的三个双引号后没有换行符, 或在当前文件结束之前没有遇到非转义的三个双引号, 则编译报错
不同于单行字符串字面量, 多行字符串字面量可以跨越多行
也很简单:
// 合法的多行字符串字面量'''humid1chhumid1ch'''
"""humid1chhumid1ch"""
'''humid1ch\'''humid1ch'''
"""humid1ch\"""humid1ch"""
'''humid1ch"""humid1ch'''
"""humid1ch'''humid1ch"""
// 不合法的多行字符串字面量'''humid1ch'''"""humid1ch"""
"""humid1ch"""humid1ch"""
"""humid1ch值得注意的是, 多行字符串字面量, 起始的"""或'''之后, 必须换行
多行原始字符串字面量
以一个或多个井号 (
#) 和一个单引号或双引号 ('或") 开头, 后跟任意数量的合法字符, 直到出现与字符串开头相同的引号和与字符串开头相同数量的井号为止在当前文件结束之前, 如果还没遇到匹配的双引号和相同个数的井号, 则编译报错
与多行字符串字面量一样, 原始多行字符串字面量可以跨越多行. 不同支持在于, 转义规则不适用于多行原始字符串字面量, 字面量中的内容会维持原样** (转义字符不会被转义) **
// 合法的多行原始字符串字面量#'humid1chhumid1ch'#
##'humid1chhumid1ch'##
##"humid1chhumid1ch"##
##"humid1ch"\humid1ch"\"##
// 不合法的多行原始字符串字面量#'humid1chhumid1ch#'
##'humid1chhumid1ch'#
##'humid1chhumid1chRune类型字面量
一个 Rune 字面量由字符
r开头, 后跟一个 (单引号或双引号) 单行字符串字面量字符串字面量内必须恰好包含一个字符
Rune类型字面量, 实际就是单个字符字面量:
// 合法的Rune类型字面量r'1'r"1"r'\r' // 转义字符r"\n" // 转义字符r'\u{4E2D}' // Unicode转义 中文字符"中" (U+4E2D)r'\u{1F600}' // Unicode转义 😀 表情符号 (U+1F600)
// 不合法的Rune类型字面量r'' // 错误: 空内容r'abc' // 错误: 多个字符r'\u{}' // 错误: 空的 Unicode 转义r'\u{123456789}' // 错误: 超过8位十六进制r' // 错误: 未闭合的引号操作符
下表列出了仓颉支持的所有操作符 (越靠近表格顶部, 操作符的优先级越高) , 关于每个操作符的详细介绍, 请参考[表达式].
| Operator | Description |
|---|---|
@ | 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 |
is | Type test |
as | Type cast |
== | Equal |
!= | Not equal |
& | Bitwise AND |
^ | Bitwise XOR |
| | |
&& | Logic AND |
|| | |
?? | coalescing |
|> | |
~> | Composition |
= | Assignment |
**= | Compound assignment |
*= | |
/= | |
%= | |
+= | |
-= | |
<<= | |
>>= | |
&= | |
^= | |
|= | |
&&= | |
||= |
注释
仓颉支持如下注释方式:
单行注释, 以
//开头多行注释, 注释内容写在
/*和*/之内, 支持嵌套
仓颉的注释语法, 和C++一致:
// 单行注释
/*多行注释*/仓颉编程语言的语言规约第一部分, 就是本篇文章阅读的内容: 词法结构
一个语言的词法结构, 在一个语言的学习中是最重要的一部分之一
大概浏览了一下仓颉文档的语言规约, 对我这个基本只接触过C/C++的人来说, 有太多熟悉又陌生的词汇
之后的文章慢慢阅读吧