python antlr
我喜欢出于多种目的处理代码,例如静态分析或自动重构。 对我来说,有趣的部分是推理从抽象语法树(AST)构建的模型。 为此,您需要一种从源文件中获取AST的方法。 可以使用ANTLR轻松完成此操作,并在此处提供完整语法的集合: https : //github.com/antlr/grammars-v4
我们将只为Python 3编写一个脚本,对于Python 2来说也应该可以正常工作。如果我们需要做一些小的调整,我们可以从这个基础上轻松地做到这一点。
获得语法
首先,我们要学习语法。
只需访问https://github.com/antlr/grammars-v4并获取所需的语法即可。 大多数语法都有非常宽松的许可。
R,Scala,Python,Swift,PHP等许多语言都有数十种语法。 Java也有一个,但是对于Java,您更喜欢使用JavaParser,对吗?
只需将语法复制到src / main / antlr下的新项目中
使用Gradle设置项目
现在,我们将使用Gradle设置构建脚本。
我们将使用ANTLR4插件从melix ,因为我觉得它更灵活的中描述的的官方文件 。
我们将在特定的程序包( me.tomassetti.pythonast.parser )中生成代码,因此将在从该程序包派生的目录(build / generate-src / me / tomassetti / pythonast / parser)中生成代码。
buildscript {repositories {maven {name 'JFrog OSS snapshot repo'url 'https://oss.jfrog.org/oss-snapshot-local/'}jcenter()}dependencies {classpath 'me.champeau.gradle:antlr4-gradle-plugin:0.1.1-SNAPSHOT'}
}repositories {mavenCentral()jcenter()
}apply plugin: 'java'
apply plugin: 'me.champeau.gradle.antlr4'antlr4 {source = file('src/main/antlr')output = file('build/generated-src/me/tomassetti/pythonast/parser')extraArgs = ['-package', 'me.tomassetti.pythonast.parser']
}compileJava.dependsOn antlr4sourceSets.main.java.srcDirs += antlr4.outputconfigurations {compile.extendsFrom antlr4
}task fatJar(type: Jar) {manifest {attributes 'Implementation-Title': 'Python-Parser','Implementation-Version': '0.0.1'}baseName = project.name + '-all'from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }with jar
}
我还添加了fatJar任务。 该任务将产生一个包含所有依赖项的JAR。 我使用它可以更轻松地将解析器导入Jetbrains MPS。
要从语法生成解析器,您只需运行gradle antlr4。
然后,您必须向您的IDE解释它应该考虑build / Generated-src下的代码。
如何调用解析器
现在让我们看看如何调用解析器。
public class ParserFacade {private static String readFile(File file, Charset encoding) throws IOException {byte[] encoded = Files.readAllBytes(file.toPath());return new String(encoded, encoding);}public Python3Parser.File_inputContext parse(File file) throws IOException {String code = readFile(file, Charset.forName("UTF-8"));Python3Lexer lexer = new Python3Lexer(new ANTLRInputStream(code));CommonTokenStream tokens = new CommonTokenStream(lexer);Python3Parser parser = new Python3Parser(tokens);return parser.file_input();}
}
我们的ParserFacade只有一个名为parse的公共方法。 它获取一个文件并返回AST。 没有比这更简单的了。
让我们看一些AST
让我们看一个简单的文件:
def sum(a, b):return a + bprint("The sum of %i and %i is %i" % (5, 3, sum(5, 3)))
现在获取AST。 我们可以使用以下代码进行打印:
public class AstPrinter {public void print(RuleContext ctx) {explore(ctx, 0);}private void explore(RuleContext ctx, int indentation) {String ruleName = Python3Parser.ruleNames[ctx.getRuleIndex()];for (int i=0;i<indentation;i++) {System.out.print(" ");}System.out.println(ruleName);for (int i=0;i<ctx.getChildCount();i++) {ParseTree element = ctx.getChild(i);if (element instanceof RuleContext) {explore((RuleContext)element, indentation + 1);}}}}
如果我们解析简单的示例并使用AstPrinter进行打印,我们将获得一个超级复杂的AST。 第一行看起来像:
file_inputstmtcompound_stmtfuncdefparameterstypedargslisttfpdeftfpdefsuitestmtsimple_stmtsmall_stmtflow_stmtreturn_stmttestlist...
对于解析器的构建方式,有很多无效的规则。 在解析时这很有意义,但是会产生非常污染的AST。 我认为有两种不同的ASTS:一种易于生成的解析AST ,另一种易于推理的逻辑AST 。 幸运的是,我们可以毫不费力地将第一个转换为后者。
一种简单的方法是列出仅包装程序的所有规则,然后跳过它们,取而代之的是唯一的子规则。 我们可能必须对此进行优化,但是作为第一步的近似,我们只是跳过只有一个子节点的节点,这是另一个解析器规则(无终端)。
这样,我们从164个节点增加到28个节点。结果逻辑AST为:
file_inputfuncdefparameterstypedargslisttfpdeftfpdefsuitesimple_stmtreturn_stmtarith_expratomatomsimple_stmtpoweratomtrailertermstringatomtestlist_compintegerintegerpoweratomtrailerarglistintegerinteger
在这棵树中,我们应该将所有内容映射到我们理解的概念,而无需人工节点,只是出于解析原因而创建的节点。
结论
编写解析器并不是我们可以产生最大价值的地方。 我们可以轻松地重用现有语法,生成解析器并使用这些解析器构建我们的智能应用程序。
那里有几个解析器生成器,其中大多数足以满足您可以实现的大多数目标。 在它们当中,我倾向于比其他人更多地使用ANTLR:它很成熟,得到支持,速度很快。 它产生的AST可以使用异构API(我们为每种类型的节点生成单个类)和同类API(我们可以询问每个节点代表哪个规则及其子列表)进行导航。
ANTLR的另一个巨大好处是存在随时可以使用的语法。 构建语法需要经验和一些工作。 特别是对于Java或Python这样的复杂GPL。 它还需要非常广泛的测试。 即使我们已经使用JavaParser解析了成千上万个文件,我们仍然发现JavaParser背后的Java 8语法存在一些小问题。 如果可以避免的话,这是现在编写自己的语法的一个很好的理由。
- 顺便说一下,所有代码都可以在github上找到: python-ast
翻译自: https://www.javacodegeeks.com/2016/02/parsing-language-java-5-minutes-using-antlr-example-python.html
python antlr