强大的ANTLR4(3)--算术表达式

news2024/9/22 13:22:15

下面要构建一个简单的计算器,规则如下:
1)可以由一系列语句构成,每条语句由换行符终止
2)一条语句可以是表达式、赋值语句或空行
3)可以有加减乘除、小括号以及变量出现

例如,文件名t.expr的内容如下:

193
a = 5
b = 6
a+b*2
(1+2)*3

用ANTLR写出来的语法是:

grammar Expr;

prog:   stat+ ; 

stat:   expr NEWLINE                
    |   ID '=' expr NEWLINE        
    |   NEWLINE                   
    ;

expr:   expr ('*'|'/') expr   
    |   expr ('+'|'-') expr   
    |   INT                    
    |   ID                    
    |   '(' expr ')'         
    ;

ID  :   [a-zA-Z]+ ;      // 匹配标识符
INT :   [0-9]+ ;         // 整数
NEWLINE:'\r'? '\n' ;     
WS  :   [ \t]+ -> skip ; // 丢弃空白字符

可以看到ANTLR非常强大,可以出现递归定义,expr的定义里面又出现了expr。

生成语法分析树:

antlr4-parse Expr.g4 prog -gui t.expr

在这里插入图片描述
生成Python代码:

antlr4 -Dlanguage=Python3 Expr.g4

几个关键源代码:
1)ExprLexer.py 词法分析器
2)ExprLexer.tokens 词法符号
3)ExprListener.py 语法监听器
4)ExprParser.py 语法解析器

可以自己补一个主程序:

import sys
from antlr4 import *
from antlr4.InputStream import InputStream
from ExprLexer import ExprLexer
from ExprParser import ExprParser


input_stream = FileStream('t.expr')
lexer = ExprLexer(input_stream)
token_stream = CommonTokenStream(lexer)
parser = ExprParser(token_stream)
tree = parser.prog()

lisp_tree_str = tree.toStringTree(recog=parser)
print(lisp_tree_str)

该程序也可以用文本形式打印出语法分析树。

(prog 
  (stat (expr 193) \n) 
  (stat a = (expr 5) \n) 
  (stat b = (expr 6) \n) 
  (stat (expr (expr a) + (expr (expr b) * (expr 2))) \n) 
  (stat (expr (expr ( (expr (expr 1) + (expr 2)) )) * (expr 3)) \n)
)

g4文件还可以拆分为更小的逻辑单元,下面内容保存在文件CommonLexerRules.g4中:

lexer grammar CommonLexerRules;

ID  :   [a-zA-Z]+ ;      // 匹配标识符
INT :   [0-9]+ ;         // 整数
NEWLINE:'\r'? '\n' ;     
WS  :   [ \t]+ -> skip ; // 丢弃空白字符

另一个文件可以使用import导入写好的规则:

