Guest User

Untitled

a guest
Feb 19th, 2018
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.45 KB | None | 0 0
  1. require 'stringio'
  2. require 'irb/ruby-lex'
  3.  
  4. # Tell the ruby interpreter to load code lines of required files
  5. # into this filename -> lines Hash. This behaviour seems to be
  6. # very undocumented and therefore shouldn't really be relied on.
  7. SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
  8.  
  9. module ProcSource
  10. def get_lines(filename, start_line = 0)
  11. case filename
  12. # special "(irb)" descriptor?
  13. when "(irb)"
  14. IRB.conf[:MAIN_CONTEXT].io.line(start_line .. -1)
  15. # special "(eval...)" descriptor?
  16. when /^\(eval.+\)$/
  17. EVAL_LINES__[filename][start_line .. -1]
  18. # regular file
  19. else
  20. # Ruby already parsed this file? (see disclaimer above)
  21. if lines = SCRIPT_LINES__[filename]
  22. lines[(start_line - 1) .. -1]
  23. # If the file exists we're going to try reading it in
  24. elsif File.exist?(filename)
  25. begin
  26. File.readlines(filename)[(start_line - 1) .. -1]
  27. rescue
  28. nil
  29. end
  30. end
  31. end
  32. end
  33.  
  34. def handle(proc)
  35. filename, line = proc.source_descriptor
  36. lines = get_lines(filename, line) || []
  37.  
  38. lexer = RubyLex.new
  39. lexer.set_input(StringIO.new(lines.join))
  40.  
  41. state = :before_constructor
  42. nesting_level = 1
  43. start_token, end_token = nil, nil
  44. found = false
  45. while token = lexer.token
  46. # we've not yet found any proc-constructor -- we'll try to find one.
  47. if [:before_constructor, :check_more].include?(state)
  48. # checking more and newline? -> done
  49. if token.is_a?(RubyToken::TkNL) and state == :check_more
  50. state = :done
  51. break
  52. end
  53. # token is Proc?
  54. if token.is_a?(RubyToken::TkCONSTANT) and
  55. token.instance_variable_get(:@name) == "Proc"
  56. # method call?
  57. if lexer.token.is_a?(RubyToken::TkDOT)
  58. method = lexer.token
  59. # constructor?
  60. if method.is_a?(RubyToken::TkIDENTIFIER) and
  61. method.instance_variable_get(:@name) == "new"
  62. unless state == :check_more
  63. # okay, code will follow soon.
  64. state = :before_code
  65. else
  66. # multiple procs on one line
  67. return
  68. end
  69. end
  70. end
  71. # token is lambda or proc call?
  72. elsif token.is_a?(RubyToken::TkIDENTIFIER) and
  73. %w{proc lambda}.include?(token.instance_variable_get(:@name))
  74. unless state == :check_more
  75. # okay, code will follow soon.
  76. state = :before_code
  77. else
  78. # multiple procs on one line
  79. return
  80. end
  81. end
  82.  
  83. # we're waiting for the code start to appear.
  84. elsif state == :before_code
  85. if token.is_a?(RubyToken::TkfLBRACE) or token.is_a?(RubyToken::TkDO)
  86. # found the code start, update state and remember current token
  87. state = :in_code
  88. start_token = token
  89. end
  90.  
  91. # okay, we're inside code
  92. elsif state == :in_code
  93. if token.is_a?(RubyToken::TkRBRACE) or token.is_a?(RubyToken::TkEND)
  94. nesting_level -= 1
  95. if nesting_level == 0
  96. # we're done!
  97. end_token = token
  98. # parse another time to check if there are multiple procs on one line
  99. # we can't handle that case correctly so we return no source code at all
  100. state = :check_more
  101. end
  102. elsif token.is_a?(RubyToken::TkfLBRACE) or token.is_a?(RubyToken::TkDO) or
  103. token.is_a?(RubyToken::TkBEGIN) or token.is_a?(RubyToken::TkCASE) or
  104. token.is_a?(RubyToken::TkCLASS) or token.is_a?(RubyToken::TkDEF) or
  105. token.is_a?(RubyToken::TkFOR) or token.is_a?(RubyToken::TkIF) or
  106. token.is_a?(RubyToken::TkMODULE) or token.is_a?(RubyToken::TkUNLESS) or
  107. token.is_a?(RubyToken::TkUNTIL) or token.is_a?(RubyToken::TkWHILE) or
  108. token.is_a?(RubyToken::TklBEGIN)
  109. nesting_level += 1
  110. end
  111. end
  112. end
  113.  
  114. if start_token and end_token
  115. start_line, end_line = start_token.line_no - 1, end_token.line_no - 1
  116. source = lines[start_line .. end_line]
  117. start_offset = start_token.char_no
  118. start_offset += 1 if start_token.is_a?(RubyToken::TkDO)
  119. end_offset = -(source.last.length - end_token.char_no)
  120. source.first.slice!(0 .. start_offset)
  121. source.last.slice!(end_offset .. -1)
  122.  
  123. # Can't use .strip because newline at end of code might be important
  124. # (Stuff would break when somebody does proc { ... #foo\n})
  125. proc.source = source.join.gsub(/^ | $/, "")
  126. end
  127. end
  128.  
  129. module_function :handle, :get_lines
  130. end
  131.  
  132. require 'yaml'
  133.  
  134.  
  135. class Proc
  136. yaml_as "tag:ruby.yaml.org,2002:proc"
  137.  
  138. def source_descriptor
  139. if md = /^#<Proc:0x[0-9A-Fa-f]+@(.+):(\d+)>$/.match(old_inspect)
  140. filename, line = md.captures
  141. return filename, line.to_i
  142. end
  143. end
  144.  
  145. attr_accessor :source
  146. def source
  147. ProcSource.handle(self) unless @source
  148. @source
  149. end
  150.  
  151. alias :old_inspect :inspect
  152. def inspect
  153. if source
  154. "proc {#{source}}"
  155. else
  156. old_inspect
  157. end
  158. end
  159.  
  160. def ==(other)
  161. if self.source and other.source
  162. self.source == other.source
  163. else
  164. self.object_id == other.object_id
  165. end
  166. end
  167.  
  168. def _dump(depth = 0)
  169. if source
  170. source
  171. else
  172. raise(TypeError, "Can't serialize Proc with unknown source code.")
  173. end
  174. end
  175.  
  176. def to_yaml(opts = {})
  177. self.source # force @source to be set
  178. YAML::quick_emit( object_id, opts ) do |out|
  179. out.map( taguri, to_yaml_style ) do |map|
  180. map.add( 'source', self.source )
  181. end
  182. end
  183. end
  184.  
  185. def self.yaml_new( klass, tag, val )
  186. if Hash === val and val.has_key? 'source'
  187. self.from_string(val['source'])
  188. else
  189. raise YAML::TypeError, "Invalid proc source: " + val.inspect
  190. end
  191. end
  192.  
  193. def self.allocate; from_string ""; end
  194.  
  195. def self.from_string(string)
  196. result = eval("proc {#{string}}")
  197. result.source = string
  198. return result
  199. end
  200.  
  201. def self._load(code)
  202. self.from_string(code)
  203. end
  204.  
  205. def self.marshal_load; end
  206. def marshal_load; end
  207. end
  208.  
  209. # EVAL_LINES__ = Hash.new
  210. #
  211. # alias :old_eval :eval
  212. # def eval(code, *args)
  213. # context, descriptor, start_line, *more = *args
  214. # descriptor ||= "(eval#{code.hash})"
  215. # start_line ||= 0
  216. # lines ||= code.grep(/.*/)
  217. # EVAL_LINES__[descriptor] ||= Array.new
  218. # EVAL_LINES__[descriptor][start_line, lines.length] = lines
  219. # old_eval(code, context, descriptor, start_line, *more)
  220. # end
  221.  
  222. if __FILE__ == $0 then
  223. require "test/unit"
  224. require "pstore"
  225. require "tempfile"
  226.  
  227. class TestProcSource < Test::Unit::TestCase
  228. def setup
  229. @hello_world = lambda { "Hello world!" }
  230. @add_three = proc { |x| x + 3 }
  231. @block = Proc.new { |blk| blk.call("aaa") }
  232. end
  233.  
  234. def check_it_works
  235. assert_equal("Hello world!", @hello_world.call)
  236. assert_equal(7, @add_three.call(4))
  237. assert_equal("aab", @block.call(proc { |x| x.succ }))
  238. end
  239.  
  240. # No need really, but we're buggering around in the depths of ruby, so best
  241. # to check nothing's fundamentally broken
  242. def test_proc
  243. check_it_works
  244. end
  245.  
  246. def test_marshal
  247. @hello_world = Marshal.load(Marshal.dump(@hello_world))
  248. @add_three = Marshal.load(Marshal.dump(@add_three))
  249. @block = Marshal.load(Marshal.dump(@block))
  250.  
  251. check_it_works
  252. end
  253.  
  254. def test_pstore
  255. Tempfile.open("proc_source_test") do |file|
  256. store = PStore.new(file.path)
  257. store.transaction do
  258. store["hello_world"] = @hello_world
  259. store["add_three"] = @add_three
  260. store["block"] = @block
  261. end
  262. store.transaction do
  263. @hello_world = store["hello_world"]
  264. @add_three = store["add_three"]
  265. @block = store["block"]
  266. end
  267. end
  268.  
  269. check_it_works
  270. end
  271.  
  272. def test_yaml
  273. @hello_world = YAML.load(YAML.dump(@hello_world))
  274. @add_three = YAML.load(YAML.dump(@add_three))
  275. @block = YAML.load(YAML.dump(@block))
  276.  
  277. check_it_works
  278.  
  279. @hello_world = YAML.load(@hello_world.to_yaml)
  280. @add_three = YAML.load(@add_three.to_yaml)
  281. @block = YAML.load(@block.to_yaml)
  282.  
  283. check_it_works
  284. end
  285. end
  286. end
Add Comment
Please, Sign In to add comment