📜  如何使用Python创建编程语言?

📅  最后修改于: 2021-08-24 04:58:59             🧑  作者: Mango

在本文中,我们将学习如何使用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。让我们做一个进行简单算术运算的编译器。因此,我们将需要一些基本标记,例如NAMENUMBERSTRING 。在任何编程语言中,两个字符之间都会有空格。因此,我们创建了一个忽略字面量。然后,我们还创建基本字面量如’=’,’+’等。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

脚注

我们构建的解释器非常基础。当然,这可以扩展为做更多的事情。可以添加循环和条件。可以实现模块化或面向对象的设计功能。模块集成,方法定义,方法的参数是可以扩展到相同功能的一些功能。