本次更新不会修改版本。 本次更新重构了文件编解码部分和顶层设计部分,封装了输入输出流,并由此删去了不必要的依赖库。
由于不同架构的机器在存储时会有大小端序之差,因此为了让字节码能跨平台编译运行,我规范化了端序。
编码生成的文件应该满足大端序,而为了照顾不同机器的不同端序,我引入了新的配置,开发者可以根据自己实际平台的端序来调整配置。
我在StamonConfig.hpp
中新增了一个枚举和两个标识符,用于标记不同机器的大小端配置,他们默认全是小端序:
enum STAMON_CODING_ENDIAN {
// 机器的大小端控制
= 0,
StamonCodingLittleEndian
StamonCodingBigEndian};
/*...中略无关代码...*/
// 编码的端序
constexpr int StamonEncodingEndian = StamonCodingLittleEndian;
// 解码的顺序
constexpr int StamonDecodingEndian = StamonCodingLittleEndian;
值得注意的是,无论本地的机器采用什么样的端序,如果满足StamonEncodingEndian==StamonDecodingEndian
(即编解码端序一致),那么编码生成的文件一定能在本地解码运行,但在其他平台上有可能出现错误。这意味着如果开发者只有在本地编译运行字节码的需求,那么不一定需要费尽心思处理大小端序冲突,只要让编解码端序一致即可。
我将IOStream.cpp
改名为BufferStream.cpp
,并完全重构了它,该文件存储了内存输入输出流的实现。在我目前的构想里,分步行动机制在执行一个动作组时,相邻的行动工具应该使用内存来存储并传递数据。BufferInStream
和BufferOutStream
成为了内存数据的载体。
解码器通常会作为动作组的第一个行动工具,在这种情况下,起始数据可能来源于键盘输入、文件读取等操作。但无论起始数据的获取形式如何,最终都要拷贝到内存中,以BufferInStream
的形式传入到解码器中。
和上述情况对称的,编码器通常会作为动作组的最后一个工具,在这种情况下,结果数据可能会被保存到文件。但无论结果数据的保存形式如何,最终都要先将结果数据拷贝到内存中,以BufferOutStream
的形式返回给分步行动机制,机制再根据具体情况处理。
在完成BufferStream.cpp
的编写后,我开始修改所有的编解码器。之前,各个解码器都是手动托管内存读取以提升安全性,冗余代码多;编码器也直接操作保存到文件,不适合分步行动机制的发展。现在,我让他们统一使用BufferStream.cpp
来托管内存数据,这同时解决了“编解码器冗余代码多”和“不适合分步行动机制发展”两大难题。
我们来看看BufferStream.cpp
中的核心接口。
首先是BufferInStream
类:
接口原型 | 功能 |
---|---|
void reset() |
重新从头开始读取 |
template<typename T> void read(T &data) |
读取一个数据 |
template<typename T, int n> void readArray(T (&data)[n]) |
读取一个数组 |
为了方便理解,我提供了一个代码片段作为例子,在该片段中,我们假设有一个BufferInStream
类型的变量stream
,并且stream接下来要读取的内存数据为0x12,0x34,0x56,0x78,0x90
,本地机器的解码端序为小端序:
char x;
char arr1[2], arr2[2];
.read(x); //读取x = 0x12
stream.readArray(arr1); //读取arr1 = {0x34, 0x56}
stream.read(arr2); //读取arr2 = {0x90, 0x78} stream
值得注意的是,arr2
没有和arr1
一样顺序读取,这是因为stream.read
将arr2
看作了一个整体,又因为本地机器是小端序的缘故,所以逆序读取了数据。以上例子不仅提供了读取的示例,也警醒了开发者不要把read
和readArray
混为一谈。
接着是BufferOutStream
类,他的用法基本与BufferInStream
对称:
接口原型 | 功能 |
---|---|
template<typename T> void write(T &data) |
写入一个数据 |
template<typename T, int n> void writeArray(T (&data)[n]) |
写入一个数组 |
在实现完BufferStream.cpp
之后,其他的Reader和Writer也被修改,所有的数据读写操作都由BufferInStream
和BufferOutStream
托管,这极大减少了冗余代码,也统一了编解码器的实现模式。
将常量表的读写功能单独提取出来,封装到ConstTabReader.cpp
和ConstTabWriter.cpp
中,这是在为未来的多字节码标准做准备。
我剔除了AST部分一些获取节点数据的方法,我认为这是不必要的,直接访问AST节点的成员即可。
因为构造函数ObjectManager()
并没有被用到,所以我删除了他。
由于LineReader
可以使用BufferInStream
实现。因此移除了LineReader。又因为LineReader被移除,所以FileMap也被认为是不需要的。所有的FileMap都被StringMap代替。
具体请参见Main.cpp
的修改。
见mascot_v2.2.9.jpg
的文件修改记录。
我认为之后的顶层前端功能都可以用函数来代替类,以此简便代码的调用方式。在这个决定上,我删除了Compiler
类。取而代之的是两个函数:
<SourceSyntax> *ParseTargetProject(
ArrayList*e,
STMException <String> *error_msg, ArrayList<String> *warning_msg,
ArrayList, bool is_support_import, ArrayList<SourceSyntax> *src,
String filename<void> filemap, SyntaxScope global_scope
StringMap);
/*
编译一个Stamon项目。
e是异常类,error_msg是存储报错信息用的列表,warning_msg是存储警告信息用的列表,filename是源码文件名,is_support_import_表示是否支持引用其他源码,src是存储各个文件语法树用的列表,filemap是用来标记文件是否被引用过的映射表,global_scope是全局作用域。
返回处理后的存储着各个文件语法树的列表(即处理后的src)。
*/
::AstNode *MergeAST(ArrayList<SourceSyntax> *syntax_list);
ast
/*
将各个语法树合并为一个。syntax_list是存储着各个文件语法树的列表。返回合并后的根节点。
*/