在上一篇文章中,我们完成了词法分析器,下面我们继续努力,今要开发的是语法分析器的AST部分,让我们开始吧!
用Python开发一个自制的编程语言(虚拟机解释型)[2] 词法分析器(2)_自制编程语言基于python_嘉定世外的JinJiayang的博客-CSDN博客
目标分析
我们在上一篇文章中已经完成了一个简单的词法分析器,将代码映射成了Token流,这次我们要分析Token流中的语法关系,并将其转换为AST树(语法树)。
注:为了方便起见,我们将语义分析的部分拆分进语法分析和执行两大板块中。
这篇文章只需搭出一个AST的框架即可。
注:我们的大多数代码都是用面向对象框架实现,感兴趣的话可以用面向过程的语法重写。
具体实现
首先在\sjserver目录下建立文件sjast.py,这个文件用于定义语法树的结构。并在\sjparser目录下建立文件sentenceparser.py,但这个文件我们这次不会用到。
我们采用类和对象实现AST树的主要语法结构,后期会涉及到大量类型检查。
sjast.py:
from typing import List, Dict
import pickle
t = 0
class Identical:
def __init__(self, name):
self.name = name
def __str__(self):
return f"identical-{self.name}"
__repr__ = __str__
class Obj(object):
def __init__(self, value=""):
self.value = value
self.line = -1
def __str__(self):
return repr(self.value)
__repr__ = __str__
class This(object):
pass
this = This()
class Statement(object):
def __init__(self):
self.type = ""
self.line = -1
self.value: Dict[str] = {}
def __str__(self):
return f"{{'{self.type}': {self.value}}}"
__repr__ = __str__
def append(self, name, value):
self.value[name] = value
class StatementUnion(object):
def __init__(self):
self.value: List[Statement] = []
def __str__(self):
return str(self.value)
__repr__ = __str__
def append(self, value):
self.value.append(value)
class AST(StatementUnion):
def __init__(self, debug=False, version=1):
super(AST, self).__init__()
self.debug = debug
self.version = version
def dumps(self, path):
with open(f"{path}", "wb") as f:
pickle_obj = pickle.dumps(self, protocol=pickle.HIGHEST_PROTOCOL)
f.write(pickle_obj)
def loads(self, path):
with open(f"{path}", "rb") as f:
obj = pickle.loads(f.read())
self.value = obj.value
self.debug = obj.debug
self.version = obj.version
这些代码并不是很复杂,我们慢慢分析:
Identical
标识符。代表着一个变量名、一个类或是其他。类似于C++代码中
int a = 1;
std::cout << a;
中的a。因为我们ScrJ语言不存在指针,所以没有奇奇怪怪的class。
__str__方法和__repr__方法是自定义对象的字符串表示,有兴趣的可以关注我这篇文章:
Obj
没有太大用处,主要是我前面自己写代码的时候为了解决一个bug加上去的
Obj表示Obj.value,自指,一般后续不大会用到。
This
虚拟对象。
当出现this关键字是,sentenceparser解析为this常量,其值随运行而变化(我们ScrJ是一个动态的语言),this在其他语言中大家应该很熟悉了。
Statement
这个类是我们后续很多操作的基础,表示一个语句或表达式。
Statement对象(简称s)具有三个属性:type: str、line: int和value: Dict[str].
s.type表示s的类型,如声明、赋值、函数调用等。
s.line表示s处于第几行,在后续的错误处理堆栈中会用到(尽管我自己也没怎么搞懂)
s.value表示s的信息,比较复杂、灵活,我们在下一节中讲到。
特别地,我们将表达式如1+1仍然抽象成一个Statement对象,而不使用额外的类来表示。
StatementUnion
没有什么好过多介绍的,表示一个语句联合体,如if{...},while{...},不作为Statement而是StatementUnion处理,方便后续操作。
AST
我们将AST看作是StatementUnion和Statement的嵌套。AST类派生自StatementUnion,我们将所有代码仍然看作一个语句联合体。
需要注意的是,AST类有两个重要方法,分别是dumps和loads。这两个方法可以将AST树保存到本地,也就是说,我们执行完parser部分后,可以转换为.sjast文件,下次可直接运行该文件而跳过parser的部分。parser涉及大量CPU计算,会浪费很多时间,保存为.sjast文件后执行的过程类似虚拟机执行的过程,如Java、Python的字节码。
dumps和loads的原理很简单,只运用了pickle库,网上有大量资源。
END
好了,今天到此为止,下一篇我们将详细讲述如何根据符号流解析AST树,保证让你拥有一个属于自己的parser。
如果你也想尝试这个项目的话,创作不易,不妨点个赞,关注本专栏,thanks~