好吧,如果您喜欢的语言是俄语,英语等,那么它就在另一个中心 。 如果是编程语言或标记语言,那么当然要自己编写分析器! 乍一看,这是非常困难的,但是幸运的是,有现成的多语言工具,相对来说,增加对新语言的支持相对容易。 今天,我将展示如何在相当短的时间内将Modelica语言支持添加到PMD分析仪 。
顺便说一句,您知道什么会降低从一系列理想的请求请求中获得的代码库的质量吗? 第三方程序员将现有项目代码的一部分复制到其补丁中,而不是将其抽象为有效的事实。 您必须承认,在某种程度上,比起劣质的代码,要捕获这样的平庸甚至更加困难-它是高质量的,甚至已经经过仔细调试,因此在这里进行本地验证还不够,您需要牢记整个代码库,但这对一个人来说并不容易...所以:如果将对Modelica的完全支持(不创建特定规则)添加到“可以运行原始检查”状态大约花了我一周的时间,然后通常一天之内就可以添加仅对复制粘贴检测器的支持!
Modelica还有什么?
顾名思义,Modelica是用于编写物理系统模型的语言。 实际上,不仅是物理的:还可以描述化学过程,动物种群的定量行为等-由der(X) = f(X)
形式的微分方程组描述,其中X
是未知数的向量。 命令性的代码段也受支持。 不明确支持偏微分方程,但是有可能将研究领域分解为几部分(就像我们可能会用通用语言所做的那样),然后写下每个元素的方程,将问题简化为上一个问题。 Modelka的诀窍在于,可以通过编译器解决der(X) = f(X)
的问题:您可以在设置中更改求解器,方程不必是线性的,等等。总之,有一些优点(我写了教科书中的公式-并奏效了)和缺点(有了更多的抽象,我们得到的控制就更少了)。 Modelika的简介是另一篇文章的主题(该文章已经在Habré出现过几次),甚至是一个完整的周期,今天它作为一个开放的并具有多个实现的实例而引起我的兴趣,但是,可惜的是,它仍然是一个潮湿的标准。
此外,Modelika一方面具有静态类型(这将有助于我们更快地编写一些有意义的分析),另一方面,在实例化模型时,不需要编译器完全检查整个库(因此,静态分析器对于捕获“睡眠”非常有用)。错误)。 最后,与某些C ++不同,C ++有大量的静态分析器和编译器,它们漂亮, 最重要的是详细信息,请参阅C ++模板 错误诊断,编译器模型仍然会定期生成内部编译器错误,这意味着即使使用相当简单的分析器,也仍有空间为用户提供帮助。
什么是PMD?
我会回答 歌 骑自行车。 一旦我想对OpenModelica的开发环境提出一些小的要求。 看到代码的另一部分是如何处理模型的保存的,我注意到在支持某些不变式的四行代码中,有一小段并不太清楚。 我不了解他与之交互的是哪种编辑器内部,而是意识到从这段代码的角度来看,我的任务是完全相同的,我只是将其放入一个函数中,以便可以重用它而不破坏它。 Menteiner说,这太好了,然后在剩下的20个地方用函数调用替换此代码。我决定不参与其中,而又制作了一个副本,并指出我需要以某种方式立即梳理所有内容而不混合使用当前补丁。 在Google上,我发现复制粘贴检测器(CPD)是PMD静态分析器的一部分,它支持的语言比分析器本身还要多。 将其设置在OMEdit代码库上后,我希望可以看到这两打的四行代码。 我只是没有看到它们(它们中的每一个都没有超过令牌数量的阈值),但是例如,我看到了将近五十行C ++代码的重复。 正如我已经说过的,导师不太可能只是从另一个文件中复制了一个巨大的片段。 但是他很容易在PR中错过这一点-因为按照定义,该代码已经满足了该项目的所有标准! 当我与导师分享看法时,他同意将清理工作作为一项单独任务的一部分是必要的。
因此,程序错误检测器(PMD)是易于扩展的静态分析器。 也许他不计算变量可以采用的一组值(尽管谁知道...),但是要向其中添加规则,您甚至不需要了解Java并以某种方式更改其代码! 事实是,第一件事并不奇怪,就是用源代码构建AST文件。 源解析树是什么样的? 到XML解析树! 因此,您可以简单地将规则描述为XPath请求-对其进行匹配,然后我们发出警告。 他们甚至有一个规则的图形调试器! 当然,更复杂的规则可以作为AST的访问者直接用Java编写。
结果 :PMD不仅可以用于苛刻的Java程序员对分析器代码承诺的苛刻和通用规则,而且还可以用于本地编码样式-即使您将自己的本地ruleset.xml推送到每个存储库中!
级别1:自动查找复制粘贴
原则上,在CPD中添加对新语言的支持通常非常简单。 我认为重述文档“如何做”没有任何意义-它非常清晰,结构清晰且分步进行。 要重述此事-只能在损坏的手机中播放。 我最好描述一下等着您(TLDR:可以) :
我会警告您,我正在Ubuntu上进行开发。 在Windows上,它在质量和启动工具的方式方面也应该都可以完美工作。
因此,要将新语言添加到CPD中,您只需要...
- 注意:如果您想在PMD 7发行之前获得对PMD的全面支持,那么最好直接升级到第2级,因为据传言,在完成的Antlr语法中,对简单方法的正常支持将出现在第7版中,但现在您会花一些时间(尽管还有一点...)
- 分叉pmd / pmd存储库 。
- 在antlr / grammars-v4中找到适合您的语言的现成语法-当然,如果该语言是内部语言,则必须自己编写,但是例如对于Modelika,它就被发现了。 当然,在这里,您需要遵守许可证的手续-我不是律师,但至少我需要指定复制来源。
- 之后,您需要创建
pmd-<your language name>
模块,将其添加到Gradle并将语法文件放在此处。 此外,在阅读了两页不费力的文档之后,请从Go的模块重做组装脚本,这是几个用于通过反射加载模块的类,那么,有一点点... - 在其中一项测试中更正参考输出,因为现在CPD支持另一种语言! 您如何找到此测试? 很简单: 他想破坏结构 。
- 赢利! 只要有现成的语法,这真的很简单
现在,位于pmd存储库的根目录中,您可以键入./mvnw clean verify
,而在pmd-dist/target
您将获得以zip存档形式存在的二进制分发文件,您需要将其解压缩并使用./bin/run.sh cpd --minimum-tokens 100 --files /path/to/source/dir --language <your language name>
运行./bin/run.sh cpd --minimum-tokens 100 --files /path/to/source/dir --language <your language name>
,位于解压目录中。 原则上,您可以从新模块中进行../mvnw clean verify
,这将大大加快组装速度,但是随后您必须正确地将已组装的jar昵称放入未打包的二进制发行版(例如,在注册新模块后进行一次组装)。
级别2:查找样式指南的错误和违规
就像我说过的那样,PMD 7中承诺将完全支持Antlr。 如果像我一样,您不想等待大海的释放,那么您将不得不从某个地方获得JJTree格式的语言语法说明。 也许您可以自己加强对任意解析器的支持-文档说这是可能的, 但是它们并不能告诉您确切的 ...我只是从同一个存储库中提取了modelica.g4,并以Anltr的语法为基础,然后手动将其重新制作为JJTree。 自然地,如果语法证明是对现有语法的处理,请再次指出源,验证对许可证的遵守情况,然后。 等
顺便说一句,对于一个精通各种解析器生成器的人来说,这不太令人惊讶。 在此之前,除非我自己在Scala上编写常规和解析器组合器,否则我会认真使用它。 因此,实际上,一开始的事实显然让我感到难过:AST,当然,我会从modelica.g4
获得的,但它看起来不是很清晰且“可用”:其中会有许多额外的节点云,如果您不看这些标记 ,那么只能看一下节点,例如, then
分支在哪里结束,而else
在哪里开始,并不总是很清楚。
再说一次,我不会讲有关JJTree和一个很好的教程的文档 -但这一次不是因为原始文档具有细节和清晰度,而是因为我本人并没有完全弄清楚它,但是文档被错误地重新传送了 ,但充满信心,显然比没有重播更糟。 我最好留下一点线索,一路走来:
- 首先, JavaCC解析器描述代码假定将在生成的解析器中进行内插的Java插入
- 在构建AST时,不要感到困惑,例如
[ Expression() ]
类的语法是可选的,并且在描述标记的上下文中-选择字符,就像在正则表达式中一样。 据我了解PMD开发人员的解释,这些是具有类似含义的相似结构-遗产,先生... - 对于根节点(在我的情况下为
StoredDefinition
),您必须指定其类型而不是void
(即ASTStoredDefiniton
) - 在节点名称后使用
#void
语法,可以将其从已解析的树中隐藏(即,它将仅影响正确的源和不正确的源以及如何嵌套其他节点) - 通过使用
void SimpleExpression() #SimpleExpression(>1)
形式的构造void SimpleExpression() #SimpleExpression(>1)
可以说,如果节点具有多个后代,则必须在结果AST中显示该节点。 当用许多具有不同优先级的运算符描述表达式时,这非常方便:也就是说,从解析器的角度来看,孤独常数1
类似于LogicExpression(AdditiveExpression(MultiplicativeExperssion(Constant(1))))
- 输入所有n个运算优先级级别 -但分析器代码将仅获得Constant(1)
- 该节点具有一个标准的变量
image
(请参见getImage
, setImage
),该变量通常setImage
该节点的“本质”:例如,对于与局部变量名称相对应的节点,将具有标识符的匹配标记复制到image
是逻辑上的(默认情况下,来自树木将被丢弃,因此,无论是可变的东西,而不仅仅是关键字,都值得复制其中包含的含义) - LOOKAHEAD-好吧,这是一首单独的歌,甚至文档中的一章都专门介绍了它
- 粗略地说,在JavaCC中,如果转到某个节点,则不能将其扔回去并尝试进行不同的解析,但是可以提前查看并决定是否去
- 在最简单的情况下,看到JavaCC警告后,
您只是在标题中说LOOKAHEAD = n
而且您会得到神秘的解析错误,因为在一般情况下,它似乎无法解决所有问题(嗯,除了通过设置数十亿个令牌,您实际上可以预览所有内容,但事实并非如此。 ) - 在嵌入式节点名称的前面,您可以根据在此处有多少个令牌来明确指出,您肯定可以做出最终决定
- 如果在一般情况下没有这样固定数量的令牌,则可以说“去这里,如果以前,从这一点开始,我们设法匹配了这样的前缀-然后是子树的常规描述”
- 注意:在一般情况下,JavaCC无法检查
LOOKAHEAD
指令的正确性-它信任您,因此至少要弄清楚为什么这样的先行就足够了的数学证明...
现在,您已经以JJTree格式描述了语言的语法, 这些简单的14个步骤将帮助您添加语言支持。 它们中的大多数都具有“创建与Java或vm的实现类似的类,但经过修改的类”的形式。 我将仅说明一些典型功能,如果其中一些功能接受我对文档的请求请求 ,这些功能就会出现在主要文档中 :
- 注释掉在汇编脚本
alljavacc.xml
(位于新模块中)中所有生成的文件的删除,您可以将它们从target/generated-sources
传输到源树。 但最好不要。 可能只有一小部分会被更改,因此最好只删除一些内容:他们看到需要更改默认实现,将其复制到源树,添加到已删除文件的列表中,进行重建-现在可以管理该文件- 特别是此文件 。 否则,将很难弄清楚到底发生了什么变化,并且这种支持很难称得上令人愉快。 - 现在,您已经实现了“主” PMD模式,就可以轻松地将JPD绑定挂在JJTree解析器上,类似于Java或其他可用实现
- 请记住要实现一个返回XPath查询主机名的方法。 在默认的实现中,要么获得无限递归(通过
toString
获得节点名称,反之亦然),通常,由于这个原因,也无法在PMD Designer中查看树,并且如果不进行此调试,语法就很难过 - 组件注册的一部分是通过将完全合格的类名称入口点中的文本文件添加到
META-INF/services
- 规则中没有声明性描述的内容(例如,检查的详细描述和错误示例)未在代码中描述,而是在
category/<language name>/<ruleset>.xml
-无论如何,您都必须在此处注册规则 - ...但是在实施测试时,显然会主动使用一些可能是本地开发的自动发现机制,因此
- 如果您被告知“为每种语言的版本添加一个简单的测试”-最好不要争论,他们说“我不需要它,它的工作原理就是这样”-也许这就是自动发现机制
- 如果您看到针对特定规则的测试,并且该类的主体仅包含注释
// no additional unit tests
,则这些不是测试,它们只是以输入数据的XML描述和预期的分析器反应的形式存在于资源中,马上:一些正确的测试和一些不正确的例子。
一个小而重要的任务:完成PMD设计器
也许无需可视化工具即可调试所有内容。 但是为什么呢? 首先,完成它非常简单。 其次,它将极大地帮助您不熟悉Java的用户:他们既简单又容易(如果这完全适用于XPath),或者至少无需重新编译PMD就能描述他们不喜欢的简单模式(在最简单的情况下) -样式指南,例如“模型包的名称始终以小写p开头”)。
与其他立即可见的错误不同,PMD Designer的问题非常隐蔽:您似乎已经了解,菜单右侧的Java铭文不是按钮,而是O_o语言选择的下拉列表,它已经出现在其中。 Modelica,因为在类路径中出现了一个用于注册入口点的新模块。 但是在这里,您可以选择语言,下载测试文件并查看AST。 这似乎是一个胜利,但是它以某种方式是黑白的,并且可以在文本中突出显示突出显示的子树-尽管没有,突出显示在那里,但是它是歪斜更新的-但是,他们怎么没想到用XPath突出显示找到的匹配项...已经在估算工作量,您考虑下一个请求请求,但是随后您不小心决定将语言切换为Java并下载PMD本身的一些源代码...噢! 它是彩色的..子树高亮显示! 嗯...事实证明,它通常会突出显示找到的匹配项,并在请求右侧的框中写出一些文本...似乎在接口渲染期间JavaFX代码中发生异常时,它会中断渲染,但不会打印到控制台...
通常,您只需要添加一个ma-a-scarlet类即可突出显示基于正则表达式的语法。 就我而言,它是net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.ModelicaSyntaxHighlighter
,需要在AvailableSyntaxHighlighters
类中注册。 请注意,这两个更改都发生在pmd-designer
存储库中,其组装工件需要放入您的二进制发行版中。
最后,它看起来像这样(GIF来自PMD Designer存储库中的README):

