问题
鉴于以下代码,我想提取muffinize
以及随之传递的所有参数而不运行代码:
$ask = false
muffinize("Shoop")
muffinize("Woop") if($ask == true)
if($ask == false)
muffinize("Pants")
end
这是我期望的输出:
Call#:Args
1:"Shoop"
2:"Woop"
3:"Pants"
到目前为止我正在做什么
我正在用Ripper解析源代码,以找到调用该方法的位置。然后我在调用方法的代码行中找到字符范围。从线上提取方法后,我用Parser gem分析它。一旦我完成它,我会将代码作为答案发布,但我有兴趣听听是否有更好的方法来做到这一点。我不知道代码解析得那么好,所以我在这一点上盲目地徘徊。
为什么不使用正则表达式
正则表达式仍然会捕获注释的代码块。可能还有其他一些我尚未考虑的角落案例。
答案 0 :(得分:1)
一般来说,静态分析用Ruby这样的动态语言编写的程序很难(读取:不可能),即在没有实际运行程序的情况下找到所有可能的方法调用或分支。其原因在于像Ruby这样的动态语言(以及其他类似Python,Perl或者现在某些甚至Java或.NET的语言)允许基于正在运行的程序中的数据动态生成方法调用。一个例子就是这个
def deliciousness(pastry)
self.send("#{pastry}ize", "Icing")
end
deliciousness("muffin")
此代码将调用muffinize
方法并将"Icing"
作为参数传递。但是,由于源代码中没有提到被调用方法的实际名称,因此您无法知道仅使用静态分析器或解析器。您可以使该示例任意地更复杂,并且还涉及代码生成和更多层间接。我的观点是,你不能确定你得到了所有东西,即使你专门报道了其他一些案例。
然而,您可以做的是跟踪代码,因为它实际运行(可能通过测试)并找到您方法的可能调用。您一定要使用静态分析来表达所有这些内容。一旦你使用例如任何网络框架,由于涉及大量的元编程和代码生成,你肯定是运气不好。
答案 1 :(得分:-1)
尽管Holger Just是完全正确的,但对我来说,我有一个用例,没有人会在没有明确输入的情况下调用该方法。这是一个奇怪的角落案例,但我认为无论如何我都会分享我的解决方案。这是我最终使用的代码:
require "parser/current"
require "ripper"
#Get the source code somehow
code = Ripper.lex(source)
callNumber = 0
code.each_with_index do |line, index|
if(line[1] == :on_ident and line[2][/^muffinize$/] != nil)
extractedCode = ""
charEnd = 0
lineSperated = nil
charStart = line[0][1]
lineNumber = line[0][0]
#Look ahead till you find the first right parenthese
i = 0
while(i < code.length-1)
if(code[index+i] != nil)
if(code[index+i][1] == :on_rparen)
charEnd = code[index+i][0][1]
break
end
end
i += 1
end
lineSeperated = source.split(/\n/)
extractedCode = lineSeperated[lineNumber-1]
extractedCode = extractedCode[charStart,(charEnd-charStart+1)]
#Use the somewhat crazy Ruby parser gem to interpret the code as the Ruby interpreter would interpret it.
callArray = Parser::CurrentRuby.parse(extractedCode).to_a
text = callArray[2].to_a[0].to_s
callNumber += 1
puts "#{callNuber}:#{text}"
end
end