最近完成了一个需求,从工程脚本代码中提取出给设备下发的命令,例如:telnet.cmd "ls",需要将 ls 提取出来。 代码有几种情况 1,变量拼接,嵌入
cmd = 'ls'
telnet.cmd cmd + 'dir0'
telnet.cmd "ls #{xxx}"
2, 循环
['ls', 'dir'].each do |cmd|
telnet.cmd cmd
end
3, 分支
if a
str = 'ls'
else
str = 'dir'
end
telnet.cmd str
4, 函数调用,以及跨文件调用
def send_cmd str
telnet.cmd str
end
send_cmd 'ls'
需求是不能有遗漏,尽量将命令提取出来。
总体思路是,用 ruby_parser 将代码转换成 s 表达式,对 s 表达式求值 s 表达式长这个样子
s(:defn, :send_cmd, s(:args, :str),
s(:call, s(:call, nil, :telnet), :cmd, s(:lvar, :str)))
深度遍历这个语法树,对每个语法单位进行求值,将方法 cmd 的参数记录下来,即得到所有的命令。
具体解决方案: 1,变量拼接处理是最简单的,在代码块范围内附带一个 hash 对象,将赋值语法,例如 s(:lasgn, :a, s(:str, "123")) 记录在 hash 中, 相应的,在获取变量的时候从 hash 中提取出来
2,循环的语法单位是:iter,例如
#[].each do |x| end
s(:iter, s(:call, s(:array), :each), s(:args, :x))
这个可以用 send 方法将:each 发送给迭代对象
3,分支的处理我的做法是,多次遍历,每次遍历到 if 分支的时候,只求值后一个分支里的语句,求值后删除,下一次遍历另外的语句 这样每个分支都会走到,缺点是会有一些信息丢失
4,函数调用,分为两部分 1, 执行:defn 函数定义的时候,将函数定义保存在函数表中 2, 在执行到:call 的时候,会对:call 的参数求值,例如求值后的表达式变成 s(:call,...参数) 其中参数的值已经求出, 将这个放入函数调用队列中,将代码全部处理后,会得到一个函数调用队列,对其迭代求值,直到队列为空。
经过以上处理,最终效果比较令人满意,唯一遗憾的是分支结构有信息丢失,或许增加改变分支的处理顺序可以弥补。