基于PLY的解释器

自作多情 提交于 2020-01-22 05:20:19

今年过年回家本来要好好放松一下的,但是闲了几天发现很无聊,于是乎想起来了以前想学的东西,但是没来得及学的,那就是解释器,但是我没把电脑带回来,大家一定想不到,我这篇博客竟然是在树莓派中写的,哈哈。废话不多说,现在开始。

首先给出这次的代码:https://download.csdn.net/download/shixiongtao/12116774。大家自己首先下载运行一下看一下效果。

虽然功能非常简单,但是基本上体现了一个完整的过程。

PLY是lex和yacc的纯python实现,需要用到两个包 ply.lex和ply.yacc,ply.lex是把程序分割为一个个的标记(token),ply.yacc则是负责根据token生成语法,并执行。


###################################################

tokens = ['ID', 'FLOAT', 'INT'] 

literals = ['=', '+', '-', '*', '/', '(', ')']

t_ignore = " \t"

t_ID = r'[a-zA-Z_][a-zA-Z_0-9]*'

def t_FLOAT(t):
    r'\d+\.\d+'
    t.value = float(t.value)
    print('t_FLOAT', t.value)
    return t

def t_INT(t):
    r'\d+'
    t.value = int(t.value)
    print('t_INT', t.value)
    return t

def t_newline(t):
    r'\n+'
    t.lexer.lineno += t.value.count("\n")

def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

# Build the lexer
import ply.lex as lex
lex.lex()

###################################################
# dictionary of ids
ids = {}

# Parsing rules

precedence = (
    ('left', '+', '-'),
    ('left', '*', '/'),
    ('right', 'UMINUS'),
)

def p_statement_assign(p):
    'statement : ID "=" expression'
    ids[p[1]] = p[3]
    for i in range(len(p)):
        print('p_statement_assign', 'pos', i, 'value', p[i])

def p_statement_expr(p):
    'statement : expression'
    print(p[1])
    for i in range(len(p)):
        print('p_statement_expr', 'pos', i, 'value', p[i])

def p_expression_plus(p):
    '''expression : expression '+' expression'''
    p[0] = p[1] + p[3]
    for i in range(len(p)):
        print('p_expression_plus', 'pos', i, 'value', p[i])

def p_expression_minus(p):
    '''expression : expression '-' expression'''
    p[0] = p[1] - p[3]
    for i in range(len(p)):
        print('p_expression_minus', 'pos', i, 'value', p[i])

def p_expression_times(p):
    '''expression : expression '*' expression'''
    p[0] = p[1] * p[3]
    for i in range(len(p)):
        print('p_expression_times', 'pos', i, 'value', p[i])

def p_expression_div(p):
    '''expression : expression '/' expression'''
    p[0] = p[1] / p[3]
    for i in range(len(p)):
        print('p_expression_div', 'pos', i, 'value', p[i])

def p_expression_uminus(p):
    "expression : '-' expression %prec UMINUS"
    p[0] = -p[2]
    for i in range(len(p)):
        print('p_expression_uminus', 'pos', i, 'value', p[i])

def p_expression_group(p):
    "expression : '(' expression ')'"
    p[0] = p[2]
    for i in range(len(p)):
        print('p_expression_group', 'pos', i, 'value', p[i])

def p_expression_float(p):
    "expression : FLOAT"
    p[0] = p[1]
    for i in range(len(p)):
        print('p_expression_float', 'pos', i, 'value', p[i])

def p_expression_int(p):
    "expression : INT"
    p[0] = p[1]
    for i in range(len(p)):
        print('p_expression_int', 'pos', i, 'value', p[i])

def p_expression_id(p):
    "expression : ID"
    try:
        p[0] = ids[p[1]]
    except LookupError:
        print("Undefined name '%s'" % p[1])
        p[0] = 0
    for i in range(len(p)):
        print('p_expression_id', 'pos', i, 'value', p[i])

def p_error(p):
    if p:
        print("Syntax error at '%s'" % p.value)
    else:
        print("Syntax error at EOF")

###################################################
import ply.yacc as yacc
yacc.yacc()

while True:
    try:
        s = input('calc > ')
    except EOFError:
        break
    if not s:
        continue
    yacc.parse(s)
    print('ids', ids)

