标识符
程序结构
函数:定义函数使用func,main函数不需要
类:struct、class、enum、interface。enum 和 interface 中仅支持定义成员函数。
变量:修饰符 变量名: 变量类型 = 初始值
表达式:可求值的语言元素。表达式不仅可以求值,还有确定的类型。包括算术运算、条件、循环、try表达式
代码块中如果有表达式,则代码块的值和类型与最后一个表达式的值和类型相同,没有表达式则为Unit类型,值为()
代码块本身不是一个表达式,不能单独使用,它将依附函数、条件或循环表达式等执行和求值
if 表达式的条件只能是布尔类型,不能以取值是否为0作为分支依据,let a = if(random()>0.5){1}else{0}
if表达式的类型:
for-in for (i in arr)表达式不能修改迭代变量i,默认是用let定义的。如果不使用迭代变量,使用通配符 _ ,for (_ in arr), for (i in 0..8 where i % 2 == 1) i 为奇数才会执行循环体
break 用于终止当前循环表达式的执行、转去执行循环表达式之后的代码,continue 用于提前结束本轮循环、进入下一轮循环。break 与 continue 表达式的类型都是 Nothing。
整数类型:
Int64或 Float64,并且:当左操作数类型为 Int64 时,右操作数只能为UInt64类型,表达式的类型为 Int64。当左操作数类型为 Float64 时,右操作数只能为 Int64 类型或 Float64 类型,表达式的类型为 Float64。浮点类型:
布尔类型:Bool,true, false
字符类型:Rune
r'zmy' or r"zmy",转义字符 \,通用字符以\u开头,跟花括号{}加1~8个十六进制数,表示Unicode值代表的字符。字符串:String
'zmy', "\"zmy\"", "\n" """
hello,
world
"""
多行原始字符串:一个或多个#跟一个单引号或双引号,##'hi \n zmy'##
插值字符串:是一种包含一个或多个插值表达式的字符串字面量,{}构成一个表达式,用表达式的值去替换。
元祖(tuple):(T1,T2,...,TN),不可改变,长度固定
let x:(Int64, Float64) = (32, 12,98)t[index] 的方式访问某个具体位置的元素 var a: Int64
var b: String
var c: Unit
var f = { => ((1, "abc"), ()) }
((a, b), c) = f() // value of a is 1, value of b is "abc", value of c is '()'
((a, b), _) = ((2, "def"), 3.0) // value of a is 2, value of b is "def", 3.0 is ignored
let c:(name: String, price: Int64) = ("apple", 20)数组类型:Array<T>,引用,不可变类型;
let a:Array<String>=['ss','bb']
let b=[1,2,3]
let a = Array<Int64>() //empty Array
let b = Array<Int64>(a) // Use a to initialize b
let c = Array<Int64>(3, item: 0) // length is 3 and all elements are 0
let d = Array<Int64>(3, {i => i + 1}) // length is 3 and all elements are initialized by the initialization function
Int64或Range,长度为array.sizeVArray<T,$n>,值类型,
var a:VArray<Int64,$3>=[1,2,3]// VArray<T, $N>(initElement: (Int64) -> T)
let b = VArray<Int64, $5>({ i => i}) // [0, 1, 2, 3, 4]
// VArray<T, $N>(item!: T)
let c = VArray<Int64, $5>(item: 0) // [0, 0, 0, 0, 0]
区间类型,Range<T>
[):start..end:step 0..10:1 // 0-9[]: start..=end:step 0..=10:1 // 0-10Uint类型,只有一个值(),仅支持=, ==, !=,print 函数、赋值表达式、复合赋值表达式、自增和自减表达式、循环表达式都是 Unit
Nothing类型,不包含任何值,是所有类型的子类型,break,continue,return和throw的类型是Nothing
目前编译器还不允许在使用类型的地方显式地使用 Nothing 类型。
定义:func add(a: Int64, b!: Int64=1): Int64{return a+b},其中a是非命名参数,b是命名参数,默认值为1,函数定义时返回值类型是可选的,调用方式为add(3, b:2)
函数类型:
(Int64, Int64)->Int64,可以为函数类型显示标记类型参数名let a:(name: String, prince: Int64)->Unit,类型参数名要么都写,要么都不写// 作为参数类型
func printAdd(add:(Int64,Int64)->Unit, a:Int64, b:Int64):Unit{
println(add(a,b))
}
// 作为返回类型
func add(a:Int64, b:Int64):Int64 {
a+b
}
// returnAdd类型为()->(Int64, Int64)->Int64,->是右结合的
func returnAdd():(Int64, Int64)->Int64 {
add
}
main() {
var a = returnAdd()
println(a(1, 2))
}
// 作为变量类型
let f:(Int64, Int64)->Int64 = add
lambda表达式:{p1:T1,...,pn:Tn=>expr|decl},如果无参数{=>expr|decl},支持立即调用let r={=>123}() // r=123
=>不可省略,除非作为尾随lambda:当函数最后一个形参是函数类型,并且函数调用对应的实参是 lambda 时// 尾随lambda
func myIf(a: Bool, fn: () -> Int64) { a() }
myIf(true, { => 100 }) // General function call
myIf(true) { // Trailing closure call
100
}
// 只有一个lambda参数时,可省略()
func f(fn: (Int64) -> Int64) { fn(1) }
f {
i=>i+1
}
func f2(lam:()->Unit){ lam() }
// println("world")作为参数去调用f2
let f2Res = f2{ println("world") }
// 当lambda赋值给变量时,参数类型从变量类型推断
var sum1:(Int64, Int64)->Int64 = { a, b => a+b }
func f(add:(a:Int64, b:Int64)->Int64):Int64 {
add(1, 2)
}
// lambda表达式作为函数实参时,参数类型从函数形参类型推断
add({a, b => a+b })
闭包:一个函数或 lambda 从定义它的静态作用域中捕获了变量,函数或 lambda 和捕获的变量一起被称为一个闭包,这样即使脱离了闭包定义所在的作用域,闭包也能正常运行。
函数的参数缺省值访问了外部局部变量 函数或lambda内访问外部局部变量 class或struct内得成员函数或lambda访问实例成员变量或this
被捕获的变量必须在闭包定义时可见,否则编译报错; 被捕获的变量必须在闭包定义时已经完成初始化,否则编译报错。
func returnAddNum(): (Int64) -> Int64 {
let num: Int64 = 10
func add(a: Int64) {
return a + num
}
add
}
// 闭包add捕获局部变量num,调用returnAddNum后,已脱离其作用域,局部变量num已销毁,但调用add时可正常访问num
main() {
let f = returnAddNum()
println(f(10))
}
如果捕获了var变量的闭包赋值给其他变量或作为函数实参使用,会发生闭包逃逸,var变量可被修改,这样会扩大闭包的作用范围,做成内存浪费或泄漏。
此类闭包只能被调用,不能作为一等公民使用,包括不能赋值给变量,不能作为实参或返回值使用,不能直接将闭包的名字作为表达式使用。
捕获具有传递性,如果一个函数 f 调用了捕获 var 变量的函数 g,且存在 g 捕获的 var 变量不在函数 f 内定义,那么函数 f 同样捕获了 var 变量,此时,f 也不能作为一等公民使用。
func h(){
var x = 1
func g() { x } // captured a mutable variable
func f() {
g() // invoked g
}
return f // Error
}
Flow表达式:流操作符包括两种:表示数据流向的中缀操作符 |> (称为 pipeline)和表示函数组合的中缀操作符 ~> (称为 composition)。
|>适用于对数据流做一系列的处理,e1|>e2 // let v=e1; e2(v) e2 是函数类型的表达式,e1 的类型是 e2 的参数类型的子类型。~>表示两个单参函数的组合,f~>g // {x=>g(f(x))},f,g 均为只有一个参数的函数类型的表达式, f(x) 的返回类型是 g(...) 的参数类型的子类型|>不能与无默认值的命名形参直接一同使用(因为命名形参的调用需要指定命名实参),可以通过lambda表达式传入函数的命名实参。函数命名形参有默认值时,也不可直接使用|>
func f1(a!:Int64):Unit{}
func f2(a!:Int64=2):Unit{}
func f3(a!:Int64, b!:Int64=2):Unit{}
let x1 = 1 |> f1 // error
let x1 = 1 |> {x:Int64 => f1(a:x)} // ok
let x2 = 1 |> f2 // error
let x3 = 1 |> f3 // ok
let x3 = 1 |> {x:Int64 => f3(x, b:3)} // ok
变长参数:参数中最后一个形参是非命名Array类型,
操作符重载:public operator func oper(): Any { }
- 在 func 关键字前面添加 operator 修饰符;
- 操作符函数只能定义在 class、interface、struct、enum 和 extend 中;
- 操作符函数具有实例成员函数的语义,所以禁止使用 static 修饰符;
- 两种实现方式:在struct、enum、class 和 interface内直接定义;使用 extend 的方式,适用于扩展第三方定义的struct、class、enum 和 interface
- 索引操作符
[]:
- 对于取值
let a=arr[i],参数列表中有1至多个非命名参数,不可以有其它命名参数,返回任意类型;- 对于赋值
arr[i]=a,参数列表中有1至多个非命名参数,只有一个命名参数value,不能有默认值。返回类型必须是Unit- 不能使用 this 或 super 调用 () 操作符重载函数
const变量:const PI = 3.14 链接
struct:值类型
静态初始化器至多一个
static init(){}
普通构造函数public init(arg1:Int64){}
主构造函数public Rectangle(name: String, let width:Int64, let hegiht:Int64){},成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时扮演定义成员变量和构造函数参数的功能。用来简化struct定义,还可添加普通形参name
如果 struct 定义中不存在自定义构造函数(包括主构造函数),并且所有实例成员变量都有初始值,则会自动为其生成一个无参构造函数
struct 之间不能继承。
访问修饰符:缺省的含义是 internal
private 表示在 struct 定义内可见。
internal 表示仅当前包及子包(包括子包的子包,详见包章节)内可见。
protected 表示当前模块(详见包章节)可见。
public 表示模块内外均可见。
mut函数:public mut func g(){}
struct 类型是值类型,其实例成员函数无法修改实例本身(值类型对自身的修改通常都应该返回新的副本,不支持原地操作,更多的是一种设计理念)
mut 只能修饰实例成员函数,不能修饰静态成员函数。
mut 函数中的 this 不能被捕获,也不能作为表达式。不能在 mut 函数中对 struct 的实例成员变量进行捕获。
struct 类型在实现 interface 的函数时必须保持一样的 mut 修饰,当 struct 的实例赋值给 interface 类型时是拷贝语义,因此 interface 的 mut 函数并不能修改 struct 实例的值
let声明的struct不能访问mut函数,因为let不可变 不能将 mut 修饰的函数作为一等公民来使用,只能调用这些 mut 函数 非 mut 的实例成员函数(包括 lambda 表达式)不能直接访问 mut 函数
enum RGBColor{|Red|Green|Blue},其中每一项为构造器,也可携带参数enum RGBColor{|Red(UInt8)|Green(UInt8)|Blue(UInt8)},也可定义成员函数、操作符函数、成员属性enum Option<T>{
|Some(T)
|None
}
let a:Option<Int64> = Some(1)
let b:?Int64 = Some(2)// 简化写法 ?T 等价于Option<T>
let c:Option<Int64> = 100 // 编译器会用Some(T)构造器将100封装成Option<Int64>, 而非类型转换
let d:?Int64 = 20
let e = None<Int64>
let score = 90
let a = match(score){
case patter => {}
case patter => {}
case _ => {}
}
常量模式
通配符模式:_用于匹配任意值,通常作为最后一个case
绑定模式:case n => "socre = ${socre}" // score = 90
Tuple模式:匹配元组,let tv=("zmy",12); case ('zmy',age)=>"zmy is ${age} years old"
类型模式:用于判断一个值的运行时类型是否是某个类型的子类型,有两种形式:_: Type(嵌套一个通配符模式 _)和 id: Type(嵌套一个绑定模式 id)case b: Base => b.f()
enum模式:let x = RGBColor.Bule(8); case Blue(n) => "Bule's alpha is ${n}"
匹配 enum 值时,要求 case 之后的模式要覆盖待匹配 enum 类型中的所有构造器,如果未做到完全覆盖,编译器将报错
模式的Refutability(反驳性):指的是模式的匹配是否唯一确定的,不存在争议
match表达式:模式之后可以接一个可选的 where cond;match 表达式要求所有匹配必须是穷尽(exhaustive)的,表达式类型:
在上下文有明确的类型要求时,要求每个 case 分支中 => 之后的代码块的类型是上下文所要求的类型的子类型;
在上下文没有明确的类型要求时,match 表达式的类型是每个 case 分支中 => 之后的代码块的类型的最小公共父类型;
当 match 表达式的值没有被使用时,其类型为 Unit,不要求各分支的类型有最小公共父类型。
if-let表达式:if(let Some(value)<-result),首先对箭头右侧的表达式求值,如果能匹配左侧的模式,则执行if分支,否则执行else分支
while-let表达式:while(let Some(data)<-expr)首先对右侧的表达式求值,如果能匹配左侧的模式,则执行循环体,然后重复执行此过程。如果模式匹配失败,则结束循环
类:class 中也支持定义普通构造函数和主构造函数,终结器(析构函数)的函数固定为 ~init(){//todo} 抽象实例成员函数默认具有 open 的语义
This类型:占位符,代指当前类的类型,只能被作为实例成员函数的返回类型来使用,子类调用父类返回This的函数,得到的是子类类型
继承:
open修饰才可被继承 open修饰符是可选的 sealed修饰符修饰抽象类,表示该抽象类只能在本包被继承, sealed包含public/open的语义,因此定义sealed abstract class时若提供public/open 修饰符,编译器将会告警, sealed不影响子类的继承可见性 open class Base{}
class Drived<:Base{}
init构造函数中可使用super(args) this(args)调用父类或其他构造函数,但两者之间只能调用一个,且必须在第一行super(args) 的形式调用父类构造函数,但不能使用 this(args) 的形式调用本类其它构造函数。函数覆盖(override):
open修饰表示可被覆盖,子类中使用override修饰(可选)redef修饰的静态函数覆盖父类静态函数接口:open interface I{}, 接口用来定义一个抽象类型,不包含数据,但可以定义类型的行为:成员函数、操作符重载、成员属性
open可选,可被sealed修饰public 修饰,不可以声明额外的访问控制修饰符,同时也要求实现类型必须使用 public 实现。& 分隔多个接口,接口间没有顺序要求interface Any{},所有接口默认继承它,所有非接口类型默认实现它。属性(Properties):提供了一个 getter 和一个可选的 setter 来间接获取和设置值。
class Foo {
private var a = 0
// b 是一个典型的属性,封装了外部对 a 的访问
public mut prop b: Int64 {
get() {
println("get")
a
}
set(value) {
println("set")
a = value
}
}
}
var x = Foo()
let y = x.b + 1 // get
x.b = y // set
mut修饰的属性,必须实先get() set(),而无mut修饰的属性,只能有get()在属性的 getter 和 setter 中访问属性自身属于递归调用,与函数调用一样可能会出现死循环的情况。
子类型关系:判定两个类型是否存在子类型关系
C2 <: C1 和 C4 <: C3,因此也有 (C2, C4) <: (C1, C3) 以及 (C4, C2) <: (C3, C1)。(U1) -> S2 和 (U2) -> S1,(U1) -> S2 <: (U2) -> S1 当且仅当 U2 <: U1 且 S2 <: S1;(子类型相比于父类型,输入更宽松,输出更严格,宽进窄出)I2 <: I1,C <: I2,以及 Bool <: I2,隐式存在 C <: I1 以及 Bool <: I1类型转换:仓颉不支持不同类型之间的隐式转换(子类型天然是父类型,所以子类型到父类型的转换不是隐式类型转换),类型转换必须显式地进行
T(e) 得到一个值等于 e,类型为 T 的值[0x0000, 0xD7FF] 或 [0xE000, 0x10FFFF] (即 Unicode scalar value)中时,返回对应的 Unicode scalar value 表示的字符,否则,编译报错(编译时可确定 num 的值)或运行时抛异常。e is T 判断某个表达式的类型是否是指定的类型(或其子类型),返回Boole as T 用于将某个表达式的类型转换为指定的类型。因为类型转换有可能会失败,所以 as 操作返回的是一个 Option 类型。type I64 = Int64,class A<T> where T <: B & Interface扩展可以为在当前 package 可见的类型(除函数、元组、接口)添加新功能,包括:
扩展虽然可以添加额外的功能,但不能变更原有类型的封装性,扩展不支持以下功能
extend String {
public func printSize(){
println("the size is ${this.size}")
}
}
let a = '123'
a.printSize() // the size is 3
被扩展类型是泛型类型时,有两种扩展方式
class Foo<T> where T<: ToString{}
extend Foo<Int64> {} // ok
class Bar{}
extend Foo<Bar> {} // error, Bar未实现ToString接口
class MyList<T> { public let data: Array<T> = Array<T>() }
extend<T> MyList<T> {} // OK
extend<R> MyList<R> {} // OK
extend<T, R> MyList<(T, R)> {} // OK
extend MyList {} // Error
extend<T, R> MyList<T> {} // Error
extend<T, R> MyList<T, R> {} // Error, 泛型只有一个类型参数,只能使用元组形式(T,R)
接口扩展:分为直接扩展和接口扩展,extend<T> Array<T> <: PrintSizeable ,使用扩展为 Array 实现 PrintSizeable 之后,就相当于在 Array 定义时实现接口 PrintSizeable
访问规则:
- 使用 private 修饰的成员只能在本扩展内使用,外部不可见。
- 使用 protected 修饰的成员除了能在本包内被访问,对包外的当前 class 子类也可以访问。
- 没有使用 private,protected 或 public 修饰的成员只能在本包内使用。
- 使用 static 修饰的成员,只能通过类型名访问,不能通过实例对象访问。
- 对 struct 类型的扩展可以定义 mut 函数。
孤儿规则:扩展定义所在包、实现接口所在被,被扩展类型所在包,不能是三个不同的包
- 扩展的实例成员可以访问this
- 但不能使用super,
- 不能访问private成员,非 private 修饰的成员均能被访问
- 扩展不能遮盖被扩展类型的任何成员。
- 扩展也不允许遮盖其它扩展增加的任何成员。
- 在同一个 package 内对同一类型可以扩展多次。
- 在扩展中可以直接调用(不加任何前缀修饰)其它对同一类型的扩展中的非 private 修饰的函数。
扩展泛型时存在泛型约束,则扩展的可见性规则如下:
扩展的导入导出:
基础 Collection 类型,包含 Array、ArrayList、HashSet、HashMap。基础特性如下:
| 类型名称 | 元素可变 | 增删元素 | 元素唯一性 | 有序序列 |
|---|---|---|---|---|
| Array<T> | Y | N | N | Y |
| ArrayList<T> | Y | Y | N | Y |
| HashSet<T> | N | Y | Y | N |
| HashMap<K,V> | K:N, V:Y | Y | K:Y, V:N | N |
ArrayList:导入import std.collection.*包
let a = ArrayList<String>() // Created an empty ArrayList whose element type is String
let b = ArrayList<String>(100) // Created an ArrayList whose element type is String, and allocate a space of 100
let c = ArrayList<Int64>([0, 1, 2]) // Created an ArrayList whose element type is Int64, containing elements 0, 1, 2
let d = ArrayList<Int64>(c) // Use another Collection to initialize an ArrayList
let e = ArrayList<String>(2, {x: Int64 => x.toString()}) // Created an ArrayList whose element type is String and size is 2. All elements are initialized by specified rule function, [0, 1]
package pkg.sub1源码根目录下(src)的包可以没有包声明,此时编译器将默认为其指定包名 default。import fullPackageName.itemName
import fullPackageName.{itemName[, itemName]}
import packageName.name as newName //原名name无法使用
异常类有:Error和Exception,自定义异常只能继承Exception,Error不可手动throw
try表达式,try-with-resources表达式
Option类型结构:
e1??e2,当e1等于Some(v)时返回v的值,否则返回e2的值 . () [] {} 使用,对于T1类型的表达式e,e?.b当e等于Some(v)时,返回Option<T2>.Some(v.b)(T2是v.b的类型),否则返回Option<T>.None。问号操作符(?)支持多层访问,以 a?.b.c?.d 为例((),[] 和 {}同理)。表达式 a 的类型需要是某个 Option<T1> 且 T1 包含实例成员 b,b 的类型中包含实例成员变量 c 且 c 的类型是某个 Option<T2>,T2 包含实例成员 d;表达式 a?.b.c?.d 的类型为 Option<T3>,其中 T3 是 T2 的实例成员 d 的类型;当 a 的值等于 Some(va) 且 va.b.c 的值等于 Some(vc) 时,a?.b.c?.d 的值等于 Option<T3>.Some(vc.d);当 a 的值等于 Some(va) 且 va.b.c 的值等于 None 时,a?.b.c?.d 的值等于 Option<T3>.None(d 不会被求值);当 a 的值等于 None 时,a?.b.c?.d 的值等于 Option<T3>.None(b,c 和 d 都不会被求值)。
仓颉线程和Native线程:仓颉线程是一个语言层面的概念,或者就是一个线程类,而Native线程是操作系统实实在在运行的一个线程,Native线程是主动选择可用的仓颉线程去运行,如果阻塞就挂起选择其他仓颉线程(M : N线程模型)。
创建线程:使用spawn后接一个无参lambda表达式:spawn {=>{}}
spawn 表达式的返回类型是 Future<T>,其中 T 是类型变元,其类型与 lambda 表达式的返回类型一致。调用 Future<T> 的三个成员函数:
get():T:阻塞等待线程执行结束,并返回执行结果get(ns: Int64): Option<T>:阻塞线程执行结束,并返回执行结果,当到达超时时间 ns(纳秒e-9) 时,如果该线程还没有执行结束,将会返回 Option<T>.None。如果 ns <= 0,其行为与 get() 相同。tryGet(): Option<T>:不阻塞线程执行,尝试获取执行结果,执行完成获得结果,否则获得Option<T>.None通过Thread.currentThread或fut.thread获得当前线程对象
可以通过 Future<T> 的 cancel() 方法向对应的线程发送终止请求,该方法不会停止线程执行。开发者需要使用 Thread 的 hasPendingCancellation 属性来检查线程是否存在终止请求。
原子操作 Atomic:仓颉提供整数类型、Bool 类型和引用类型的原子操作,Atomic只能使用构造函数
load, store, swap, compareAndSwap, fetchAdd, fetchSub, fetchAnd, fetchOr, fetchXor,交换操作和算术操作的返回值是修改前的值;compareAndSwap 是判断当前原子变量的值是否等于 old 值,如果等于,则使用 new 值替换,返回true;否则不替换,返回false。load, store, swap, compareAndSwap可重入互斥锁 ReentrantMutex:可重入互斥锁的作用是对临界区加以保护,使得任意时刻最多只有一个线程能够执行临界区的代码。可重入是指线程获取该锁后可再次获得该锁。当前线程获取被其他线程持有的锁不行,会被阻塞,当锁被释放时,该线程会被唤醒。本线程可以再次获取自己持有的锁
Monitor:是一个内置的数据结构,它绑定了互斥锁和单个与之相关的条件变量(也就是等待队列)。Monitor 可以使线程阻塞并等待来自另一个线程的信号以恢复执行
MultiConditionMonitor:是一个内置的数据结构,它绑定了互斥锁和一组与之相关的动态创建的条件变量
synchronized 关键字:配ReentrantMutex一起使用,可以在其后跟随的作用域内自动进行加锁解锁操作
线程局部变量 ThreadLocal
get(): Option<T>:获得仓颉线程局部变量的值,如果值不存在,则返回 Option<T>.Noneset(value: Option<T>):如果传入 Option<T>.None,该局部变量的值将被删除,在线程后续操作中将无法获取宏必须声明在独立的包中(不能和其他 public 函数一起),含有宏的包使用 macro package 来声明。
Tokens:代表程序片段(可以执行的),由若干Token组成,Token可理解为词法单元,取值为enum TokenKind,通过提供 TokenKind 和(对于标识符和字面量)Token 的字符串,可以直接构造任何 Token。构造函数:
Token(k: TokenKind)
Token(k: TokenKind, v: String)
Tokens() // 构造空列表
Tokens(tks: Array<Token>)
Tokens(tks: ArrayList<Token>)
quote:用于构造Tokens,可以使用插值$(input),$后只跟一个标识符的时候可以省略(),其他情况应加上()以表达正确的意图
部分 AST 节点的 ArrayList 列表也可以被插值
let tokens = quote(
arr = $(intList) // [1, 2, 3, 4, 5]
x = $(float) // 1.0
s = $(str) // "Hello"
)
println(tokens)
/*
arr =[1, 2, 3, 4, 5]
x = 1.000000
s = "Hello"
*/
在仓颉语言的编译过程中,首先通过词法分析将代码转换成 Tokens,然后对 Tokens 进行语法解析,得到一个语法树。每个语法树的节点可能是一个表达式、声明、类型、模式等。仓颉 ast 库提供了每种节点对应的类,它们之间具有适当的继承关系。其中,主要的抽象类如下:
通过 ast 库,基本上每种节点都可以从 Tokens 解析。有两种调用解析的方法:使用解析表达式和声明的函数、使用构造函数进行解析
parseExpr(input: Tokens): Expr:将输入的 Tokens 解析为表达式 parseExprFragment(input: Tokens, startFrom!: Int64 = 0): (Expr, Int64):将输入 Tokens 的一个片段解析为表达式,片段从 startFrom 索引开始,解析可能只消耗从索引 startFrom 开始的片段的一部分,并返回第一个未被消耗的 Token 的索引(如果消耗了整个片段,返回值为 input.size) parseDecl(input: Tokens, astKind!: String = ""):将输入的 Tokens 解析为声明,astKind 为额外的设置,具体请见《仓颉编程语言库 API》文档parseDeclFragment(input: Tokens, startFrom!: Int64 = 0): (Decl, Int64):将输入 Tokens 的一个片段解析为声明,startFrom 参数和返回索引的含义和 parseExpr 相同。BinaryExpr(quote(a + b)), FuncDecl(quote(func f1(x: Int64) { return x + 1 }))节点的组成部分,每个组成部分都是 public mut prop,因此可以被查看和更新

let binExpr = BinaryExpr(quote(x * y))
binExpr.leftExpr = BinaryExpr(quote(a + b))
println(binExpr.toTokens()) // (a + b) * y
binExpr.op = Token(TokenKind.ADD)
println(binExpr.toTokens()) // a + b + y
ast 库具备在输出语法树时自动添加括号的功能
非属性宏:只接受被转换的代码,不接受其他参数(属性)public macro MacroName(args: Tokens): Tokens {}, 宏调用使用 () 括起来。括号里面可以是任意合法 tokens,也可以是空。当宏作用于声明时,一般可以省略括号,像是注解一样:
@MacroName func name() {} // Before a FuncDecl
@MacroName struct name {} // Before a StructDecl
@MacroName class name {} // Before a ClassDecl
@MacroName var a = 1 // Before a VarDecl
@MacroName enum e {} // Before a Enum
@MacroName interface i {} // Before a InterfaceDecl
@MacroName extend e <: i {} // Before a ExtendDecl
@MacroName mut prop i: Int64 {} // Before a PropDecl
@MacroName @AnotherMacro(input) // Before a macro call
属性宏:属性宏的定义会增加一个 Tokens 类型的输入 public macro Foo(attrTokens: Tokens, inputTokens: Tokens): Tokens {},属性宏调用时新增的入参 attrTokens 通过 [] 传入,例如:@Foo[1+](2+3)
非属性宏调用时不能加 [],属性宏调用时必须加 [],但可以为空
嵌套宏:仓颉语言不支持宏定义的嵌套;有条件地支持在宏定义和宏调用中嵌套宏调用。(定义和调用中可以包含其他的宏调用)
assertParentContext 来保证内层宏调用一定嵌套在特定的外层宏调用中。如果内层宏调用这个函数时没有嵌套在给定的外层宏调用中,该函数将抛出一个错误。库函数 InsideParentContext 同样用于检查内层宏调用时是否嵌套在特定的外层宏调用中,该函数返回一个布尔值setItem 向外层宏发送信息;随后,当外层宏执行时,调用标准库函数 getChildMessages 接收每一个内层宏发送的信息(一组键/值对映射)宏的编译
# 当前目录: src
# 先编译宏定义文件在当前目录产生默认的动态库文件(允许指定动态库的路径,但不能指定动态库的名字)
cjc macros/m.cj --compile-macro
# 编译使用宏的文件,宏替换完成,产生可执行文件
cjc demo.cj -o demo
# 运行可执行文件
./demo
并行宏展开:可以在编译宏调用文件时添加 --parallel-macro-expansion 选项,启用并行宏展开的能力。编译器会自动分析宏调用之间的依赖关系,无依赖关系的宏调用可以并行执行
diagReport:自定义报错接口 diagReport。方便定义宏的用户,在解析传入 tokens 时,对错误 tokens 内容进行自定义报错。
public func diagReport(level: DiagReportLevel, tokens: Tokens, message: String, hint: String): Unit
// level: 报错信息等级: DiagReportLevel.ERROR, DiagReportLevel.WARNING
// tokens: 报错信息中所引用源码内容对应的 tokens
// message: 报错的主信息
// hint: 辅助提示信息
输出宏展开结果:在编译使用宏的文件时,在选项中,增加--debug-macro,即使用仓颉宏的 debug 模式,在 debug 模式下,会生成临时文件 demo.cj.macrocall,其中包含展开后的宏
宏包定义和导出:仓颉宏的定义需要放在由 macro package 声明的包中,被 macro package 限定的包仅允许宏定义对外可见(不是宏不能对外可见),其他声明仅本包内可见。
在 macro package 中允许其它 macro package 和非 macro package 符号被重导出,在非 macro package 中仅允许非 macro package 符号被重导出
即在定义宏的包内可以重新导出其他的宏包和非宏包,而在普通的包中只能重导出非宏包(重导出就是使用public修饰的import)。
内置编译标记:
源码位置:用于在编译时获取源代码的位置。这几个编译标记可以在任意表达式内部使用,只要能符合类型检查规则即可
@sourcePackage() 展开后是一个 String 类型的字面量,内容为当前宏所在的源码的包名@sourceFile() 展开后是一个 String 类型的字面量,内容为当前宏所在的源码的文件名@sourceLine() 展开后是一个 Int64 类型的字面量,内容为当前宏所在的源码的代码行条件编译:使用 @When 标记,是一种在程序代码中根据特定条件选择性地编译不同代码段的技术 @When[os == "Linux"]。
仓颉不支持编译条件嵌套,@When[os == "Windows"] @When[os == "Linux"]
@When[...] 作为内置编译标记,在导入前处理,由宏展开生成的代码中含有 @When[...] 会编译报错@M0 @When[os == "Windows"]
仓颉提供了五个内置条件变量: os、 backend、 cjc_version、 debug 和 test。仓颉条件编译允许开发者自由组合多个条件编译选项。支持逻辑运算符组合多个条件,支持括号运算符明确优先级
Windows、Linux、macOS、HarmonyOS。cjnative、cjnative-x86、cjnative-x86_64、cjnative-arm、cjnative-aarch64、cjvm、cjvm-x86、cjvm-x86_64、cjvm-arm、cjvm-aarch64。==、!=、>、<、>=、<= 六种操作符,格式为 xx.xx.xx 支持每个 xx 支持 1-2 位数字,计算规则为补位 (补齐 2 位) 比较,例如:0.18.8 < 0.18.11, 0.18.8 == 0.18.08。-g 编译选项, 可以用于在编译代码时进行调试和发布版本之间的切换。debug 条件仅支持逻辑非运算符(!)。--test。test 条件仅支持逻辑非运算符(!)。可以用于区分测试代码与普通代码。@FastNative:仓颉提供 @FastNative 标记用于优化对 C 函数的调用。值得注意的是 @FastNative 只能用于 foreign 声明的函数。
cjpm:仓颉包管理器,`cjpm [subcommand][-v|-h]
[package]
cjc-version = "0.49.1" # 所需 `cjc` 的最低版本要求,必须
name = "demo" # 模块名及模块 root 包名,必须
description = "nothing here" # 描述信息,非必须
version = "1.0.0" # 模块版本信息,必须
compile-option = "" # 额外编译命令选项,非必须
link-option = "" # 链接器透传选项,可透传安全编译命令,非必须
output-type = "executable" # 编译输出产物类型,必须
src-dir = "" # 指定源码存放路径,非必须
target-dir = "" # 指定产物存放路径,非必须
package-configuration = {} # 单包配置选项,非必须
[workspace] # 工作空间管理字段,与 package 字段不能同时存在
members = []
build-members = []
test-members = []
compile-option = ""
link-option = ""
target-dir = ""
[dependencies] # 源码依赖配置项
aoo = { version = "1.0.0" } # 导入中心仓依赖
boo = "1.1.0" # 导入中心仓依赖
coo = { git = "xxx",branch = "dev" , version = "1.0.0"} # 导入 `git` 依赖,`version`字段可缺省
doo = { path = "./pro1" ,version = "1.0.0"} # 导入源码依赖,`version`字段可缺省
[test-dependencies] # 测试阶段的依赖配置项
[ffi.c] # 导入 `c` 库依赖
clib1.path = "xxx"
[profile] # 命令剖面配置项
build = {}
test = {}
customized-option = {}
[target.x86_64-unknown-linux-gnu] # 后端和平台隔离配置项
compile-option = "value1" # 额外编译命令选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须
link-option = "value2" # 链接器透传选项,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须
[target.x86_64-w64-mingw32.dependencies] # 适用于对应 target 的源码依赖配置项,非必须
[target.x86_64-w64-mingw32.test-dependencies] # 适用于对应 target 的测试阶段依赖配置项,非必须
[target.cjvm.bin-dependencies] # 仓颉二进制库依赖,适用于特定 target 的编译流程和指定该 target 作为交叉编译目标平台的编译流程,非必须
path-option = ["./test/pro0", "./test/pro1"]
[target.cjvm.bin-dependencies.package-option]
"pro0.xoo" = "./test/pro0/pro0.xoo.cjo"
"pro0.yoo" = "./test/pro0/pro0.yoo.cjo"
"pro1.zoo" = "./test/pro1/pro1.zoo.cjo"
init Init a new cangjie module
check Check the dependencies
update Update cjpm.lock
tree Display the package dependencies in the source code
build Compile the current module
run Compile and run an executable product
test Unittest a local package or module
clean Clean up the target directory
publish Push a module to the repository
load Load a module from the repository
list Get module list from the repository
install Install a cangjie binary
uninstall Uninstall a cangjie binary