语法文档
源码结构
nature 源码以 .n 作为扩展名称,例如 main.n,以下是一个简单程序示例
import fmt // import 模块导入,fmt 模块是标准库的字符串格式化模块
fn main() { // fn 用于函数声明,main 函数作为程序的执行入口
fmt.printf('hello %s', 'world') // 调用 fmt 模块中的 printf 函数进行格式化输出
}
这个程序的功能是在控制台输出 hello world
只有 fn、import、var、const、type 这几种语句可以直接在文件顶层声明,并具有全局作用域,能够被其他模块进行引用,其他语句,如 if、for、return 等只能在函数作用域内进行声明。
import fmt
var global_v = 1
const PI = 3.14
type global_t = int
fn main() {
var localv = 2
type local_t = int
if true {
}
for true {
}
}
if true { // x 不允许在全局作用域中声明 if 语句
}
变量
自动类型推导
var foo = 1 // v 声明变量 foo 并赋值,foo 类型自动推导为 int
var foo = 1 // x 同一作用域下,不允许重复声明变量
if (true) {
var foo = 2 // v 不同作用域下允许重复声明
}
不使用类型推导,直接声明变量的类型
int foo = 1 // v
u8 foo2 = 1 // v 字面量自动类型推导
float bar = 2.2 // v
string car = 'hello world' // v 字符串使用单引号包裹
string car2 = "hello world" // v 支持双引号
string car3 = `hello
world` // v 支持反引号
foo = 2 // v 变量允许重新定值
foo = 'hello world' // x foo 已经定义为 int 类型变量,不允许使用字符串赋值
i8 f2 = 12 // v 字面量能够根据类型进行自动转换
i16 f3 // x 变量声明必须赋值
复合类型变量定义
string bar = '' // v 请使用这种方式声明一个空的字符串
[int] baz = [] // v 声明空 vec
var baz2 = vec_new<string>('default value', 10) // 方式 2
{float} s = {} // v 声明空 set
var s = set_new<float>() // v 方式2
{string:float} m = {} // v 声明空 map
var m = map_new<string,float>() // v 方式2
(int,bool,bool) t = (1, true, false) // v 定义 tuple, 使用方式 t[0], t[1], t[2]
var (t1, t2, t3) = (1, true, 'hello') // tuple 解构声明
var sum = fn(int a, int b):int { // 闭包声明
return a + b
}
var baz = [] // x, 无法推导 vec element 的类型
bar = null // x 不允许将 null 赋值给任何类型复合类型或者简单类型
如何赋值为 null?
// 方式 1 nullable
string? s = null
s = 'i am string'
// 方式 2
nullable<string> s2 = null
s2 = 'hello world'
// 方式 3 union
type union_test = int|float|null
union_test u = null
// 方式 3 any
any s3 = null // any 是一种特殊类型的 union,默认包含任意类型
常量
const PI = 3.14 // v float 类型,常量定义不能声明类型,编译器会自动进行推导
const STR = 'hello world' // v 字符串类型
const INDEX = 1 // v int 类型
const B = true // v bool 类型
const STR2 = STR + 'byebye' // v 常量之间折叠计算
const AREA = PI * 2 * 2 // v 计算示例,支持和常见的运算符
const RESULT = test() // x 常量定义只支持字面量 integer/float/string/bool 类型
常量定义推荐使用大写下划线分词。
控制结构
if 语法
if condition {
// 当 condition 为 true 时执行的代码
} else {
// 当 condition 为 false 时执行的代码
}
condition 的类型必须是 bool
可以使用 else 语法来检查多个条件,示例
int foo = 23
if foo > 100 {
print('foo > 100')
} else if foo > 20 {
print('foo > 20')
} else {
print('else handle')
}
for 语法
for
语句用于循环执行代码块。nature 语言中的 for
语句有三种主要形式:经典循环、条件循环和迭代循环。
经典循环
经典循环用于执行固定次数的循环。基本语法如下:
var sum = 0
for int i = 1; i <= 100; i += 1 {
sum += i
}
println('1 +..+100 = ', sum)
❗️注意:Nature 中没有
++
语法,请使用i+=1
代替i++
。for
后面的表达式不需要括号包裹。
条件循环
条件循环用于根据条件执行循环,类似于 C 语言中的 while
表达式。基本语法如下
var sum = 0
var i = 0
for i <= 100 {
sum += i
i += 1
}
println('1 +..+100 = ', sum)
在这个示例中,循环会一直执行,直到 i
大于 100。最终输出与经典循环相同。
迭代循环
迭代循环用于遍历集合类型,支持 vec、map、string、chan,基本语法如下
遍历 vec
var list = [1, 1, 2, 3, 5, 8, 13, 21]
for v in list {
println(v)
}
遍历 map
var m = {1:10, 2:20, 3:30, 4:40}
for k in m {
println(k)
}
在这个示例中,循环遍历 map
中的每个键,并输出它们。
同时遍历键和值,对于 vec 来说 k 就是 index
for k,v in m {
println(k, v)
}
遍历 channel
// example: var ch = chan_new<int>()
// yield 直到 ch.recv() 收到消息唤醒当前 coroutine
for v in ch {
println('recv v ->', v)
}
中断与跳过
关键字 break
用于退出当前循环,continue
则跳过本次循环逻辑立刻进入到循环判断逻辑。
函数
函数是 nature 中的一等公民,其可以像作为值传递。nature 支持多种函数定义和使用方式。
函数定义
基本函数定义语法如下
fn function_name(parameter_list):return_type {
// 函数体
}
一个简单的加法函数
fn sum(int a, int b):int {
return a + b
}
匿名函数
nature 支持匿名函数,并会对匿名函数进行闭包处理。
fn main() {
int c = 3
var f = fn(int a, int b):int {
return a + b + c
}
var result = f(1, 2) // 调用匿名函数
}
可变参数
函数支持可变数量的参数,使用 固定格式 ...
+ vec<T>
创建一个可变参数
fn sum(...[int] numbers):int {
var result = 0
for v in numbers {
result += v
}
return result
}
println(sum(1, 2, 3, 4, 5))
参数解构
支持在函数调用时解构参数
fn printf(string fmt, ...[any] args) {
var str = sprintf(fmt, ...args)
print(str)
}
多返回值
nature 编程语言并不支持多返回值,但是可以使用 tuple 解构语法模拟多返回值,后续也会对 tuple 数据结构进行更加详细的介绍
fn divide(int a, int b):(int, int) {
return (a / b, a % b)
}
// 使用 tuple 解构接收返回值
var (quotient, remainder) = divide(10, 3)
函数类型
使用 fn 声明函数类型,函数类型通常可以省略参数名称。
type calc_fn = fn(int,int):int // 声明 fn 类型
fn apply(calc_fn f, int x, int y):int {
return f(x, y)
}
fn main() {
var result = apply(fn(int a, int b):int {
return a + b
}, 3, 4)
println(result)
}
补充说明
nature 中的函数必须显式声明函数的参数类型和返回类型。如果函数不需要返回值,可以省略返回类型声明,也可以使用 void 类型声明该函数无返回值
fn print() { // v
}
fn print():void { // v
}
nature 编程语言总是使用类型前置,包括返回值的类型。如果你认为 return_type 放在参数的后面是类型后置,让你有些混乱,那你可以这么理解
// 这是返回值类型后置
func sum(a int, b int) (c int) {
c = a + b
return c
}
// 这是返回值类型前置
fn sum(int a, int b):(int c) {
c = a + b
return c
}
总之返回值在函数定义中的位置,与类型前置还是后置没有任何关系!
注释
单行注释
// 这是一个单行注释
var str = `hello world` // 这也是一个单行注释
多行注释
/*
这是一个多行注释
可以跨越多行
*/
var str = `hello world`
类型系统
数值类型
类型 | 字节数 | 说明 |
---|---|---|
int | - | 有符号整型,与平台CPU位宽一致(64位平台占8字节) |
i8 | 1 | 8位有符号整型 |
i16 | 2 | 16位有符号整型 |
i32 | 4 | 32位有符号整型 |
i64 | 8 | 64位有符号整型 |
uint | - | 无符号整型,与平台CPU位宽一致 |
u8 | 1 | 8位无符号整型 |
u16 | 2 | 16位无符号整型 |
u32 | 4 | 32位无符号整型 |
u64 | 8 | 64位无符号整型 |
float | - | 浮点数,与平台CPU位宽一致(64位平台等同于f64) |
f32 | 4 | 单精度浮点数 |
f64 | 8 | 双精度浮点数 |
bool | 1 | 布尔类型,值为 true/false |
复合类型
类型名称 | 存储位置 | 语法 | 示例 | 说明 |
---|---|---|---|---|
string | heap | string | string str = 'hello' | 字符串类型, 可以使用单引号、双引号、反引号声明字符串类型 |
vec | heap | [T] | [int] list = [1, 2, 3] | 动态数组 |
map | heap | {T:T} | {int:string} m = {1:'a'} | map 可以使用 for 迭代遍历 |
set | heap | {T} | {int} s = {1, 2, 3} | 集合 |
tup | heap | (T) | (int, bool) t = (1, true) | 元组 |
function | heap | fn(T):T | fn(int,int):int f = fn(a,b){...} | 函数类型 |
channel | heap | chan<T> | var c = chan_new<T>() | 通信管道 |
struct | stack | struct {T field} | struct{} | 结构体 |
array | stack | [T;n] | [int;3] a = [1,2,3] | 固定长度数组 |
特殊类型
类型名称 | 说明 | 示例 |
---|---|---|
self | 结构体方法中引用自身,只能在 fn extend 中使用 | |
ptr | 安全指针,不允许为null | ptr<person> p = new person() |
anyptr | unsafe int 指针,等同于 uintptr, 常用于和 c 语言交互和 unsafe 转换。 | 除了 float 外,任何类型都可以通过 as 转换为 anyptr 类型 |
rawptr | unsafe 可空指针,使用 & load addr 语法可以获得 rawptr,使用 * indirect addr 可以解引用 | rawptr<int> len_ptr = &len |
union | 联合类型,仅支持通过 type 定义声明 | type number = float|int |
any | 特殊联合类型,union 了所有类型 |
类型操作
类型定义
type my_int = int
type person_t = struct{
int age
float height
}
type node_t = struct{
int id
[node_t] children // v 使用动态数组或者指针嵌套引用类型
rawptr<node_t> next // v 通过裸指针引用
ptr<node_t>? next // v 指针+nullable 引用
node_t next // x 循环引用
[node_t;2] foo // x 循环引用
node2_t bar // x 循环引用
}
type nullable<T> = T|null // 自定义 union 类型 + 类型参数泛型
type throwable = interface{} // 接口定义,后续有详细介绍
类型转换
使用 as
关键字进行显式类型转换
-
支持 integer/float 类型之间相互进行转换
-
支持 string 与
[u8]
类型之间相互转换 -
支持 anyptr 和除了 float 以外的任意类型相互转换
-
底层数据结构相同的情况下自定义类型与原始类型之间可以相互转换
-
as
除了用于类型转换外还用于 union、any 类型断言
int i = 42.5 as int // 浮点数转整数
[u8] bytes = "hello" as [u8] // 字符串转 vec
string str = bytes as string // vec<u8> 转换为 字符串
anyptr ptr = &i as anyptr // rawptr<int> 转 anyptr
type myint = int
myint foo = 12
int bar = foo as int // myint -> int
int baz = bar as myint // int -> myint
字面量在绝大多数场景中支持隐式类型转换,如果无法识别具体类型则默认为 int 或 float 类型。
f32 a = 1.1 // 自动推导转换
f64 b = 2.2 // 自动推导转换
any c = 1.1 // 无法推导,默认为 float 类型
i8 d = 1
i16 e = 1
i32 f = 1
any g = 1 // 无法推导,默认为 int 类型
类型扩展
nature 支持对内置类型和自定义类型进行 method 扩展。
内置类型
支持扩展的内置类型包括:bool
、string
、int
、int8
、int16
、int32
、int64
、uint
、uint8
、uint16
、uint32
、uint64
、float
、float32
、float64
、chan
、vec
、map
、set
、tuple
。
内置类型 extend
fn string.find_char(u8 char, int after):int {
int len = self.len()
for int k = after; k < len; k += 1 {
if self[k] == char {
return k
}
}
return -1
}
自定义类型 extend
type square = struct {
int length
int width
}
fn square.area():int {
// 在类型扩展中使用 self 进行数据引用
return self.length * self.width
}
type box<T> = struct{
T length
T width
}
// 泛型参数需要和类型声明保持一致
fn box<T>.area():T{
return self.length * self.width
}
self 是什么类型? self 总是指向堆中的安全指针,所以如果是 string/vec/map 等默认在堆中存储的数据结构,self 的类型和原有类型保持一致。在堆中分配意味着该数据会被 GC 管理,所以是安全的数据。
如果是 struct/int/float 等默认存储在栈上的类型,self 总是引用其安全指针,也就是 ptr<T>
。那如何调用类型扩展方法?
// 对于 string 来说这很简单, string 存储在堆中,所以可以直接进行调用
var str = 'hello world'
var c = str.find_char()
// 但是对于标量类型以及结构体来说,则需要使用 new 关键字将数据在堆中分配,比如
var s = new square(length=5, width=10) // s type is ptr<square>
var a = s.area() // self is ptr<square>
但如果有一个在栈上分配的数据类型想要调用类型扩展函数怎么办?
var s = square{}
// 如果 method 不会进行跨线程引用,也许我们可以通过 as 转换出一个非安全的指针欺骗编译器
(&s as anyptr as ptr<square>).area()
// 我们也可以使用内置的宏 @ula(unsafe load addr),这个宏做了上面的类型转换
@ula(s).area()
// 如果我们希望内存安全的同时也不想调用 new 关键字进行堆分配呢?
// 那我们还能使用 @sla(safe load addr) 宏,其会触发简单的逃逸分析,将 s 默认在堆中分配。
@sla(s).area()
但到处都是 @sla 宏会让人有些烦躁,所以最终我们可以这样!
var s = square{}
s.area() // v eq @sla(s).area()
是的,对于栈上的数据类型调用 method 时,会自动添加 @sla 宏。但这不够智能,这是一种妥协的方式,会这么妥协的原因是后续会基于逃逸分析进行更加智能且安全的 auto @sla。
上述的文档有些冗长,但是希望使用者能够记住,在 method 中的 self 关键字总是一个安全的指向堆中的指针(当然 @ula 除外)。
算数运算符
优先级 | 关键字 | 使用示例 | 说明 |
---|---|---|---|
1 | () | (1 + 1) | 表达式分组 |
2 | - | -12 | 负数 |
2 | ! | !true | 逻辑非 |
2 | ~ | ~12 | 按位取反 |
2 | & | &q | 加载数据地址 |
2 | * | *p | *ptr_var 解引用 |
3 | / | 1 / 2 | 除 |
3 | * | 1 * 2 | 乘 |
3 | % | 5 % 2 | 余数 |
4 | + | 1 + 1 | 加 |
4 | - | 1 - 1 | 减 |
5 | << | 100 << 2 | 按位左移 |
5 | >> | 100 >> 2 | 按位右移 |
6 | > | 1 > 2 | 大于 |
6 | >= | 1 >= 2 | 大于等于 |
6 | < | 1 < 2 | 小于 |
6 | <= | 1 <= 2 | 小于等于 |
7 | == | 1 == 2 | 等于 |
7 | != | 1 != 2 | 不等于 |
8 | & | 1 & 2 | 按位与 |
9 | ^ | 1 ^ 2 | 按位异或 |
10 | | | 1 | 2 | 按位或 |
11 | && | true && true | 逻辑与 |
12 | || | true || true | 逻辑或 |
13 | = | a = 1 | 赋值运算符 |
13 | %= | a %= 1 | 相当于 a = a % 1 |
13 | *= | a *= 1 | a = a * 1 |
13 | /= | a /= 1 | a = a / 1 |
13 | += | a += 1 | a = a + 1 |
13 | -= | a -= 1 | a = a - 1 |
13 | |= | a |= 1 | a = a | 1 |
13 | &= | a &= 1 | a = a & 1 |
13 | ^= | a ^= 1 | a = a ^ 1 |
13 | <<= | a <<= 1 | a = a << 1 |
13 | >>= | a >>= 1 | a = a >> 1 |
& 获取的地址类型是 rawptr<T>
,rawptr 是 unsafe 的裸指针,允许为 null。指针可能会悬空或者指向无效的内存区域。需要开发者自己保证指针安全性,因为仅在必要情况下,如和 C 语言这样的内存不安全语言交互时使用裸指针。
nature 编程语言开发中推荐使用 new 关键字进行 GC 堆分配获取安全指针。
结构体
结构体必须通过 type
关键字声明,也就是定义新的类型,不支持匿名结构体。
基本语法
// 结构体声明
type person_t = struct {
string name
int age
bool active
}
// 结构体初始化,结构体默认在栈上初始化。
var p = person_t{
name = 'Alice',
age = 25
}
// 使用 new 可以在堆上初始化, 并获取结构体指针
ptr<person> p2 = new person(name = 'Bob', age = 30)
p2.name = 'Tom' // 自动解引用
默认值
struct 默认值只支持简单的常量,不支持闭包,函数调用等复杂默认值。
type sub_t = struct{
var name = 'alice' // 存在默认值时支持类型自动推导
}
type person_t = struct{
string name = 'unnamed'
bool active = true
sub_t sub1 // 注意!此时没有默认值
var sub2 = sub_t{} // 包含默认值
}
// 使用默认值初始化
// p.name is 'unnamed', p.active is true,
// p.sub1.name is '', p.sub2.name is 'alice'
var p = person_t{}
嵌套与组合
type outer = struct {
int x
rect r = rect{}
}
type animal = struct {
string name
}
type dog = struct {
animal base
string breed
}
type dog = struct {
struct {
string name
} base // x 不要使用匿名结构体, 虽然能够声明也无法进行初始化赋值, 仅用于内存结构转换使用
string breed
}
数据结构
string
字符串是 nature 语言的内置数据类型,数据存储在堆上。字符串和动态数组 [u8]
在堆上的存储结构一致,所以字符串与 [u8]
之间可以进行任意的类型转换。
string s1 = 'hello world' // 非特殊情况直接使用单引号声明字符串
string s2 = "hello world"
string s3 = `hello
world`
// 使用 + 号进行字符串拼接
var s4 = s1 + ' one piece'
// 字符串比较
bool b1 = 'hello' == 'hello' // true
bool b2 = 'a' < 'b' // true
// 获取字符串长度
int len = s1.len()
// 通过索引访问和修改
s1[0] = 72 // 将第一个字符修改为 'H'(ASCII 码 72)
// 字符串与字节数组转换
[u8] bytes = s1 as [u8]
string s5 = bytes as string
// 字符串遍历,按照 char 进行遍历
for v in s1 {
// v 的类型为 u8,表示 ASCII 码值
println(v)
}
由于单引号也用于声明字符串,所以如果需要获取字符串的 char 类型,可以通过以下几种方式
// 方式 1 通过常量定义
const A = 97
const B = 99
u8 c = A
// 方式 2 通过数组引用
u8 c2 = '@'[0]
字符串的更多操作可以调用标准库中字符串处理库 import strings
import strings
fn main() {
var s = 'hello world world'
var index = s.find('wo')
var list = s.split(' ')
var list2 = ['nice', 'to', 'meet', 'you']
var s2 = strings.join(list2, '-')
var b = s.starts_with('hello')
// 更多的方法可以参考标准库文档
}
vec
vec 是 nature 的内置动态数组类型,支持动态扩容,存储在堆上。并发调用不安全,需要主动加锁。
// 声明与初始化
[int] list = [1, 2, 3] // 语法 1:推荐使用 [T] 声明
vec<int> list2 = [1, 2, 3] // 语法 2:使用 vec<T> 声明
// 创建空 vec, 其中第一个参数是初始化的默认类型
var list3 = vec_new<int>(0, 10)
// 初始化字符串数组,默认值是 string
var list7 = vec_new<string>("hello", 10)
// 类似 vec_new,并根据默认参数类型自动推导
var list5 = [0;10]
// 指定 cap 初始化, len = 0
var list6 = vec_cap<int>(10)
// 自动推导空 vec 类型
[int] list4 = []
list[0] = 10 // 修改元素
var first = list[0] // 获取元素
list.push(4) // 添加元素
var len = list.len() // 获取 vec 长度
var cap = list.cap() // 获取 vec 容量
// 切片和合并
var slice0 = list[1..3] // 获取下标 1 到 3(不含) 的切片
var slice2 = list[..3] // 获取 0 到 3(不含) 的切片
var slice3 = list[1..] // 获取 1 到 len(不含) 的切片
// 合并两个 vec 为新的 vec
var new_list = list.concat([4, 5, 6])
// 追加到 list 上
list.append([4, 5, 6])
// 遍历
for value in list {
}
for index,value in list {
}
map
map 是 nature 的内置映射表类型,用于存储键值对。并发调用不安全,需要主动加锁。
// 声明与初始化
var m3 = {'a': 1, 'b': 2} // 类型推导
{string:int} m1 = {'a': 1, 'b': 2} // 使用 {T:T} 声明
map<string,int> m2 = {'a': 1, 'b': 2} // 使用 map<T,T> 声明
{string:int} empty = {} // 空 map 声明方式 1
var empty = map_new<string,int>() // 空 map 声明方式 2
// 插入/更新元素
m1['c'] = 3
// 获取元素值时, 如果元素不存在则会产生 panic error,需要通过 catch 拦截 panic
var v = m1['a']
var v2 = m1['a'] catch e {
// handle notfound
}
m1.del('b') // 删除元素
var exists = m1.contains('a') // 检查键是否存在
var size = m1.len() // 获取元素数量
// 遍历
for k in m1 { // 仅遍历键
println(k)
}
for k, v in m1 { // 同时遍历键和值
println(k, v)
}
set
set 是 nature 的内置集合类型,用于存储唯一的元素。set 存储在堆上,元素不允许重复。并发调用不安全,需要主动加锁。
// 声明与初始化
{int} s1 = {-32, -64, 13} // 使用 {T} 声明
set<int> s3 = {1, 2, 3} // 使用 set<T> 声明
// 空集合声明
{int} empty = {}
var empty = set_new<int>()
// 基本操作
s1.add(111) // 添加元素
s1.del(13) // 删除元素
var exists = s1.contains(13) // 检查元素是否存在
var found = {1, 2, 3}.contains(2) // 支持方法链式调用
// 遍历
for v in s1 {
println(v)
}
tup
tuple 是 nature 的内置类型,用于将一组不同类型的数据聚合在一个结构中。tuple 存储在堆上,在栈上只保存指针。
var tup = (1, 1.1, true) // v 声明并赋值,多个元素使用逗号分隔
var tup = (1) // x tuple 中至少需要包含两个元素,否则无法区分是表达式还是 tup
var foo = tup[0] // v index 0 表示 tuple 中的第一个元素,以此类推
// x tuple 中的元素访问不允许出现表达式,只允许通过 int 字面量访问
var foo = tup[1 + 1]
tup[0] = 2 // v 修改 tuple 中的值
tup 支持解构赋值
// v 值允许通过 var 进行自动类型推导来连续创建多个变量
var (foo, bar, car) = (1, 2, true)
// x 禁止声明类型,只允许通过 var 自动类型推导
(custom_type, int, bool) (foo, bar, car) = (1, 2, true)
// v 嵌套形式的创建多个变量
var (foo, (bar, car)) = (1, (2, true))
// x 创建变量时,左侧不允许使用表达式
var list = [1, 2, 3]
var (list[0], list[1]) = (2, 4)
// v 修改变量 foo,bar 的值,可以进行快速变量值交换
(foo, bar) = (bar, foo)
// v 嵌套形式修改变量的值操作
(foo, (bar, car)) = (2, (4, false))
// x 左值与右值的类型不匹配
(foo, bar, car) = (2, (4, false))
// v tuple 赋值操作中允许使用左值表达式 如ident/ident[T]/ident.T
(list[0], list[2]) = (1, 2)
// x 1+1 属于右值表达式
(1 + 1, 2 + 2) = (1, 2)
arr
arr 是定长数组,和 c 语言中的数据结构一致。
[u8;3] array = [1, 2, 3] // 声明一个长度为 3,元素为 u8 类型的数组
array[0] = 12
array[1] = 24
var a = array[7]
arr 默认在栈上或者数据结构中进行内存分配,vec 则在堆上进行分配。从 size 计算上可以体现
type t1 = struct {
[u8;12] array
}
var size = @sizeof(t1) // 12 * 1 = 12byte
type t2 = struct {
[u8] list
}
var size = @sizeof(t2) // vec is pointer size = 8byte
和 c 语言不同的一点是,在 nature 中使用 array 作为参数时基于值传递。
错误处理
基本语法
throw 语法使用示例
// x 错误使用方式, can't use throw stmt in a fn without an errable! declaration. example: fn rem(...):void!
fn rem(int dividend, int divisor):int {
throw errorf('....')
}
// v 正确使用方式, 函数必须声明包含 errable(!), 使用 `!` 符号表示函数可能抛出错误
fn rem(int dividend, int divisor):int! {
if divisor == 0 {
throw errorf('divisor cannot zero')
}
return dividend % divisor
}
可以使用 catch 语法捕获错误
var result = rem(10, 0) catch e { // e 实现了 throwable interface, 可以调用相关 method
println(e.msg())
1 // catch body 的最后一行表达式具备值传递的效果,可以将 1 赋值给 result
}
也可以使用 try catch 来捕获多行表达式错误
try {
var a = 1
var b = a + 1
rem(10, 0)
var c = a + b
} catch e { // e 实现了 throwable interface, 可以调用相关 method
println('catch err:', e.msg())
}
error 如果没有被捕获,会随着函数调用栈向上传递,直到被协程调度器捕获并退出程序。
fn bar():void! {
throw errorf('error in bar')
}
fn foo():void! {
bar()
}
fn main():void! {
foo()
}
编译并运行会得到错误追踪栈
coroutine 'main' uncaught error: 'error in bar' at nature-test/main.n:2:22
stack backtrace:
0: main.bar
at nature-test/main.n:2:22
1: main.foo
at nature-test/main.n:6:11
2: main.main
at nature-test/main.n:10:11
main 函数作为程序统一入口默认会添加
errable(!)
,不需要额外声明
设计思路
nature 的错误处理是基于类似 Rust 的 Result<T,E>
的设计思路,但是实际表现和语法 api 设计上更加贴近 try + catch + throw 的模式。
与 Rust 进行类比
// int! 等同于 rust 中的 Result<int,err>
// int? 等同于 rust 中的 Option<int>
fn rem(int dividend, int divisor):int! {
if divisor == 0 {
// 等同于 return Err('xxx')
throw errorf('divisor cannot zero')
}
// 等同于 return Ok(xxx)
return dividend % divisor
}
fn main() {
// 等同于 int result = rem(10, 0)?
int result = rem(10, 0)
/**
等同于
let result = match rem(10, 0) {
Err(e) => {
debug('has err', e)
0
}
Ok(r) => r
}
*/
int result = rem(10, 0) catch e {
println('has err', e.msg())
0
}
}
由于 nature 会自动进行错误解构并向上传递,所以在大多数时候并不需要每一个层级都进行错误处理,仅仅在真的需要的时候进行 catch 处理。
throwable
throwable 是内置的 interface,throw 关键字后面的表达式必须实现该 interface。
import fmt
type throwable = interface{
fn msg():string
}
type errort:throwable = struct{
string message
bool is_panic
}
fn errort.msg():string {
return self.message
}
fn errorf(string format, ...[any] args):ptr<errort> {
var msg = fmt.sprintf(format, ...args)
return new errort(message = msg)
}
内置函数 errorf 的返回值是 errort 类型,该类型实现了 throwable 接口,所以 errorf 函数可以用于 throw 关键字。基于 throwable 接口,我们可以更加灵活的定义 error 的类型。
panic
panic 是一种特殊错误类型,panic 不会随着函数调用链自动传递,而是会直接导致程序崩溃退出。 最常见如索引越界
var list = [1, 2, 3]
var a = list[4]
编译并运行会得到错误
coroutine 'main' panic: 'index out of vec [4] with length 3' at nature-test/main.n:3:18
panic 也可以 catch 或者 try catch 捕获,但是由于 panic 不会沿着调用链传递,所以必须立刻捕获而不能在调用链的上层函数中进行捕获
// panic 捕获方式与 error 相同
var a = list[4] catch e {
println(e.msg())
}
// 使用内置函数 panic 函数可以手动抛出 panic
panic('failed')
联合类型
联合类型(Union Types)允许一个变量持有多种可能的类型之一,nature 提供了灵活的联合类型系统。
基本语法
// 使用 | 运算符来声明多个类型的联合
type nullable<T> = T|null // 可空类型
type number = int|float // 数值类型
❗ 联合类型只能在全局的 type 定义中进行声明,不支持匿名声明
nullable
nullable 基于 union type 实现,由于比较常用,所以进行语法优化,使用 ?
符号可以快速声明 nullable 类型
int? foo = null // 等同于 nullable<int> foo = null
string? bar = "hello" // 等同于 nullable<string> bar = "hello"
类型断言
类型断言使用和类型转换一样的 as 语法
int? foo = 42
int val = foo as int // 将联合类型断言为具体类型
类型检查
is 语法用于检查联合类型当前存储的类型
int? foo = 42
bool is_int = foo is int // true
bool is_null = foo is null // false
// 在条件语句中使用类型检查时, 如果可以通过逻辑判断推导 foo 的具体类型,则会在编译时触发自动类型断言
if foo is int {
// auto as: foo = foo as int
println("foo is an integer", foo + 1)
}
// x 无法进行自动类型推断
if !(foo is null) {
var bar = foo + 1 // binary type inconsistency, left is 'union', right is 'i64'
}
模式匹配
match 语法对 union type 进行了特殊优化,可以快速的进行类型匹配以及自动断言。
int? foo = null
int result = match foo {
is int -> foo // auto: foo = foo as int
is null -> -1 // auto: foo = foo as null
}
any
any 是一种特殊的 union type,其可以包含任意类型。并且小范围的联合类型可以赋值给包含这些类型的大范围联合类型,any 包含最大的类型范围
any foo = 1
int? bar = null
foo = bar // v
bar = foo // x bar 的范围小于 foo
type nullable2 = int|bool|null|string
nullable2 bar2 = bar // v, nullable2 包含更大的类型范围
接口
声明方式
type measurable = interface{
fn area():int
fn perimeter():int
}
// 泛型参数声明
type measurable<T> = interface{
fn area():T
fn perimeter():T
}
完整声明示例
type measurable<T> = interface{
fn perimeter():T
fn area():T
}
// type 实现 interface
type rectangle: measurable<i64> = struct{
i64 width
i64 height
}
fn rectangle.area():i64 {
return self.width * self.height
}
fn rectangle.perimeter():i64 {
return 2 * (self.width + self.height)
}
接口可以作为函数参数,只要实现了该接口就能通过函数参数示例
fn print_shape(measurable<i64> s) {
println(s.area(), s.perimeter())
}
fn main() {
// 值传递
var r = rectangle{width=3, height=4}
print_shape(r)
// 指针引用传递
var r1 = new rectangle(width=15, height=18)
print_shape(r1)
}
传递的参数如果是 ptr,将会自动解构 ptr 包含的类型是否实现了 measurable,type 可以实现多个 interface,多个 interface 之间使用 ,
分隔
type measurable<T> = interface{
fn perimeter():T
fn area():T
}
type updatable = interface{
fn update(i64)
}
type rectangle: measurable<i64>,updatable = struct{
i64 width
i64 height
}
fn rectangle.area():i64 {
return self.width * self.height
}
fn rectangle.perimeter():i64 {
return 2 * (self.width + self.height)
}
fn rectangle.update(i64 i) {
self.width = i
self.height = i
}
可以通过 is
判断 interface 的具体类型
fn use_com(combination c):int {
if c is square {
// auto as: square c = c as square, c is interface
c.unique()
return 1
}
if c is ptr<square> {
// auto as: ptr<square> c = c as ptr<square>
c.unique()
return 2
}
return 0
}
// 同样支持 match is
fn use_com(combination c):int {
return match c {
is square -> 10
is ptr<square> -> 20
_ -> 0
}
}
interface 支持 nullable
fn use(testable? test) {
if (test is testable) { // test = test as testable
println('testable value is', test.nice())
} else {
println('test not testable')
}
}
接口也可以作为泛型约束使用,在后续的泛型章节介绍。nature 编程语言不支持鸭子类型,必须主动声明实现某个接口。
接口支持组合快速声明
type measurable<T> = interface{
fn perimeter():T
fn area():T
}
type updatable = interface{
fn update(i64)
fn area():i64
}
type combination: measurable<i64>,updatable = interface{
fn to_str():string
}
// square 需要实现 to_str + area + update + perimeter 方法。
type square:combination = i64
由于存在组合关系,所以如果 square 实现了接口 combination,则其默认也实现了接口 measurable 和 updatable。如下面的示例
type measurable<T> = interface{
fn perimeter():T
fn area():T
}
type combination: measurable<i64> = interface{
fn to_str():string
}
type square:combination = i64
fn square.area():i64 {
i64 length = *self as i64
return length * length
}
fn square.perimeter():i64 {
return (*self * 4) as i64
}
fn square.to_str():string {
return 'hello world'
}
fn use_mea(measurable<i64> m):int {
return m.perimeter()
}
fn main():void! {
var sp = new square(8)
println(use_mea(sp))
}
模式匹配
nature 提供了强大的模式匹配功能,通过 match
表达式可以实现复杂的条件分支逻辑。
基本语法
match 表达式的基本语法如下:
match subject {
pattern1 -> expression1
pattern2 -> expression2
...
_ -> default_expression // 默认分支
}
值匹配
可以直接匹配字面量值
var a = 12
match a {
1 -> println('one')
12 -> println('twelve') // 匹配成功
20 -> println('twenty')
_ -> println('other')
}
支持字符串匹配
match 'hello world' {
'hello' -> println('greeting')
'hello world' -> println('full greeting') // 匹配成功
_ -> println('other')
}
表达式匹配
可以不携带 subject,此时只要 pattern 表达式的结果为 true 就能进行匹配,但只会匹配第一个结果为 true 的表达式并执行对应的 body
match {
12 > 0 && 0 > 0 -> println('case 1')
(13|(1|2)) == 15 -> println('case 2') // 匹配成功
(1|2) > 3 -> println('case 3')
_ -> println('default')
}
自动断言
对于联合类型,可以使用 is
进行类型匹配,当 match subject 是 var 时,匹配成功后会自动进行类型断言
any value = 2.33
var result = match value {
is int -> 0.0
is float -> value // auto as: var value = value as float
_ -> 0.0
}
result 的类型会根据第一个分支的返回类型进行自动推导。
代码块与返回值
match 分支中可以使用代码块,最后一行表达式将会作为块作用域的返回值
fn main() {
string result = match {
(12 > 13) -> {
var msg = 'case 1'
msg
}
(12 > 11) -> {
var msg = 'case 2'
msg // 匹配成功,返回值 msg 将赋值给 result
}
_ -> 'default'
}
println(result)
}
泛型
类型参数
基本语法使用 <T>
来声明类型参数,其中 T 是类型参数名称。可以声明多个类型参数,多个参数之间用逗号分隔。
// 单个类型参数
type box<T> = struct {
T value
}
// 多个类型参数
type pair<T, U> = struct {
T first
U second
}
type result<T> = T|error // 泛型联合类型
type list<T> = [T] // 泛型数组类型
类型参数支持嵌套
type wrapper<T> = struct {
box<T> inner // 嵌套使用泛型类型
}
// 使用嵌套泛型
var w = wrapper<int>{
inner = box<int>{value = 42}
}
泛型函数
// 泛型函数声明
fn sum<T>(T a, T b):T {
return a + b
}
// 泛型函数调用
var result = sum<int>(1, 2) // 显式指定类型
var result2 = sum(1.1, 2.2) // 类型自动推导
// 类型参数定义 method
type box<T> = struct {
T value
}
fn box<T>.get():T {
return self.value
}
// 类型参数 method 和 fn 泛型冲突,暂时无法同时使用
fn box<T>.get<U>():U { // x method 暂不支持泛型参数
}
泛型约束
nature 的泛型约束暂时还不完善,只会验证传递给泛型的参数是否满足泛型约束声明的类型,而没有验证在泛型函数使用中是否满足泛型约束的使用,在后续的更新中会解决这个问题。
nature 的泛型约束支持三种类型,三种类型约束不允许相互组合,只能选取一种类型约束方式
// union 约束
type test_union = int|bool|float
fn test<T:test_union>(T param) {
println(param)
}
// union 约束可以简写
fn test<T:int|bool|float>(T param) {
println(param)
}
// interface 约束
type test_interface = interface{
fn bar()
}
type test_interface2 = interface{
fn bar()
}
// 参数需要实现 interface 中包含的方法。
fn test<T:test_interface&test_interface2>(T param) {
println(param)
}
使用示例
// 定义泛型结构体
type pair<T, U> = struct {
T first
U second
}
// 定义泛型方法
fn pair<T, U>.swap():(U, T) {
return (self.second, self.first)
}
fn main() {
// 创建泛型实例
var p = pair<int, string>{
first = 42,
second = "hello"
}
// 调用泛型方法
var (s, i) = p.swap()
}
协程
协程是一种用户态的轻量级线程,可以在单个系统线程上运行多个协程。
基本使用
使用 go
关键字
var fut = go sum(1, 2) // 创建一个共享协程
使用 @async
宏, 可以携带 flag 参数来定义协程的行为
var fut = @async(sum(1, 2), co.SAME) // SAME 表示新的协程和当前协程共用 processor
future
协程创建后会返回一个 future 对象, 此时协程已经在运行中,但是不会阻塞当前协程,可以通过 await()
方法阻塞并等待协程执行完成并获取返回值:
import co // 标准库 co 包含一些协程常用函数
fn sum(int a, int b):int {
co.sleep(1000) // 模拟耗时操作, sleep 单位 ms
return a + b
}
fn main() {
var fut = go sum(1, 2)
var result = fut.await() // 等待协程执行完成并获取结果
println(result) // 输出: 3
}
使用
co.sleep()
可以让当前协程让出并休眠指定的毫秒数 使用co.yield()
可以直接让出当前协程的执行权等待下一次调度
mutex
mutex(互斥锁)是一种并发控制机制,用于保护共享资源,确保在同一时刻只有一个协程可以访问被保护的资源。
import co.mutex as m
// 创建互斥锁
var mu = m.mutex_t{}
// 加锁
mu.lock()
// 临界区代码
// ...
// 解锁
mu.unlock()
错误处理
协程中的错误也可以通过 catch
语法捕获
fn div(int a, int b):int! {
if b == 0 {
throw errorf("division by zero")
}
return a / b
}
fn main() {
var fut = go div(10, 0)
var result = fut.await() catch e {
println("error:", e.msg())
0 // 返回默认值
}
}
如果协程中的错误没有被捕获,程序将会终止。
channel
channel 是 nature 提供的协程间通信的机制,用于在不同协程之间安全地传递数据。
基本用法
// 创建无缓冲 channel
var ch = chan_new<int>() // 创建传递 int 类型数据的 channel
var ch_str = chan_new<string>() // 创建传递 string 类型数据的 channel
// 创建带缓冲的 channel
var ch_buf = chan_new<int>(5) // 创建缓冲大小为 5 的 channel
// 发送数据
ch.send(42) // 发送数据到 channel
ch_str.send('hello')
// 接收数据
var value = ch.recv() // 从 channel 接收数据
var msg = ch_str.recv()
channel 状态
ch.close() // 关闭 channel
bool closed = ch.is_closed() // 判断协程 是否关闭
var ok = ch.is_successful() // 在协程关闭状态下可以检查最近的读取或者写入操作是否成功
协程在关闭状态下发送数据会产生 error,可以使用 catch 捕获。未接收完成的 chan buf 可以继续进行 recv,接收完成后再次 recv 会抛出 error
channel recv 操作支持使用 for iterator 迭代
fn handle(chan<int> ch) {
for v in ch { // 编译器会自动调用 recv 并 yield 直到接收到数据
println('recv v ->', v)
}
}
在使用无缓冲 channel 时,数据必须被对端处理协程才可以继续执行,当对端没有处理者时,当前协程会 yield 并等待 channel 中的数据堆对端 send 或者 recv 成功。
当使用存在缓冲区的 channel 时,只有当 channel 满载时,才会发生 yield 等待。
select 语句
select 语句用于同时监听多个 channel 的操作,语法结构类似于 match,但只用于 channel 操作。
select {
ch1.on_recv() -> msg {
// 处理从 ch1 接收到的数据
}
ch2.on_send(value) -> {
// ch2 发送成功后的处理
}
_ -> {
// 默认分支,当所有 channel 都不可操作时执行
}
}
多个 case 同时就绪时,select 会随机选择一个分支执行。默认分支不是必须的,当不存在默认分支且没有 case 就绪时,当前协程会 yield 等待 case 就绪。
select 会自动 catch closed error,可以通过 ch.is_successful()
检查当前被唤醒 channel 本次操作是否成功。
使用示例
简单的生产者消费者模式
// 生产者
go (fn(chan<int> ch):void! {
ch.send(42)
})(ch)
// 消费者
var value = ch.recv()
使用带缓冲 channel 实现限流器
var limiter = chan_new<u8>(10) // 最多允许 10 个并发任务
for u8 i = 0; i < 100; i+=1 {
limiter.send(i) // 获取令牌
go (fn():void! {
// 处理任务
limiter.recv() // 释放令牌
})()
}
使用 select 实现超时控制
var ch = chan_new<string>()
var timeout = chan_new<bool>()
select {
ch.on_recv() -> msg {
println("received:", msg)
}
timeout.on_recv() -> {
println("operation timeout")
}
}
内置宏
nature 编程语言使用 @ 符号进行宏调用,当前版本并不支持自定义宏。但内置了一些必须的宏函数
var size = @sizeof(i8) // sizeof 读取类型占用栈内存
type double = f64
var hash = @reflect_hash(double) // 读取类型 hash 值。
@async(delay_sum(1, 2), 0) // 创建协程
// 使用 ula 避免对 package struct 进行堆分配
@ula(package).set_age(25)
var a = @default(T) // 初始化默认值, 可以用于泛型中的默认值赋值
函数标签
函数标签是一种特殊的函数声明语法,用于为函数添加元数据或修改函数的行为。标签使用 #
符号开头,必须放置在函数声明之前。
#linkid
#linkid
标签用于自定义函数的链接器符号名称
#linkid print_message
fn log(string message):void {
// 函数实现
}
如果 log 定义在 main.n 文件中,则默认 log 在可执行文件中的默认符号是 main.log
如果定义了 linkid,则在可执行文件中的符号为 print_message
linkid 最常用的方式是声明 c 语言中的头文件,比如
#linkid sleep
fn sleep(int second)
当函数没有 body 时,nature 将该函数视为模板函数,可以直接在 nature 代码中进行调用。链接器会去查找对应的 sleep 符号所在的位置进行正确的引导调用。
#local
#local
标签用于标记函数的可见性,表明该函数仅在当前模块内可见
#local
fn internal_helper():void {
// 函数实现
}
编译器实际上不会对 local 添加任何限制,这是一种约定俗成
模块
模块是 nature 中组织代码基本单位。每个 .n
文件都是一个独立的模块。
main 模块
每个 nature 程序必须包含一个 main
函数作为程序入口点
// main.n
import fmt
fn main() {
fmt.printf("Hello, World!")
}
编译指令中指定的入口文件则会视为 main 模块,如 nature build main.n
import
使用 import 关键字可以导入其他模块或者标准库
// 基本导入,默认截取 user 作为模块 ident
import "user.n"
// 自定义模块关键字
import "user.n" as u
// 导入标准库
import fmt
fn main() {
var new_user = user.create_user("alice")
var another = u.create_user("bob")
fmt.printf('name is %s', another.name)
}
基于文件的模块导入有着严格的路径限制,仅仅支持相对路径,并且不支持使用 ./
或者 ../
引入。因此 import 文件只能 import 当前目录或者子目录中的模块,无法 import 父目录中的模块。
包管理
nature 安装包中内置了 npkg 程序作为包管理软件,npkg 需要配合 package.toml 进行使用。
package.toml
在项目根目录下创建 package.toml
可以自动启用包管理功能,该文件来定义项目信息和依赖
# 基础信息
name = "myproject" # 项目名称
version = "1.0.0" # 版本号
authors = ["Alice <a@example.com>"]
description = "项目描述"
license = "MIT"
type = "bin" # bin 或 lib
entry = "main" # 库的入口文件(type = "lib" 时使用)
# 依赖包,可以通过 git 或者本地路径指定
[dependencies]
rand = { type = "git", version = "v1.0.1", url = "jihulab.com/nature-lang/rand" }
local_pkg = { type = "local", version = "v1.0.0", path = "./local" }
依赖管理
在 package.toml 所在目录使用 npkg sync
命令同步依赖管理中的 package。package 会被同步到 $HOME/.nature/package 目录。
$HOME/.nature/package
├── caches
└── sources
├── jihulab.com.nature-lang.os@v1.0.1
│ ├── main.n
│ └── package.toml
└── local@v1.0.0
├── main.linux_amd64.n
├── main.linux.n
├── main.n
└── package.toml
导入语法
nature 将文件名称作为 module ident
import rand // 导入包的主模块(等同于 import rand.main)
import rand.utils.seed // 导入指定模块,也就是 rand/utils/seed.n 文件
import rand.utils.seed as s // 自定义模块名称
import
会按照以下顺序查找模块
- 当前项目 package.toml 中的 name 字段,也就是引用当前项目的其他 module
- 项目依赖(dependencies 中定义的三方包)
- 标准库
跨平台支持
可以通过文件名区分当前文件的应用平台,例如当使用 import syscall
时,会按以下顺序查找导入的模块
syscall.{os}_{arch}.n
syscall.{os}.n
syscall.n
当前支持的平台有
- os: linux、darwin
- arch: amd64、arm64、riscv64
冲突处理
当导入包的名称冲突时,可以在 dependencies 中使用不同的键名
[dependencies]
rand_v1 = { type = "git", version = "v1.0", url = "jihulab.com/nature-lang/rand" }
rand_v2 = { type = "git", version = "v2.0", url = "jihulab.com/nature-lang/rand" }
然后使用不同的名称导入
import rand_v1
import rand_v2
由于基于文件 module,所以编辑器并没有进行循环 import 的检测,但实际开发中推荐区分代码的层级关系,避免进行循环 import。
和 C 语言交互
除了 nature 内置的 libc/libuv 库以外,我们也可以在 package.toml 引用其他的静态库文件,编译器会自动链接相关架构的静态库文件。在 nature 代码中通过 #linkid
标签声明对应的函数模板并调用即可。链接器会自动进行正确的链接。
不仅仅是 C 语言,只要是能够生成静态库的编程语言,nature 都能够方便的与之交互。但 nature 是基于 musl libc 进行静态编译的,所以静态库也需要基于 musl libc 进行纯 static 编译。推荐使用 musl-gcc 组件来编译静态库。
由于 nature 可以自定义链接器和链接参数,所以也可以通过链接参数进行静态库的引用,如
nature build --ld '/usr/bin/ld' --ldflags '-nostdlib -static -lm -luv' main.n
nature 默认集成了 musl libc 和 macOS C 库,可以直接使用相关函数,通过 import libc
直接调用相关函数即可
import libc
fn main() {
i32 r = libc.rand()
}
静态库与模板函数声明
在 package.toml
中通过 [links]
节点定义需要链接的静态库:
[links]
libz = {
linux_amd64 = 'libs/libz_linux_amd64.a',
darwin_amd64 = 'libs/libz_darwin_amd64.a',
linux_arm64 = 'libs/libz_linux_arm64.a',
darwin_arm64 = 'libs/libz_darwin_arm64.a'
}
使用 #linkid
标签和函数模板来声明需要调用的 C 函数 id 和相关参数
#linkid gzopen
fn gzopen(anyptr fname, anyptr mode):anyptr
#linkid sleep
fn sleep(int second)
调用示例
// zlib.n
#linkid gzopen
fn gzopen(anyptr fname, anyptr mode):anyptr
// main.n
import zlib
import libc
fn main() {
var output = "output.gz"
var gzfile = zlib.gzopen(output.to_cstr(), "wb".to_cstr())
if gzfile == null {
throw errorf("failed to open gzip file")
}
// ...
}
类型映射
nature 与 C 语言的类型映射关系
nature 类型 | C 类型 | 说明 |
---|---|---|
anyptr | uintptr | 通用指针类型 |
rawptr<T> | T* | 类型化指针 |
i8/u8 | int8_t/uint8_t | 8位整型 |
i16/u16 | int16_t/uint16_t | 16位整型 |
i32/u32 | int32_t/uint32_t | 32位整型 |
i64/u64 | int64_t/uint64_t | 64位整型 |
i32 | int | |
int | size_t | 平台相关的整型,在64位系统上等同于int64_t |
f32 | float | 32位浮点 |
f64 | double | 64位浮点 |
[T;n] | T[n] | 固定长度数组,N为编译时常量 |
struct | struct | nature struct 与 c 语言 struct 采用相关对齐方式及 ABI 处理方式 |
获取 c 语言字符串以及指针
import libc
var str = "hello"
libc.cstr ptr = str.to_cstr() // 获取字符串地址
string str2 = ptr.to_string() // cstr 转换为 nature 字符串
// 获取 rawptr 类型
rawptr<tm_t> time_ptr = &time_info
// 获取 anyptr 类型
// 任何 nature 类型(除了浮点型)都可以转换为 anyptr 类型
anyptr c_ptr = time_info as anyptr
注意事项
- c 语言是内存不安全的,所以需要格外注意内存相关问题。使用 rawptr 和 anyptr 可以轻易的绕开 nature 编程语言的安全检查
- nature 编程语言是基于协作式调度的,当调用阻塞的 c 函数,如 sleep、read、write 等,会导致 nature 调度器阻塞。调度器阻塞会导致其他协程无法运行,并且无法进行 GC 处理。
格式化
nature fmt 工具还未进行开发,所以需要进行一些简单的编写约定。
var bar = '' // stmt 结尾不需要携带 ;
var global_v = 12 // 除了常量定义以外,其他任何 ident 都推荐小写下划线分词(包括文件名称)
const GLOBAL_V = 12 // 常量推荐大写下划线分词
if true {
var foo = 1 // 使用 4 个空格进行缩进
}
call_test(
1,
2, // 多行参数, 需要在最后一行添加 ,
)
var v = [
1,
2,
3, // 同上
]
var m = {
"a": 1,
"b": 2, // 同上
}
type person_t:io.reader,io.writer = struct{ // struct 和 { 之间不需要空格
var f = fn() {
}
int a = 1
int b
bool c
}
var s = person_t{ // 结构体 ident 和 { 之间不需要空格
name: "john",
age: 18, // 同上
}
// 1. 函数定义 '{' 和函数声明需要在同一行
// 2. 返回参数和 ')' 之间需要空格
// 3. 每个参数之间需要空格
// 4. 返回值不需要空格
fn test(int arg1, int arg2):int {
}
// for 循环格式
for int i = 0; i < 12; i += 1 {
}
// match 格式
match a {
v -> {
}
_ -> {
}
}
关键字
类型关键字
- void, any, null, bool, ptr, rawptr, anyptr
- int, i64, i32, i16, i8
- uint, u64, u32, u16, u8
- float, f64, f32
- struct, interface
- vec, map, set, tup, chan
声明关键字
- var - 变量声明
- const - 常量定义
- type - 类型定义
- fn - 函数定义
- import - 导入模块
- new - 创建实例
控制流关键字
- if, else, else if
- for, in, break, continue
- return
- match, select
- try, catch, throw
其他关键字
- go - 并发原语
- as - 类型转换
- is - 类型判断
- true, false - bool 值
- null - 空值
保留关键字
impl, let, pub, package, static, macro, alias