今天整理了一下学习编译原理时,实现语言Medusa时写的笔记,搞出了一篇类似说明文档的东西,介绍下Medusa
语法部分
Medusa是一门动态脚本语言,语法和代码格式借鉴Python,运行代码在顶层书写。
Medusa不识别':' ';'等符号,但识别white spcae和换行符:
1 + -9:执行结果是-8
1 + - 9:语法分析错误
语句Statement暂分func defination、if、while、return、expression几类。
若干statements组成block,由'{'、'}'分隔,block是def、if、while句式
的主体结构
if expression { statements } else { statements }
while expression { statements }
def name( paras ) { statements }
顶层代码定义的变量将放入global
environment
中,block的执行将有自己的local environment,并且local env带有指向父环境的指针
函数定义以'def'关键词起始,不支持在block中定义函数,所有的函数由func_list列表保存
Medusa带有简单的报错处理,错误信息有行号、字符位置、出错记号流以及提示,水平有限,不支持错误校正、预测处理,报错后即终止程序。
内部设计和结构
interpreter:
解释器类,包含解释过程中需要的绝大数的数据结构,如记号流、函数表、全局环境、stack等等。init过程完成初始化工作,并向函数表中添加内置函数,目前内置函数仅有print函数
词法分析:
使用自己写的正则表达式引擎(支持基本的正则符号:'*' '+' '|' '('和')'),构建状态机输出,状态转移矩阵。词法scaner对源程序文本做检查,输出记号流,遇到不符合正则规则的字符串,报错并exit。
构造状态机的算法:首先将正则的中缀形式转换成后缀形式,例如a|b|c|d变为a b c d | | |,使用栈完成不确定有限状态自动机的构造,再利用闭包算法将NFA转换为DFA。
语法分析:
过程没有使用yacc、bison等自动生成器,使用递归下降算法手写而成,程序的上下文无关语法表示如下:
proc -> def id(id...){ statements }
|statement
statement -> returnexp
| exp
| ifexp {statements} else {statements}
| whileexp {statements}
表达式exp使用算术运算符、比较运算符、赋值运算符连接callfunc(函数调用)、variable(变量)、literal(字面常量)等节点,
在语法分析模块中的表示形式是AST。
Medusa 没有bool型变量,不同于C、c++中可将数值型转换为bool型,
if、while句子只判别exp结果非0和0
。
语法分析过程还将向函数表添加自定义函数
语义分析:
由于Medusa是一门动态类型的语言,变量不需要声明,直接定义并使用,所以传统语义分析中的类型检查部分实现较难,解释器将此功能化为执行期执行,而变量符号表也随之移到执行期间建立
执行:
execute.cc为执行过程代码,执行statements,最重要的exp执行过程是后序遍历AST树,递归调用求解表达式的值;
执行过程解释器还将完成垃圾回收工作,gc算法是mark&sweep
;
符号表
以 pair<string,MdsObject*>形式表示了程序上下文, 多个env对象可区分程序的作用域,每个env对象都有parent域指向父环境,最顶层的env就是global env,当前作用域没有变量声明时,搜索上层环境;
stack用于保存exp计算时的临时值,这些值没有变量名。
内存分配:
采用两级分配器模型,最底层是封装了malloc、free函数的内存池,用于向内核申请大块内存;次级分配器是对象分配器,维护了多条空闲区域链表,链表放置了大小8、16、24...126字节的空闲区域,解释器空间申请将从这里得到满足,空闲区域不足时将向内存池申请新的空间。分配的内存块前置一个BlockMeta类,放置mark标志位和大小信息。
(内存模块是为了个人理解内存模型和分配策略而造的轮子,实际上malloc、free即可,这里说明一下)
变量模型:
Medusa的类型舍弃了C、JAVA中的原始类型设计,
变量的值不在放在变量内,而是
参考了python,采用引用模型。
Medusa包含三个内置类型,int、string、list,为了减少工作量,省去了float、bool、long等类型。三种内置类型分别对应MdsIntObject、MdsStringObject、MdsListObject三个类,继承自MdsObject,利用多态实现不同的调用以及提供统一的上层接口;
所有的值对象在内存中都只有唯一的拷贝,并且放入int_map、string_map、list_map三张表中,表的索引分别是对象值的hash值(
int、char*、void*[]
);
env维护的是一个 “pair<string(变量名),
MdsObject*
>” 的数组;
表达式“c = a+b”的执行如下:在local env中寻找关键字“a”、“b”,提取变量值,即
MdsObject的指针,计算两个对象的相加值,并利用结果的hash值在
int_map、string_map、list_map寻找对象,没有则新建并插入,最后将env中关键字“c”项的内容写为结果对象的地址。
gc:
Medusa支持对象的垃圾回收,mark&sweep的起始点为全局环境、函数局部环境链表、用于运算过程的stack容器中的对象(保存临时变量)。gc触发时机是每10次内存申请操作时。