咱们开始一行一行的解析。

tokens = ['ID', 'FLOAT', 'INT'] 

在ply.lex中会默认读取我们声明的tokens变量,在这个变量中存储的是可能的标记种类。

比如a = 1+1.1,在这段程序中包含了三种标记,a(ID),1(INT),1.1(FLOAT)。

literals = ['=', '+', '-', '*', '/', '(', ')']

这一行生命的是字面量,同样的ply.lex会默认读取我们声明的literals变量,其实这也算是token只不过这个token特殊的地方在于,这个token的type和value是同一个值。

t_ignore = " \t"

这一行代表省略所有的空格。

t_ID = r'[a-zA-Z_][a-zA-Z_0-9]*'

这一行给出具体的ID这个token的正则表达式,以a-z,A-Z,和下划线为开头,以a-z,A-Z,0-9和下划线,重复任意次。

def t_FLOAT(t):
    r'\d+\.\d+'
    t.value = float(t.value)
    print('t_FLOAT', t.value)
    return t

前面的t_ID是给出ID这个token的正则表达式,ply.lex的token对应的正则表达式也可以以函数定义,这里给出了FLOAT的正则表达式定义,r'\d+\.\d+',整数出现多次,小数点,整数出现多次。在进入到这个函数的时候,首先t就是匹配到的token,t.value就是具体匹配到的字符串,这里需要把字符串转换成float数字,t.value = float(t.value),之后再返回t。

def t_INT(t):
    r'\d+'
    t.value = int(t.value)
    print('t_INT', t.value)
    return t

这个是对应INT这个token处理的函数。

def t_newline(t):
    r'\n+'
    t.lexer.lineno += t.value.count("\n")

这个是识别行号的处理函数,当识别到一个新行的时候,通过t.lexer.lineno来累加行号。

def t_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

这是处理错误的函数,在检查的错误的时候,这里通过简单的打印,并跳过这个错误字符。

# Build the lexer
import ply.lex as lex
lex.lex()

最终导入ply.lex包,这个包就会自动读取上面写的规则。需要注意的是,上面这个规则定义的顺序决定了规则匹配的优先级,先定义的规则,在匹配的时候具有较高的优先级,具体来说,t_FLOAT的优先级必须比t_INT的优先级要高,如果弄反了,那么一个浮点数就会解析为两个整数,和一个错误。

上面就是ply.lex部分,下面开始ply.yacc部分。

ids = {}

首先定义变量存储的字典。

precedence = (
    ('left', '+', '-'),
    ('left', '*', '/'),
    ('right', 'UMINUS'),
)

这里声明了操作符的优先级。

def p_statement_assign(p):
    'statement : ID "=" expression'
    ids[p[1]] = p[3]
    for i in range(len(p)):
        print('p_statement_assign', 'pos', i, 'value', p[i])

这里给出第一个模式,将一个表达式的值赋值给一个ID。

这里通过ids这个字典,将所有的变量名和变量值对应存储起来。最好的for循环是为了打印p中的内存,是为了学习使用,并不是必须的,下面的都是这样。

def p_statement_expr(p):
    'statement : expression'
    print(p[1])
    for i in range(len(p)):
        print('p_statement_expr', 'pos', i, 'value', p[i])

第二个模式,当单独输入一个变量时,直接打印这个变量的值。

def p_expression_plus(p):
    '''expression : expression '+' expression'''
    p[0] = p[1] + p[3]
    for i in range(len(p)):
        print('p_expression_plus', 'pos', i, 'value', p[i])

def p_expression_minus(p):
    '''expression : expression '-' expression'''
    p[0] = p[1] - p[3]
    for i in range(len(p)):
        print('p_expression_minus', 'pos', i, 'value', p[i])

def p_expression_times(p):
    '''expression : expression '*' expression'''
    p[0] = p[1] * p[3]
    for i in range(len(p)):
        print('p_expression_times', 'pos', i, 'value', p[i])

def p_expression_div(p):
    '''expression : expression '/' expression'''
    p[0] = p[1] / p[3]
    for i in range(len(p)):
        print('p_expression_div', 'pos', i, 'value', p[i])

这四个模式是四则运算。

