使用ANTLR分析和修改源代码;我做错了吗?

问题描述:

我正在写一个程序,我需要解析一个JavaScript源文件,提取一些事实,并插入/替换部分代码。我需要做各种各样的事情的简化描述,给出此代码:使用ANTLR分析和修改源代码;我做错了吗?

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来做我所需要的事情。有没有更适合我解决这个问题的方法?

+0

*“有没有更适当的方法来解决此问题?”:否,不是AFAIK。你解析你的输入,操纵它,然后自己输出它。像Dave提到的那样,StringTemplate可以帮助你解决这个问题。 – 2012-07-23 09:47:01

因此,事实证明,我实际上可以使用重写树语法并使用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(); 

你看过string template库。 ANTLR写的是同一个人,他们打算一起工作。这听起来像它会适合做你想要的,即。输出匹配的语法规则作为格式化文本。

Here is an article on translation via ANTLR

+0

我希望避免创建代码来输出整个JavaScript程序,当我需要做的是注入并替换部分JavaScript代码。但是如果我需要创建一个完整的输出引擎,字符串模板看起来很有前途。感谢您对本文的有用链接。 – Jacob 2012-07-23 15:20:45

你所试图做的是叫做program transformation,也就是自动生成从另外一个程序的。你所做的“错误”是假设解析器是你需要的,并且发现它不是,你必须填补这个空白。

这样做的工具具有解析器(用于构建AST),意味着修改AST(包括过程和模式定向)以及将(修改后的)AST转换回合法源代码的漂亮的打印机。你似乎正在为ANTLR没有配备漂亮的打印机而苦苦挣扎;这不是其哲学的一部分; ANTLR是一个(很好)解析器生成器。其他答案已经建议使用ANTLR的“字符串模板”,它本身不是漂亮的打印机,但可以用来实现一个,但实现一个。这比看起来更难做;在compiling an AST back to source code上看到我的回答。

这里真正的问题是广泛的,但假设“如果我有一个解析器,我正在构建复杂的程序分析和转换工具。”的假设。请参阅我的文章Life After Parsing对此进行长期讨论;基本上,你需要更多的工具来“完成”解析器来做到这一点,除非你想自己重建一大部分基础架构,而不是继续完成任务。实际程序转换系统的其他有用功能通常包括源到源转换,这大大简化了查找和替换树中复杂模式的问题。

举例来说,如果你有源到源转换能力(我们的工具中,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)文本。 metaquotes中有很多元转义**,它们表示特殊的重写规则语言项目。通常这些是参数名称,并表示参数表示的任何类型的名称树,或表示外部元过程调用(例如first_arg;您会注意到它的参数列表(,)是metaquoted!),或者最后是“标记“,如”替换“,这是一种特殊的树,代表未来有意做更多的转换。

这组特定的规则通过用barized版本替换候选函数调用来工作,其中附加意图“替换”以转换列表。另外两个转换通过逐个处理列表中的元素来转换“替换”,并将替换进一步推到列表的下方,直到它最终脱落并完成替换为止。 (这是一个循环的转换等价物)。

您的具体示例可能会有所不同,因为您确实对细节不确切。

应用这些规则修改解析树后,DMS可以轻松打印结果(某些配置中的默认行为是“解析为AST,应用规则直到用尽,相当便宜”,因为这很方便)。

您可以在(High School) Algebra as a DMS domain上看到“定义语言”,“定义重写规则”,“应用规则和相纸”的完整过程。

其他程序转换系统包括TXLStratego。我们将DMS想象成这些工业强度版本,其中我们构建了所有基础设施,包括many standard language parsers and prettyprinters

+0

谢谢你的详细解答。我希望避免写一个漂亮的打印机(或者,就DMS而言,我想要一台高保真打印机)。我只想注入/替换部分代码,所以如果我不得不编写代码来输出整个程序来执行此操作,那将是不幸的。 – Jacob 2012-07-23 15:17:39

+0

我了解你的愿望,以避免工作: - }坏消息:当你想改变代码很难做到。好消息是:有人已经完成了所有要点: - } – 2012-07-23 15:35:44