Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >浅尝antlr4

浅尝antlr4

作者头像
Kevinello
发布于 2022-08-19 03:08:46
发布于 2022-08-19 03:08:46
1.8K00
代码可运行
举报
运行总次数:0
代码可运行

浅尝Antlr4

前言

Antlr是什么

In a word, 多源语言多目标语言的一个语法分析框架

以下是官方文档的解释:

ANTLR(ANother Tool for Language Recognition)是一个功能强大的解析器生成器,用于读取,处理,执行或翻译结构化文本或二进制文件。它被广泛用于构建语言,工具和框架。ANTLR从语法上生成一个解析器,该解析器可以构建解析树,还可以生成一个侦听器接口(或访问者),从而可以轻松地对所关注短语的识别做出响应。 ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, or translating structured text or binary files. It’s widely used to build languages, tools, and frameworks. From a grammar, ANTLR generates a parser that can build parse trees and also generates a listener interface (or visitor) that makes it easy to respond to the recognition of phrases of interest.

Github项目地址

这次使用antlr的诱因是whosbug中使用的ctags(另一个语法分析器)只对c系语言支持较好,对java等语言的支持欠佳(甚至可以说很差了),为了whosbug的鲁棒性我认为还是有必要换一个语法分析器的

几个需要了解的词

AST:抽象语法树

target language:antlr可以根据源语言的.g4文件生成不同语言(target language)的分析代码 各种target language的文档(有些很简略)