def p_expression_uminus(p):
    "expression : '-' expression %prec UMINUS"
    p[0] = -p[2]
    for i in range(len(p)):
        print('p_expression_uminus', 'pos', i, 'value', p[i])

这个模式是对变量取负数。

def p_expression_group(p):
    "expression : '(' expression ')'"
    p[0] = p[2]
    for i in range(len(p)):
        print('p_expression_group', 'pos', i, 'value', p[i])

这个是括号对应的模式。

def p_expression_float(p):
    "expression : FLOAT"
    p[0] = p[1]
    for i in range(len(p)):
        print('p_expression_float', 'pos', i, 'value', p[i])

def p_expression_int(p):
    "expression : INT"
    p[0] = p[1]
    for i in range(len(p)):
        print('p_expression_int', 'pos', i, 'value', p[i])

def p_expression_id(p):
    "expression : ID"
    try:
        p[0] = ids[p[1]]
    except LookupError:
        print("Undefined name '%s'" % p[1])
        p[0] = 0
    for i in range(len(p)):
        print('p_expression_id', 'pos', i, 'value', p[i])

浮点数,整数,ID的模式。

def p_error(p):
    if p:
        print("Syntax error at '%s'" % p.value)
    else:
        print("Syntax error at EOF")

最后是错误处理。

最后就是通过输入字符串,并解释字符串,输出中间详细信息,和最终的结果。

import ply.yacc as yacc
yacc.yacc()

while True:
    try:
        s = input('calc > ')
    except EOFError:
        break
    if not s:
        continue
    yacc.parse(s)
    print('ids', ids)

最后给出一个运行实例。

>>> ================================ RESTART ================================
>>> 
calc > a = 1
t_INT 1
p_expression_int pos 0 value 1
p_expression_int pos 1 value 1
p_statement_assign pos 0 value None
p_statement_assign pos 1 value a
p_statement_assign pos 2 value =
p_statement_assign pos 3 value 1
ids {'a': 1}
calc > b = 2
t_INT 2
p_expression_int pos 0 value 2
p_expression_int pos 1 value 2
p_statement_assign pos 0 value None
p_statement_assign pos 1 value b
p_statement_assign pos 2 value =
p_statement_assign pos 3 value 2
ids {'b': 2, 'a': 1}
calc > c = a + b + (3 * 4 / 5) - 6
p_expression_id pos 0 value 1
p_expression_id pos 1 value a
p_expression_id pos 0 value 2
p_expression_id pos 1 value b
p_expression_plus pos 0 value 3
p_expression_plus pos 1 value 1
p_expression_plus pos 2 value +
p_expression_plus pos 3 value 2
t_INT 3
p_expression_int pos 0 value 3
p_expression_int pos 1 value 3
t_INT 4
p_expression_int pos 0 value 4
p_expression_int pos 1 value 4
p_expression_times pos 0 value 12
p_expression_times pos 1 value 3
p_expression_times pos 2 value *
p_expression_times pos 3 value 4
t_INT 5
p_expression_int pos 0 value 5
p_expression_int pos 1 value 5
p_expression_div pos 0 value 2.4
p_expression_div pos 1 value 12
p_expression_div pos 2 value /
p_expression_div pos 3 value 5
p_expression_group pos 0 value 2.4
p_expression_group pos 1 value (
p_expression_group pos 2 value 2.4
p_expression_group pos 3 value )
p_expression_plus pos 0 value 5.4
p_expression_plus pos 1 value 3
p_expression_plus pos 2 value +
p_expression_plus pos 3 value 2.4
t_INT 6
p_expression_int pos 0 value 6
p_expression_int pos 1 value 6
p_expression_minus pos 0 value -0.5999999999999996
p_expression_minus pos 1 value 5.4
p_expression_minus pos 2 value -
p_expression_minus pos 3 value 6
p_statement_assign pos 0 value None
p_statement_assign pos 1 value c
p_statement_assign pos 2 value =
p_statement_assign pos 3 value -0.5999999999999996
ids {'c': -0.5999999999999996, 'b': 2, 'a': 1}
calc > 

到这块,这段简单的程序就能支持简单的四则运算了,下面接着要学的就是支持if,else,for循环了。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!