介绍
Ruby支持元编程,简单的说就是在运行时改变程序自身。利用这一点,可以做出很有趣的程序。本文介绍利用ruby演示程序的执行过程,显示包括函数调用和数据变化。
演示程序执行
Ruby每个class都有一个方法method_missing(name, *args)。当调用对象的方法不存在时,系统默认调用这个方法来处理。这就相当于一个系统后门,可以得到方法调用的轨迹。
下面的程序演示如何把大象放进冰箱里。
class User def act f = Frig.new f.open f.put_elephant f.close end end class Frig def indent; ' '*4; end def method_missing(name, *args) if name[0]=='_' return end puts "#{indent}Frig.#{name} " end end user = User.new user.act
输出为:
Frig.open
Frig.put_elephant
Frig.close
这里面user.act没有打印出来。为了做到这一点,需要把method_missing放到一个公共类中共享;而且需要把act变成未知方法,激活method_missing;然后再利用一个技巧,映射到已知的方法。
在这里把已知方法定义成下划线开头,在调用的地方取掉下划线,让method_missing完成两者之间的连接。
class Show def initialize(name = "", pos = 0) @name, @pos = name, pos end def indent; ' '*@pos*4; end def method_missing(name, *args) if name[0]=='_' return end puts "#{indent}#{@name}.#{name} " if methods.grep(/_#{name}/) send "_#{name}", *args end end end class User < Show def _act f = Frig.new("frig", 1) f.open f.put_elephant f.close end end class Frig < Show end user = User.new user.act
输出为:
user.act
frig.open
frig.put_elephant
frig.close
添加数据显示
上一节演示了程序的执行,但是充其量只是一个调用树。为了进一步揭示程序的运行状态,这一节加入状态机。
class Stm def initialize @edges = Hash.new @current = '$' end def get_current; @current; end def empty?; @edges.empty?; end def def_edge(from, to, event) edges = @edges[from.to_s] if edges==nil @edges[from.to_s] = Hash.new end @edges[from.to_s][event.to_s] = to.to_s end def accept(event) edges = @edges[@current.to_s] st = nil if edges!=nil st = edges[event.to_s] if st!=nil @current = st end end if st==nil "**error** #{event} is not accepted " else st end end end class Show def initialize(name = "", pos = 0) @name, @pos = name, pos @m_stm = Stm.new end def indent; ' '*@pos*4; end def method_missing(name, *args) if name[0]=='_' return end print "#{indent}#{@name}.#{name} " if !@m_stm.empty? error = @m_stm.accept(name.to_s) if error && error[0]=='*' puts error+" when #{@name}'s = #{@m_stm.get_current}" else puts 'STATUS("'+error+"\")" end else puts "" end if methods.grep(/_#{name}/) send "_#{name}", *args end end end class User < Show def _act f = Frig.new("frig", 1) f.open f.put_elephant #f.put_elephant f.close end end class Frig < Show def initialize(name = "", pos = 0) super @m_stm.def_edge('$', 'door is opened', 'open') @m_stm.def_edge('door is opened', 'door is closed', 'close') @m_stm.def_edge('door is opened', 'door is opened w/ elephant', 'put_elephant') @m_stm.def_edge('door is opened w/ elephant', 'door is closed w/ elephant', 'close') end end
输出:
user.act
frig.open STATUS("door is opened")
frig.put_elephant STATUS("door is opened w/ elephant")
frig.close STATUS("door is closed w/ elephant")
如果把大象两次放入冰箱,程序还会报错:
user.act
frig.open STATUS("door is opened")
frig.put_elephant STATUS("door is opened w/ elephant")
frig.put_elephant **error** put_elephant is not accepted when frig's = door is opened w/ elephant
frig.close STATUS("door is closed w/ elephant