造个轮子--用Python写个编程语言-判断与循环
前言
okey,很好,在上一篇文章当中,我们实现了这个基本的逻辑运算,所以的话,在这里,我们将可以实现到我们的这个判断和循环了。由于这里的话,我们的操作其实和先前的操作都是类似的,所以在代码层面上的操作非常,简单。但是在语法层面上面看起来有点抽象。
那么当然先看到这里实现的效果:
这里的话,没办法,我觉得如果使用中文的话,实在是太别扭了,所以换回来了,对应的报错也是换回来了,但是相关的提示后面还是会做成中文的,但是这个翻译为语义的话,实在是太麻烦了。
所以在这里,我们再来看看这个语法表述。
语法描述
首先,我们要做的很简单,就是实现这个判断语句 if <statement>
else <statement>
又或者 if elif else 这样的结构。当然我们还有循环要实现,但是这里的话我们先来看到这个判断。
判断
首先我们明白一个道理,那就是这个IF里面的这个条件的话,可以有非常多的内容,也就是说,这个IF里面包含了这个布尔运算,而这个布尔运算的话,又包含了表达式(然后这个表达式包含了很多信息)并且这个IF这样的运算应该是要放在后面进行判断的。也就是说,IF 作为一个节点在运行的时候,是放在后面的。
还记得,我们一开始举的例子吗: 在解析成AST的时候,其实这个 IF 就是其中一个节点,他也是一样没有任何区别。只是,这个节点的话,有明显的顺序要求,要先IF,然后有ElSE或者ELIF。
循环
那么同样的,循环也是一样的,只是在循环这边会有跟多的操作而已,这里没有什么不同。当然 具体的,在代码的代码实现里面可以看到非常的清晰明了。
之后的话,我们来看到我们现在的语法的表示:
((KEYWORD:AND|KEYWORD:OR) comp-expr)*
comp-expr : NOT comp-expr : arith-expr ((EE|LT|GT|LTE|GTE)
arith-expr)*
arith-expr : term ((PLUS|MINUS) term)*
term : factor ((MUL|DIV) factor)*
factor : (PLUS|MINUS) factor : power
power : atom (POW factor)*
atom : INT|FLOAT|IDENTIFIER : LPAREN expr RPAREN : if-expr
: for-expr : while-expr
if-expr : KEYWORD:IF expr KEYWORD:THEN expr
(KEYWORD:ELIF expr KEYWORD:THEN expr)*
(KEYWORD:ELSE expr)?
for-expr : KEYWORD:FOR IDENTIFIER EQ expr KEYWORD:TO expr
(KEYWORD:STEP expr)? KEYWORD:THEN expr
while-expr : KEYWORD:WHILE expr KEYWORD:THEN expr
是的在这里的话,我们的循环有两个
词法解析
那么同样的老规矩了,不管要怎么实现,第一步都是需要先处理这个。 那么先定义关键词:
KEYWORDS = [
'var',
'and',
'or',
'not',
'if',
'elif',
'else',
'for',
'to',
'step',
'while',
'then'
]
因为这块的话,只要做关键词配对就ok了,所以的话,我们这里在词法解析这部分的代码不用动。
语法解析
虽然在词法解析这里我们什么都不用动,但是在这边语法解析这里还是要处理的。这个时候的话,我们这里的语法解析已经写的很复杂了。
定义节点
那么在这里的话,我们定义了三个节点:
IfNode 类表示 if 语句的节点。它具有以下属性和方法:
cases:表示 if 语句的条件和对应的代码块的列表。 else_case:表示 if 语句的 else 分支的代码块(可选)。 pos_start:表示节点在源代码中的起始位置,为第一个条件的起始位置。 pos_end:表示节点在源代码中的结束位置,为 else 分支的结束位置(如果存在),否则为最后一个条件的结束位置。
ForNode 类表示 for 循环的节点。它具有以下属性和方法:
var_name_tok:表示循环变量的令牌(token)对象。 start_value_node:表示循环变量的起始值的节点对象。 end_value_node:表示循环变量的结束值的节点对象。 step_value_node:表示循环变量的步长值的节点对象。 body_node:表示循环体的节点对象。 pos_start:表示节点在源代码中的起始位置,为变量名的起始位置。 pos_end:表示节点在源代码中的结束位置,为循环体的结束位置。
WhileNode 类表示 while 循环的节点。它具有以下属性和方法:
condition_node:表示循环条件的节点对象。 body_node:表示循环体的节点对象。 pos_start:表示节点在源代码中的起始位置,为条件的起始位置。 pos_end:表示节点在源代码中的结束位置,为循环体的结束位置。
class IfNode:
def __init__(self, cases, else_case):
self.cases = cases
self.else_case = else_case
self.pos_start = self.cases[0][0].pos_start
self.pos_end = (self.else_case or self.cases[len(self.cases) - 1][0]).pos_end
class ForNode:
def __init__(self, var_name_tok, start_value_node, end_value_node, step_value_node, body_node):
self.var_name_tok = var_name_tok
self.start_value_node = start_value_node
self.end_value_node = end_value_node
self.step_value_node = step_value_node
self.body_node = body_node
self.pos_start = self.var_name_tok.pos_start
self.pos_end = self.body_node.pos_end
class WhileNode:
def __init__(self, condition_node, body_node):
self.condition_node = condition_node
self.body_node = body_node
self.pos_start = self.condition_node.pos_start
self.pos_end = self.body_node.pos_end
生成节点
然后的话,我们要做的就是生成这个节点,那么这个节点的话,就是这样处理:
首先我们这里还是有三个方法:
判断节点
ok,这里的话,我们一个一个来看到这个节点,我们先来看到这个判断节点的处理。首先的话,这个if,节点必然有一个then节点。所以这里的问题,在于我们还要去判断这个闭合。
我们先直接看到代码:
res = ParseResult()
cases = []
else_case = None
#s首先我们看到这个节点是不是if节点
if not self.current_tok.matches(TT_KEYWORD, 'if'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'if'"
))
res.register_advancement()
self.advance()
#节点内部的情况节点
# if
# /
# condition
condition = res.register(self.expr())
if res.error: return res
#步入到下一个节点,由于前面解析condition的时候,我们也是会移动我们的token指针的
#所以当我们结束这个condition的组合的时候,回到当前的if节点,那么下一个节点如果没有
#then,那么就错了
if not self.current_tok.matches(TT_KEYWORD, 'then'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'then'"
))
#接下来的操作同理
res.register_advancement()
self.advance()
expr = res.register(self.expr())
if res.error: return res
cases.append((condition, expr))
while self.current_tok.matches(TT_KEYWORD, 'elif'):
res.register_advancement()
self.advance()
condition = res.register(self.expr())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'then'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'then'"
))
res.register_advancement()
self.advance()
expr = res.register(self.expr())
if res.error: return res
cases.append((condition, expr))
if self.current_tok.matches(TT_KEYWORD, 'else'):
res.register_advancement()
self.advance()
else_case = res.register(self.expr())
if res.error: return res
return res.success(IfNode(cases, else_case))
循环节点
之后的话,是我们的循环节点,这里的原理都是类似的:
for_expr 方法首先判断当前令牌是否为关键字 'for',如果不是则抛出语法错误。然后解析变量名和等号,获取循环变量的起始值。接下来判断当前令牌是否为关键字 'to',如果不是则抛出语法错误。然后解析结束值。如果当前令牌是关键字 'step',则解析步长值;否则步长值设为 None。最后判断当前令牌是否为关键字 'then',如果不是则抛出语法错误。解析循环体的表达式并返回生成的 ForNode 对象。
while_expr 方法首先判断当前令牌是否为关键字 'while',如果不是则抛出语法错误。解析循环条件的表达式。然后判断当前令牌是否为关键字 'then',如果不是则抛出语法错误。解析循环体的表达式并返回生成的 WhileNode 对象。
def for_expr(self):
"""
for i=1 to 6 step 1 then ...
这个是我们的这个for循环的结构,所以我们解析的时候
我们要按照这个去处理
先解析 内部的这个i=x
然后是 to
然后是 step
之后是then
"""
res = ParseResult()
if not self.current_tok.matches(TT_KEYWORD, 'for'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'for'"
))
res.register_advancement()
self.advance()
if self.current_tok.type != TT_IDENTIFIER:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected identifier"
))
var_name = self.current_tok
res.register_advancement()
self.advance()
if self.current_tok.type != TT_EQ:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected '='"
))
res.register_advancement()
self.advance()
start_value = res.register(self.expr())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'to'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'to'"
))
res.register_advancement()
self.advance()
end_value = res.register(self.expr())
if res.error: return res
if self.current_tok.matches(TT_KEYWORD, 'step'):
res.register_advancement()
self.advance()
step_value = res.register(self.expr())
if res.error: return res
else:
step_value = None
if not self.current_tok.matches(TT_KEYWORD, 'then'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'then'"
))
res.register_advancement()
self.advance()
body = res.register(self.expr())
if res.error: return res
return res.success(ForNode(var_name, start_value, end_value, step_value, body))
def while_expr(self):
"""
这个也是类似先是 while() then 处理
"""
res = ParseResult()
if not self.current_tok.matches(TT_KEYWORD, 'while'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'while'"
))
res.register_advancement()
self.advance()
condition = res.register(self.expr())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'then'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'then'"
))
res.register_advancement()
self.advance()
body = res.register(self.expr())
if res.error: return res
return res.success(WhileNode(condition, body))
解释器
okey,之后的话,就是我们解释器的内容了。这里的话我们的大体执行流程还是和以前一样的,但是区别的话,就是,这里我们要维护的信息更多了。因为先前AST已经构造了了,我们只要判断当前节点的类型,然后执行对应的操作,是结合左右子树,还是什么什么。
但是在这里的话,我们在这边的操作现在变得复杂了一点。
处理判断节点
首先的的话,我们这里直接看到我们的这个函数:
def visit_IfNode(self, node, context):
res = RTResult()
for condition, expr in node.cases:
condition_value = res.register(self.visit(condition, context))
if res.error: return res
if condition_value.is_true():
expr_value = res.register(self.visit(expr, context))
if res.error: return res
return res.success(expr_value)
if node.else_case:
else_value = res.register(self.visit(node.else_case, context))
if res.error: return res
return res.success(else_value)
return res.success(None)
现在对Number这个家伙的话,我们又加入了一个方法,is_true,这个方法没啥。 我们这边不是有判断嘛: 没啥东西。
循环处理
这个循环处理的话,就稍微麻烦一点,这里的话我们主要看到for循环
def visit_ForNode(self, node, context):
res = RTResult()
"""
这个下面的这个是我们的for循环,所以的话
for i=1 to 6 step 1 then ...
"""
start_value = res.register(self.visit(node.start_value_node, context))
if res.error: return res
end_value = res.register(self.visit(node.end_value_node, context))
if res.error: return res
if node.step_value_node:
step_value = res.register(self.visit(node.step_value_node, context))
if res.error: return res
else:
step_value = Number(1)
i = start_value.value
if step_value.value >= 0:
condition = lambda: i < end_value.value
else:
condition = lambda: i > end_value.value
while condition():
#循环处理里面的结果,然后把结果存起来
context.symbol_table.set(node.var_name_tok.value, Number(i))
i += step_value.value
res.register(self.visit(node.body_node, context))
if res.error: return res
return res.success(None)
之后的话,这个while也是一样的。