Lexer:antlr中的词法分析器(词法分析

Parser:antlr中的语法分析器(语法分析

Listener:是antlr中的独有概念,与传统源码分析不同,antlr提供Listener这一API供用户自定义自己的分析器,这种方式可以很大程度上使语法更易于阅读(按每位用户自己的设计),同时使得它们能避免与特定的应用程序耦合在一起,以下是官方的解释(官方文档):

其它相关概念见antlr在github上的官方文档

安装antlr4

官方文档

安装Java(1.7版或更高版本),这个不会就入土8

下载antlr4

添加antlr-4.9-complete.jarCLASSPATH

将其放入.bash_profile,就不需要每次都改环境变量了

为ANTLR Tool和 TestRig创建alias:

输入antlr4验证一下安装情况:

获取targer language为python的分析模块

获取.g4语法文件

ANTLR的GitHub项目中提供了用于不同语言的语法文件(.g4)

官方g4文件收录库

这次的需求先重点解决java的语法分析问题,所以一开始我找到了java9的g4文件,但生成分析代码的时候报错了: Incorrectly generated code for Python 3 target,google了一番找到了对应的issue:https://github.com/antlr/grammars-v4/issues/739

更换成https://github.com/antlr/grammars-v4/tree/master/java/java中的.g4文件后就没问题了

生成分析模块

按官方文档生成分析模块源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
antlr4 -Dlanguage=Python3 JavaLexer.g4
antlr4 -Dlanguage=Python3 JavaParser.g4

生成结果见下图:

其中JavaLexer.py,JavaParser.py,JavaParserListener.py是我们需要重点关注的

安装antlr4-python3-runtime

这步没什么好说的,直接pip install完事

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
pip install antlr4-python3-runtime

创建自定义Listener

我的目录结构如下:

analyzer.py

分析模块入口,main所在位置,废话不多说,上码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import logging.config
from ast_java.ast_processor import AstProcessor
from ast_java.basic_info_listener import BasicInfoListener

logging.config.fileConfig('log/utiltools_log.conf')
AST_ANALYZER = AstProcessor(logging, BasicInfoListener())


def analyze_java(target_file_path):
    return AST_ANALYZER.execute(target_file_path)


if __name__ == '__main__':
    analyze_java('testfiles/java/AllInOne7.java')

ast_processor.py

调用antlr的语法分析模块,生成AST,供自定义Listener使用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from antlr4 import FileStream, CommonTokenStream, ParseTreeWalker
from ast_java.JavaLexer import JavaLexer
from ast_java.JavaParser import JavaParser
from pprint import pformat


class AstProcessor:

    def __init__(self, logging, listener):
        self.logging = logging
        self.logger = logging.getLogger(self.__class__.__name__)
        self.listener = listener

    def execute(self, input_source):
        parser = JavaParser(CommonTokenStream(JavaLexer(FileStream(input_source, encoding="utf-8"))))
        walker = ParseTreeWalker()
        walker.walk(self.listener, parser.compilationUnit())
        self.logger.debug('Display all data extracted by AST. \n' + pformat(self.listener.ast_info, width=160))
        return self.listener.ast_info

basic_info_listener.py

这部分就完全是自定义的了,同时也是源码分析的关键,在这部分设计的分析模式决定了分析结果的数据结构

简单来说就是继承JavaParserListener,然后扩展自己需要的内容

具体的使用还是需要自己去读一下源码,这里放一下我写的作为参考:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
from ast_java.JavaParserListener import JavaParserListener
from ast_java.JavaParser import JavaParser


class BasicInfoListener(JavaParserListener):

    def __init__(self):
        self.call_methods = []
        self.ast_info = {
            'packageName': '',
            'className': '',
            'implements': [],
            'extends': '',
            'imports': [],
            'fields': [],
            'methods': []
        }

    # Enter a parse tree produced by JavaParser#packageDeclaration.
    def enterPackageDeclaration(self, ctx: JavaParser.PackageDeclarationContext):
        self.ast_info['packageName'] = ctx.qualifiedName().getText()

    # Enter a parse tree produced by JavaParser#importDeclaration.
    def enterImportDeclaration(self, ctx: JavaParser.ImportDeclarationContext):
        import_class = ctx.qualifiedName().getText()
        self.ast_info['imports'].append(import_class)

    # Enter a parse tree produced by JavaParser#methodDeclaration.
    def enterMethodDeclaration(self, ctx: JavaParser.MethodDeclarationContext):

        print("Start line: {0} | End line: {1} | Method name: {2}".format(ctx.start.line, ctx.methodBody().stop.line, ctx.getChild(1).getText()))
        self.call_methods = []

    # Exit a parse tree produced by JavaParser#methodDeclaration.
    def exitMethodDeclaration(self, ctx: JavaParser.MethodDeclarationContext):
        c1 = ctx.getChild(0).getText()  # ---> return type
        c2 = ctx.getChild(1).getText()  # ---> method name
        params = self.parse_method_params_block(ctx.getChild(2))

        method_info = {
            'startLine': ctx.start.line,
            'endLine': ctx.methodBody().stop.line,
            'returnType': c1,
            'methodName': c2,
            'params': params,
            'depth': ctx.depth(),
            'callMethods': self.call_methods
        }
        self.ast_info['methods'].append(method_info)

    # Enter a parse tree produced by JavaParser#methodCall.
    def enterMethodCall(self, ctx: JavaParser.MethodCallContext):
        line_number = str(ctx.start.line)
        column_number = str(ctx.start.column)
        self.call_methods.append(line_number + ' ' + column_number + ' ' + ctx.parentCtx.getText())

    # Enter a parse tree produced by JavaParser#classDeclaration.
    def enterClassDeclaration(self, ctx: JavaParser.ClassDeclarationContext):
        child_count = int(ctx.getChildCount())
        if child_count == 7:
            # class Foo extends Bar implements Hoge
            # c1 = ctx.getChild(0)  # ---> class
            c2 = ctx.getChild(1).getText()  # ---> class name
            # c3 = ctx.getChild(2)  # ---> extends
            c4 = ctx.getChild(3).getChild(0).getText()  # ---> extends class name
            # c5 = ctx.getChild(4)  # ---> implements
            # c7 = ctx.getChild(6)  # ---> method body
            self.ast_info['className'] = c2
            self.ast_info['implements'] = self.parse_implements_block(ctx.getChild(5))
            self.ast_info['extends'] = c4
        elif child_count == 5:
            # class Foo extends Bar
            # or
            # class Foo implements Hoge
            # c1 = ctx.getChild(0)  # ---> class
            c2 = ctx.getChild(1).getText()  # ---> class name
            c3 = ctx.getChild(2).getText()  # ---> extends or implements

            # c5 = ctx.getChild(4)  # ---> method body
            self.ast_info['className'] = c2
            if c3 == 'implements':
                self.ast_info['implements'] = self.parse_implements_block(ctx.getChild(3))
            elif c3 == 'extends':
                c4 = ctx.getChild(3).getChild(0).getText()  # ---> extends class name or implements class name
                self.ast_info['extends'] = c4
        elif child_count == 3:
            # class Foo
            # c1 = ctx.getChild(0)  # ---> class
            c2 = ctx.getChild(1).getText()  # ---> class name
            # c3 = ctx.getChild(2)  # ---> method body
            self.ast_info['className'] = c2

    # Enter a parse tree produced by JavaParser#fieldDeclaration.
    def enterFieldDeclaration(self, ctx: JavaParser.FieldDeclarationContext):
        field = {
            'fieldType': ctx.getChild(0).getText(),
            'fieldDefinition': ctx.getChild(1).getText()
        }
        self.ast_info['fields'].append(field)

    def parse_implements_block(self, ctx):
        implements_child_count = int(ctx.getChildCount())
        result = []
        if implements_child_count == 1:
            impl_class = ctx.getChild(0).getText()
            result.append(impl_class)
        elif implements_child_count > 1:
            for i in range(implements_child_count):
                if i % 2 == 0:
                    impl_class = ctx.getChild(i).getText()
                    result.append(impl_class)
        return result

    def parse_method_params_block(self, ctx):
        params_exist_check = int(ctx.getChildCount())
        result = []
        # () ---> 2
        # (Foo foo) ---> 3
        # (Foo foo, Bar bar) ---> 3
        # (Foo foo, Bar bar, int count) ---> 3
        if params_exist_check == 3:
            params_child_count = int(ctx.getChild(1).getChildCount())
            if params_child_count == 1:
                param_type = ctx.getChild(1).getChild(0).getChild(0).getText()
                param_name = ctx.getChild(1).getChild(0).getChild(1).getText()
                param_info = {
                    'paramType': param_type,
                    'paramName': param_name
                }
                result.append(param_info)
            elif params_child_count > 1:
                for i in range(params_child_count):
                    if i % 2 == 0:
                        param_type = ctx.getChild(1).getChild(i).getChild(0).getText()
                        param_name = ctx.getChild(1).getChild(i).getChild(1).getText()
                        param_info = {
                            'paramType': param_type,
                            'paramName': param_name
                        }
                        result.append(param_info)
        return result

这里简单说明一下几个重要的点,便于理解:

  • BasicInfoListener继承JavaParserListener,供用户自定义遍历AST的方法
  • ast_info为分析结果dict
  • JavaParserListener覆盖在BasicInfoListener中定义的挂钩点分析方法,并实现其自己的分析过程 例如,enterPackageDeclaration,顾名思义,它在Java源码包定义的开头(即enter)被调用 参数ctx(上下文)具有不同的类型,但是由于存在父类,因此任何上下文类都可以访问语法解析所需的基本信息(通过getChild,getParent等方法)

还有很多的细节信息其实都有,这里就不一一赘述(都在源码里啦)

测试

到这里分析模块就完成啦,用官方提供的Java被测源码试一下效果8

命令行输出:

ast_info:

Done(antlr比ctags不知道好用多少倍)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-11-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
我的 IDE 太重了,所以迁到了 Emacs
作者 | Renato Athaydes 译者 | Sambodhi 策划 | 褚杏娟 IDE(Integrated Development Environment,集成开发环境)是一种伟大的工具,可以使开发者的生活更轻松。我简直不敢想象,没有它们的工作会是什么样子。 但是,它们为我们提供的帮助并非无偿,而我也正遭受着这种后果。我使用 2019 年左右生产的、性能不佳的 MacBook Air ,在它上面运行我最喜欢的 IDE、Jetbrains 的 IntelliJ IDEA 社区版,但整个经历
深度学习与Python
2023/02/28
1.2K0
我的 IDE 太重了,所以迁到了 Emacs
科技礼品的方案,以帮助完善您的假日购物清单(Computing)
今天是“网购星期一”(Cyber Monday),今年这一节日的意义并不大,因为相关的销售自11月起一直在进行,零售商们试图尽一切可能赚取每一分利润。然而,正如大多数人一样,我也经常购物,无论是买给别人还是买给自己。
谭雪儿
2020/12/18
5990
我们离不开的 Linux 内核模块 | Linux 中国
Linux 内核今年就要满 30 岁了! 如果你像我们一样对此特别重视,那么让我们本周用几个特别的文章来庆祝 Linux。
用户8639654
2021/09/06
2.5K0
八种最常见Docker开发模式 别说你还不知道
Docker已迅速成为本人最喜欢的基础工具之一,以便构建可重复软件产品,从而带来尽可能静态的服务器环境。   我在本文中将概述我在使用Docker的过程中开始反复出现的几种模式。我不指望它们会带来多少新奇或惊喜,但希望其中一些有用,我也很想听听各位在使用Docker过程中遇到的模式。   我试用Docker的基础是保持在卷中持续的状态,那样Docker容器本身可以随意重建,而不会丢失数据(除非我改动容器状态,而不更新Docker文件(Dockerfile)的状态,而经常重建容器有助于改掉这个坏习惯)。
静一
2018/03/21
1.5K0
八种最常见Docker开发模式 别说你还不知道
开发容器:可重用的开发环境
作者 | Avdi Grimm 译者 | 明知山 策划 | 丁晓昀 拿着 Chromebook 在洗车房做开发 那天,我把车开到了洗车场。这是一个高级洗车场,你把车交给工作人员,然后等着他们把车里里外外清洗干净。 我要做的就是打发时间了。我还有一些代码要写,但当时我只有一台装在包里的小 Chromebook 和 WiFi 连接。 于是,我在 GitHub Codespaces 中打开了这个项目,然后在上次停下的地方继续,在云端运行我的开发环境。 不只是编辑器,而是整个为我的项目定制的虚拟机。
深度学习与Python
2023/04/01
1.3K0
开发容器:可重用的开发环境
作为 CEO 使用 Emacs 的两年经验之谈
两年前,我写了一篇博客,并取得了一些反响。这让我有点受宠若惊。那篇博客写的是我准备将 Emacs 作为我的主办公软件,当时我还是 CEO,现在已经是 CTO 了。现在回想起来,我发现我之前不是做程序员就是做软件架构师,而且那时我也喜欢用 Emacs 写代码。重新考虑使用 Emacs 是一次令我振奋的尝试,但我不太清楚这次行动会造成什么反响。在网上,那篇博客的评论也是褒贬不一,但是还是有数万的阅读量,所以总的来说,我写的是一个蛮有意思的题材。在 Reddit 和 HackerNews 上有些令人哭笑不得的回复,说我的手会变成鸡爪,或者说我会因白色的背景而近视。在这里我可以很高兴地回答,到目前为止并没有出现什么特别糟糕的后果,相反,我的手腕还因此变得更灵活了。还有一些人担心,说使用 Emacs 会耗费一个 CEO 的精力。把 Fugue 从一个在我家后院的灵感变成强大的产品,并有一大批忠实的顾客,我发现在做这种真正复杂之事的时候,Emacs 可以给你带来安慰。还有,我现在仍然在用白色的背景。
用户1880875
2021/09/23
5130
如何选购一部基于Linux系统的笔记本电脑(Computing)
购买基于Linux的笔记本电脑应该很容易,尤其是在大牌厂商的网站上。但事实并非如此。你必须采用变通的方法才能成功,否则就会花费更多的钱!
谭雪儿
2021/01/15
3.1K0
Linux 正在吞噬 Windows 和 Chrome OS!
Windows 10 和 Chrome OS 都采用 Linux 内核及其上运行的软件。
iMike
2020/02/21
2.7K0
笔记本如何升级硬件?
https://www.kingston.com/cn/memory/search
简单并不简单
2019/08/13
2.1K0
《稀缺》第4章 行为经济学告诉我们的道理
第4章 行为经济学告诉我们的道理 1美元的价值,在穷人眼中和富人眼中是不一样的。环境条件会影响富人对1美元的价值判断。当穷人在评估1美元的价值时,会用上大脑中内化的衡量标准,而不会依赖环境进行判断。穷人是金钱价值方面的专家,他们更接近于“经济人”。人们对货币价值的衡量是相对的,这是行为经济学中的经典结论 请想象你要花一天时间去购物,购物清单中的一样物品是DVD播放机。在一天要结束时,你在一家商店里找到了自己想要的品牌与型号,价格是100美元。这个价格还算合理,但不是你当天看到最优惠的——还有一家商店仅卖65
yeedomliu
2020/08/04
9780
2020年杰出技术产品
每年,我都会回顾当年我选择的所有本周产品,并挑选对我印象最深刻的产品,以摘取年度产品称号。
用户8078865
2020/12/25
6850
微软 Surface DUO:重新思考智能手机
https://www.technewsworld.com/story/86843.html
zstt8054929
2020/12/23
4860
python:Pandas里千万不能做的5件事
作为一个在进入数据分析领域之前干过开发的攻城狮,我看到我的同行以及新手在使用 Pandas 时会犯很多低级错误。
王图思睿
2021/06/16
1.6K0
实用的函数式编程
函数式编程 (functional programming) 正式开始有长足的发展始于 10 年前, 从那时起, 我开始看到 Scala, Clojure 和 F# 这样的语言得到关注. 这种关注并非只是像 "哇, 一个新语言, 酷!" 这样短暂的热度, 而是确实有某些实在的原因在推动着它 -- 或者至少我们是这么认为的.
用户1558438
2018/08/23
1.1K0
【杂谈】想成为机器学习学霸?先学会做笔记吧
话不多说,上标准。以下我总结了身边学编程的小哥哥们和小姐姐们对云笔记app的几类需求:
用户1508658
2019/07/28
2K0
「镁客晚报」苹果批Surface Pro很弱,然而iOS被爆比安卓更易感染病毒!
镁客网——我们关注智能硬件 1、库克批Surface Book很弱,认为微软已迷失 11月12日消息,据外媒报道,苹果CEO蒂姆·库克今天开始了他的iPad Pro宣传之旅。本周早些时候库克表示iPa
镁客网
2018/05/25
7560
MacBook Pro 为什么值得我写一篇博文——程序猿使用感悟
研究生生涯伊始,撑过大学四年的 Dell 灵越 N4050 笔记本电脑就再次罢工了,一直想换电脑的冲动终于要付诸行动了,本来准备再换一个性价比比较高的 win 系列笔记本就行了,但是让我意外的是研究生新入学后会发放奖学金....一下子换电脑的资金就富裕了,可选择的余地就更大了。对与苹果电脑的高逼格我是早就心驰神往,但比较高的售价与某些软件兼容性的顾虑一直是我购买的主要障碍,现在钱已经不是问题,剩下的就是考略兼容性以及实用性的问题了,性价比?买苹果就不要考了什么性价比了吧。
流川枫
2018/09/12
21.2K0
MacBook Pro 为什么值得我写一篇博文——程序猿使用感悟
2021DIY电脑配置入门篇(包含各cpu显卡天梯图对比)
对于电脑来说,预算是最重要的!没有预算,一切都是空谈。没预算默认外星人Area51M(价格在2万左右),现在电脑往往充当一种娱乐需求,相对来说比较次要,因此大多数人配电脑预算都不算太高,好在现在DIY市场比较平价,3000块就能装到不错的电脑了。
全栈程序员站长
2022/09/14
2.3K0
用 Python 制作各种用途的二维码
当你提到二维码时,大多数人想到的是仓库管理或产品标签等 "工业 "应用,但这篇文章在很大程度上是关于二维码的个人和社会用途。
周萝卜
2022/12/27
9780
用 Python 制作各种用途的二维码
Web正文字体发展简史
当你正在纠结选择什么样的字体大小,尤其是在您尝试适应不同的屏幕和场景时。回顾一下网页字体发展的历史变化,或许会给你一个新的视角。
ConardLi
2020/03/04
1.2K0
Web正文字体发展简史
推荐阅读
相关推荐
我的 IDE 太重了,所以迁到了 Emacs
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验