# 抽象语法树编译到 llvm

> 上一节: https://bbs.125.la/thread-14706398-1-1.html
> 源代码 git: http://gogs.mkyr.fun:99/myuan/elang


## 前言

本节的目标是把上一节的「语言」编译到 llvm, 要想简单快捷地生成 llvm IR, 官方的方案是 C++, 虽然有 C 绑定, 但是我测试编译出来的 dll 易语言总是莫名其妙报错或者找不到到导出函数, 而在 dllexport viewer 中却可以看到. 编译 llvm dll 然后让易语言调用花了大部分的时, 以至于这篇文章拖到了现在. 

另外还有一个选项是 binaryen, 如果是 binaryen IR 的话, 倒还可以用易语言手写生成, 但是 binaryen 最终是运行在 wasm 上的, 不直接原生运行的话, 就代表着无法简单使用操作系统 API, 以及与已有的易语言生态相容, 那么这次的经验就对未来无用了. 

最终, 我选择了 Python 的 llvmlite 绑定, Python 的 llvmlite 也是对 C API 的封装, 未来也许有机会白嫖 llvmlite 的 DLL. 

## 未来

编译器前端要用 DSL 生成不能用易语言写, 后端又不能调用 llvm (最接近的是一个 clang 的 SDK, https://bbs.125.la/forum.php?mod=viewthread&tid=14543661), 所以生成 IR 的逻辑也不能用易语言写, 可以说`使用易语言编译易语言`的最终目标完全落空. 我之前写过的那些目标本来都是为了这个最终目标努力的, 虽然某些目标仍然可以用易语言实现(比如易语言实现 lisp, 栈虚拟机, 语法扩展), 但是我现在的确非常意兴阑珊. 

加上易语言又跟易语言 IDE 强绑定, 之前考虑易语言语法扩展的时候, 我还觉得可以扩展 IDE 来兼容新语法, 但是后来我翻了一些逆向出来的东西, 发现以我的能力基本碰不了易语言 IDE 核心逻辑部分, 如果是在其他地方写然后转译成易语言, 那等于换了一门语言, 毕竟手写易语言的文本格式太扯了, 肯定是用另一种表达生成易语言的, 那能换一门语言的时候, 为什么不用 C# 呢?  

综合来讲, 现在唯一能做的, 就是用易语言做一个易语言的虚拟机, 但是众所周知易语言语法已经很古老了, 我绝对没有勇气完全用易语言实现易语言的解释器, 现在版本的易语言写起来太无聊了. 第一篇第二篇实际上我曾经用 Python 实现过, 约 200 行就能完成, 但是在易语言里花了 844 行, 单独虚拟机我在 Python 中总共花了 95 行(平均每行 30 字符), 相当于是 Python 300 行就够完成全部了, 而易语言用了 1359 行. 我曾见过有人用 Python 实现了 lc3 虚拟机, 大概花了 688 行, 按这个比例光 lc3 虚拟机部分大约在易语言里需要两千八百行, 易语言语法前端估计又要三四千行. 这样又回到了扩展语法, 而上面分析看来扩展语法是很麻烦的. 

关于扩展语法与现有 IDE 兼容, 如果你有任何解决问题的想法, 请留言告知.  
只要能扩展语法, 我可以先用旧易语言扩展语法, 扩展差不多了就在其他语言用 socket 接口把 llvm 的相关操作封装起来, 让扩展后的易语言通过 socket 调用, 最终勉强还算是`使用易语言编译易语言`. 


## 操作分解

在虚拟机中, 我自己写的代码可以随意操作逻辑, 但是到 llvm 层面, 就得自己新加逻辑来控制常量匹配了. 以如下代码举例:

```
f(0) = 1
f(x) = x * f(x - 1)
f(x, y) = f(x) + f(y)
```

应当编译成四个函数, 一个总入口, 三个子函数

```c
// f(0)
inline double f1(double _) {
    return 1;
} 
// f(x)
inline double f2(double x) {
    return x * f(1, x - 1, 0);
}
// f(x, y)
inline double f3(double x, double y) {
    return f(1, x, 0) + f(1, y, 0);
}

double f(int args_count, double arg0, double arg1) {
    switch (args_count) {
        case 1:
            if (arg0 == 0) 
                return f1(arg0);
            else
                return f2(arg0);
            break;
        case 2:
            return f3(arg0, arg1);

    }
}

```

然后这门语言除了函数就没东西了, 就这样. 

归纳一下用到的功能

- 加减乘除
- 语言内部函数调用
- 判断
- 外部 C 函数, printf

> 实际上再加点关键词, 就可以实现 lisp 了

## AST 导出

没什么好说的, 手写了一个按 JSON 格式输出 AST 的函数, 这是最后的荣耀, 最后的完全使用易语言标准库完成的东西

## LLVM IR 生成

> python3.9

```python

```


## 虚拟机 in Python

```python
from json import load
import string
from collections import defaultdict


ast = load(open("ast.json", encoding="utf8"))
funcs = defaultdict(list) # {name: [{args: body}]}

op_funcs = {
    "+": lambda x, y: x + y, "-": lambda x, y: x - y,
    "*": lambda x, y: x * y, "/": lambda x, y: x / y,
}
builtin_func = {
    'output': lambda *x: print(*x),
    'dir': lambda *_: print(funcs)
}

is_number = lambda x: all([i in string.digits + "." for i in x])

get_dict_key = lambda x: list(x.keys())
get_dict_value = lambda x: list(x.values())


def eval_function(name: str, params: list, env: dict):
    current_funcs = funcs.get(name)
    candidate_func_match = [0 for _ in current_funcs]

    for func_index, (args, body) in enumerate(current_funcs):
        if len(args) != len(params):
            candidate_func_match[func_index] -= 99999
            continue
        for i, arg in enumerate(args):
            if not all([a in string.digits for a in arg]):
                continue
            # only digits here
            if params[i] != float(arg):
                candidate_func_match[func_index] -= 99999
                break
            candidate_func_match[func_index] += 1

    candidate_func = sorted(
        zip(current_funcs, candidate_func_match),
        key=lambda x: x[1]
    )

    if not candidate_func or candidate_func[-1][1] < 0:
        print("error: cannot find a suitable overloaded function for", name)
        return
    
    args, body = candidate_func[-1][0]
    return eval_line(body, dict(zip(args, params)))


def eval_line(line: dict, envs: dict):
    if isinstance(line, dict):
        name: str = get_dict_key(line)[0]
        params: list = get_dict_value(line)[0]
    else:
        print('error line', line)
        return
    if name == 'root':
        for arg in params:
            eval_line(arg, envs)
    elif name.startswith('#'):
        pass
    elif name == '括号':
        return eval_line(params[0], envs)
    elif name == '=':
        assert len(params) == 2
        func_name, args = list(params[0].items())[0]
        args = [get_dict_key(i)[0] for i in args]
        funcs[func_name].append([args, params[1]])
    elif is_number(name):
        assert not params
        return float(name)
    elif name in op_funcs:
        assert len(params) == 2
        return op_funcs[name](
            eval_line(params[0], envs), 
            eval_line(params[1], envs)
        )
    elif name in builtin_func:
        return builtin_func[name](">", *[
            eval_line(arg, envs) for arg in params
        ])
    elif name in funcs:
        return eval_function(name, [
            eval_line(arg, envs) for arg in params
        ], envs)
    elif name in envs:
        return envs.get(name)
    else:
        print('cannot find', name)
eval_line(ast, {})
```