live_ast 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|