9 个版本
0.0.0-dev09 | 2024年1月20日 |
---|---|
0.0.0-dev08 | 2024年1月11日 |
0.0.0-dev03 | 2023年12月26日 |
0.0.0-dev02 | 2023年11月30日 |
#285 在 编程语言 中
96 每月下载量
21KB
155 行
Galvan
Rust 的高级伴侣语言。
[!重要] 这是一个正在进行中的项目,处于开发阶段。目前它处于一个业余项目状态,此处描述的大多数功能尚未实现。我在业余时间做这个项目 - 如果您喜欢这里提出的思想,并想帮忙,请随时联系我或在这里的 GitHub 上发起讨论。
动机
Galvan 不是什么
Galvan 并非旨在取代 Rust。它是一种转译为 Rust 的伴侣语言。这意味着,Galvan 的抽象不一定是无成本的,而是 Galvan 尝试为大多数用例选择一个合理的默认值。
Galvan 并不适用于低级编程 - 因此您不应使用它来构建解析器、编译器或音频压缩库。相反,Galvan 旨在用于高级应用程序,如 CLI 工具、Web 服务器等。最终目标是实现与 Rust 的完全互操作性,这样您就可以用 Galvan 编写应用程序,并依靠完整的 Rust 生态系统。
为什么选择 Galvan?
Rust 是一种非常好的语言,但它并非总是每个任务的最佳选择。Rust 的语法可能相当冗长,其类型系统和借用检查器 - 虽然非常强大 - 对于高级应用程序来说可能是一个负担,对于初学者来说可能令人不知所措。对于低级库和所谓的“系统编程”,Rust 在有用的抽象和掌握实现细节之间找到了一个完美的平衡点。然而,对于应用程序级编程来说,提供对常见用例的合理选择非常重要。这就是 Galvan 出现的地方:它提供了一种简洁的语法和简化 Rust 编写的方式 - 而不必担心生命周期、所有权等问题。
Galvan 之旅
Galvan 简介
Galvan 是一种将代码转译为 Rust 的现代编程语言。它提供了一种简洁的语法,同时利用 Rust 生态系统的全部功能。
基本语法和字符串格式化
在 Galvan 中,main
不是一个函数,而是一个“入口点”。
main {
let name = "Galvan"
print("Welcome to {name}, the modern language!")
}
请注意,Galvan 字符串始终支持内联格式参数。
函数
与 Rust 类似,函数使用 fn
关键字定义,并返回最后一个表达式的值
fn add(a: Int, b: Int) -> Int {
a + b
}
非常短的函数也可以使用 = 定义,并自动推断其返回类型
[!警告] 使用 = 定义函数尚未实现
fn add(a: Int, b: Int) = a + b
这些函数的体内不允许有换行符。
类型
在Galvan中,类型是通过使用type
关键字定义的。
/// A struct definition
pub type Color {
r: Int
g: Int
b: Int
}
// Structs can also use the named tuple syntax
pub type Person(name: String, age: Int)
/// A type alias
pub type Human = Person
/// A tuple type
pub type Couple(Person, Person)
枚举可以具有关联值,可以是所有变体或特定变体。枚举也使用type
关键字声明。
[!WARNING] 枚举尚未实现
/// An enum type
/// Enums can have general fields that are accessible to all enum variants
pub type Theme(name: String) {
Plain
/// Like in Rust, enum variants can have associated values, either named or unnamed
Monochrome(Color)
/// Unlike in Rust, '(' is also used for enum variants with named fields
Dark(background: Color, foreground: Color)
Light(background: Color, foreground: Color)
}
成员函数
所有函数都是声明为顶级。如果它们的第一参数命名为self
,它们可以作为成员函数调用
pub type Dog(name: String)
fn bark(self: Dog) {
print("{self.name} barks")
}
main {
let dog = Dog(name: "Bello")
dog.bark()
}
集合
Galvan为集合类型提供了语法糖。
pub type IntArray = [Int] // This is a Vec
pub type StringSet = {String} // This is a HashSet
pub type MyDict = {String: Int} // This is a HashMap
pub type OrderedDict = [String: Int] // This is an IndexMap
有序类型使用[]
,无序类型使用{}
。
可选类型和结果类型
Galvan为可选类型和结果类型提供了简洁的语法。
type OptionalInt = Int?
type FileOrErr = File!
type FileOrIoErr = File!IoError
错误变体指定在!
符号之后。如果没有给出,则使用灵活的错误类型。
[!WARNING]
!
、?
和??
尚未实现
fn open_file(path: String) -> File! {
let file = File::open(path)!
let contents = file.read_to_string()?.find("foo")?.uppercase() ?? ""
contents
}
!
运算符解包结果并在结果为错误时提前返回。这与Rust中的?
运算符相同。
?
是Galvan中的安全调用运算符。只有当结果不是错误也不是none时,后续表达式才会被评估。
??
是空合并运算符,您可以使用它为左侧表达式是none时提供一个默认值。空合并运算符的右侧不能是返回或抛出表达式。
联合类型
Galvan支持在需要类型标识符的所有地方使用联合类型。
[!WARNING] 联合类型尚未实现
fn print_value(value: Int | String) {
print("Value: {value}")
}
按值传递和按引用传递
可变参数与不可变函数参数
默认情况下,参数是按值传递的。如果参数需要被修改,可以使用mut
关键字按引用传递:为了保持一致性,允许使用let
关键字,但它冗余,因为默认情况下参数是按值传递的。
fn add_one(mut value: Int) {
value += 1
}
// Using `let` is not necessary here but allowed
fn incremented(let value : Int) -> Int {
value + 1
}
Galvan的mut value: T
与Rust的value: &mut T
等效。Galvan没有不可变引用,因为所有值都是按需复制的。
// No copy is happening here as the value is not mutated
// Arguments are passed by value by default
fn bark_at(self: Dog, other: Dog) {
print("{self.name} barks at {other.name}")
}
// A copy is happening here as the value is mutated
fn shout_at(self: Dog, other: Dog) {
// Redeclaring is neccessary as value parameters cannot be mutated
let other = other
// Copy is happening here
other.name = other.name.uppercase()
print("{self.name} shouts at {other.name}")
}
fn grow(mut self: Dog) {
// This mutates the original value as it is passed by reference
self.age += 1
}
存储引用
可以存储在结构体中的引用必须声明为堆引用。这是通过在声明前加上ref
来完成的。
pub type Person {
name: String
age: Int
// This is a heap reference
ref dog: Dog
}
main {
// Note that constructors use '(' with named arguments
ref dog = Dog(name: "Bello", age: 5)
// The `dog` field now points to the same entity as the `dog` variable
let person = Person(name: "Jochen, age: 67, dog: ref dog)
dog.age += 1
print(person.dog.age) // 6
print(dog.age) // 6
}
堆引用使用原子引用计数,在不再需要时自动释放,并且始终是可变的。与let
和mut
值不同,ref
值遵循引用语义,即它们指向同一个对象。因此,它们始终是可变的。
参数修饰符
在调用具有mut
或ref
参数的函数时,必须分别注释参数。对于成员函数的接收器不是这种情况。
fn make_uppercase(mut arg: String) { ... }
fn store(ref arg: String) { ... }
main {
ref my_string = "This is a heap ref"
// Argument must be annotated as mutable
make_uppercase(mut my_string)
// Argument must be annotated as ref
store(ref my_string)
}
通过将参数注释为 mut
,调用者承认在调用此函数时可能对给定参数进行原地修改。不可变变量或不可变结构体实例的成员(使用 let
声明)不能作为 mut
传递。
通过将参数注释为 ref
,调用者承认函数可能存储一个可变(堆)引用。只有声明为 ref
的变量和成员可以作为 ref
传递。
控制流
循环
与 Rust 一样,循环可以产生一个值
[!WARNING] 循环尚未实现
mut i = 0
let j = loop {
if i == 15 {
return i
}
i += 1
}
print(j) // 15
print(i) // 15
也支持 for 循环
[!WARNING] for 循环尚未实现
for 0..<n {
print(it)
}
循环变量可以通过 it
关键字访问,但也可以使用闭包参数语法显式命名
for 0..<n |i| {
print(i)
}
注意,范围声明使用 ..<
(排他上限)或 ..=
(包含上限)。
如果-否则
[!WARNING] 嵌套 if-else 尚未实现
if condition {
print("Condition is true")
} else if other_condition {
print("Other condition is true")
} else {
print("No condition is true")
}
尝试
您可以使用 try 解包结果或可选值
[!WARNING] 通过
it
的隐式参数尚未实现
try potential_error {
print("Optional was {it}")
} else {
print("Error occured: {it}")
}
解包变体可以通过 it 关键字访问,就像在闭包中一样。您也可以使用闭包参数语法命名它们以显式声明
try potential_error |value| {
print("Optional was {value}")
} else |error| {
print("Error occured: {error}")
}
与 if
一样,您也可以在没有 else 分支的情况下使用 try
try potential_error |value| {
print("Optional was {value}")
}
let optional = try potential_error |successful| { successful }
这可以用于将解包的值传递给函数,或将 Result 转换为 Optional。
返回和抛出
返回值是隐式的,但是您可以使用 return
关键字提前返回
[!WARNING] return 关键字尚未实现
fn fib(n: Int) -> Int {
if n <= 1 {
return n
}
fib(n - 1) + fib(n - 2)
}
使用 throw
关键字提前返回错误
[!WARNING] throw 关键字尚未实现
fn checked_divide(a: Float, b: Float) -> Float! {
if b == 0 {
throw "Division by zero"
}
a / b
}
泛型
在 Galvan 中,类型标识符始终以大写字母开头。使用小写字母开头会引入类型参数
[!WARNING] 泛型尚未实现
type Container {
value: t
}
fn get_value(self: Container<t>) -> t {
self.value
}
可以使用 where
关键字指定边界
fn concat_hash(self: t, other: t) -> t where t: Hash {
self.hash() ++ other.hash()
}
运算符
内置运算符
Galvan 提供了广泛的内置运算符。虽然它们都有 ASCII 变体,但 Galvan 在合理的情况下也接受 Unicode 符号。
算术运算符
+
:加法-
:减法*
:乘法/
:除法%
:余数^
:乘方
[!NOTE] Galvan 不提供与
∧
和∨
对应的 Unicode 逻辑运算符,因为它们可能会与v
和^
分别混淆。如果您想使用 Unicode 运算符,您可以自己定义它们。逻辑运算符
and
,&&
:逻辑与or
,||
:逻辑或xor
,^^
:逻辑异或not
,!
:逻辑非
[!警告] 位运算符尚未实现,但位运算符前缀为 b
b|
:按位或b&
:按位与b^
:按位异或b<<
:按位左移b>>
:按位右移b~
:按位非
比较运算符
==
:相等!=
、≠
:不等<
:小于<=
、≤
:小于等于>
:大于>=
、≥
:大于等于===
、≡
:指针相等,仅适用于堆引用!==
、≢
:指针不等,仅适用于堆引用
集合运算符
++
:连接--
:移除[]
:索引[:]
:切片in
、∈
、∊
:成员资格
Unicode 和自定义运算符
Galvan 支持 Unicode 和自定义运算符
[!警告] 自定义运算符尚未实现
@infix("⨁")
fn xor(lhs: n, rhs: n) = lhs ^^ rhs
@prefix("√")
fn sqrt(n: Float) = n.sqrt()
main {
let a_bool = true
let other_bool = false
let value = if a_bool ⨁ other_bool { √16.0 } else { 3.0 }
}
本节定义了自定义中缀 ⨁
和前缀 √
运算符。请注意,前缀运算符和操作数之间不允许有空格。中缀运算符必须由空格包围。
闭包
闭包使用参数列表语法定义
let add = |a, b| a + b
闭包类型使用箭头语法
fn map(self: [t], f: t -> u) -> [u] {
mut result = []
for self {
result.push(f(it))
}
result
}
尾随闭包
允许带有尾随闭包的函数省略参数列表和参数列表周围的括号
iter
.map { it * 2 }
// Trailing closures with only one parameter can use the it keyword instead of naming it explicitly
.filter { it % 2 == 0 }
// The parameter list before the trailing closure can be omitted
.reduce start |acc, e| { acc + e }
尾随闭包也可以使用编号参数而不是提供参数列表
iter
.map { #0 * 2 }
.filter { #0 % 2 == 0 }
.reduce start { #0 + #1 }
无括号函数调用
在语句或作为赋值的右侧,可以省略函数调用的括号
fn add(a: Int, b: Int) -> Int {
a + b
}
main {
let result = add 2, 3
print result // 5
}
由于此语法仅在赋值和语句中允许使用,因此您不能在例如函数参数中使用它。这避免了歧义。也不允许使用此语法来表示不接受参数的函数,以避免与变量混淆。
测试
每个阻碍编写单元测试的因素都是一个尚未编写的单元测试。因此,Galvan 提供了一种简洁的语法,以便在任何 .galvan 文件中快速编写单元测试
test {
assert 2 == 2
}
test "Ensure that addition works correctly" {
assert 2 + 2 == 4
}
与 'main' 类似,'test' 不是一个函数,而是入口点。测试可以接受一个字符串作为描述。虽然这是可选的,但强烈建议在单元测试中添加简短描述。
依赖关系
~6–15MB
~180K SLoC