```python
grammar LibExpr;
import CommonLexerRules; 
prog:   stat+ ; 

stat:   expr NEWLINE                
    |   ID '=' expr NEWLINE        
    |   NEWLINE                   
    ;

expr:   expr ('*'|'/') expr   
    |   expr ('+'|'-') expr   
    |   INT                    
    |   ID                    
    |   '(' expr ')'         
    ;

ANTLR4的容错处理

antlr4-parse LibExpr.g4 prog -gui
(1+2
34*69
^Z

可以看到缺少右括号的错误提示。
在这里插入图片描述

利用访问器构建一个计算器

可以在语法规则里添加标签,这样生成的源代码里。

grammar LabeledExpr; 

prog:   stat+ ;

stat:   expr NEWLINE                # printExpr
    |   ID '=' expr NEWLINE         # assign
    |   NEWLINE                     # blank
    ;

expr:   expr op=('*'|'/') expr      # MulDiv
    |   expr op=('+'|'-') expr      # AddSub
    |   INT                         # int
    |   ID                          # id
    |   '(' expr ')'                # parens
    ;

MUL :   '*' ; // assigns token name to '*' used above in grammar
DIV :   '/' ;
ADD :   '+' ;
SUB :   '-' ;
ID  :   [a-zA-Z]+ ;      // match identifiers
INT :   [0-9]+ ;         // match integers
NEWLINE:'\r'? '\n' ;     // return newlines to parser (is end-statement signal)
WS  :   [ \t]+ -> skip ; // toss out whitespace

生成访问器程序:

antlr4 -Dlanguage=Python3 -no-listener -visitor LabeledExpr.g4

生成的程序中有一个与以前不太一样的源程序LabeledExprVisitor.py:

# Generated from LabeledExpr.g4 by ANTLR 4.11.1
from antlr4 import *
if __name__ is not None and "." in __name__:
    from .LabeledExprParser import LabeledExprParser
else:
    from LabeledExprParser import LabeledExprParser

# This class defines a complete generic visitor for a parse tree produced by LabeledExprParser.

class LabeledExprVisitor(ParseTreeVisitor):

    # Visit a parse tree produced by LabeledExprParser#prog.
    def visitProg(self, ctx:LabeledExprParser.ProgContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#printExpr.
    def visitPrintExpr(self, ctx:LabeledExprParser.PrintExprContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#assign.
    def visitAssign(self, ctx:LabeledExprParser.AssignContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#blank.
    def visitBlank(self, ctx:LabeledExprParser.BlankContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#parens.
    def visitParens(self, ctx:LabeledExprParser.ParensContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#MulDiv.
    def visitMulDiv(self, ctx:LabeledExprParser.MulDivContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#AddSub.
    def visitAddSub(self, ctx:LabeledExprParser.AddSubContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#id.
    def visitId(self, ctx:LabeledExprParser.IdContext):
        return self.visitChildren(ctx)


    # Visit a parse tree produced by LabeledExprParser#int.
    def visitInt(self, ctx:LabeledExprParser.IntContext):
        return self.visitChildren(ctx)



del LabeledExprParser

我们现在要评估计算器里的表达式的值,需要自己构建一个访问器:

from LabeledExprVisitor import LabeledExprVisitor
from LabeledExprParser import LabeledExprParser


class EvalVisitor(LabeledExprVisitor):
    def __init__(self):
        self.memory = {}  # 计算器的“内存”,保存着变量名和变量值的关系

    def visitAssign(self, ctx):
        var_name = ctx.ID().getText()  # id在'='的左侧,变量名
        value = self.visit(ctx.expr())  # 计算右侧表达式的值
        self.memory[var_name] = value
        return value

    def visitPrintExpr(self, ctx):
        value = self.visit(ctx.expr())  # 计算expr子节点的值
        print(value)
        return 0

    def visitInt(self, ctx):
        return ctx.INT().getText()

    def visitId(self, ctx):
        name = ctx.ID().getText()
        if name in self.memory:
            return self.memory[name]
        return 0

    def visitMulDiv(self, ctx):
        left = int(self.visit(ctx.expr(0)))  # 左侧子表达式的值
        right = int(self.visit(ctx.expr(1)))  # 右侧子表达式的值
        if ctx.op.type == LabeledExprParser.MUL:
            return left * right
        return left / right

    def visitAddSub(self, ctx):
        left = int(self.visit(ctx.expr(0)))  # 左侧子表达式的值
        right = int(self.visit(ctx.expr(1)))  # 右侧子表达式的值
        if ctx.op.type == LabeledExprParser.ADD:
            return left + right
        return left - right

    def visitParens(self, ctx):
        return self.visit(ctx.expr())

主程序:

import sys
from antlr4 import *
from antlr4.InputStream import InputStream
from LabeledExprLexer import LabeledExprLexer
from LabeledExprParser import LabeledExprParser
from EvalVisitor import EvalVisitor

input_stream = FileStream('t.expr')

lexer = LabeledExprLexer(input_stream)
token_stream = CommonTokenStream(lexer)
parser = LabeledExprParser(token_stream)
tree = parser.prog()

visitor = EvalVisitor()
visitor.visit(tree)

对于文章一开头的t.expr里的几个表达式,可以求出下面三个值:

193
17
9

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/132722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】PriorityQueue梳理

【Java】PriorityQueue梳理 简介 PriorityQueue是优先队列的意思。优先队列的作用是能保证每次取出的元素都是队列中权值最小的。这里牵涉到了大小关系,元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过…

linux的例行性工作

一,单一执行的例行性工作 定时任务,将来的某个时间点执行 使用单一理性工作的命令:at -> atd 命令 服务名 查看atd状态,看有没有这个服务,查看结果为有 我们使用 at 命令来生成所要运行的工作,并将…

Taro+nutui h5使用nut-signature 签名组件的采坑记录

近期在使用Taro(“tarojs/taro”: “3.4.0-beta.0”) Nutui (3.1.16)开发H5时,需要一个签名功能结果在小程序上运行正常的 nut-signature组件,在h5上出问题了 首先问题是 : Nutui的 签名组件(nut-signature&#xff…

加解密与HTTPS(3)

您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~ 除了对称加密算法和非对称加密算法,再就是最后的一种加密算法了:不可逆加密算法。 对称加密算法和非对称加密算法在处理明文的过程中需要…

线程池ThreadPoolExecutor的源码中是如何解决并发问题的?

ThreadPoolExecutor面临哪些线程安全问题 ThreadPoolExecutor俗称线程池,作为java.util.concurrent包对外提供基础实现,以内部线程池的形式对外提供管理任务执行,线程调度,线程池管理等等服务。 然而为高效并发而生ThreadPoolExe…

C++项目实战:职工管理系统

1.管理系统的要求 系统可以管理公司内部所有员工的信息 主要使用c实现一个基于多态的职工管理系统 公司中的职工分为三类:普通员工、经理、老板,显示信息时需要显示职工编号、职工姓名、职工岗位以及职责 普通员工职责:完成经理安排的各项任…

oh my 毕设-人体姿态估计综述

文章目录What is Human Pose Estimation?Classical vs. Deep Learning-based approachesClassical approaches to 2D Human Pose EstimationDeep Learning-based approaches to 2D Human Pose EstimationHuman Pose Estimation using Deep Neural NetworksOpenPoseAlphaPose (…

想要努力赚钱,培养四种基础能力

这四种基础能力分别是:认知力、学习力、执行力、复盘力。我们的认知和思维,很大程度上,都是由所处的环境和圈子决定的。在同一个环境和圈子里面呆久了,你的认知就会被固化了。穷人最根本的枷锁,不是缺乏资金&#xff0…

excel图表技巧:看看,这个饼图象不象罗盘?

说到制作柱形图、条形图、饼图,相信大家都没有问题,直接选中数据,再插入对应的图表就行了,可如果要制作一张双层饼图你还会吗?“啥?还有双层饼图?”嘿嘿,不知道了吧,双层…

PVE+NUT+群晖等配置

文章目录配置文件说明默认配置(翻译的)ups.conf(设置ups通信相关)upsd.conf(设置ups客户访问的相关信息)upsd.users(设置upsd用户)nut.conf(nut的配置,主要是模式,决定使用哪些文件)upsmon.confupssched.confupssched-cmd官方手册写的可以的文章只需要实现&#xff…

excel数据查找:内容查找统计的函数公式

判断单元格是否包含特定内容是平时工作中经常会遇到的一类问题,常见于包含备注信息的表格中。例如下面这个考勤汇总表,需要根据备注中的内容判断该员工是否存在加班的情况,就属于这类问题。 遇到这类问题该如何处理,常用的公式做法…

klee2.3 教程1-2

1. klee2.3 安装 system:unbuntu 20.04 note: llvm-13klee2.3z3-4.10 1.1 install dependencies KLEE 需要 LLVM 的所有依赖项(请参阅此处),以及更多。特别是,您应该安装下面列出的程序和库。graphviz/doxygen是可…

初级C语言之【操作符】

🦖作者:学写代码的恐龙 🦖博客主页:学写代码的恐龙博客主页 🦖专栏:【初级c语言】 🦖语录:❀未来的你,一定会感谢现在努力奋斗的自己❀ 初级C语言之【操作符详解】一&am…

综合能源系统分析的统一能路理论(三):《稳态与动态潮流计算》(Python代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

spring6笔记3(bean的循环依赖,手写spring框架,ioc注解开发,JdbcTemplate)

第九章、Bean的循环依赖问题 9.1 什么是Bean的循环依赖 A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。 比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。 public class Husband…

【java线程池详解】

java线程池详解线程的基本状态Executor框架Executor框架组成部分Executor框架使用示意图Runnable接口、Callable接口ExecutorsFuture接口和实现Future接口的FutureTask类Future和FutureTask的关系ThreadPoolExecutor类ThreadPoolExecutor 饱和策略(拒绝策略&#xf…

MySQL去重,一条SQL语句完美解决【去重留一】

此处以某消费记录表(consume_record)为例,SQL语句如下: DELETE consume_record FROM consume_record, ( SELECT min(id) id, user_id, monetary, con…

Qt第五十五章:Qt Design Studio设计登录页并打包到python运行

目录 一、Qt Design Studio 二、导出所有文件到QRC(不要改动默认的QRC文件名称) 三、QRC转换成py 1.删除Constants.qml中的 2.将App.qml和Screen01.qml中的 3.转换 4、将QRC文件和转换后的py文件,复制到python项目中使用。 一、Qt Des…

【云原生 Kubernetes】k8s集群部署springboot项目

一、前言 本篇,我们将基于k8s集群,模拟一个比较接近实际业务的使用场景,使用k8s集群部署一个springboot的项目,我们的需求是: 部署SpringBoot项目到阿里云服务器 ;基于容器打包,推送私有镜像仓…

Presto 之 BTreeIndex 索引代码走读

一. 前言 本文主要介绍在Presto(OpenLookeng)中的BTree索引的代码实现。关于BTree索引原理的介绍可以参考官网资料openLooKeng documentation。 二. BTreeIndex 索引建立 在Presto中,BTreeIndex 索引是通过mapdb中的BTreeMap数据结构实现的&a…