Bird 编程语言:第一部分






4.92/5 (127投票s)
一种新的通用编程语言,旨在快速、高级且易于使用。
关于 Bird 的文章
- Bird 编程语言:第一部分
- Bird 编程语言:第二部分
- Bird 编程语言:第三部分
目录
引言
我开发这门语言是因为我觉得其他语言并非万能。C++ 以速度著称,但其语法(尤其是头文件)以及缺乏 C# 般的高级功能,在我看来会减慢开发速度。调试 C++ 也可能很困难。但 C# 是一门托管语言,它限制了底层编程和应用程序性能。3D 图形在 C# 中的速度比 C++ 慢大约 1.5-2 倍。
我从 2010 年 3 月开始用 C# 开发 Bird。它是一门强类型原生语言。其性能似乎能与目前的 C++ 编译器竞争,并且它将拥有高级语言的特性以及一些新功能。还有很多东西我尚未实现,但它已经可以用于编写小程序。语法类似于 C# 和 C++,但进行了一些修改,以使代码更简洁并提高可读性。我曾计划做一个 C# 解析器,但目前已暂停。我将在未来重新开始。库与 .NET 类似,基本功能将得以实现。所以我想应该不难理解。
运行程序的先决条件
示例代码附带等效的 C++ 代码以进行性能比较。为了编译它们,需要安装并配置好 MinGW、Clang 的 `PATH` 变量,但这并非强制。Visual C++ 编译器使用需要将 _"vcvarsall.bat"_ 的路径设置到 _"Run - VC++.bat"_ 文件中。
使用 Bird 创建程序
编译器可以从命令行运行,使用 _"Binaries"_ 目录中的 _"Bird.exe"_。
Bird.exe -x -nodefaultlib -lBirdCore -entry Namespace.Main Something.bird -out Something.exe
我为示例代码编写了 _.bat_ 文件,因此不需要使用命令行。 `-x` 表示编译器在编译完成后运行输出文件。输入文件可以是 Bird 文件、C、C++ 或对象文件。
可以使用 `-l` 选项指定库。目前,默认包含 `BirdCore` 和 `BlitzMax`。`-nodefaultlib` 会禁用它们。 BlitzMax 是另一种编程语言,其函数对于图形是必需的,因为我尚未实现这些功能。
对象文件和存档文件也可以作为输出文件,以便在其他语言中使用。可以通过 `-format` 属性指定。这些是它的可能值:
app | 可执行文件 |
arc | 存档文件,不包含库,需要链接到 exe。 |
obj | 对象文件,只包含 _.bird_ 文件的汇编。其他文件和库不包含在内。 |
语法
一个简单的函数
using System
void Main()
Console.Write "Enter a number: "
var Number = Convert.ToInt32(Console.ReadLine())
if Number == 0: Console.WriteLine "The number is zero"
else if Number > 0: Console.WriteLine "The number is positive"
else Console.WriteLine "The number is negative"
for var i in 1 ... 9
Console.WriteLine "{0} * {1} = {2}", i, Number, i * Number
Console.WriteLine "End of the program"
代码块的指示基于行前的空白字符。同一作用域的总有相同数量的空白字符。冒号可用于使命令在同一行中拥有内部块。编译器需要知道前一个表达式在哪里结束。如果没有表达式,例如带有 else 但没有 if 的语句,则不需要冒号。
函数可以在没有括号的情况下调用,如果返回的值不被使用。在 `for` 循环中,`var` 关键字表示 `i` 的类型,这与初始值(`1` -> `int`)相同。三个点表示 `i` 的第一个值是 `1`,并且包含右侧的值,所以最后一个值是 `9`。
我曾考虑过允许声明不带类型(或 `var` 关键字)的变量,但这可能因为变量名拼写错误而导致 bug。
字面量
数字字面量可以有不同的基数和类型。`$` 表示十六进制,`%` 表示二进制。十六进制字母必须大写,以区别于类型表示法,后者是类型的小写缩写,位于数字末尾。
$FFb // An unsigned byte
-$1Asb // A signed byte
%100 // A binary number
链式比较运算符
我认为这可以在 C 语言中实现,因为在某些情况下它会很有用。每个子表达式只执行一次,因此比使用 `and` 连接两个关系运算更快。
bool IsMouseOver(int x, y, w, h)
return x <= MouseX() < x + w and y <= MouseY() < y + h
关系运算符只能朝一个方向,以便与泛型参数区分开。
别名
这类似于 C# 中的 `using` 别名指令和 C++ 的 `typedef` 关键字,但在 Bird 中,别名可以为任何东西创建,甚至为变量。声明顺序无关紧要,因此可以这样做:
alias int32 = int
alias int64 = long
alias varalias = variable
int64 variable
元组
元组基础
元组类似于结构体,但它们没有名称。与 .NET 元组不同,Bird 元组是值类型。元组是命名或未命名值的组合,这些值可以具有不同的类型。例如:
var a = (1, 2) // Type: (int, int)
var b = (1, 2f) // Type: (int, float)
var c = ("Something", 10) // Type: (string, int)
在这种情况下,成员的引用通过其索引完成(例如 `a_tuple.0`),但它们也可以获得名称:
alias vec2 = (float x, y)
const var v = (2, 3 to vec2
const var vx = v.x
const var vy = v.y
这些变量可以声明为常量,因为编译器将 `(2, 3)` 解释为单个常量。
元组还可以用于交换变量的值:
a, b = b, a
元组作为向量
向量操作基于元组。SSE/SSE2 打包指令将由元组操作生成,而不是向量化。Cross 函数可以这样写:
alias float3 = (float x, y, z)
float3 Cross(float3 a, b)
return a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
向量函数将在 `Math` 类中定义,其中一些已经实现。不使用 `float3` 类型,可以这样写,使用未命名的成员:
float, float, float Cross((float, float, float) a, b)
return a.1 * b.2 - a.2 * b.1,
a.2 * b.0 - a.0 * b.2,
a.0 * b.1 - a.1 * b.0
元组解构
可以以类似于交换变量的方式解构元组:
float x, y, z
x, y, z = Cross(a, b)
或者如果使用了 `var`,则可以一行完成:
(var x, var y, var z) = Cross(a, b)
必须在所有变量之前写入 `var`,以便编译器能够决定哪个是现有变量。例如,如果 `(var x, y, z) = ...` 会被解释为三个新变量,那么就不可能引用现有的 `y`、`z`。
For 循环
for var i in 0 ... 9
for var i in 0 .. 10
两个循环的意思相同。两个点表示 `i` 不会取右侧的值,而三个点则会取该值。
for var x, y in 0 .. Width, 0 .. Height
这与两个嵌套循环相同。`x` 从 `0` 到 `Width-1`,`y` 从 `0` 到 `Height-1`。带 `y` 变量的循环是内层循环。`break` 命令会退出两个循环。也可以这样写:
for var x, y in (0, 0) .. (Width, Height)
如果只指定了一个数字,则它将是所有变量的初始值或最终值。所以这与前面的一样:
for var x, y in 0 .. (Width, Height)
如果有两个点,可以创建一个单一的 for 循环,遍历它们矩形中的所有点。在这种情况下,`x` 变量从 `P1.0` 到 `P2.0`,`y` 从 `P1.1` 到 `P2.1`。
var P1 = (10, 12)
var P2 = (100, 110)
for var x, y in P1 ... P2
/ Something
`step` 可用于指定循环变量增加的幅度。它可以是标量或具有相同规则的元组。它在每个周期将 `1` 加到 `i`,将 `2` 加到 `j`。下一个循环将 `i` 增加 `1`,`j` 增加 `2`。
for var i, j in 1 .. 20 step (1, 2)
其他循环
`while`、`do-while` 循环类似于 C 语言:
var i = 1
while i < 100
i *= 2
i = 1
do
i *= 2
while i < 100
我创建了两个新的,代码可以写得更简洁。`repeat` 会根据参数指定的次数执行某个操作。`cycle` 会创建一个无限循环。
结构体
结构体可以包含字段、方法、构造函数等。`new` 操作符,如果类型未指定,它会创建一个与转换到的类型相同的对象。在这个程序中,它是返回类型。原始类型是 `var` 类型,它总是自动更改为另一种类型。
struct Rect
public float X, Y, Width, Height
public Rect(float X, Y, Width, Height)
this.X = X
this.Y = Y
this.Width = Width
this.Height = Height
public Rect Copy()
return new(X, Y, Width, Height)
public Rect Copy_2()
return new:
X = this.X
Y = this.Y
Width = this.Width
Height = this.Height
public float GetValue(int i)
switch i
case 0: return X
case 1: return Y
case 2: return Width
case 3: return Height
default return 0
我想指出,`case` 块的末尾永远不需要使用 `break` 命令。但我也不确定 `switch` 语句是否有必要,我从不使用它,我认为 `if` 条件要简单得多,尤其是在 C# 中,`case` 块必须以某种跳转命令退出。
字符串
最重要的 .NET 函数已经实现。我还没有实现 GC,所以对象将一直分配,直到应用程序退出。目前这不是问题。
using System
void Main()
Console.WriteLine "adfdfgh".PadRight(10) + "Something"
Console.WriteLine "adfdh".PadRight(10) + "Something"
Console.WriteLine
Console.WriteLine "adfdfgh".Contains("fdf")
Console.WriteLine "adfdfgh".Contains("fdfh")
Console.WriteLine
Console.WriteLine "adfdfgh".Replace('d', 'f')
Console.WriteLine "adfdfgh".Replace("d", "ddd")
Console.WriteLine "adfdfghléáőúó".ToUpper()
数组
引用类型数组
这是声明和初始化一维引用数组的方法:
var Array1D_1 = new int[234]
var Array1D_2 = new[]: 1, 2, 3
var Array1D_3 = new[]:
1
2
3
var Array1D_4 = new[]:
1, 2
3, 4
编译器在解释初始值之前会考虑维数的数量。值可以用括号和换行符分隔。如果找到的维数比指定的少一个,则换行符也是维数分隔符。我不确定这是否好,我可能会在未来移除它,因为它有点模糊。但也可以只使用括号来实现。
var Array2D_1 = new[,]: (1, 2), (3, 4)
var Array2D_2 = new[,]:
1, 2
3, 4
var Array2D_3 = new[,]:
(1000, 1001, 1002, 1003, 1004, 1005
1006, 1007, 1008, 1009, 1010, 1011)
(2000, 2001, 2002, 2003, 2004, 2005
2006, 2007, 2008, 2009, 2010, 2011)
固定大小数组
这些是值类型,存储在堆栈上。它们的类型用大小标记,与引用数组不同(例如 `int[10]`)。这是创建它们的方法:
int[5] Arr1 = new
int[5] Arr2 = default
`default` 关键字与 C# 中的一样。如果类型可以推断出来,指定类型只是可选的。在这种情况下,它与目标变量相同。`new` 也是如此,它会是 `new (int[5])()`。值类型的 `new` 与 `default` 含义相同。两个数组中的所有值都初始化为零。可以指定初始值,如下所示:
var FixedArr1D = [0, 1, 2, 3] // Type: int[4]
var FixedArr2D = [(0, 1), (2, 3)] // Type: int[2, 2]
byte[4] FixedArr1D_2 = [0, 1, 2, 3]
由于编译器在评估初始值之前会考虑变量的类型,因此 FixedArr1D_2 数组可以无错误地声明。
固定大小数组可以通过隐式转换转换为引用类型:
double[] Arr = [0, 1, 2]
Func [0, 1, 2, 3]
void Func(double[] Arr)
// Something
long[], byte[] GetArrays()
return [0, 1, 2], [2, 3, 4, 5]
指针和长度
这种数组(或者更确切地说是元组)的表示法是 `T[*]`(T 是任意类型),它实际上是 `(T*, uint_ptr Length)` 的简写。它对于不安全编程很有用。我创建它是为了避免为同一目的编写两个变量。引用类型和固定大小数组都可以隐式转换为它。
using System
void OutputFloats(float[*] Floats)
for var i in 0 .. Floats.Length
Console.WriteLine Floats[i]
void Main()
OutputFloats [0, 1, 2]
var Floats = Memory.Allocate(sizeof(float) * 3) to float*
for var i in 0 .. 3: Floats[i] = i + 10.5f
OutputFloats (Floats, 3)
带 ref, out 的参数
使用 `ref`,可以将参数用作输入和输出。`out` 只能用于输出,但它确保变量获得一个值:
using System
void OutputFunc(ref int x)
Console.WriteLine x
x++
void Func(out int x)
x = 10
void Main()
Func out var x
OutputFunc ref x
OutputFunc ref x
OutputFunc ref x
用 `ref` 传递的变量在调用函数之前必须有一个值,`out` 参数在离开函数之前必须被赋值。可以使用 `unsafe_ref` 来绕过这些检查。
命名和可选参数
只有没有默认值的参数才必须指定:
// The definition of BlitzMax.Graphics
IntPtr Graphics(int Width, Height, Depth = 0, Hertz = 60, Flags = 0)
Graphics 800, 600
Graphics 800, 600, 32
使用命名参数时,不必指定前面的参数:
Graphics Width: 800, Height: 600
Graphics 800, 600, Hertz: 75
属性和索引器
它们用冒号标记。属性被视为变量,使用它们时,编译器会调用 `set` 和 `get` 方法。对于索引器,还可以指定参数:
class Class
int _Something
public int Something:
get return _Something
set _Something = value
public int AutomaticallyImplementedProperty:
get
set
public int this[int Index]:
get return Index * 2
public int NamedIndexer[int Index]:
get return this[Index]
void Main()
Class Obj = new
Console.WriteLine Obj[3]
Console.WriteLine Obj.NamedIndexer[4]
运算符函数
可以为结构体和类定义运算符,它们默认不允许这样做:
class Class
int _Something
public static void operator ++(Class Obj)
Obj._Something++
void Main()
Class Obj = new
Obj++
获取 R 值的地址
有时参数必须通过指针传递。在 Bird 中,可以从常量和 R 值查询地址,它会自动复制到一个变量:
using System
/* The constructor of Array class:
public Array(IDENTIFIER_PTR ArrayType, uint_ptr[*] Dimensions,
uint_ptr ItemSize, void* InitialData = null) */
int[,] CreateIntArray2D(uint_ptr Width, Height)
var Obj = new Array(id_desc_ptr(int[,]), [Width, Height], 4)
return reinterpret_cast<int[,]>(Obj)
int[] CreateIntArray1D()
const uint_ptr Length = 16
uint_ptr[*] Dimensions = (new: Pointer = &Length, Length = 1)
var Obj = new Array(id_desc_ptr(int[]), Dimensions, 4)
return reinterpret_cast<int[]>(Obj)
`[Width, Height]` 表达式的类型是 `uint_ptr[2]`,所以当它转换为 `uint_ptr*` 时,编译器必须查询地址。因此,它会创建一个新变量,该变量将被赋值给 `[Width, Height]`,并获取此变量的地址。它对第二个函数中的 `&Length` 也执行相同的操作。`reinterpret_cast` basically does nothing,它只是改变表达式节点的类型,就像转换指针一样。
引用相等运算符
`===` 和 `!==` 运算符可用于比较对象的引用。它的作用与 `Object.ReferenceEquals` 相同。`==` 也可以用于此目的,但它可以通过运算符函数重写。
public bool StringReferenceEquals(string A, B)
return A === B
高阶函数
函数的类型可以用 `->` 标记。左侧是输入参数,右侧是输出参数。还可以指定调用约定和修饰符。例如:`birdcall string, object -> int, float`。当有多个输出时,返回类型将成为一个元组。将来,我计划以类似的方式允许所有函数具有多个输出。
using System
int GetInt(int x)
return x + 1
int Test((int -> int) Func)
return Func(2)
void Main()
var Time = Environment.TickCount
var Sum = 0
for var i in 0 .. 100000000
Sum += Test(GetInt)
Time = Environment.TickCount - Time
Console.WriteLine "Time: " + Time + " ms"
Console.WriteLine "Sum: " + Sum
这个小示例展示了它的工作原理。我也用 C# 编写了它,这是我的机器上的性能结果:
编译器 | Bird | C# |
时间 | 719 ms | 2234 ms |
实际上它实现得非常简单。高阶函数只是一个对象和一个函数指针的元组 `(object Self, void* Pointer)`。如果函数是静态的,`Self` 成员可以为 null。可以使用 `static` 关键字创建静态函数指针:`static int -> float`。调用非静态函数时,`Pointer` 成员会被转换为函数指针。如果 `Self` 不为 null,它也会被添加到参数中。这就是 `Test` 函数被提取的方式:
int Test((int -> int) Func)
return if Func.Self == null: (Func.Pointer to (static int -> int))(2)
else (Func.Pointer to (static object, int -> int))(Func.Self, 2)
为了运行得更快,可以将参数替换为函数指针。这样它运行只需 542 毫秒。
int Test((static int -> int) Func)
return Func(2)
历史
- 2013/1/1:高阶函数、堆栈对齐以及许多重构。
- 2012/11/10:作用域解析运算符,更改了转换运算符,`to` 关键字与 `is` 和 `as` 运算符具有相同的语法,并且不允许歧义代码。
- 2012/9/22:参数数组,改进的 x86 性能。
- 2012/8/18:实现了 `stackalloc`、指针和长度数组、未指定类型的 `new` 和 `default`、静态构造函数、`checked`、`unchecked`、带 `<>` 的泛型参数(仅在 `reinterpret_cast` 中)。
- 2012/7/18:对象类型转换、装箱、拆箱、`is` `as` `xor` 运算符、低级反射、改进的 x86 代码生成。
- 2012/6/16:异常处理、`try-catch-finally`、常量也可以在函数内部声明。
- 2012/5/19:数组、对象初始化、可以获取 R 值的地址、`ref`、`out` 参数、添加了示例的 Visual C++ 编译。
- 2012/5/2:改进了性能、标识符别名(代替 `typedef`)、字符串、引用相等运算符(`===`、`!==`)、二进制文件可以链接到程序集、将名称从 Anonymus 改为 Bird。