#编程语言 #编译器 #内存 #esolang #smpl #后端 #编译

bin+lib fr

一种具有不寻常编译后端的高级编程语言

2 个版本

0.1.1 2020年2月6日
0.1.0 2020年2月2日

#687 in 编程语言

MIT 许可证

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)后,编译过程就可以开始了。

老实说,我并不确定自己是否完全理解整个编译器的工作范围。很多东西看起来就像是魔法一样在运作。尽管如此,我仍会尽力给出有意义的解释。不要相信这个软件,因为老实说,我根本不知道它为什么能工作

  1. 初始化栈和堆

    • 编译开始时,编译器会预留出它所知道将会需要的内存。这些内存包括返回寄存器和六个用于数学运算的其他寄存器。尽管看起来我选择六个寄存器是因为大多数CPU有六个寄存器,但实际上我选择六个是因为这是在esolangs.org上列出的任何原子数学算法在brainfuck中所需要的最小临时单元格数。

    • 栈内存分配完毕后,计算用于组合栈和堆的总内存分配量。

  2. 内联函数调用

    • 基本上,所有函数体都会被复制并粘贴到调用它们的地点。

    • 尽管函数被内联了,但它们仍然有栈帧,在调用前后分配和释放函数内存。

  3. 静态计算栈分配

    • 这可能是编译过程中的最糟糕的部分。这完全是多余的,我本应该设置一个预定的栈限制,比如8192字节或类似的东西。

    • 当一个作用域被实例化时,一个新的作用域会被推入当前作用域层次结构。在作用域中定义变量时,它们会在环境栈帧中分配。当作用域结束时,作用域中的每个变量都会被释放。注意,我从未在寄存器的列表中提到栈指针的释放?那是因为那里没有栈指针。所有栈内存都在编译时得到计算。这意味着对于每个作用域,栈的存储容量会精确地增长到所需的大小。编译器可以精确计算出在运行时任意时刻将分配多少内存到栈上。

    • 我恨我自己用这种方式实现栈。永远不要这样做。只需要实现pushpop就足够了。不要像我一样,你会后悔你的愚蠢

  4. 将原子操作转换为SMPL/brainfuck

    • 我是用了esolangs.org关于brainfuck算法的页面,这非常方便。将中间表示作为brainfuck的超集构建实际上是一个拥有大量预建算法的好方法。我发现这一部分极其简单且易于实现。最难的部分是实现指针的正确性。我的测试程序有很多次出现了无法解释的错误,而且总是是代表SMPL中指针的算法。事实上,编译器中可能还存在相当数量的内存错误。

    • 避免错误的最有效方法是启用brainfuck兼容模式。这不仅允许你使用工业级强度的brainfuck编译器和解释器运行你的代码,还可以让你摆脱对奇怪内存行为的头痛。

  5. 优化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