live_ast 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES.rdoc +6 -0
- data/MANIFEST +54 -0
- data/README.rdoc +388 -0
- data/Rakefile +19 -0
- data/devel/jumpstart.rb +983 -0
- data/lib/live_ast/ast_eval.rb +13 -0
- data/lib/live_ast/ast_load.rb +15 -0
- data/lib/live_ast/base.rb +56 -0
- data/lib/live_ast/cache.rb +14 -0
- data/lib/live_ast/error.rb +30 -0
- data/lib/live_ast/evaler.rb +66 -0
- data/lib/live_ast/linker.rb +107 -0
- data/lib/live_ast/loader.rb +69 -0
- data/lib/live_ast/parser.rb +48 -0
- data/lib/live_ast/replace_load.rb +14 -0
- data/lib/live_ast/replace_raise.rb +21 -0
- data/lib/live_ast/to_ast.rb +17 -0
- data/lib/live_ast/to_ruby.rb +12 -0
- data/lib/live_ast/version.rb +3 -0
- data/lib/live_ast.rb +4 -0
- data/test/ast_eval_feature_test.rb +11 -0
- data/test/ast_load_feature_test.rb +11 -0
- data/test/backtrace_test.rb +159 -0
- data/test/covert_define_method_test.rb +23 -0
- data/test/def_test.rb +35 -0
- data/test/define_method_test.rb +41 -0
- data/test/define_singleton_method_test.rb +15 -0
- data/test/encoding_test/bad.rb +1 -0
- data/test/encoding_test/cp932.rb +6 -0
- data/test/encoding_test/default.rb +5 -0
- data/test/encoding_test/eucjp.rb +6 -0
- data/test/encoding_test/koi8.rb +6 -0
- data/test/encoding_test/koi8_shebang.rb +7 -0
- data/test/encoding_test/usascii.rb +6 -0
- data/test/encoding_test/utf8.rb +6 -0
- data/test/encoding_test.rb +51 -0
- data/test/error_test.rb +115 -0
- data/test/eval_test.rb +269 -0
- data/test/flush_cache_test.rb +98 -0
- data/test/lambda_test.rb +56 -0
- data/test/load_path_test.rb +84 -0
- data/test/load_test.rb +85 -0
- data/test/noninvasive_test.rb +51 -0
- data/test/readme_test.rb +11 -0
- data/test/recursive_eval_test.rb +52 -0
- data/test/redefine_method_test.rb +83 -0
- data/test/reload_test.rb +108 -0
- data/test/shared/ast_generators.rb +124 -0
- data/test/shared/main.rb +110 -0
- data/test/stdlib_test.rb +11 -0
- data/test/thread_test.rb +44 -0
- data/test/to_ast_feature_test.rb +15 -0
- data/test/to_ruby_feature_test.rb +15 -0
- data/test/to_ruby_test.rb +86 -0
- metadata +223 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'live_ast/base'
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
private
|
5
|
+
|
6
|
+
#
|
7
|
+
# For use in noninvasive mode (<code>require 'live_ast/base'</code>).
|
8
|
+
#
|
9
|
+
# Same behavior as the built-in +load+ except that AST-accessible
|
10
|
+
# objects are created.
|
11
|
+
#
|
12
|
+
def ast_load(file, wrap = false)
|
13
|
+
LiveAST::Loader.load(file, wrap)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'live_ast/parser'
|
4
|
+
require 'live_ast/loader'
|
5
|
+
require 'live_ast/evaler'
|
6
|
+
require 'live_ast/linker'
|
7
|
+
require 'live_ast/cache'
|
8
|
+
require 'live_ast/error'
|
9
|
+
|
10
|
+
module LiveAST
|
11
|
+
NATIVE_EVAL = Kernel.method(:eval) #:nodoc:
|
12
|
+
|
13
|
+
class << self
|
14
|
+
#
|
15
|
+
# For use in noninvasive mode (<code>require 'live_ast/base'</code>).
|
16
|
+
#
|
17
|
+
# Extract an object's AST.
|
18
|
+
#
|
19
|
+
def ast(obj) #:nodoc:
|
20
|
+
case obj
|
21
|
+
when Method, UnboundMethod
|
22
|
+
Linker.find_method_ast(obj.owner, obj.name, *obj.source_location)
|
23
|
+
when Proc
|
24
|
+
Linker.find_proc_ast(obj)
|
25
|
+
else
|
26
|
+
raise TypeError, "No AST for #{obj.class} objects"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Flush unused ASTs from the cache. See README.rdoc before doing
|
32
|
+
# this.
|
33
|
+
#
|
34
|
+
def flush_cache
|
35
|
+
Linker.flush_cache
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# For use in noninvasive mode (<code>require 'live_ast/base'</code>).
|
40
|
+
#
|
41
|
+
# Equivalent to Kernel#ast_eval.
|
42
|
+
#
|
43
|
+
def eval(*args) #:nodoc:
|
44
|
+
Evaler.eval(args[0], *args)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# For use in noninvasive mode (<code>require 'live_ast/base'</code>).
|
49
|
+
#
|
50
|
+
# Equivalent to Kernel#ast_load.
|
51
|
+
#
|
52
|
+
def load(file, wrap = false) #:nodoc:
|
53
|
+
Loader.load(file, wrap)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module LiveAST
|
2
|
+
class MultipleDefinitionsOnSameLineError < ScriptError
|
3
|
+
def message
|
4
|
+
"AST requested for a method or block that shares a line " <<
|
5
|
+
"with another method or block."
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ASTNotFoundError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class RawEvalError < ASTNotFoundError
|
13
|
+
def message
|
14
|
+
"Must use ast_eval instead of eval in order to obtain AST."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class NoSourceError < ASTNotFoundError
|
19
|
+
def message
|
20
|
+
"No source found for the requested AST. " <<
|
21
|
+
"Are you sure it was written in ruby?"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FlushedError < ASTNotFoundError
|
26
|
+
def message
|
27
|
+
"The requested AST was flushed from the cache."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module LiveAST
|
2
|
+
module Evaler
|
3
|
+
class << self
|
4
|
+
def eval(parser_source, *args)
|
5
|
+
evaler_source, bind, *location = handle_args(*args)
|
6
|
+
|
7
|
+
file, line = handle_location(bind, *location)
|
8
|
+
file = Linker.strip_token(file)
|
9
|
+
|
10
|
+
key, _ = Linker.new_cache_synced(parser_source, file, line, false)
|
11
|
+
|
12
|
+
begin
|
13
|
+
NATIVE_EVAL.call(evaler_source, bind, key, line)
|
14
|
+
rescue Exception => ex
|
15
|
+
fix_backtrace(ex.backtrace)
|
16
|
+
raise ex
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# match eval's error messages
|
22
|
+
#
|
23
|
+
def handle_args(*args)
|
24
|
+
unless (2..4).include? args.size
|
25
|
+
raise ArgumentError,
|
26
|
+
"wrong number of arguments (#{args.size} for 2..4)"
|
27
|
+
end
|
28
|
+
unless args[1].is_a? Binding
|
29
|
+
raise TypeError,
|
30
|
+
"wrong argument type #{args[1].class} (expected Binding)"
|
31
|
+
end
|
32
|
+
args[0] = arg_to_str(args[0])
|
33
|
+
args[2] = arg_to_str(args[2]) unless args[2].nil?
|
34
|
+
args
|
35
|
+
end
|
36
|
+
|
37
|
+
def arg_to_str(arg)
|
38
|
+
begin
|
39
|
+
arg.to_str
|
40
|
+
rescue
|
41
|
+
raise TypeError, "can't convert #{arg.class} into String"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# match eval's behavior
|
47
|
+
#
|
48
|
+
def handle_location(bind, *location)
|
49
|
+
case location.size
|
50
|
+
when 0
|
51
|
+
NATIVE_EVAL.call("[__FILE__, __LINE__]", bind)
|
52
|
+
when 1
|
53
|
+
[location.first, 1]
|
54
|
+
else
|
55
|
+
location
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def fix_backtrace(backtrace)
|
60
|
+
backtrace.map! { |line|
|
61
|
+
LiveAST::Linker.strip_token line
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module LiveAST
|
2
|
+
module Attacher
|
3
|
+
VAR_NAME = :@_live_ast
|
4
|
+
|
5
|
+
def attach_to_proc(obj, ast)
|
6
|
+
obj.instance_variable_set(VAR_NAME, ast)
|
7
|
+
end
|
8
|
+
|
9
|
+
def fetch_proc_attachment(obj)
|
10
|
+
if obj.instance_variable_defined?(VAR_NAME)
|
11
|
+
obj.instance_variable_get(VAR_NAME)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def attach_to_method(klass, method, ast)
|
16
|
+
unless klass.instance_variable_defined?(VAR_NAME)
|
17
|
+
klass.instance_variable_set(VAR_NAME, {})
|
18
|
+
end
|
19
|
+
klass.instance_variable_get(VAR_NAME)[method] = ast
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_method_attachment(klass, method)
|
23
|
+
if klass.instance_variable_defined?(VAR_NAME)
|
24
|
+
klass.instance_variable_get(VAR_NAME)[method]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Linker
|
30
|
+
REVISION_TOKEN = "|ast@"
|
31
|
+
|
32
|
+
@caches = {}
|
33
|
+
@counter = "a"
|
34
|
+
@mutex = Mutex.new
|
35
|
+
|
36
|
+
class << self
|
37
|
+
include Attacher
|
38
|
+
|
39
|
+
def find_proc_ast(obj)
|
40
|
+
@mutex.synchronize do
|
41
|
+
fetch_proc_attachment(obj) or (
|
42
|
+
ast = find_ast(*obj.source_location) or raise FlushedError
|
43
|
+
attach_to_proc(obj, ast)
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_method_ast(klass, name, *location)
|
49
|
+
@mutex.synchronize do
|
50
|
+
case ast = find_ast(*location)
|
51
|
+
when nil
|
52
|
+
fetch_method_attachment(klass, name) or raise FlushedError
|
53
|
+
else
|
54
|
+
attach_to_method(klass, name, ast)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_ast(*location)
|
60
|
+
raise NoSourceError unless location.size == 2
|
61
|
+
raise RawEvalError if location.first == "(eval)"
|
62
|
+
ast = fetch_from_cache(*location)
|
63
|
+
raise MultipleDefinitionsOnSameLineError if ast == :multiple
|
64
|
+
ast
|
65
|
+
end
|
66
|
+
|
67
|
+
def fetch_from_cache(file, line)
|
68
|
+
cache = @caches[file]
|
69
|
+
if !cache and !file.index(REVISION_TOKEN)
|
70
|
+
#
|
71
|
+
# File was loaded by 'require'.
|
72
|
+
# Play catch-up: assume it has not changed in the meantime.
|
73
|
+
#
|
74
|
+
_, cache = new_cache(Loader.read(file), file, 1, true)
|
75
|
+
end
|
76
|
+
cache.fetch_ast(line) if cache
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# create a cache along with a uniquely-identifing key for it
|
81
|
+
#
|
82
|
+
def new_cache(contents, file, user_line, file_is_key)
|
83
|
+
key = file_is_key ? file : file + REVISION_TOKEN + @counter
|
84
|
+
cache = Cache.new(contents, user_line)
|
85
|
+
@caches[key] = cache
|
86
|
+
@counter.next!
|
87
|
+
return key, cache
|
88
|
+
end
|
89
|
+
|
90
|
+
def new_cache_synced(*args)
|
91
|
+
@mutex.synchronize do
|
92
|
+
new_cache(*args)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def flush_cache
|
97
|
+
@mutex.synchronize do
|
98
|
+
@caches.delete_if { |key, _| key.index REVISION_TOKEN }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def strip_token(file)
|
103
|
+
file.sub(/#{Regexp.quote REVISION_TOKEN}[a-z]+/, "")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: us-ascii
|
2
|
+
|
3
|
+
module LiveAST
|
4
|
+
module Loader
|
5
|
+
MAGIC_COMMENT = /\A(?:#!.*?\n)?\s*\#.*(?:en)?coding\s*[:=]\s*([^\s;]+)/
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def load(file, wrap)
|
9
|
+
file = find_file(file)
|
10
|
+
|
11
|
+
# guards to protect toplevel locals
|
12
|
+
header, footer, warnings_ok = header_footer(wrap)
|
13
|
+
|
14
|
+
parser_src = read(file)
|
15
|
+
evaler_src = header << parser_src << footer
|
16
|
+
|
17
|
+
run = lambda do
|
18
|
+
Evaler.eval(parser_src, evaler_src, TOPLEVEL_BINDING, file, 1)
|
19
|
+
end
|
20
|
+
warnings_ok ? run.call : suppress_warnings(&run)
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def read(file)
|
25
|
+
contents = File.read(file, :encoding => "BINARY")
|
26
|
+
encoding = contents[MAGIC_COMMENT, 1] || "US-ASCII"
|
27
|
+
contents.force_encoding(encoding)
|
28
|
+
end
|
29
|
+
|
30
|
+
def header_footer(wrap)
|
31
|
+
if wrap
|
32
|
+
return "class << Object.new;", ";end", true
|
33
|
+
else
|
34
|
+
locals = NATIVE_EVAL.call("local_variables", TOPLEVEL_BINDING)
|
35
|
+
|
36
|
+
params = locals.empty? ? "" : ("|;" + locals.join(",") + "|")
|
37
|
+
|
38
|
+
return "lambda do #{params}", ";end.call", locals.empty?
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def suppress_warnings
|
43
|
+
previous = $VERBOSE
|
44
|
+
$VERBOSE = nil
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
$VERBOSE = previous
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_file(file)
|
53
|
+
if file.index Linker::REVISION_TOKEN
|
54
|
+
raise "refusing to load file with revision token: `#{file}'"
|
55
|
+
end
|
56
|
+
search_paths(file) or raise LoadError, "no such file to load -- #{file}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def search_paths(file)
|
60
|
+
return file if File.file? file
|
61
|
+
$LOAD_PATH.each do |path|
|
62
|
+
target = path + "/" + file
|
63
|
+
return target if File.file? target
|
64
|
+
end
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ruby_parser'
|
2
|
+
require 'sexp_processor'
|
3
|
+
|
4
|
+
module LiveAST
|
5
|
+
class Parser < SexpProcessor
|
6
|
+
def parse(source)
|
7
|
+
@defs = {}
|
8
|
+
process RubyParser.new.parse(source)
|
9
|
+
@defs
|
10
|
+
end
|
11
|
+
|
12
|
+
def process_defn(sexp)
|
13
|
+
result = Sexp.new
|
14
|
+
result << sexp.shift
|
15
|
+
result << sexp.shift
|
16
|
+
result << process(sexp.shift)
|
17
|
+
result << process(sexp.shift)
|
18
|
+
|
19
|
+
store_sexp(result, sexp.line)
|
20
|
+
s()
|
21
|
+
end
|
22
|
+
|
23
|
+
def process_iter(sexp)
|
24
|
+
line = sexp[1].line
|
25
|
+
|
26
|
+
result = Sexp.new
|
27
|
+
result << sexp.shift
|
28
|
+
result << process(sexp.shift)
|
29
|
+
result << process(sexp.shift)
|
30
|
+
result << process(sexp.shift)
|
31
|
+
|
32
|
+
#
|
33
|
+
# ruby_parser bug: a method without args attached to a
|
34
|
+
# multi-line block reports the wrong line. workaround.
|
35
|
+
#
|
36
|
+
if result[1][3].size == 1
|
37
|
+
line = sexp.line
|
38
|
+
end
|
39
|
+
|
40
|
+
store_sexp(result, line)
|
41
|
+
s()
|
42
|
+
end
|
43
|
+
|
44
|
+
def store_sexp(sexp, line)
|
45
|
+
@defs[line] = @defs.has_key?(line) ? :multiple : sexp
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'live_ast/base'
|
2
|
+
|
3
|
+
module Kernel
|
4
|
+
private
|
5
|
+
|
6
|
+
alias_method :live_ast_original_raise, :raise
|
7
|
+
|
8
|
+
def raise(*args)
|
9
|
+
ex = begin
|
10
|
+
live_ast_original_raise(*args)
|
11
|
+
rescue Exception => ex
|
12
|
+
ex
|
13
|
+
end
|
14
|
+
|
15
|
+
ex.backtrace.reject! { |line| line.index __FILE__ }
|
16
|
+
|
17
|
+
LiveAST::Evaler.fix_backtrace ex.backtrace
|
18
|
+
|
19
|
+
live_ast_original_raise ex
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'live_ast/base'
|
2
|
+
|
3
|
+
[Method, UnboundMethod].each do |klass|
|
4
|
+
klass.class_eval do
|
5
|
+
# Extract the AST of this object.
|
6
|
+
def to_ast
|
7
|
+
LiveAST::Linker.find_method_ast(owner, name, *source_location)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Proc
|
13
|
+
# Extract the AST of this object.
|
14
|
+
def to_ast
|
15
|
+
LiveAST::Linker.find_proc_ast(self)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'ruby2ruby'
|
2
|
+
|
3
|
+
require 'live_ast/base'
|
4
|
+
|
5
|
+
[Method, UnboundMethod, Proc].each do |klass|
|
6
|
+
klass.class_eval do
|
7
|
+
# Generate ruby code which reflects the AST of this object.
|
8
|
+
def to_ruby
|
9
|
+
Ruby2Ruby.new.process(LiveAST.ast(self))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/live_ast.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require_relative 'shared/main'
|
2
|
+
|
3
|
+
# test for raise redefinition side-effects: unsort this TestCase from
|
4
|
+
# other TestCases.
|
5
|
+
|
6
|
+
define_unsorted_test_case "BacktraceTest", RegularTest do
|
7
|
+
def test_raise_in_eval
|
8
|
+
3.times do
|
9
|
+
orig = exception_backtrace do
|
10
|
+
eval %{
|
11
|
+
|
12
|
+
raise
|
13
|
+
|
14
|
+
|
15
|
+
}, binding, "somewhere", 1000
|
16
|
+
end
|
17
|
+
|
18
|
+
live = exception_backtrace do
|
19
|
+
ast_eval %{
|
20
|
+
|
21
|
+
raise
|
22
|
+
|
23
|
+
|
24
|
+
}, binding, "somewhere", 1000
|
25
|
+
end
|
26
|
+
|
27
|
+
assert_equal orig.first, live.first
|
28
|
+
assert_match(/somewhere:1002/, live.first)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_raise_no_overrides
|
33
|
+
3.times do
|
34
|
+
orig = exception_backtrace do
|
35
|
+
eval %{
|
36
|
+
|
37
|
+
|
38
|
+
raise
|
39
|
+
|
40
|
+
}, binding, __FILE__, (__LINE__ + 9)
|
41
|
+
end
|
42
|
+
|
43
|
+
live = exception_backtrace do
|
44
|
+
ast_eval %{
|
45
|
+
|
46
|
+
|
47
|
+
raise
|
48
|
+
|
49
|
+
}, binding
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal orig.first, live.first
|
53
|
+
here = Regexp.quote __FILE__
|
54
|
+
assert_match(/#{here}/, live.first)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_raise_using_overrides
|
59
|
+
3.times do
|
60
|
+
orig = exception_backtrace do
|
61
|
+
eval %{
|
62
|
+
|
63
|
+
|
64
|
+
raise
|
65
|
+
|
66
|
+
}, binding, __FILE__, (__LINE__ + 9)
|
67
|
+
end
|
68
|
+
|
69
|
+
live = exception_backtrace do
|
70
|
+
ast_eval %{
|
71
|
+
|
72
|
+
|
73
|
+
raise
|
74
|
+
|
75
|
+
}, binding, __FILE__, __LINE__
|
76
|
+
end
|
77
|
+
|
78
|
+
assert_equal orig.first, live.first
|
79
|
+
here = Regexp.quote __FILE__
|
80
|
+
assert_match(/#{here}/, live.first)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_raise_using_only_file_override
|
85
|
+
3.times do
|
86
|
+
orig = exception_backtrace do
|
87
|
+
eval %{
|
88
|
+
|
89
|
+
|
90
|
+
raise
|
91
|
+
|
92
|
+
}, binding, __FILE__
|
93
|
+
end
|
94
|
+
|
95
|
+
live = exception_backtrace do
|
96
|
+
ast_eval %{
|
97
|
+
|
98
|
+
|
99
|
+
raise
|
100
|
+
|
101
|
+
}, binding, __FILE__
|
102
|
+
end
|
103
|
+
|
104
|
+
assert_equal orig.first, live.first
|
105
|
+
here = Regexp.quote __FILE__
|
106
|
+
assert_match(/#{here}/, live.first)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_raise_after_eval
|
111
|
+
raise_after_eval("raise", false)
|
112
|
+
raise_after_eval("1/0", false)
|
113
|
+
|
114
|
+
require 'live_ast/replace_raise'
|
115
|
+
|
116
|
+
raise_after_eval("raise", true)
|
117
|
+
raise_after_eval("1/0", false)
|
118
|
+
end
|
119
|
+
|
120
|
+
def raise_after_eval(code, will_succeed)
|
121
|
+
3.times do
|
122
|
+
orig = eval %{
|
123
|
+
|
124
|
+
lambda { #{code} }
|
125
|
+
|
126
|
+
|
127
|
+
}, binding, "somewhere", 1000
|
128
|
+
|
129
|
+
live = ast_eval %{
|
130
|
+
|
131
|
+
lambda { #{code} }
|
132
|
+
|
133
|
+
|
134
|
+
}, binding, "somewhere", 1000
|
135
|
+
|
136
|
+
orig_top = exception_backtrace { orig.call }.first
|
137
|
+
live_top = exception_backtrace { live.call }.first
|
138
|
+
|
139
|
+
assert_equal orig_top, LiveAST::Linker.strip_token(live_top)
|
140
|
+
|
141
|
+
if will_succeed
|
142
|
+
assert_equal orig_top, live_top
|
143
|
+
here = Regexp.quote __FILE__
|
144
|
+
assert_match(/somewhere:1002/, live_top)
|
145
|
+
else
|
146
|
+
assert_not_equal orig_top, live_top
|
147
|
+
assert_match(/somewhere.*?:1002/, live_top)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_tokens_stripped
|
153
|
+
exception_backtrace do
|
154
|
+
ast_eval %{ ast_eval %{ ast_eval %{raise}, binding }, binding }, binding
|
155
|
+
end.each do |line|
|
156
|
+
assert_nil line.index(LiveAST::Linker::REVISION_TOKEN)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|