使用ANTLR分析和修改源代码; 我做错了吗?
我正在编写一个程序,我需要解析JavaScript源文件,提取一些事实,并插入/替换部分代码。 给出这个代码,我需要做的事情的简单描述是:
foo(['a', 'b', 'c']);
提取'a'
, 'b'
和'c'
并将代码重写为:
foo('bar', [0, 1, 2]);
我使用ANTLR来解析需求,生成C#3代码。 其他人已经贡献了JavaScript语法。 解析源代码正在工作。
我遇到的问题是搞清楚如何实际正确地分析和修改源文件。 我试图采取的每种方法都能够解决问题,导致我陷入死胡同。 我不禁想到,我没有按照预期使用该工具,或者在处理AST时只是太多新手。
我的第一个方法是使用TokenRewriteStream
进行解析,并为我感兴趣的规则实施EnterRule_*
部分方法。虽然这似乎使得令牌流的修改变得非常容易,但我的分析没有足够的上下文信息。 看起来,我所能访问的只是一个平坦的代币流,它并没有告诉我足够的整个代码结构。 例如,要检测foo
函数是否被调用,只需查看第一个标记就行不通,因为这也会错误匹配:
a.b.foo();
为了让我能够进行更复杂的代码分析,我的第二种方法是使用重写规则修改语法来生成更多的树。 现在,第一个示例代码块产生这个:
Program CallExpression Identifier('foo') ArgumentList ArrayLiteral StringLiteral('a') StringLiteral('b') StringLiteral('c')
这对分析代码非常有用。 但是,现在我无法轻易重写代码。 当然,我可以修改树结构来表示我想要的代码,但我不能用它来输出源代码。 我曾希望与每个节点相关的令牌至少能够提供足够的信息,以便知道原始文本中需要进行修改的位置,但我所得到的只是令牌索引或行/列号。 要使用行号和列号,我必须在源代码中进行一次尴尬的第二遍。
我怀疑我错过了解如何正确使用ANTLR来做我需要的事情。 有没有更适合我解决这个问题的方法?
你所要做的就是所谓的程序转换,即从另一个程序自动生成一个程序。 你所做的“错误”是假设解析器是你需要的,并且发现它不是,你必须填补这个空白。
这样做的工具有解析器(用于构建AST),意味着修改AST(包括过程和模式定向)以及将(修改后的)AST转换回合法源代码的漂亮打印机。 你似乎正在为ANTLR没有配备漂亮的打印机而苦苦挣扎; 这不是其哲学的一部分; ANTLR是一个(很好)解析器生成器。 其他答案已经建议使用ANTLR的“字符串模板”,它本身不是漂亮的打印机,但可以用来实现一个,但实现一个。 这比看起来更难做; 看到我的答案编译一个AST回源代码。
这里真正的问题是一个广泛但错误的假设,即“如果我有一个解析器,我正在构建复杂的程序分析和转换工具。” 请参阅我关于解析后的生活的文章,对此进行长期讨论; 基本上,你需要更多的工具来“完成”解析器来做到这一点,除非你想自己重建一大部分基础架构,而不是继续完成任务。 实际程序转换系统的其他有用功能通常包括源到源转换,这大大简化了查找和替换树中复杂模式的问题。
例如,如果您有源代码到源代码的转换功能(我们的工具DMS Software Reengineering Toolkit),那么您可以使用这些DMS转换编写部分示例代码更改:
domain ECMAScript.
tag replace; -- says this is a special kind of temporary tree
rule barize(function_name:IDENTIFIER,list:expression_list,b:body):
expression->expression
= " function_name ( '[' list ']' ) "
-> "function_name( firstarg(function_name), replace(list))";
rule replace_unit_list(s:character_literal):
expression_list -> expression_list
replace(s) -> compute_index_for(s);
rule replace_long_list(s:character_list, list:expression_list):
expression_list -> expression_list
"replace(s,list)-> "compute_index_for(s),list";
与规则外部的“元”程序“first_arg”(知道如何计算“bar”给定标识符“foo”[我猜你想要做到这一点),“compute_index_for”给出了字符串文字,知道什么整数来替换它。
个别重写规则具有参数列表“(...)”,其中表示子树的槽被命名,左侧充当匹配的模式,并且右侧充当替换,两者通常在元语词中引用“在目标语言(例如JavaScript)文本中分离重写规则语言文本在元语言里有很多元转义**,它们表示一个特殊的重写规则语言项目,通常这些是参数名称,并且表示任何类型的参数表示的名称树,或表示外部元过程调用(例如first_arg;您会注意到它的参数列表(,)是metaquoted!),或者最后是一个“标记”,例如“replace”,它是一个代表未来有意做更多转变的特殊种类的树。
这个特定的规则集通过用barized版本替换候选函数调用来工作,其中附加的意图“替换”来转换列表。 另外两个转换通过逐个处理列表中的元素来转换“替换”,并将替换进一步推到列表的下方,直到它最终脱落并完成替换为止。 (这是一个循环的转换等价物)。
您的具体示例可能会有所不同,因为您对细节确实不太确切。
应用这些规则修改已解析的树后,DMS可以轻松打印结果(某些配置中的默认行为是“对AST进行解析,应用规则直至用尽,prettyprint AST”,因为这很方便)。
您可以在(高中)代数中看到一个完整的“定义语言”,“定义重写规则”,“应用规则和相纸”过程作为DMS域。
其他程序转换系统包括TXL和Stratego。 我们将DMS想象成这些工业强度版本,其中我们构建了包括许多标准语言解析器和漂亮打印机在内的所有基础架构。
你看过字符串模板库吗? ANTLR写的是同一个人,他们打算一起工作。 这听起来像它会适合做你想要的,即。 输出匹配的语法规则作为格式化文本。
这是一篇关于通过ANTLR翻译的文章
所以事实证明,我实际上可以使用重写树语法并使用TokenRewriteStream
插入/替换标记。 另外,它的确很容易做到。 我的代码类似于以下内容:
var charStream = new ANTLRInputStream(stream);
var lexer = new JavaScriptLexer(charStream);
var tokenStream = new TokenRewriteStream(lexer);
var parser = new JavaScriptParser(tokenStream);
var program = parser.program().Tree as Program;
var dependencies = new List<IModule>();
var functionCall = (
from callExpression in program.Children.OfType<CallExpression>()
where callExpression.Children[0].Text == "foo"
select callExpression
).Single();
var argList = functionCall.Children[1] as ArgumentList;
var array = argList.Children[0] as ArrayLiteral;
tokenStream.InsertAfter(argList.Token.TokenIndex, "'bar', ");
for (var i = 0; i < array.Children.Count(); i++)
{
tokenStream.Replace(
(array.Children[i] as StringLiteral).Token.TokenIndex,
i.ToString());
}
var rewrittenCode = tokenStream.ToString();
链接地址: http://www.djcxy.com/p/43705.html
上一篇: Using ANTLR to analyze and modify source code; am I doing it wrong?