# 编译器开发文档

这些源码位于``src/compiler``目录下

我们规定：Stamon源码的文件后缀为``.st``

词法分析器的变动不大。所以我们先来看看语法分析器。

目前的语法分析器已经没有已知bug了。本次的更改也着重于实现对多文件的语法分析。

最重要的是新增的``Compiler.hpp``，Compiler主要对编译器进行了封装。你只需要向Compiler提供相应的参数，就能实现编译。

以下是Compiler的讲解：

* ``Compiler(STMException *e, ArrayList<String> *error_msg, ArrayList<String> *warning_msg)``：构造函数，需要传入异常类、错误信息列表指针以及警告信息列表指针。在执行后续操作时，如有错误信息或警告信息，会存入该指针中。
* ``void compile(String filename, bool isSupportImport)``：编译源码，``filename``为需要编译的源码文件名，``isSupportImport``声明了是否支持导入其他源码（有些平台并不支持多文件操作，所以我设置了这个参数），如果源码当中需要导入源码，则报错。在执行该函数之后应该要检查是否存在异常。编译出的结果会存入``src``成员。
* 这里需要补充的是：``src``成员的类型为``ArrayList<SourceSyntax>``，其中，``SourceSyntax``定义于``Parser.cpp``。``SourceSyntax``由``ast::AstNode* program（单个文件的语法树）``和``String filename（文件名）``构成。

### 编写了Ast与AstIr之间的互转工具

这些工具位于``src/ir``目录下

``AstIr``是一种中间代码表示形式，可以将Ast展开为一个数组。这样就可以把Ast转为AstIr，并将AstIr以字节码的方式写入。读取时也只需要读取AstIr，并重组成Ast即可。

AstIr的格式和HTML的格式类似：由逻辑单元、数据单元和结束单元组成。一棵Ast可以通过深度优先遍历转为AstIr。

这样的描述也许会较为拗口，我们来看一个示例：

> 考虑以下Ast:
> ```
> Add
> |-a
> |-Sub
> |--b
> |--1
> ```
> 该Ast可以转为以下AstIr：
> ```
> <Add>         //此为逻辑单元，以此类推
> <data val=a>  //此为数据单元，以此类推
> <Sub>
> <data val=b>
> <data val=1>
> <end>         //此为结束单元，以此类推
> <end>
> ```
> 这段AstIr和上面的Ast本质上是等价的。这样只需将Ast转为AstIr，就能获得一系列单元，方便存入文件。

逻辑单元本质上就是Ast节点的非叶子节点，数据单元本质上就是Ast节点的叶子节点。

我们先来讲一讲``AstIr.cpp``。

AstIr单元的实现是：

```C++
class AstIr {
    public:
        int type;
        /*
            * 一些特别的规定
            * 当type为-1，代表这是一个结束符，即</>
            */
        int data;
        //如果这个IR是字面量或标识符，则data存储着其在常量表中的下标
        //否则存储这个IR的具体信息（例如运算符类型）
        String filename;	//IR所在的文件名
        int lineNo;	//IR所在的行号
};
```

由于Ast当中的叶子节点只有两种可能：标识符或字面量，所以我把两者统一了起来，设立了``DataType``的子类``IdenConstType``，这样常量表里也能存储标识符了。

而数据单元作为Ast的叶子节点，他的type统一为AstLeafType。其中AstLeaf是专门为了运行时而定制的。

> ``Ast``和``Running-Ast``的区别就在于：Running-Ast的叶子节点皆为``AstLeaf``，而Ast非然。

AstIrConverter类用于将Ast数据生成为AstIr（同时兼任着AstIr转Running-Ast的任务），它值得关注的接口有：

* ``ArrayList<AstIr> gen(AstNode* program)``：迭代遍历语法树，编译成AstIr
* ``AstNode* read(ArrayList<AstIr> ir)``：AstIr转Ast，如果ir不正确，程序会运行时错误，所以请在运行该函数前检查ir

至此，Stamon的源码将会被转为AstIr，接下来则是写入AstIr至文件了，这一部分的内容位于``src/Stamon.hpp``，暂时按下不表，在介绍完解释器后会解释。


### 完成了SFN机制

SFN机制在本项目原所在仓库中有提及，这里再次摘抄：

> SFN，全称Stamon For Native。是StamonVM的一个调用外部功能的机制。你可以用它与解释器交互。

用不太准确但方便理解的说法是：SFN和JNI类似，都是一种本地库调用机制。

SFN的源码位于``src/sfn/SFN.cpp``。

SFN在Stamon中的语法规定为``sfn port, arg;``，其中port必须为字符串，代表着端口号，使用不同的端口号会调用不同的本地库（类似于汇编中的IO），arg则是参数，在调用SFN后，arg可能会变为调用后的结果。

SFN中的本地库可以由用户自定义，可扩展性高，不过我认为应该要给SFN划分具体的标准，哪一部分端口保留用作标准的本地库，哪一部分交给用户自定义。我可能会在后续进行调整。

SFN类的主要接口有：

* ``SFN(STMException* e, vm::ObjectManager* objman)``：构造函数，e为异常类，objman为当前运行时的ObjectManager对象。你可以在构造函数里绑定自己的本地库。详细请参见源码。
* ``void call(String port, datatype::Variable* arg)``：根据端口号调用本地库。

> ——改编自``工作日志/20240512.md``