在本文中,我们将学习如何使用SLY(Sly Lex Yacc)和Python创建自己的编程语言。在深入探讨该主题之前,请注意,这不是初学者的教程,您需要对以下先决条件有所了解。
先决条件
- 关于编译器设计的粗略知识。
- 对词法分析,解析和其他编译器设计方面的基本了解。
- 了解正则表达式。
- 熟悉Python编程语言。
入门
为Python安装SLY。 SLY是一个词法分析工具,可以使我们的过程变得更加容易。
pip install sly
建立词法分析器
编译器的第一阶段是将所有字符流(已编写的高级程序)转换为令牌流。这是通过一个称为词法分析的过程来完成的。但是,通过使用SLY可以简化此过程
首先,让我们导入所有必要的模块。
Python3
from sly import Lexer
Python3
class BasicLexer(Lexer):
tokens = { NAME, NUMBER, STRING }
ignore = '\t '
literals = { '=', '+', '-', '/',
'*', '(', ')', ',', ';'}
# Define tokens as regular expressions
# (stored as raw strings)
NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
STRING = r'\".*?\"'
# Number token
@_(r'\d+')
def NUMBER(self, t):
# convert it into a python integer
t.value = int(t.value)
return t
# Comment token
@_(r'//.*')
def COMMENT(self, t):
pass
# Newline token(used only for showing
# errors in new line)
@_(r'\n+')
def newline(self, t):
self.lineno = t.value.count('\n')
Python3
from sly import Parser
Python3
class BasicParser(Parser):
#tokens are passed from lexer to parser
tokens = BasicLexer.tokens
precedence = (
('left', '+', '-'),
('left', '*', '/'),
('right', 'UMINUS'),
)
def __init__(self):
self.env = { }
@_('')
def statement(self, p):
pass
@_('var_assign')
def statement(self, p):
return p.var_assign
@_('NAME "=" expr')
def var_assign(self, p):
return ('var_assign', p.NAME, p.expr)
@_('NAME "=" STRING')
def var_assign(self, p):
return ('var_assign', p.NAME, p.STRING)
@_('expr')
def statement(self, p):
return (p.expr)
@_('expr "+" expr')
def expr(self, p):
return ('add', p.expr0, p.expr1)
@_('expr "-" expr')
def expr(self, p):
return ('sub', p.expr0, p.expr1)
@_('expr "*" expr')
def expr(self, p):
return ('mul', p.expr0, p.expr1)
@_('expr "/" expr')
def expr(self, p):
return ('div', p.expr0, p.expr1)
@_('"-" expr %prec UMINUS')
def expr(self, p):
return p.expr
@_('NAME')
def expr(self, p):
return ('var', p.NAME)
@_('NUMBER')
def expr(self, p):
return ('num', p.NUMBER)
Python3
class BasicExecute:
def __init__(self, tree, env):
self.env = env
result = self.walkTree(tree)
if result is not None and isinstance(result, int):
print(result)
if isinstance(result, str) and result[0] == '"':
print(result)
def walkTree(self, node):
if isinstance(node, int):
return node
if isinstance(node, str):
return node
if node is None:
return None
if node[0] == 'program':
if node[1] == None:
self.walkTree(node[2])
else:
self.walkTree(node[1])
self.walkTree(node[2])
if node[0] == 'num':
return node[1]
if node[0] == 'str':
return node[1]
if node[0] == 'add':
return self.walkTree(node[1]) + self.walkTree(node[2])
elif node[0] == 'sub':
return self.walkTree(node[1]) - self.walkTree(node[2])
elif node[0] == 'mul':
return self.walkTree(node[1]) * self.walkTree(node[2])
elif node[0] == 'div':
return self.walkTree(node[1]) / self.walkTree(node[2])
if node[0] == 'var_assign':
self.env[node[1]] = self.walkTree(node[2])
return node[1]
if node[0] == 'var':
try:
return self.env[node[1]]
except LookupError:
print("Undefined variable '"+node[1]+"' found!")
return 0
Python3
if __name__ == '__main__':
lexer = BasicLexer()
parser = BasicParser()
print('GFG Language')
env = {}
while True:
try:
text = input('GFG Language > ')
except EOFError:
break
if text:
tree = parser.parse(lexer.tokenize(text))
BasicExecute(tree, env)
现在,让我们来构建延伸从SLY词法分析器类的类BasicLexer。让我们做一个进行简单算术运算的编译器。因此,我们将需要一些基本标记,例如NAME , NUMBER , STRING 。在任何编程语言中,两个字符之间都会有空格。因此,我们创建了一个忽略字面量。然后,我们还创建基本字面量如’=’,’+’等。NAME标记基本上是变量的名称,可以由正则表达式[a-zA-Z _] [a-zA-Z0-9_]定义*。 STRING标记是字符串值,并以引号(“”)限制。这可以通过正则表达式\“。*?\”来定义。
每当我们找到数字时,都应将其分配给令牌NUMBER ,并且数字必须存储为整数。我们正在做一个基本的可编程脚本,所以我们只用整数来编写它,但是,可以随意将其扩展为小数,long等。我们也可以发表评论。每当我们找到“ //”时,我们都会忽略该行接下来的内容。我们做新行字符同样的事情。因此,我们建立了一个基本的词法分析器,它将字符流转换为令牌流。
Python3
class BasicLexer(Lexer):
tokens = { NAME, NUMBER, STRING }
ignore = '\t '
literals = { '=', '+', '-', '/',
'*', '(', ')', ',', ';'}
# Define tokens as regular expressions
# (stored as raw strings)
NAME = r'[a-zA-Z_][a-zA-Z0-9_]*'
STRING = r'\".*?\"'
# Number token
@_(r'\d+')
def NUMBER(self, t):
# convert it into a python integer
t.value = int(t.value)
return t
# Comment token
@_(r'//.*')
def COMMENT(self, t):
pass
# Newline token(used only for showing
# errors in new line)
@_(r'\n+')
def newline(self, t):
self.lineno = t.value.count('\n')
建立一个解析器
首先,让我们导入所有必要的模块。
Python3
from sly import Parser
现在,让我们来构建它扩展了词法类的类BasicParser。来自BasicLexer的令牌流将传递给变量令牌。定义了优先级,这与大多数编程语言相同。下面程序中编写的大多数解析非常简单。当什么都没有时,该语句将不传递任何内容。本质上,您可以按键盘上的Enter键(无需输入任何内容)并转到下一行。接下来,您的语言应使用“ =”理解分配。这在下面的程序的第18行中处理。分配给字符串时,可以完成相同的操作。
Python3
class BasicParser(Parser):
#tokens are passed from lexer to parser
tokens = BasicLexer.tokens
precedence = (
('left', '+', '-'),
('left', '*', '/'),
('right', 'UMINUS'),
)
def __init__(self):
self.env = { }
@_('')
def statement(self, p):
pass
@_('var_assign')
def statement(self, p):
return p.var_assign
@_('NAME "=" expr')
def var_assign(self, p):
return ('var_assign', p.NAME, p.expr)
@_('NAME "=" STRING')
def var_assign(self, p):
return ('var_assign', p.NAME, p.STRING)
@_('expr')
def statement(self, p):
return (p.expr)
@_('expr "+" expr')
def expr(self, p):
return ('add', p.expr0, p.expr1)
@_('expr "-" expr')
def expr(self, p):
return ('sub', p.expr0, p.expr1)
@_('expr "*" expr')
def expr(self, p):
return ('mul', p.expr0, p.expr1)
@_('expr "/" expr')
def expr(self, p):
return ('div', p.expr0, p.expr1)
@_('"-" expr %prec UMINUS')
def expr(self, p):
return p.expr
@_('NAME')
def expr(self, p):
return ('var', p.NAME)
@_('NUMBER')
def expr(self, p):
return ('num', p.NUMBER)
解析器还应该在算术运算中进行解析,这可以通过表达式来完成。假设您想要如下所示的内容。在这里,它们都被逐行制作为令牌流,并逐行进行解析。因此,根据上面的程序,a = 10类似于第22行。b = 20时也是如此。 a + b类似于第34行,它返回一个解析树(“ add”,(“ var”,“ a”),(“ var”,“ b”))。
GFG Language > a = 10
GFG Language > b = 20
GFG Language > a + b
30
现在,我们将令牌流转换为解析树。下一步是对其进行解释。
执行
口译是一个简单的过程。基本思想是采用该树并逐步遍历该树,并分层评估算术运算。一遍又一遍地递归调用此过程,直到评估整个树并检索到答案为止。例如,假设5 + 7 +4。此字符流首先在词法分析器中标记为令牌流。然后对令牌流进行解析以形成解析树。解析树本质上返回(’add’,(’add’,(’num’,5),(’num’,7)),(’num’,4))。 (见下图)
解释器将首先添加5和7,然后递归调用walkTree并将5加到7的结果加4。因此,我们将得到16。下面的代码执行相同的过程。
Python3
class BasicExecute:
def __init__(self, tree, env):
self.env = env
result = self.walkTree(tree)
if result is not None and isinstance(result, int):
print(result)
if isinstance(result, str) and result[0] == '"':
print(result)
def walkTree(self, node):
if isinstance(node, int):
return node
if isinstance(node, str):
return node
if node is None:
return None
if node[0] == 'program':
if node[1] == None:
self.walkTree(node[2])
else:
self.walkTree(node[1])
self.walkTree(node[2])
if node[0] == 'num':
return node[1]
if node[0] == 'str':
return node[1]
if node[0] == 'add':
return self.walkTree(node[1]) + self.walkTree(node[2])
elif node[0] == 'sub':
return self.walkTree(node[1]) - self.walkTree(node[2])
elif node[0] == 'mul':
return self.walkTree(node[1]) * self.walkTree(node[2])
elif node[0] == 'div':
return self.walkTree(node[1]) / self.walkTree(node[2])
if node[0] == 'var_assign':
self.env[node[1]] = self.walkTree(node[2])
return node[1]
if node[0] == 'var':
try:
return self.env[node[1]]
except LookupError:
print("Undefined variable '"+node[1]+"' found!")
return 0
显示输出
为了显示解释器的输出,我们应该编写一些代码。该代码应首先调用词法分析器,然后是解析器,然后是解释器,最后检索输出。输入然后显示在外壳上。
Python3
if __name__ == '__main__':
lexer = BasicLexer()
parser = BasicParser()
print('GFG Language')
env = {}
while True:
try:
text = input('GFG Language > ')
except EOFError:
break
if text:
tree = parser.parse(lexer.tokenize(text))
BasicExecute(tree, env)
有必要知道我们没有处理任何错误。因此,只要您执行编写规则未指定的操作,SLY就会显示错误消息。
执行您使用编写的程序,
python you_program_name.py
脚注
我们构建的解释器非常基础。当然,这可以扩展为做更多的事情。可以添加循环和条件。可以实现模块化或面向对象的设计功能。模块集成,方法定义,方法的参数是可以扩展到相同功能的一些功能。