Auto语言是一门跨生态的“融合语言”,基本理念是:
Auto语言应用在汽车行业,未来会不断探索,拓宽边界。
Auto语言的远景目标是“万物自动化”(One Lang to Rule Them All)。
Auto语言是Soutek公司推出的技术产品Soutek AutoStack的开源版本。
Auto语言可用于如下场景:
Better C
,生成C/Rust源码例如,如下两个AutoLang语言文件:
// math.at
pub fn add(a int, b int) int {
a + b
}
// main.at
use math.add
fn main {
println(add(1, 2))
}
可以生成三个C文件:math.h, math.c和main.c:
// math.h
#pragma once
#include <stdint.h>
int32_t add(int32_t a, int32_t b);
// math.c
#include <stdint.h>
#include "math.h"
int32_t add(int32_t a, int32_t b) {
return a + b;
}
#include <stdio.h>
#include <stdint.h>
#include "math.h"
int main(void) {
printf("%d\n", add(1, 2));
return 0;
}
// 可以调用标准库
use std.fs: list, join, is_dir
// 变量
mut dir = "~/code/auto"
// {key : value}对,这是配置数据的一部分
root: dir
// 函数调用
src: dir.join("src")
// 字符串拼接,$表示变量查找
assets: `$dir/assets`
// 字符串拼接,#表示在配置中查找key
styles: `#assets/styles`
// 数组
docs: ["README.md", "LICENSE"]
// 所有的子目录
subs: dir.list().filter(is_dir)
// 对象
project: {
name: "auto"
skip: [".git", ".auto"]
}
上面的配置对应的JSON文件如下:
{
"dir": "/home/user/data",
"src": "/home/user/data/src",
"assets": "/home/user/data/assets",
"styles": "/home/user/data/assets/styles",
"docs": ["README.md", "LICENSE"],
"subs": ["/home/user/data/src/app", "/home/user/data/src/lib"],
"project": {
"name": "auto",
"skip": [".git", ".auto"]
}
}
为了更方便得生成类XML/YAML的树状结构,AutoConfig提供了节点(Node)的概念。 节点和数组、对象结合起来,形成AutoConfig的树状结构。
parent(k1: v1, k2: v2) {
kid(k1: v1) {
// more kids
}
kid(k2: v2) {
// more kids
}
}
这和XML的语法结构是一一对应的,相当于:
<parent k1="v1" k2="v2">
<kid k1="v1" />
<kid k2="v2" />
</parent>
例如,下面的配置文件表示一张成绩表:
class(name: "三3班", count: 55) {
student(name: "张三", age: 18) {
score(subject: "语文", score: 80) {}
score(subject: "数学", score: 90) {}
score(subject: "英语", score: 85) {}
}
student(name: "李四", age: 19) {
score(subject: "语文", score: 85) {}
score(subject: "数学", score: 95) {}
score(subject: "英语", score: 80) {}
}
student(name: "王五", age: 20) {
score(subject: "语文", score: 85) {}
score(subject: "数学", score: 95) {}
score(subject: "英语", score: 80) {}
}
}
对应的XML如下:
<class name="三3班" count="55">
<student name="张三" age="18">
<score subject="语文" score="80" />
<score subject="数学" score="90" />
<score subject="英语" score="85" />
</student>
<student name="李四" age="19">
<score subject="语文" score="85" />
<score subject="数学" score="95" />
<score subject="英语" score="80" />
</student>
</class>
乍一看大家可能会觉得,“这和直接写XML有什么区别”? 一旦数据多起来,AutoConfig的可编程优势就体现出来了:
// 调用函数获取学生信息
let info = fetch_class_scores("三3班")
class(name: info.name, count: info.count) {
for s in info.students {
student(name: s.name, age: s.age) {
for score in s.scores {
score(subject: score.subject, score: score.score) {}
}
}
}
}
这种基于节点的格式,非常适合用来描述UI等树状配置。 和XML、YAML不同,AutoConfig是可编程的,因此要更加灵活强大。
AutoMan
是Auto语言的构建工具,支持编译、依赖包管理和Auto/C混合编程。
AutoMan
可以看作是CMake的替代品。
AutoMan
的配置文件一般叫做pac.at
,用来描述一个工程包。
project: "osal"
version: "v0.0.1"
// 依赖项目,可以指定参数
dep("FreeRTOS", "v0.0.3") {
heap: "heap_5"
config_inc: "demo/inc"
}
// 本工程中的库
lib("osal") {
// 子目录
dir("hsm") {
skip: ["hsm_test.h", "hsm_test.c"]
}
dir("log") {}
link: FreeRTOS
}
// 可以输出到不同的平台,指定不同的编译工具链、架构和芯片
port("cmake", "win32") {}
port("iar", "ls1480") {
// 芯片对应的SDK
device("Lanshan-LS1480-SDK") {
}
}
// 可执行文件
app("demo") {
// 静态链接
links: ["osal"]
// 指定输出文件名
out: "demo.bin"
}
AutoShell是类似Bash的Shell脚本语言,可以试下跨平台统一语法。 相对于AutoScript,添加了对Shell命令调用格式的支持:
mkdir -p src/app
上述的Shell脚本语法会被转换为AutoScript中的如下代码:
mkdir("src/app", p=true)
这样设计有4个好处:
下面是AutoShell的一些用法展示:
// Auto的Shell模式
#!auto
// 脚本模式下内置了常用的库
print "Hello, world!"
// 下面的命令会自动转化为函数调用:`mkdir("src/app", p=true)`
mkdir -p src/app
// 更多的命令
cd src/app
touch main.rs
// 也可以定义变量和函数
let ext = ".c"
fn find_c_files(dir) {
ls(dir).filter(|f| f.endswith(ext)).sort()
}
// 可以顺序调用命令
touch "merged.txt"
for f in find_c_files("src/app") {
cat f >> "merged.txt"
}
// 可以异步调用多个命令
let downloads = for f in readlines("remote_files.txt").map(trim) {
async curl f"http://database.com/download?file=${f}"
}
// 可以选择等待所有的文件都下载完成
await downloads.join()
// 如果是AutoShell没有支持的命令,也可以调用底层真正的shell程序:
// NOTE: 这个模式下,语法就不是跨平台的了,因此需要做平台判断
when sys.shell() {
is sys.POWERSHELL shell("del -Force -Recurse ./logs")
is sys.BASH shell("rm -rf ./logs")
}
Auto语言根据后缀名,采用了不同的“场景”,因此可以支持不同的语法。
Auto语言的脚本(Auto Script)文件后缀名为.as
。
在这个场景下,所有一级语句中的函数调用,都可以写成类似bash
命令的风格。
例如:
grep -Hirn TODO .
会被转化为如下函数:
grep(key="TODO", dir=".", H=true, i=true, r=true, n=true)
类似于Python的Jinja2
模板。
<html>
<head>
<title>${title}</title>
</head>
<body>
<h1>${title}</h1>
<ul>
$ for n in 1..10 {
<li>Item $n</li>
$ }
</ul>
</body>
</html>
AutoTemplate是AutoGen
代码生成系统的基础。
AutoUI
是Auto语言的UI框架,基于Zed/GPUI
实现。
可以支持Windows/Linxu/MacOS/Web等多种平台。
AutoUI的语法风格类似Kotlin,代码组织模式类似于Vue.js。
// 任何一个实现了`View`特征的类型,都可以作为一个UI组件来使用
type Counter as View {
// 类型的成员,相当于MVC中的Model
count int = 0
// `view`方法,返回一个节点数据。相当于MVC中的View
fn view() {
col {
button("+") {
onclick: "click:inc" // 点击事件,暂时用字符串类型,未来会扩展成枚举数据类型
}
label(`Count: ${count}`) {}
button("-") {
onclick: "click:dec" // 点击事件,暂时用字符串类型,未来会扩展成枚举数据类型
}
button("reset") {
onclick: "click:reset" // 点击事件,暂时用字符串类型,未来会扩展成枚举数据类型
}
}
}
// 事件处理函数
fn on(ev str) {
when ev {
is "click:inc" count += 1
is "click:dec" count -= 1
is "click:reset" count = 0
else print(`Unknown event: ${ev}`)
}
}
}
fn main() {
app("Counter Example") {
counter() {}
}
}
上述的Auto代码会被翻译成对应的RustUI库代码,然后编译成可执行的UI程序。
下面是生成的UI界面:
Note: 在上一版实现(参见分支gpui2
),AutoUI
不是通过翻译成Rust,而是在Rust代码中动态解析,直接渲染出来的。
这样的动态解释有如下好处:
counter.at
文件后,AutoUI
会自动重绘,不需要重新编译。但这样的坏处是:
在最近的版本gpui3
中,我尝试了转译成Rust的方案。这么做运行效率最高,但是开发时的响应周期会比较低。
因此,未来考虑将两种方案都实现,并根据场景选择使用。
Debug
模式中,使用动态解释,方便开发调试。Release
模式中,使用转译成Rust,生成静态的UI程序。注意:上述所有场景用途,并未完全实现,现在进度大致如下:
Better C
:v0.1初步完成了Auto2C的转译器,可以翻译简单的函数、控制流等。预计v0.2版本实现绝大部分功能。在auto语言里,有四种不同类型的“存量”,用来存放与访问数据:
let
):定量是声明之后就不能再改变的量,但是可以取地址和访问。相当于Rust中的let
。mut
):这种存量的值可以任意改变,但是类型一旦确定就不能再改变。这其实就是C/C++中的普通变量。在Rust中,这样的变量用let mut
声明。const
):常量是声明之后就不能再改变的量,但是可以取地址和访问。相当于Rust中的const
。var
):幻量是最自由的量,可以任意改变值和类型,一般用于脚本环境,如配置文件、DSL、脚本代码等。// 定量
let b = 1
// Error! 定量不能修改
b = 2
// 可以用来计算新的存量
let f = e + 4
// 定量可以重新声明,但类型不能改变
let b = b * 2
// 变量定义,编译器可以自动推导类型
mut a = 1
// 变量的定义可以指定类型
mut b bool = false
// 声明多个变量
mut c, d = 2, 3
// 变量可以修改,也叫“赋值”
a = 10
// 甚至可以交换两个变量的值
c, d = d, c
// 常量定义:常量只能是全局量
const PI = 3.14
// 幻量:幻量是最自由的量,可以任意改变值和类型,一般用于脚本环境
var x = 1
x = "hello"
x = [x+"1", x+"2", x+"3"]
// 数组
let arr = [1, 2, 3, 4, 5]
// 下标
println(arr[0])
println(arr[-1]) // 最后一个元素
// 切片
let slice = arr[1..3] // [2, 3]
let slice1 = arr[..4] // [1, 2, 3, 4]
let slice2 = arr[3..] // [4, 5]
let slice3 = arr[..] // [1, 2, 3, 4, 5]
// 范围(Range)
let r = 0..10 // 0 <= r < 10
let r1 = 0..=10 // 0 <= r <= 10
// 对象
mut obj = {
name: "John",
age: 30,
is_student: false
}
// 访问对象成员
println(obj.name)
// 成员赋值
obj.name = "Tom"
// get or else
println(obj.get_or("name", "Unknown"))
// get or insert
println(obj.get_or_insert("name", 10))
// 所有成员
println(obj.keys())
println(obj.values())
println(obj.items())
// 遍历对象
for k, v in obj {
println(f"obj[{k}] = {v}")
}
// 删除
obj.remove("name")
Grid是Auto语言的二维数组,可以用于表格数据。 Grid可以扩展为类似DataFrame/Tensor的多维结构,用来和Python交互,进行AI相关的开发。
// 定义一个Grid
let data = grid {
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
}
// 转化为JSON
var json = data.to_json()
生成的JSON如下:
{
"data": [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
}
这样搞看起来似乎没什么区别,但grid提供了比JSON更多的功能:
// 获取grid的信息
println(data.shape()) // (3, 3)
println(data.width()) // 3
println(data.height()) // 3
// 按行访问
println(data[0]) // [1, 2, 3]
println(data[1]) // [4, 5, 6]
println(data[2]) // [7, 8, 9]
// 或者
println(data.row(0)) // [1, 2, 3]
println(data.row(1)) // [4, 5, 6]
println(data.row(2)) // [7, 8, 9]
// 按列访问
println(data(0)) // [1, 4, 7]
println(data(1)) // [2, 5, 8]
println(data(2)) // [3, 6, 9]
// 或者
println(data.col(0)) // [1, 4, 7]
println(data.col(1)) // [2, 5, 8]
println(data.col(2)) // [3, 6, 9]
// 矩阵转换
let transposed = data.transpose()
// 其他矩阵操作
let sum = data.sum()
let mean = data.mean()
let std = data.std()
let min = data.min()
let max = data.max()
除了这些操作之外,Grid还支持更丰富的行列信息配置:
let data = grid("a", "b", "c") {
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
}
// 转换成JSON数组时,可以指定命名形式,即每行数据都是一个对象:
let json = data.to_json(named:true)
得到如下JSON:
{
"grid": {
"cols": ["a", "b", "c"],
"rows": [
{"a": 1, "b": 2, "c": 3},
{"a": 4, "b": 5, "c": 6},
{"a": 7, "b": 8, "c": 9}
]
}
}
// 函数定义
fn add(a int, b int) int {
a + b
}
// 函数变量(Lambda)
let mul = |a int, b int| a * b
// 函数作为参数
fn calc(a int, b int, op |int, int| int) int {
op(a, b)
}
// 函数调用
calc(2, 3, add)
calc(4, 5, mul)
calc(6, 7, |a, b| a / b)
在Auto语言中,值的传递可以有如下几种形式:
sys
代码块中。引用比拷贝节省了内存空间和复制时间,但引用实际上也是通过地址进行间接访问的,所以访问时间会比拷贝略慢。
对于较小的数据,如int、float、bool,或者类似于Point{x, y}
这种简单的数据类型,传递时进行拷贝的代价很小,往往比引用更合适。
我们把这种类型叫做“数值类型”。
对于较大的数据,如Vec<T>
、HashMap<K, V>
、String
等,传递时进行拷贝的代价较大,往往用引用更合适。
我们把这种类型叫做“引用类型”。
因此,Auto语言针对不同的数据,采取了不同的传递方式:
下面举两个例子:
// 数值类型:默认拷贝传递
let a = 1
let b = a // 这里b是a的一份拷贝
mut c = a // 这里c是a的一份拷贝,而且c可以修改
c = 2
println(c) // 2
println(a) // 1 // a没有变化
// 引用类型:默认引用传递
let a = [1, 2, 3, 4, 5] // 数组默认是引用类型
let b = a // 这里b是a的一个引用,在使用b的时候,就和使用a一样。内存中只存在一个数组。
mut c = a // 错误!由于a是不可修改的,所以可修改的c不能引用它。
mut d = copy a // 如果想进行修改,可以显式地复制它。
d[0] = 9 // d = [9, 2, 3, 4, 5]
println(a) // a = [1, 2, 3, 4, 5], a数组没变
上面的例子中,使用copy
关键字,显式地进行了拷贝。
但这样效率显然不高,因此我们还有一个“两全其美”的办法,那就是转移:
// 转移传递
let a = [1, 2, 3, 4, 5]
let b = move a // 转移后,a不能再使用
println(a) // Error! a已经不能再使用
mut c = move b // b转移给了c,由于是转移,c可以选择修改
c[0] = 9 // c = [9, 2, 3, 4, 5]
println(b) // Error! b已经不能再使用
我们可以看到,a
的值在转移到b
之后,它的声明周期就结束了。
从此存量a
不复存在,但它的“灵魂”会继续在b
中存活。
同样,b
转移给c
时,由于转移操作实际上一种“转世重生”、“借尸还魂”,
因此c
可以拥有和b
不一样的属性,比如mut
。
转移相当于把拷贝和引用的好处结合在一起了,但代价是什么呢? 代价是需要编译器能够逐行分析每个存量的生命周期。 也需要程序员能够分辨出来,某个存量,什么时候就已经挂掉了。
Rust程序员很多时候在跟编译器斗争,就是因为没搞清楚每个存量的生命周期。
由于转移和指针都是比较高阶的功能,Auto语言的早期版本暂时不会实现他们, 只是作为设计放在这里。
上面讲的拷贝和转移,都是直接操作数据,而引用和指着,则是间接地操作数据。
引用和指针的主要区别有两个:
sys
代码块中执行(类似于Rust的unsafe
块)。// 引用
let a = [0..99999] // 我们用一个很大的数组
let b = a // 如果直接新建一个b的值,那么会把a的值拷贝一份
let c = ref a // 此时c只是a的一个“参考视图”,它本身并不存数据,也没有拷贝操作。
b = 2 // Error: 引用不能修改原始量的值
// 这里的`buf`参数,实际上是个引用
fn read_buffer(buf Buffer) {
for n in buf.data {
println(n)
}
}
// mut ref可以用来修改变量:
mut x = 1
fn inc(a mut ref int) {
a += 1
}
inc(x)
println(x) // 2
// 指针
// 指针和引用不同的地方在于,因为它和原始量指向同一个地址,因此可以修改原始量的值。
mut x = 1
sys {
mut p = ptr x
p.target += 1 // 间接修改x的值,注意这里和C不一样,用的是`.target`
}
println(x) // 2
// 在函数调用时,指针类型的参数,可以修改原始量
mut m = 10
fn inc(a ptr int) {
a += 10
}
inc(m)
println(m) // 20
// 指针还可以直接进行地址运算
sys { // 注意:地址运算要放在sys块中
mut arr = [1, 2, 3, 4, 5]
mut p = ptr arr // p的类型是 Ptr<[5]int>
println(p) // [1, 2, 3, 4, 5]
p[0] = 101 // 直接修改arr[0]的值
println(arr) // [101, 2, 3, 4, 5]
mut o = p // 记住p的地址
p.inc(2) // 地址自增2,此时p指向的是arr[2]
println(p) // [3, 4, 5]
println(o[0]) // 101
p.jump(o) // 跳回到o
println(p) // [101, 2, 3, 4, 5]
}
// 条件判断
if a > 0 {
println("a is positive")
} else if a == 0 {
println("a is zero")
} else {
println("a is negative")
}
// 循环访问数组
for n in [1, 2, 3] {
println(n)
}
// 循环修改数组的值
mut arr = [1, 2, 3, 4, 5]
for ref n in arr {
n = n * n
}
println(arr) // [1, 4, 9, 16, 25]
// 循环一个范围
for n in 0..5 {
println(n)
}
// 带下标的循环
for i, n in arr {
println(f"arr[{i}] = {n}")
}
// 无限循环
mut i = 0
loop {
println("loop")
if i > 10 {
break
}
i += 1
}
// 模式匹配,类似switch/match
when a {
// is 用于精确匹配
is 41 println("a is 41")
// in 用于范围匹配
in 0..9 println("a is a single digit")
// if 用于条件匹配
if a > 10 println("a is a big number")
// as 用于类型判断
as str println("a is a string")
// 其他情况
else println("a is a weired number")
}
enum Axis {
Vertical // 0
Horizontal // 1
}
// 带成员的枚举
enum Scale {
name str
S("Small")
M("Medium")
L("Large")
}
// 枚举变量
mut a = Scale.M
// 访问枚举成员
println(a.name)
// 枚举匹配
when a as Scale {
is S println("a is small")
is M println("a is medium")
is L println("a is large")
else println("a is not a Scale")
}
// 联合枚举
enum Shape union {
Point(x int, y int)
Rect(x int, y int, w int, h int)
Circle(x int, y int, r int)
}
// 联合枚举匹配
mut s = get_shape(/*...*/)
when s as Shape {
is Point(x, y) println(f"Point($x, $y)")
is Rect(x, y, w, h) println(f"Rect($x, $y, $w, $h)")
is Circle(x, y, r) println(f"Circle($x, $y, $r)")
else println("not a shape")
}
// 获取联合枚举的数据
mut p = s as Shape.Point
println(p.x, p.y)
// 类型别名
type MyInt = int
// 类型组合
type Num = int | float
// 自定义类型
type Point {
x int
y int
// 方法
fn distance(other Point) float {
use std.math.sqrt;
// 这里的`x`相当于其他语言的`this.x`
sqrt((x - other.x) ** 2 + (y - other.y) ** 2)
}
}
在类型的方法中,对象实例的成员,如上面例子里的x
和y
,可以直接访问。这是因为Auto语言在方法调用时,会把对象实例的视野也加入到方法的视野中。
假如实例成员的名称和参数或者局部存量名字冲突,则可以使用.x
来区分。
此时.x
表示成员,即相当于this.x
或self.x
;
而x
则表示参数或普通存量。
例如:
// 自定义类型
type Point {
x int
y int
// 方法
fn move(x int, y int) int {
.x = x
.y = y
}
}
如果想要直接使用实例自身,则可以用self
表示。
type Node {
parent *Node
kids []*Node
pub fn mut add(mut kid *Node) {
kid.parent = &self
.kids.add(kid)
}
}
注意方法中的两个mut
:
fn
与方法名之间的mut
表示要修改实例自身mut
表示要修改这个参数// 新建类型的实例
// 默认构造函数
mut myint = MyInt(10)
print(myint)
// 命名构造函数
mut p = Point(x:1, y:2)
println(p.distance(Point(x:4, y:6)))
// 自定义构造函数。注意:`static`表示方法是静态方法,一般用于构造函数。静态方法里不能用`.`来访问实例成员
Point {
pub static fn new(x int, y int) Point {
Point{x, y}
}
pub static fn stretch(p Point, scale float) Point {
Point{x: p.x * scale, y: p.y * scale}
}
}
// 使用自定义构造函数
mut p1 = Point.new(1, 2)
mut p2 = Point.stretch(p1, 2.0)
除了在类型内部定义方法,我们还可以在外部给类型“扩展”新的方法。
扩展方法的关键字是ext
,即extends
type Point {
pub x int
pub y int
}
ext Point {
pub fn to_str() str {
`Point($x, $y)`
}
}
注意,和内部方法不同,扩展方法只能访问Point
中公开的成员,
因此我们上面的例子给x
和y
添加了pub
修饰。
如果需要访问私有的变量,那么直接把方法定义在类型内部即可。
扩展方法的用处是可以给第三方库定义好的类型, 甚至系统类型添加新的功能。
例如,如果要给系统的字符串类型str
添加一个新功能:
ext str {
pub fn shape_shift() {
for c in self {
if c.is_up() {
c.lower()
} else {
c.upper()
}
}
}
}
let s = "HellO"
let t = s.shape_shift()
assert_eq(t, "hELLo")
Auto语言扩展了Rust的接口(trait)概念,可以支持更多的模式匹配。 在Auto语言中,这种用来判断类型特征的结构,被称为一个类型的特征(Spec)。
Auto的特征有三类:
// 接口特征 Interface Spec
spec Printer {
// 符合Printable特征的类型,必须有print方法
fn print()
}
// 自定义的类型
type MyInt {
data int
// 直接实现接口的方法
pub fn print() {
println(.data)
}
}
// 也可以通过扩展类型方法来实现
ext MyInt {
pub fn print() {
println(.data)
}
}
// 接口可以包含多个方法
spec Indexer<T> {
fn size() usize
fn get(n usize) T
fn set(n usize, value T)
}
type IntArray {
data []int
pub static fn new(data int...) IntArray {
IntArray{data: data.pack()}
}
// 实现Indexer接口
pub fn size() int {
data.len()
}
pub fn get(n int) int {
data[n]
}
pub fn set(n int, value int) {
data[n] = value
}
}
// 表达式特征
spec Number = int | uint | byte | float
// 使用表达式特征
fn add(a Number, b Number) Number {
a + b
}
add(1, 2) // OK
add(1, 2.0) // OK
add(1, "2") // Error!
// 如果名字太长,也可以这么写:
fn <T = Number> add(a T, b T) T {
a + b
}
表达式特征还可以用来实现类型别名:
spec MyInt = int
此时,MyInt就等价于int,可以用于任何需要int的地方。 对于C/C++语言程序员,这就相当于一个宏。
用于类型特征的判别函数,其参数是type
类型,返回值是bool
类型。
fn predicate(t type) bool {
// ...
true
}
例如,下面的IsArray
函数用来判别是不是可以线性迭代:
// 判别函数
fn IsIterable(t type) bool {
when t {
// 是一个数组,其元素类型可以任意
is []any true
// 或者有next()方法
if t.has_method("next") true
// 或者实现了Indexer接口
is Indexer true
else false
}
}
// 这里参数arr的类型只要通过了IsArray(T)的判断,就能够调用,否则报错
// 注意:这里使用了`if`表达式,表示在编译期调用判别函数
fn add_all(arr if IsIterable) {
mut sum = 0
for n in arr {
sum += n
}
return sum
}
let arr = [1, 2, 3, 4, 5]
// OK,因为arr是一个`[]int`数组
add_all(arr)
mut my_arr = IntArray.new(1, 2, 3, 4, 5)
// OK,因为my_arr实现了Indexer接口
add_all(my_arr)
mut d = "hello"
// Error! d既不是[]int数组,也没有实现Indexer接口
add_all(d)
// 生成器
fn fib() {
mut a, b = 0, 1
loop {
yield b
a, b = b, a + b
}
}
// 使用生成器
for n in fib() {
println(n)
}
// 或者函数式
fib().take(10).foreach(|n| println(n))
// 任意函数
fn fetch(url str) str {
// ...
}
// do关键字表示异步调用
let r = do fetch("https://api.github.com")
// 返回的是一个Future,需要等待结果
println(wait r)
// 多个异步调用
let tasks = for i in 1..10 {
do fetch(f"https://api.github.com/$i")
}
// 等待所有任务都完成(或者超时)
let results = wait tasks
println(results)
// 节点配置,可以提前指定节点的属性对应的类型,如果配置,也可以按照默认配置使用
node button(name str) {
text str
scale Scale
onclick str
}
// 新建节点
button("btn1") {
text: "Click me"
scale: Scale.M
onclick: "click:btn1"
}
// 多层节点
ul {
li {
label("Item 1: ") {}
button("btn1") {
text: "Click me"
onclick: "click:btn1"
}
div { label("div1") {} }
}
li { label("Item 2") {} }
li { label("Item 3") {} }
}
Auto语言编译器本身只依赖于Rust和Cargo。
> git clone git@gitee.com:auto-stack/auto-lang.git
> cd auto-lang
> cargo run
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。