语言规范
Caju 的工作方式:语法、类型、语句、函数、错误与模块。示例均用 0.5.0 版本编译,展示的是真实输出。
01概览
Caju 是一门编译型编程语言,采用静态类型并支持类型推断。编译器生成 x86-64 机器码:Linux 上为 ELF 可执行文件,Windows 上为 PE。它还能生成可在 Node 和浏览器中运行的 WebAssembly 模块。编译器和运行时都用 Caju 本身编写。
语言自带开发所需的一切:编译器、测试运行器(caju test)和包管理器(caju pkg),统一在一个 caju 命令中。
一个程序由一个或多个 .caju 文件组成。文件顶层的语句按从上到下的顺序执行:无需声明 main 函数。
fn greet(name: str) -> str { return "Hello, " + name + "!"} print(greet("world"))02词法元素
注释。以 # 或 // 开头,直到行尾。
语句结束。换行或 ; 结束一条语句。表达式不能在运算符之后延续到下一行:请把它拆成几个变量。
标识符。由字母(包括带变音符的拉丁字母,如 é 和 ç)、数字和 _ 组成,不能以数字开头。
关键字。不能用作名称:
letconstifelsewhilefortoandornottruefalseimportfromexportfnreturnexterndllexportstructinterfacenilswitchcasedefaultbreakcontinuefallthroughdefergoselectchanmaprange字面量。
a := 1_000; b := 0xFF; c := 0b1010; d := 0o17print(a, b, c, d, 1.5e3)print("tab\tquote\" \u00e9 \x41", `raw\n`, 'a')03类型
声明中的类型是可选的:不标注时由编译器推断;标注后,赋予其他类型的值会导致编译错误。
别名。byte = uint8,rune = int32,string = str,float64 = num。
零值。未赋值的变量以其类型的零值开始:0、""、false、nil,或所有字段均为零值的结构体。
04变量与常量
x := 10x = "ten"05表达式与运算符
+ 也用于拼接字符串。没有 ~ 运算符:要按位取反,请用 x ^ -1。
类型转换写成函数调用的形式:int(x)、num(x)、str(x)、uint8(x)、float32(x)、byte(x)、rune(x) 等。
print(2 ** 10, 7 % 3, 6 &^ 3, 1 << 4, -8 >> 1)print(true and not false, !true && false)print(str(3.5), int(3.9), num(2))06控制流
if 可以在条件前加一条语句,用 ; 分隔。条件两侧不加括号。
循环。for 有四种形式,另外还有 while:
break 和 continue 可以带标签,用于跳出或继续外层循环。
switch 依次与每个 case 比较,一个 case 可以列出多个值。各分支不会自动进入下一个分支,除非以 fallthrough 结尾。
for j = 1 to 2 { print("j", j) } outer: for p := 0; p < 3; p++ { for q := 0; q < 3; q++ { if q == 1 { continue outer } if p == 2 { break outer } print(p, q) }} i := 3switch i {case 1, 2: print("small")case 3: print("three") fallthroughdefault: print("default")}07函数
函数在文件顶层用 fn 声明。每个参数都有类型。返回类型写在 -> 之后,省略时根据 return 推断。
函数可以返回多个值,如 -> (int, error),返回值也可以命名:单独的 return 会返回它们的当前值。
在函数内部,可以用 fn(...) { } 创建函数值。它们会捕获外部变量(闭包)。
fn split(n: int) -> (q: int, r: int) { q = n / 10 r = n % 10 return}print(split(47)) add := fn(a: int, b: int) -> int { return a + b }print(add(2, 3))... 用于在调用时展开列表,如 append(a, b...)。08结构体、方法与接口
结构体把带类型的字段组合在一起。字面量可以按顺序给值,如 Point{1, 2},也可以按名称给值,如 Point{y: 2}。未给出的字段取零值。
方法是带接收者的函数:fn (p: Point) dist() 接收一份副本,而 fn (p: *Point) move() 接收原值并可以修改它。
嵌入:把一个结构体嵌入另一个结构体,外层结构体就获得内层的字段和方法。
接口是隐式实现的:一个类型只要拥有接口的方法,就实现了该接口。接口可以嵌入其他接口。x.(T) 取回存放的类型;使用 v, ok := x.(T) 时不会引发 panic。
interface Named { name() -> str }interface Greeter { Named; greet() -> str } struct P { n: str }fn (p: P) name() -> str { return p.n }fn (p: P) greet() -> str { return "hi " + p.n } gs := []Greeter{P{"ana"}}print(gs[0].greet(), gs[0].name())09错误、defer 与 panic
错误是 error 类型的值。可以用 errors.New("message") 创建,也可以用任何带有 Error() -> str 方法的类型。可能失败的函数把错误作为最后一个返回值。
defer 安排一个调用在函数结束时执行;这些调用按从后到前的顺序运行。panic 中断执行并逐层回退调用;在 defer 中调用 recover 可以止住 panic。运行时错误(如索引越界)同样会引发 panic。
struct NotFound { name: str }fn (e: *NotFound) Error() -> str { return e.name + " not found" } fn find(name: str) -> error { if name != "caju" { return &NotFound{name} } return nil} err := find("mango")if err != nil { print(err.Error()) }10内置函数
11模块与包
每个文件都是一个模块。只有用 export 声明的名称才能被其他文件使用。
# mods/geo.cajuexport struct Point { x: int, y: int }export fn origin() -> Point { return Point{} }fn hidden() -> int { return 1 }import "./mods/geo" print(geo.origin())print(geo.hidden())12包管理器
Caju 自带包管理器 caju pkg。没有中央服务器:一个包就是根目录下有 caju.toml 文件的 GitHub 仓库。包名就是仓库路径,程序导入的也正是这个名字。
[package]name = "github.com/acme/strutil"version = "1.1.0"description = "Text helpers"license = "MIT" [dependencies]"github.com/acme/other" = "1.0.0"版本。已发布的版本是格式为 vX.Y.Z 的 git tag,其编号须与 caju.toml 中的 version 一致。两者不符时,包管理器会拒绝该包。
版本选择。每个依赖注明自己所需的最低版本。当两个包对同一个包要求不同版本时,取各最低版本中最高的那个。因此,只有当某个 caju.toml 要求更高版本时结果才会改变,而不会因为有人发布了新版本而改变。MAJOR 版本不同(如 1.x 与 2.x)的版本互不兼容。
版本锁定。caju.lock 为每个直接或间接使用的包记录版本、提交和文件的 SHA-256 哈希。请把它提交到仓库:这样所有人编译的都是完全相同的文件。
校验。编译前,caju build 和 caju run 会检查 caju.lock 是否与 caju.toml 同步,以及每个包的文件是否与锁定的哈希一致。任何差异都会导致编译中止:
import "github.com/acme/strutil" print(strutil.shout("caju"))存放位置。包通过 git(按版本 tag)下载到 ~/.caju/pkg 缓存中。add、remove 和 update 只修改 caju.toml 的 [dependencies] 部分,不会改动文件的其他内容。
import "github.com/acme/strutil/extra";一个包只应导入其 [dependencies] 中列出的内容。13Windows 系统调用
extern 声明 Windows DLL 中的函数,调用约定为 stdcall(默认)或 cdecl。参数使用接口类型:i32、u32、i64、u64、f32、f64、ptr、bool、str 和 wstr,返回类型可以是 void。这类调用只存在于用 --target windows 编译的程序中。
extern "user32.dll" fn MessageBoxA(hwnd: ptr, text: str, title: str, flags: u32) -> i32 MessageBoxA(0, "Hello from Caju", "caju", 0)14编译与工具
15开发中
- 原生编译器中的 goroutine、通道和
select。go、chan和select已是保留字。 - Windows 上的
alloc、peek、poke、readstr、dllexport和原生 DLL。 - 以结构体、数组或
any为键的映射。