这些源码位于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(文件名)
构成。这些工具位于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单元的实现是:
class AstIr {
public:
int type;
/*
* 一些特别的规定
* 当type为-1,代表这是一个结束符,即</>
*/
int data;
//如果这个IR是字面量或标识符,则data存储着其在常量表中的下标
//否则存储这个IR的具体信息(例如运算符类型)
; //IR所在的文件名
String filenameint 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)
:迭代遍历语法树,编译成AstIrAstNode* read(ArrayList<AstIr> ir)
:AstIr转Ast,如果ir不正确,程序会运行时错误,所以请在运行该函数前检查ir至此,Stamon的源码将会被转为AstIr,接下来则是写入AstIr至文件了,这一部分的内容位于src/Stamon.hpp
,暂时按下不表,在介绍完解释器后会解释。
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