2 个版本
0.1.1 | 2020年2月6日 |
---|---|
0.1.0 | 2020年2月2日 |
#687 in 编程语言
1.5MB
41K SLoC
免费
一个针对更糟糕编程语言的糟糕编程语言。
我决定将这种编程语言命名为 free,因为它没有任何内存限制。基本上,free 程序不可能发生段错误或类似的事情:你可以随意对任何内存位置进行赋值。这不是设计的目的。这仅仅是目标编程语言(SMPL)的一个副产品。
SMPL
SMPL,发音为 "simple",是一种几乎与 brainfuck 完全相同的编程语言。它非常容易实现;SMPL 只是 brainfuck 的超集,增加了 3 个额外的运算符。这些运算符(&
、*
和 ?
)使 SMPL 能够动态地而不是静态地管理内存,这是 brainfuck 中不可能的。考虑以下问题。
在 brainfuck 中,表示一个数字数组非常简单。它们可以像这样存储在磁带上。
char array[6]
|
v
[0, 0, 0, 1, 2, 3, 4, 5, 0, 0, 0, ...]
但是,当数组需要增长或者你想要表示数组中元素的指针时会发生什么?
在 brainfuck 中表示指针没有任何真正的优雅解决方案。
然而,借助 SMPL 的力量,我们可以优雅地表示指针操作。
SMPL 运算符 | 描述 |
---|---|
> | 将指针移动到右侧 |
< | 将指针移动到左侧 |
+ | 增加指针下的内存单元格 |
- | 减少指针下的内存单元格 |
. | 输出指针下的单元格中存储的字符 |
, | 输入一个字符并将其存储在指针下的单元格中 |
[ | 如果指针下的单元格为零,则跳过匹配的 ] |
] | 如果指针下的单元格不为零,则跳转到匹配的 [ |
* | 将指针设置为当前单元格的值 |
& | 将指针设置回上一次 * 之前的值 |
? | 使用指针下的单元格的值,将磁带左侧连续零的起始地址存储在当前单元格中 |
编译过程
输入代码解析并生成抽象语法树(AST)后,编译过程就可以开始了。
老实说,我并不确定自己是否完全理解整个编译器的工作范围。很多东西看起来就像是魔法一样在运作。尽管如此,我仍会尽力给出有意义的解释。不要相信这个软件,因为老实说,我根本不知道它为什么能工作。
-
初始化栈和堆
-
编译开始时,编译器会预留出它所知道将会需要的内存。这些内存包括返回寄存器和六个用于数学运算的其他寄存器。尽管看起来我选择六个寄存器是因为大多数CPU有六个寄存器,但实际上我选择六个是因为这是在esolangs.org上列出的任何原子数学算法在brainfuck中所需要的最小临时单元格数。
-
栈内存分配完毕后,计算用于组合栈和堆的总内存分配量。
-
-
内联函数调用
-
基本上,所有函数体都会被复制并粘贴到调用它们的地点。
-
尽管函数被内联了,但它们仍然有栈帧,在调用前后分配和释放函数内存。
-
-
静态计算栈分配
-
这可能是编译过程中的最糟糕的部分。这完全是多余的,我本应该设置一个预定的栈限制,比如
8192
字节或类似的东西。 -
当一个作用域被实例化时,一个新的作用域会被推入当前作用域层次结构。在作用域中定义变量时,它们会在环境栈帧中分配。当作用域结束时,作用域中的每个变量都会被释放。注意,我从未在寄存器的列表中提到栈指针的释放?那是因为那里没有栈指针。所有栈内存都在编译时得到计算。这意味着对于每个作用域,栈的存储容量会精确地增长到所需的大小。编译器可以精确计算出在运行时任意时刻将分配多少内存到栈上。
-
我恨我自己用这种方式实现栈。永远不要这样做。只需要实现
push
和pop
就足够了。不要像我一样,你会后悔你的愚蠢。
-
-
将原子操作转换为SMPL/brainfuck
-
我是用了esolangs.org关于brainfuck算法的页面,这非常方便。将中间表示作为brainfuck的超集构建实际上是一个拥有大量预建算法的好方法。我发现这一部分极其简单且易于实现。最难的部分是实现指针的正确性。我的测试程序有很多次出现了无法解释的错误,而且总是是代表SMPL中指针的算法。事实上,编译器中可能还存在相当数量的内存错误。
-
避免错误的最有效方法是启用brainfuck兼容模式。这不仅允许你使用工业级强度的brainfuck编译器和解释器运行你的代码,还可以让你摆脱对奇怪内存行为的头痛。
-
-
优化SMPL/brainfuck
-
因为SMPL和brainfuck非常简约,所以优化非常有效。像清除已释放内存这样的小事情在技术上是不必要的,如果需要的话可以优化掉。
-
这是编译过程中在可选地将SMPL转换为C之前的最后一步。
-
经过所有这些步骤后,编译好的free
程序就准备好执行了。
语法和标志
Free的语法受到了Rust的强烈启发。这是因为一个客观的事实:Rust是迄今为止最好的编程语言。
每个Free程序都包含一个start
函数(就像main
函数)。
fn start() {
// This is a variable declaration
def str = "Hello world!";
println(str);
}
我非常想使用let
关键字,因为它非常漂亮,但在Free中没有变量是常量的。我使用def
,因为它不那么误导人,而且我认为var
更丑。
Free也有两种不同的控制流程结构:while循环和if-else语句。
if-else语句的功能与其他语言中的if语句相同。但是没有else-if表达式。
fn start() {
if test() {
// this code will run if test() is non-zero
println("True!");
} else {
// this code will run if test() is zero
println("False!");
}
}
fn test() {
return 1;
}
然而,while循环却大不相同。由于Free内存管理的特性,不能将函数调用用作while循环的条件。变量必须用来存储while循环的条件,因为它们在其作用域内的内存带位置始终是恒定的。
fn start() {
def running = 1;
while running {
println("running!");
}
}
同样地,由于while循环需要变量来存储条件,只能引用变量。不过,任何值都可以解引用。
fn start() {
def a = 5;
// print `a` as a digit by adding ascii code for 0
println(add(a, 48));
inc(&a);
// print `a` as a digit by adding ascii code for 0
println(add(a, 48));
}
// No type is needed for `ptr`. All variables are typeless because type checking is hard.
fn inc(ptr) {
*ptr = add(*ptr, 1);
}
使用alloc
函数,现在我们可以使用动态内存分配了!
fn start() {
// Allocate 16 bytes of memory and store the pointer to that block in `str`
def str = alloc(16);
if 1 {
*str = "True!\n\0";
} else {
*str = "False!\n\0";
}
cprint(str);
}
fn cprint(str) {
def counter = 0;
def running = *add(str, counter);
while running {
print(running);
counter = add(counter, 1);
running = *add(str, counter);
}
}
我们还需要能够释放我们分配的内存。
fn start() {
// Allocate 128 bytes of memory and store the pointer to that block in `str`
def size = 128;
def str = alloc(size);
free(str, size);
}
// free_byte only frees a single cell, so free must be implemented manually
fn free(ptr, size) {
while size {
size = sub(size, 1);
// free_byte is built in
free_byte(add(ptr, size));
}
// Store 0 in the return register
return 0;
}
示例输出
现在来展示一些糟糕的输出代码。
以下是Free中的“Hello World”。
// This flag enables brainfuck compatibility mode.
// This disables pointer operations and any number literal greater than 255
#[enable(brainfuck)]
fn start() {
println("Hello, world!");
}
这段代码被编译成以下内容。
请原谅我所创造的东西。
[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++>+<>>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[-]>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<+>>>>>>>-]<<<<<<<[>>>>>>>+<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>-]<<<<<<<<[>>>>>>>>+<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>-]<<<<<<<<<[>>>>>>>>>+<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>-<>]<<<<<<<<<<[>>>>>>>>>>+<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>-]<<<<<<<<<<<[>>>>>>>>>>>+<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>-<>]<<<<<<<<<<<<[>>>>>>>>>>>>+<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>-]<<<<<<<<<<<<<[>>>>>>>>>>>>>+<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>.>.>.>.>.>.>.>.>.>.>.>.>.>.<<>>>[-]++++++++++.<<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<>>>[-][-]<<<<<<<<<<<<<<<<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<
糟糕,这太糟糕了。然而,它相当简单地展示了编译过程的工作原理。让我来分解一下。
这是编译器输出的第一部分,也是最容易理解的部分。在这里,编译器正在分配和清除寄存器单元,以及将用于存储字符串字面量的栈单元。
[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<
然后,编译器实际上将字符串字面量的数据赋值到栈上的内存位置。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++>+++++++++++++++++++++++++++++++++>+<
然后,编译器将字符串复制到新的内存位置,以便println
函数进行操作。
>>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<[-]>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<+>>>>>>>-]<<<<<<<[>>>>>>>+<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>-]<<<<<<<<[>>>>>>>>+<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>-]<<<<<<<<<[>>>>>>>>>+<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>-<>]<<<<<<<<<<[>>>>>>>>>>+<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>-]<<<<<<<<<<<[>>>>>>>>>>>+<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>-<>]<<<<<<<<<<<<[>>>>>>>>>>>>+<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>-]<<<<<<<<<<<<<[>>>>>>>>>>>>>+<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<[>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>-]<<<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>[-]<<<<<<<<<<<<<<<>[<>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>>>>>-<>]<<<<<<<<<<<<<<<<<<<<[>>>>>>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<<<-]
最后,程序打印出字符串并清理栈。
[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<>>>[-][-]<<<<<<<<<<<<<<<<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<<<<<<<<<<<<<<<[-]>[-]>[-]>[-]>[-]>[-]>[-]>[-]<<<<<<<
尽管大部分输出代码是可以理解的,但还有一些部分对我来说仍然很困惑。
例如,我不完全确定为什么当程序存储“Hello world!”字符串时会出现>+<
,但不管怎样。它起作用了,我已经耗尽了我对为什么它存在的关心。
使用和安装
安装Free的最佳方式是使用Rust包管理器。
cargo install -f fr
然后,可以使用fr
二进制文件编译Free文件。
fr in.fr
gcc out.c
./a.out
依赖关系
~12MB
~224K SLoC