从RegExp开始搓一个简单的解释器
Fri Dec 01 2023
# 从RegExp开始搓一个简单的脚本语言
## Ez Regular Expression
来点简单的正则语法( 可能也不会很简单,因为已经开始预查了( 一些基本的语法就直接跳过(
### 预查
如果要匹配一个Matrix
,但是不想匹配一个Matrix2
,这个时候,就可以用预查来完成(
可以用向前预查Lookahead assertion,写成Matrix(?!2)
,来匹配所有Matrix
且下一个字符不是2
的字串,肯定预查为Matrix(?=2)
有的时候,想匹配一个Number
,但是不想匹配一个INumber
,这个时候,也可以用预查来完成(
以上面的例子写regex,用向后预查Lookbehind assertion,可以写成(?<!I)Number
,这会匹配所有Number
且前一个字符不为I
的字串
当然这是否定预查,也可以使用肯定预查(?<=I)Number
,这会匹配所有Number
且前一个字符为I
的字串,注意,匹配到的字串不包括I
## 开搓!
### 创建venv
对的,用python搓一个,为什么用python呢,因为我懒。
python3 -m venv venv
. ./venv/bin/activate
pip install regex matplotlib
regex
是解释器的核心之一,正则写的好,解释器就完成了一部分(
matplotlib
是课程要求(因为要求是做一个能用来绘图的语言,所以直接用matplotlib
### 字面量的匹配
先定义一些字面量的类型,直接写成正则:
pattern_literal_string = re.compile(r'^\s*"([^"]*)"\s*')
pattern_literal_integer = re.compile(r"^\s*(\d+)(?![\d\.A-z])\s*")
pattern_literal_float = re.compile(r"^\s*(\d+\.\d*|\d*\.\d+)(?![\d\.A-z])\s*")
pattern_literal_boolean = re.compile(r"^\s*(true|false)(?![A-z\d_])\s*")
pattern_literal_null = re.compile(r"^\s*(null)(?![A-z0-9_])\s*")
pattern_literal_range = re.compile(r"\s*(\d+)\.\.(\d+)\s*")
要注意这里的正则限定了开头,但是没有限定末尾,所以一定要写好匹配的边界,不然可能会漏掉内容或者匹配到了额外的内容。然后就是写一个函数,来处理这些字面量:
def from_literal(token: str):
if m := pattern_literal_string.match(token):
return Var("string", m.group(1), const=True), m.end()
elif m := pattern_literal_integer.match(token):
return Var("integer", int(m.group(1)), const=True), m.end()
elif m := pattern_literal_float.match(token):
return Var("float", float(m.group(1)), const=True), m.end()
elif m := pattern_literal_boolean.match(token):
return Var("bool", m.group(1) == "true", const=True), m.end()
elif m := pattern_literal_null.match(token):
return Var("object", None, const=True), m.end()
elif m := pattern_literal_range.match(token):
return Var("range", (int(m.group(1)), int(m.group(2))), const=True), m.end()
else:
return None
这里会涉及到一个Var
类型,定义如下:
class Var:
runtime_type_bindings = {
"string":"string",
"int":"integer",
"float":"float",
"bool":"bool",
"tuple":"tuple",
"NoneType":"null"
}
def asVar(val):
if isinstance(val,Var):
return val
elif (typename := type(val).__name__) in Var.runtime_type_bindings:
return Var(Var.runtime_type_bindings[typename],val)
else:
raise Exception(f"unknown type: {typename}")
def unwrap(var:'Var'):
if not isinstance(var,Var):
return var
if var.type == "tuple":
return [Var.unwrap(i) for i in var.value]
else:
return var.value
def __init__(self,type,value,name=None,const=False) -> None:
self.type = type
self.value = value
self.name = name
self.const = const
def __repr__(self) -> str:
return f"[{"const" if self.const else "var"} {self.type}::{self.name if self.name else ""}]({self.value})"
def __str__(self) -> str:
return self.__repr__()
def set(self,val):
if self.const:
raise Exception("const filed is immutable")
if self.type == None:
self.type = val.type
self.value = val.value
return
if val.type != self.type:
raise Exception(f"variable type mismatch: {self.type} != {val.type}")
self.value = val.value
def __eq__(self, __value: object) -> bool:
if isinstance(__value,Var):
return self.type == __value.value and self.value == __value.value
return False
def __call__(self, args: 'Var',ctx:list[dict]):
if self.type != "function":
raise Exception("not a function")
args = Var.unwrap(args)
retVal = self.value(ctx, args if isinstance(args,list) else [args])
return Var.asVar(retVal)
这个Var
其实就是一个wrapper,用来在运行时进行一些简单的类型检查(虽然写到后面直接开摆了),和引用在scope里面的Var
对象,并修改所绑定的值
### 标识符和关键词和运算符
syntax_reserved_keywords = ["for", "in", "if", "else", "while", "break", "continue", "return"]
pattern_identifier = re.compile(r"^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*")
pattern_brance_open = re.compile(r"^\s*\(\s*")
pattern_brance_close = re.compile(r"^\s*\)\s*")
pattern_operators = re.compile(r"\s*([+\-\*/%=]|==|is)\s*")
pattern_comma = re.compile(r"^\s*,\s*")
pattern_keywords = re.compile(rf"^\s*({'|'.join(syntax_reserved_keywords)})\s*")
这里写了好几个关键词,但是其实只用到了for
和in
以及写一下各个运算符所对于的函数:
def op_add(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value + b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value + b.value)
def op_sub(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value - b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value - b.value)
def op_mul(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value * b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value * b.value)
def op_div(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value / b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value / b.value)
def op_mod(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value % b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value % b.value)
def op_assign(operands, ctx):
lval, rval = operands
lval.set(rval)
ctx[-1][lval.name] = lval
return lval
def op_equal(operands, ctx):
lval, rval = operands
return Var("bool", lval == rval)
def op_is(operands, ctx):
lval, rval = operands
if lval.type == None:
return op_assign(operands, ctx)
else:
return op_equal(operands, ctx)
operators_map = {
"+": op_add,
"-": op_sub,
"*": op_mul,
"/": op_div,
"%": op_mod,
"=": op_assign,
"==": op_equal,
"is": op_is,
}
### 开始解析表达式
def from_expression(expr: str):
if pattern_comment.match(expr):
return None
if not (m := pattern_expression.match(expr)):
return None
expr = m.group(1)
ops = []
while len(expr):
if brance_close := pattern_brance_close.match(expr):
ops.append("brance_close")
expr = expr[brance_close.end() :]
elif brance_open := pattern_brance_open.match(expr):
ops.append("brance_open")
expr = expr[brance_open.end() :]
elif keyword := pattern_keywords.match(expr):
ops.append(("keyword", keyword.group(1)))
expr = expr[keyword.end() :]
elif operator := pattern_operators.match(expr):
ops.append(("operator", operator.group(1)))
expr = expr[operator.end() :]
elif comma := pattern_comma.match(expr):
ops.append("join")
expr = expr[comma.end() :]
elif identifier := pattern_identifier.match(expr):
ops.append(("identifier", identifier.group(1)))
expr = expr[identifier.end() :]
elif literal := from_literal(expr):
value, l = literal
ops.append(("literal", value))
expr = expr[l:]
else:
raise SyntaxError("invalid token: " + expr)
stacks = []
current = optree = []
for op in ops:
if op == "brance_open":
current.append(new := [])
stacks.append(current)
current = new
elif op == "brance_close":
if len(current) > 1:
new = []
grouping = False
for x in current:
if x == "join":
grouping = True
elif grouping:
if new[-1][0] == "group":
new[-1][1].append(x)
else:
new[-1] = ("group", [new[-1], x])
else:
new.append(x)
current.clear()
current.extend(new)
current = stacks.pop()
if len(current) > 1 and current[-2][0] == "identifier":
current[-2] = ("call", current[-2], current[-1])
current.pop()
else:
current.append(op)
def parse(opt: list):
if not opt:
return None
if isinstance(opt, tuple):
if opt[0] == "call":
return ("call", opt[1], parse(opt[2]))
elif opt[0] == "group":
return ("group",[parse(item) for item in opt[1]])
return opt
if not isinstance(opt, list):
raise Exception(f"invalid opt: {opt}")
if len(opt) == 1:
return parse(opt[0])
for i, o in enumerate(opt):
if isinstance(o, list):
opt[i] = parse(o)
def match(*pattern: str):
for op, p in zip(opt, pattern):
if p == "**":
continue
if not isinstance(op, tuple):
return False
matched = False
for p_ in p.split("|"):
raise Exception(f"invalid expression")
result = parse(optree)
ptree(result)
return result
### 表达式求值
def eval_expr(expr, ctx):
if not expr or not (type := expr[0]):
return None
if len(expr) == 1:
return eval_expr(expr[0],ctx)
# debug("\n==========context============")
# for scope in ctx:
# for val in scope.values():
# debug(val)
# debug("=========expression==========\n", expr, "\n==========output=============")
if type == "literal":
return expr[1]
elif type == "identifier":
for scope in reversed(ctx):
if expr[1] in scope:
return scope[expr[1]]
return Var(None, None, expr[1])
elif type == "group":
val = Var("tuple",[eval_expr(i, ctx) for i in expr[1]],const=True)
# debug("eval group", expr[1],val)
return val
elif type == "func":
F,A = expr[1:]
args = [eval_expr(i, ctx) for i in A]
return F(args, ctx)
elif type == "call":
F, A = expr[1:]
func, args = eval_expr(F, ctx), eval_expr(A,ctx)
# debug("eval call",func,args)
return func(args, ctx)
elif type == "for":
I, A, B = expr[1:]
iterVar = Var("integer", 0, name=I[1])
range_begin, range_end = A[1].value
ctx[-1][iterVar.name] = iterVar
for i in range(range_begin, range_end):
iterVar.value = i
eval_expr(B, ctx)
else:
raise Exception(f"invalid expression: {expr}")
写两个方便debug的函数
# region debug utils
def debug(*args,**kwargs):
print("debug::",*args,**kwargs)
def ptree(subtree,depth=0,split="--"):
if depth == 0:
print("")
print(subtree)
print("==========parse tree==========")
for i in subtree:
if isinstance(i,list):
ptree(i,depth+1)
elif isinstance(i,tuple):
print(f"[{depth}]:|"+split*depth,[x if not isinstance(x,list) else "<list>" for x in i])
for x in i:
if isinstance(x,list):
ptree(x,depth+1)
else:
print(f"[{depth}]:|"+split*depth,i)
## endregion
### 测试功能
from syntax import *
from math import atan2, sin, cos, sqrt, tan
from matplotlib import pyplot as plt
points = []
ctx = [{}]
@func_import(ctx, "print")
def myprint(*a, ctx=None):
print("***myprint***", *a)
@func_import(ctx, "sin")
def mysin(args, ctx=None):
return sin(args[0])
@func_import(ctx, "cos")
def mycos(args, ctx=None):
return cos(args[0])
@func_import(ctx, "tan")
def mytan(args, ctx=None):
return tan(args[0])
@func_import(ctx, "draw")
def mydraw(args, ctx=None):
x, y = args
offset_x, offset_y = ctx("origin")
scale_x, scale_y = ctx("scale")
rot = ctx("rot")
x, y = x * scale_x, y * scale_y
r = sqrt(x * x + y * y)
theta = atan2(y, x) + rot
x = r * cos(theta) + offset_x
y = r * sin(theta) + offset_y
points.append((x, y))
@func_import(ctx, "plot")
def myplot(args, ctx=None):
plt.scatter(*zip(*points))
plt.show()
for statement in """
1+1+1+1+1+1+1+1+1+1+1+1
origin is (1, 1); 设置原点的偏移量
rot is 0.5; -- 设置旋转角度
scale is (0.1, 12); 设置横坐标和纵坐标的比例
print("origin is", origin, ", rot is", rot, ", scale is", scale); 输出当前设置的参数
for T in 0..1000 draw(T,((sin((T*0.01))+1)+(cos((T*0.01))*cos((T*0.01))))); 从0到1000,每次增加1
plot()
""".splitlines():
# debug("statement:", statement)
result = eval_expr(from_expression(statement), ctx)
# debug("result:",result)
it works
## 具体代码分析
摸了
## 改进方向
- 加入流程控制
- 优化运算符的解析,支持更复杂的表达式
# 实验报告版(大部分内容相同,只是为了模板改了改)
## 1. 总体任务
题目:为函数绘图语言编写一个解释器,解释器接受用绘图语言编写的源程序,经语法和语义分析之后,将源程序所规定的图形显示在显示屏(或窗口)中。 目的:通过自己动手编写解释器,掌握语言翻译特别是语言识别的基本方法
- 会使用正则表达式设计简单的语法
- 会用递归下降子程序编写编译器或解释器
- 会写报告
## 2. 函数绘图语言简介
### 2.1 运算符
+
,-
,*
,/
,%
: 基本算数运算=
: 赋值运算==
: 判断相等is
: 当lval
未定义类型时,将rval
的赋给lval
; 当lval
已定义类型时,判断lval
与rval
是否相等
### 2.2 基本类型
#### 2.2.1 字面量类型
string
: 字符串类型,运行时内部类型为str
e.g."some string"
integer
: 整数类型,运行时内部类型为int
e.g.114
float
: 浮点数类型,运行时内部类型为float
e.g.114.514
bool
: boolean类型,运行时内部类型为bool
e.g.true
orfalse
range
: range类型,运行时内部类型为tuple[int, int]
e.g.0..1919
从0
到1919
(不包含)的整数序列- 作为
for
表达式的范围参数时会进行自动推导成generator,a..b
会被推导为range(a,b)
- 作为
#### 2.2.2 内部类型
tuple
: 元组,用于函数调用参数和定义复杂结构,由,
表达式生成(在作为函数参数的时候,嵌套的元组会被自动压平成一维列表)function
: 函数,目前只支持使用@func_import
导入python函数,暂不支持自定义函数
### 2.3 基本语法
- 注释:
;
- 标识符:
/[A-z_][A-z0-9_]*/
- 定义变量:
<varname> = <value>
or<varname> is <value>
- 基本运算符:
<lval> <op> <rval>
- 元组定义:
<tuple> = (<value1>, <value2>, ...)
- 函数调用:
<funcname>(<arg1>, <arg2>, ...)
- for循环:
for <loop_var> in <iterable> <expression>
### 2.4 变量
- 变量具有作用域,在搜索时从当前上下文向上搜索
- 变量可以由字面量或运行时导入产生,这个时候的变量是immutable,不可以修改值
- 变量可以由赋值运算符产生,这个时候的变量可以修改值
### 2.5 内置函数
解释器后端是python,可以使用@func_import
decorator在特定上下文中导入python函数
print
: 打印print("scale is", scale)
draw
: 添加点draw(1, 1)
draw
函数会使用当前上下文中的origin
,scale
,rot
三个变量
plot
: 绘图plot()
sin
: 正弦函数sin(1)
cos
: 余弦函数cos(1)
tan
: 正切函数tan(1)
### 2.6 绘图函数
绘图函数涉及三个全局变量:
origin
: 原点偏移量origin is (1, 1)
rot
: 旋转角度rot is 0.5
scale
: 横纵坐标比例scale is (0.1, 12)
draw
函数接受x
,y
两个参数,在调用时,会对坐标进行变换操作,然后将变换后的坐标添加到散点列表中,坐标变换公式如下:
x, y = x * scale_x, y * scale_y
r = sqrt(x * x + y * y)
theta = atan2(y, x) + rot
x = r * cos(theta) + offset_x
y = r * sin(theta) + offset_y
plot
函数调用时,会将散点列表使用matplotlib绘制出来,并显示窗口,阻塞当前线程
## 3. 函数绘图程序举例
origin is (1, 1); 设置原点的偏移量
rot is 0.5; -- 设置旋转角度
scale is (0.1, 12); 设置横坐标和纵坐标的比例
print("origin is", origin, ", rot is", rot, ", scale is", scale); 输出当前设置的参数
for T in 0..1000 draw(T,((sin((T*0.01))+1)+(cos((T*0.01))*cos((T*0.01))))); 从0到1000,每次增加1
plot(); 绘图
## 4. 开发环境及配置
- Operating System: Fedora Workstation 39
- Python version:3.12.0
- IDE: Visual Studio Code (Language server: Pylance)
使用的Python包:
- regex: python内置的正则表达式包
- matplotlib: 绘图包
## 5. 基本原理与解决思路
解释器运行逻辑:
- 初始化解释器上下文,加载内置函数
- 读入所有源代码文件,按
\n
拆分成多个statements,逐行分析 - 使用正则表达式循环匹配当前行,生成指令序列
- 记未分析的字符串为
expr
,初始化指令序列ops
为空 - 使用正则表达式匹配
expr
,如果匹配成功,将匹配到的内容加入ops
,并将expr
被匹配到的部分截断 - 循环直至
expr
为空或者无法匹配,抛出异常,终止解释器
- 记未分析的字符串为
- 根据指令序列生成指令图
- 初始化栈
stacks
为空,当前帧为current
,指令图optree
指向current
,即图的根节点 - 遍历指令序列
ops
- 如果当前指令为
brance_open
,将current
压入栈stacks
,创建新的帧new
,将new
加入current
,并将current
置为new
- 如果当前指令为
brance_close
,将current
中的join
操作进行解析,生成group
指令,弹出stacks
栈顶,并将current
置为弹出的栈顶元素,初步处理函数调用 - 如果当前指令不是
brance_open
或brance_close
或join
,将当前指令加入current
- 如果当前指令为
- 初始化栈
- 从指令图递归生成表达式
- 如果当前节点为
group
,递归生成子表达式,将子节点加入group
的子表达式参数列表 - 如果当前节点为
call
,递归生成子表达式,将子节点加入call
的子表达式参数列表 - 如果当前节点为原子表达式,直接返回
- 如果当前表达式为运算符表达式,递归生成左右子表达式,将左右子表达式加入运算符表达式的参数列表
- 如果当前表达式为for循环表达式,递归生成循环变量、迭代器、循环体,将循环变量、迭代器、循环体加入for循环表达式的参数列表
- 如果当前表达式无法匹配,抛出异常,终止解释器
- 如果当前节点为
- 在当前上下文中求值表达式
- 如果当前表达式为字面量,返回字面量的运行时引用
- 如果当前表达式为标识符,从当前上下文向上搜索,返回第一个匹配的变量
- 如果当前表达式为元组,递归求值子表达式,返回元组
- 如果当前表达式为函数调用,递归求值子表达式,返回函数调用结果
- 如果当前表达式为for循环,递归求值循环变量、迭代器、循环体,对每次循环修改当前
ctx
中绑定的迭代变量,并对循环体表达式求值,返回空 - 如果当前表达式无法匹配,抛出异常,终止解释器
## 6. 关键类及主要方法
### 字面量的匹配
先定义一些字面量的类型,直接写成正则:
pattern_literal_string = re.compile(r'^\s*"([^"]*)"\s*')
pattern_literal_integer = re.compile(r"^\s*(\d+)(?![\d\.A-z])\s*")
pattern_literal_float = re.compile(r"^\s*(\d+\.\d*|\d*\.\d+)(?![\d\.A-z])\s*")
pattern_literal_boolean = re.compile(r"^\s*(true|false)(?![A-z\d_])\s*")
pattern_literal_null = re.compile(r"^\s*(null)(?![A-z0-9_])\s*")
pattern_literal_range = re.compile(r"\s*(\d+)\.\.(\d+)\s*")
要注意这里的正则限定了开头,但是没有限定末尾,所以一定要写好匹配的边界,不然可能会漏掉内容或者匹配到了额外的内容。然后就是写一个函数,来处理这些字面量:
def from_literal(token: str):
if m := pattern_literal_string.match(token):
return Var("string", m.group(1), const=True), m.end()
elif m := pattern_literal_integer.match(token):
return Var("integer", int(m.group(1)), const=True), m.end()
elif m := pattern_literal_float.match(token):
return Var("float", float(m.group(1)), const=True), m.end()
elif m := pattern_literal_boolean.match(token):
return Var("bool", m.group(1) == "true", const=True), m.end()
elif m := pattern_literal_null.match(token):
return Var("object", None, const=True), m.end()
elif m := pattern_literal_range.match(token):
return Var("range", (int(m.group(1)), int(m.group(2))), const=True), m.end()
else:
return None
这里会涉及到一个Var
类型,定义如下:
class Var:
runtime_type_bindings = {
"string":"string",
"int":"integer",
"float":"float",
"bool":"bool",
"tuple":"tuple",
"NoneType":"null"
}
def asVar(val):
if isinstance(val,Var):
return val
elif (typename := type(val).__name__) in Var.runtime_type_bindings:
return Var(Var.runtime_type_bindings[typename],val)
else:
raise Exception(f"unknown type: {typename}")
def unwrap(var:'Var'):
if not isinstance(var,Var):
return var
if var.type == "tuple":
return [Var.unwrap(i) for i in var.value]
else:
return var.value
def __init__(self,type,value,name=None,const=False) -> None:
self.type = type
self.value = value
self.name = name
self.const = const
def __repr__(self) -> str:
return f"[{"const" if self.const else "var"} {self.type}::{self.name if self.name else ""}]({self.value})"
def __str__(self) -> str:
return self.__repr__()
def set(self,val):
if self.const:
raise Exception("const filed is immutable")
if self.type == None:
self.type = val.type
self.value = val.value
return
if val.type != self.type:
raise Exception(f"variable type mismatch: {self.type} != {val.type}")
self.value = val.value
def __eq__(self, __value: object) -> bool:
if isinstance(__value,Var):
return self.type == __value.value and self.value == __value.value
return False
def __call__(self, args: 'Var',ctx:list[dict]):
if self.type != "function":
raise Exception("not a function")
args = Var.unwrap(args)
retVal = self.value(ctx, args if isinstance(args,list) else [args])
return Var.asVar(retVal)
这个Var
其实就是一个wrapper,用来在运行时进行一些简单的类型检查(虽然写到后面直接开摆了),和引用在scope里面的Var
对象,并修改所绑定的值
### 标识符和关键词和运算符
syntax_reserved_keywords = ["for", "in", "if", "else", "while", "break", "continue", "return"]
pattern_identifier = re.compile(r"^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*")
pattern_brance_open = re.compile(r"^\s*\(\s*")
pattern_brance_close = re.compile(r"^\s*\)\s*")
pattern_operators = re.compile(r"\s*([+\-\*/%=]|==|is)\s*")
pattern_comma = re.compile(r"^\s*,\s*")
pattern_keywords = re.compile(rf"^\s*({'|'.join(syntax_reserved_keywords)})\s*")
这里写了好几个关键词,但是其实只用到了for
和in
,其他关键字保留备用
以及写一下各个运算符所对于的函数:
def op_add(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value + b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value + b.value)
def op_sub(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value - b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value - b.value)
def op_mul(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value * b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value * b.value)
def op_div(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value / b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value / b.value)
def op_mod(operands, ctx):
a, b = operands
assert isinstance(a, Var) and isinstance(b, Var)
if a.type == "integer" and b.type == "integer":
return Var("integer", a.value % b.value)
elif a.type == "float" or b.type == "float":
return Var("float", a.value % b.value)
def op_assign(operands, ctx):
lval, rval = operands
lval.set(rval)
ctx[-1][lval.name] = lval
return lval
def op_equal(operands, ctx):
lval, rval = operands
return Var("bool", lval == rval)
def op_is(operands, ctx):
lval, rval = operands
if lval.type == None:
return op_assign(operands, ctx)
else:
return op_equal(operands, ctx)
operators_map = {
"+": op_add,
"-": op_sub,
"*": op_mul,
"/": op_div,
"%": op_mod,
"=": op_assign,
"==": op_equal,
"is": op_is,
}
### 开始解析表达式
此处对应章节5中的3. 4. 5. 三部分
def from_expression(expr: str):
if pattern_comment.match(expr):
return None
if not (m := pattern_expression.match(expr)):
return None
expr = m.group(1)
ops = []
while len(expr):
if brance_close := pattern_brance_close.match(expr):
ops.append("brance_close")
expr = expr[brance_close.end() :]
elif brance_open := pattern_brance_open.match(expr):
ops.append("brance_open")
expr = expr[brance_open.end() :]
elif keyword := pattern_keywords.match(expr):
ops.append(("keyword", keyword.group(1)))
expr = expr[keyword.end() :]
elif operator := pattern_operators.match(expr):
ops.append(("operator", operator.group(1)))
expr = expr[operator.end() :]
elif comma := pattern_comma.match(expr):
ops.append("join")
expr = expr[comma.end() :]
elif identifier := pattern_identifier.match(expr):
ops.append(("identifier", identifier.group(1)))
expr = expr[identifier.end() :]
elif literal := from_literal(expr):
value, l = literal
ops.append(("literal", value))
expr = expr[l:]
else:
raise SyntaxError("invalid token: " + expr)
stacks = []
current = optree = []
for op in ops:
if op == "brance_open":
current.append(new := [])
stacks.append(current)
current = new
elif op == "brance_close":
if len(current) > 1:
new = []
grouping = False
for x in current:
if x == "join":
grouping = True
elif grouping:
if new[-1][0] == "group":
new[-1][1].append(x)
else:
new[-1] = ("group", [new[-1], x])
else:
new.append(x)
current.clear()
current.extend(new)
current = stacks.pop()
if len(current) > 1 and current[-2][0] == "identifier":
current[-2] = ("call", current[-2], current[-1])
current.pop()
else:
current.append(op)
def parse(opt: list):
if not opt:
return None
if isinstance(opt, tuple):
if opt[0] == "call":
return ("call", opt[1], parse(opt[2]))
elif opt[0] == "group":
return ("group",[parse(item) for item in opt[1]])
return opt
if not isinstance(opt, list):
raise Exception(f"invalid opt: {opt}")
if len(opt) == 1:
return parse(opt[0])
for i, o in enumerate(opt):
if isinstance(o, list):
opt[i] = parse(o)
def match(*pattern: str):
for op, p in zip(opt, pattern):
if p == "**":
continue
if not isinstance(op, tuple):
return False
matched = False
for p_ in p.split("|"):
raise Exception(f"invalid expression")
result = parse(optree)
ptree(result)
return result
### 表达式求值
def eval_expr(expr, ctx):
if not expr or not (type := expr[0]):
return None
if len(expr) == 1:
return eval_expr(expr[0],ctx)
# debug("\n==========context============")
# for scope in ctx:
# for val in scope.values():
# debug(val)
# debug("=========expression==========\n", expr, "\n==========output=============")
if type == "literal":
return expr[1]
elif type == "identifier":
for scope in reversed(ctx):
if expr[1] in scope:
return scope[expr[1]]
return Var(None, None, expr[1])
elif type == "group":
val = Var("tuple",[eval_expr(i, ctx) for i in expr[1]],const=True)
# debug("eval group", expr[1],val)
return val
elif type == "func":
F,A = expr[1:]
args = [eval_expr(i, ctx) for i in A]
return F(args, ctx)
elif type == "call":
F, A = expr[1:]
func, args = eval_expr(F, ctx), eval_expr(A,ctx)
# debug("eval call",func,args)
return func(args, ctx)
elif type == "for":
I, A, B = expr[1:]
iterVar = Var("integer", 0, name=I[1])
range_begin, range_end = A[1].value
ctx[-1][iterVar.name] = iterVar
for i in range(range_begin, range_end):
iterVar.value = i
eval_expr(B, ctx)
else:
raise Exception(f"invalid expression: {expr}")
### 初始化运行时上下文和测试代码
from syntax import *
from math import atan2, sin, cos, sqrt, tan
from matplotlib import pyplot as plt
points = []
ctx = [{}]
@func_import(ctx, "print")
def myprint(*a, ctx=None):
print("***myprint***", *a)
@func_import(ctx, "sin")
def mysin(args, ctx=None):
return sin(args[0])
@func_import(ctx, "cos")
def mycos(args, ctx=None):
return cos(args[0])
@func_import(ctx, "tan")
def mytan(args, ctx=None):
return tan(args[0])
@func_import(ctx, "draw")
def mydraw(args, ctx=None):
x, y = args
offset_x, offset_y = ctx("origin")
scale_x, scale_y = ctx("scale")
rot = ctx("rot")
x, y = x * scale_x, y * scale_y
r = sqrt(x * x + y * y)
theta = atan2(y, x) + rot
x = r * cos(theta) + offset_x
y = r * sin(theta) + offset_y
points.append((x, y))
@func_import(ctx, "plot")
def myplot(args, ctx=None):
plt.scatter(*zip(*points))
plt.show()
for statement in """
1+1+1+1+1+1+1+1+1+1+1+1
origin is (1, 1); 设置原点的偏移量
rot is 0.5; -- 设置旋转角度
scale is (0.1, 12); 设置横坐标和纵坐标的比例
print("origin is", origin, ", rot is", rot, ", scale is", scale); 输出当前设置的参数
for T in 0..1000 draw(T,((sin((T*0.01))+1)+(cos((T*0.01))*cos((T*0.01))))); 从0到1000,每次增加1
plot()
""".splitlines():
# debug("statement:", statement)
result = eval_expr(from_expression(statement), ctx)
# debug("result:",result)
## 7. 测试截图
主要代码已包含在上一节中,这里只展示运行结果
(自己截图)
## 8. 总体体会(遇到的问题与体会)
- 解释器运行过程中,尤其是进行语法分析和表达式求值的时候,会进行非常多递归操作,调试难度较大
- 把问题复杂化了,本次实验的语法点较少,且语法形式很单一,完全可以直接匹配特定字符串,但是为了拓展性做了很多workaround