小计
如果您已完成所有这些级别,那么您现在已经:
- 复制粘贴检测器
- 规则引擎
- 可视化工具,用于调试AST并将其转换为方便的形式进行分析(如我们已经看到的,并不是所有相同语言的语法都同样有用!)
- 您可以在无需重新编译PMD的情况下使用相同的可视化工具来调试XPath规则,而无需重新编译PMD和一般的Java知识(当然,XPath也不是BASIC,但至少是一种标准而非本地查询语言)
希望您也了解以下事实:语法现在已经是用于实现语言支持的稳定API-除非绝对必要,否则请勿更改语法(或将其转换为由其描述的AST的功能),并且,如果您进行了更改,请作为重大更改进行通知,然后用户会很沮丧:很可能不是每个人都会为他们的规则编写测试,而当规则检查代码,然后在没有警告的情况下停止时,这非常可悲-而且就像一年前完全崩溃的备份一样...
故事还不止于此:至少必须编写一些有用的规则。
但这还不是全部:PMD本机支持范围和声明。 每个AST节点都有与其关联的范围:类,函数,循环的主体……整个文件,最糟糕的是! 并且在每个范围中都有一个直接包含的定义(声明)列表。 与在其他情况下一样,建议通过与其他语言(例如Modelika)的类比实现(但在撰写本文时,坦率地说,我的拉取请求中的逻辑是原始的)。 scopes declarations visitor, - ScopeAndDeclarationFinder
, — , , , - , read-only AST. , .
public class ModelicaHandler extends AbstractLanguageVersionHandler {
结论
PMD . , «» Clang Static Analyzer , . , CPD ( ), .