ParseTree 1.7.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +32 -0
- data/Manifest.txt +2 -0
- data/README.txt +28 -24
- data/Rakefile +14 -7
- data/lib/parse_tree.rb +47 -14
- data/lib/sexp.rb +46 -3
- data/lib/sexp_processor.rb +15 -19
- data/lib/unified_ruby.rb +147 -0
- data/test/pt_testcase.rb +158 -53
- data/test/test_parse_tree.rb +2 -1
- data/test/test_sexp.rb +28 -3
- data/test/test_sexp_processor.rb +74 -17
- data/test/test_unified_ruby.rb +229 -0
- metadata +8 -6
data/History.txt
CHANGED
@@ -1,3 +1,35 @@
|
|
1
|
+
=== 2.0.0 / 2007-08-01
|
2
|
+
|
3
|
+
* 2 major enhancements:
|
4
|
+
|
5
|
+
* Rewrite methods completely rewritten. Rewriters:
|
6
|
+
* are no longer recursive.
|
7
|
+
* are no longer required to empty the sexp coming in.
|
8
|
+
* are depth first, so rewriter gets passed everything already normalized.
|
9
|
+
* keep rewriting until type doesn't change.
|
10
|
+
* are magical goodness.
|
11
|
+
* Added UnifiedRuby module to aid others in writing clean SexpProcessors:
|
12
|
+
* bmethod/dmethod/fbody unified with defn
|
13
|
+
* fcall/vcall unified with call
|
14
|
+
* resbody unified with itself (lots of different forms)
|
15
|
+
|
16
|
+
* 5 minor enhancements:
|
17
|
+
|
18
|
+
* Add #modules to Module.
|
19
|
+
* Add Sexp::for shortcut for Sexp.from_array ParseTree.translate(...).
|
20
|
+
* Add parens for :block nodes as appropriate. May be overzealous.
|
21
|
+
* Made add_to_parse_tree global for reuse by other C extensions.
|
22
|
+
* Made test_ruby2ruby MUCH more rigorous with circular testing.
|
23
|
+
|
24
|
+
* 6 bug fixes:
|
25
|
+
|
26
|
+
* Added $TESTING = true to pt_testcase.rb. OOPS!
|
27
|
+
* Fixed some circular bugs, mostly by hacking them out, wrt operator precidence.
|
28
|
+
* Fixed splat arg processing when arg has no name.
|
29
|
+
* Fixed trinary operator.
|
30
|
+
* Fixed BMETHOD with no arguments.
|
31
|
+
* Removed hacky "self." thing for defs at top level PT use.
|
32
|
+
|
1
33
|
=== 1.7.1 / 2007-06-05
|
2
34
|
|
3
35
|
* 3 minor enhancements:
|
data/Manifest.txt
CHANGED
@@ -11,6 +11,7 @@ lib/composite_sexp_processor.rb
|
|
11
11
|
lib/parse_tree.rb
|
12
12
|
lib/sexp.rb
|
13
13
|
lib/sexp_processor.rb
|
14
|
+
lib/unified_ruby.rb
|
14
15
|
lib/unique.rb
|
15
16
|
test/pt_testcase.rb
|
16
17
|
test/something.rb
|
@@ -19,4 +20,5 @@ test/test_composite_sexp_processor.rb
|
|
19
20
|
test/test_parse_tree.rb
|
20
21
|
test/test_sexp.rb
|
21
22
|
test/test_sexp_processor.rb
|
23
|
+
test/test_unified_ruby.rb
|
22
24
|
validate.sh
|
data/README.txt
CHANGED
@@ -3,12 +3,12 @@ ParseTree
|
|
3
3
|
http://www.zenspider.com/ZSS/Products/ParseTree/
|
4
4
|
support@zenspider.com
|
5
5
|
|
6
|
-
|
6
|
+
== DESCRIPTION:
|
7
7
|
|
8
8
|
ParseTree is a C extension (using RubyInline) that extracts the parse
|
9
9
|
tree for an entire class or a specific method and returns it as a
|
10
10
|
s-expression (aka sexp) using ruby's arrays, strings, symbols, and
|
11
|
-
integers.
|
11
|
+
integers.
|
12
12
|
|
13
13
|
As an example:
|
14
14
|
|
@@ -32,23 +32,26 @@ becomes:
|
|
32
32
|
nil],
|
33
33
|
[:return, [:lit, 0]]]]]
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
+ Uses RubyInline, so it just drops in.
|
38
|
-
+ Includes SexpProcessor and CompositeSexpProcessor.
|
39
|
-
+ Allows you to write very clean filters.
|
40
|
-
+ Includes parse_tree_show, which lets you quickly snoop code.
|
41
|
-
+ echo "1+1" | parse_tree_show -f for quick snippet output.
|
42
|
-
+ Includes parse_tree_abc, which lets you get abc metrics on code.
|
43
|
-
+ abc metrics = numbers of assignments, branches, and calls.
|
44
|
-
+ whitespace independent metric for method complexity.
|
45
|
-
+ Includes parse_tree_deps, which shows you basic class level dependencies.
|
46
|
-
+ Only works on methods in classes/modules, not arbitrary code.
|
47
|
-
+ Does not work on the core classes, as they are not ruby (yet).
|
35
|
+
== FEATURES/PROBLEMS:
|
48
36
|
|
49
|
-
|
37
|
+
* Uses RubyInline, so it just drops in.
|
38
|
+
* Includes SexpProcessor and CompositeSexpProcessor.
|
39
|
+
* Allows you to write very clean filters.
|
40
|
+
* Includes UnifiedRuby, allowing you to automatically rewrite ruby quirks.
|
41
|
+
* ParseTree#parse_tree_for_string lets you parse arbitrary strings of ruby.
|
42
|
+
* Includes parse_tree_show, which lets you quickly snoop code.
|
43
|
+
* echo "1+1" | parse_tree_show -f for quick snippet output.
|
44
|
+
* Includes parse_tree_abc, which lets you get abc metrics on code.
|
45
|
+
* abc metrics = numbers of assignments, branches, and calls.
|
46
|
+
* whitespace independent metric for method complexity.
|
47
|
+
* Includes parse_tree_deps, which shows you basic class level dependencies.
|
48
|
+
* Does not work on the core classes, as they are not ruby (yet).
|
50
49
|
|
51
|
-
|
50
|
+
== SYNOPSYS:
|
51
|
+
|
52
|
+
sexp_array = ParseTree.translate(klass)
|
53
|
+
sexp_array = ParseTree.translate(klass, :method)
|
54
|
+
sexp_array = ParseTree.translate("1+1")
|
52
55
|
|
53
56
|
or:
|
54
57
|
|
@@ -75,20 +78,21 @@ or:
|
|
75
78
|
|
76
79
|
% ./parse_tree_abc myfile.rb
|
77
80
|
|
78
|
-
|
81
|
+
== REQUIREMENTS:
|
79
82
|
|
80
|
-
|
83
|
+
* RubyInline 3.6 or better.
|
81
84
|
|
82
|
-
|
85
|
+
== INSTALL:
|
83
86
|
|
84
|
-
|
85
|
-
|
87
|
+
* rake install_gem
|
88
|
+
* sudo rake install
|
89
|
+
* sudo gem install ParseTree
|
86
90
|
|
87
|
-
|
91
|
+
== LICENSE:
|
88
92
|
|
89
93
|
(The MIT License)
|
90
94
|
|
91
|
-
Copyright (c) 2001-
|
95
|
+
Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
|
92
96
|
|
93
97
|
Permission is hereby granted, free of charge, to any person obtaining
|
94
98
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -9,10 +9,11 @@ require './lib/parse_tree.rb'
|
|
9
9
|
Hoe.new("ParseTree", ParseTree::VERSION) do |p|
|
10
10
|
p.rubyforge_name = "parsetree"
|
11
11
|
p.summary = "Extract and enumerate ruby parse trees."
|
12
|
-
p.
|
13
|
-
p.
|
12
|
+
p.summary = p.paragraphs_of("README.txt", 2).join("\n\n")
|
13
|
+
p.description = p.paragraphs_of("README.txt", 2..6, 8).join("\n\n")
|
14
|
+
p.changes = p.paragraphs_of("History.txt", 1..6).join("\n\n")
|
14
15
|
p.clean_globs << File.expand_path("~/.ruby_inline")
|
15
|
-
p.extra_deps << ['RubyInline', '>= 3.
|
16
|
+
p.extra_deps << ['RubyInline', '>= 3.6.0']
|
16
17
|
p.spec_extras[:require_paths] = proc { |paths| paths << 'test' }
|
17
18
|
end
|
18
19
|
|
@@ -34,8 +35,14 @@ end
|
|
34
35
|
|
35
36
|
desc 'Show what tests are not sorted'
|
36
37
|
task :sort do
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
begin
|
39
|
+
sh "pgrep '^ \\\"(\\w+)' test/pt_testcase.rb | cut -f 2 -d\\\" > x"
|
40
|
+
sh "sort x > y"
|
41
|
+
sh "diff x y"
|
42
|
+
|
43
|
+
sh 'for f in lib/*.rb; do echo $f; grep "^ *def " $f | grep -v sort=skip > x; sort x > y; echo $f; echo; diff x y; done'
|
44
|
+
sh 'for f in test/test_*.rb; do echo $f; grep "^ *def.test_" $f > x; sort x > y; echo $f; echo; diff x y; done'
|
45
|
+
ensure
|
46
|
+
sh 'rm -f x y'
|
47
|
+
end
|
41
48
|
end
|
data/lib/parse_tree.rb
CHANGED
@@ -7,6 +7,19 @@ begin require 'rubygems'; rescue LoadError; end
|
|
7
7
|
|
8
8
|
require 'inline'
|
9
9
|
|
10
|
+
class Module
|
11
|
+
def modules
|
12
|
+
ancestors[1..-1]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Class
|
17
|
+
def modules
|
18
|
+
a = self.ancestors
|
19
|
+
a[1..a.index(superclass)-1]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
10
23
|
##
|
11
24
|
# ParseTree is a RubyInline-style extension that accesses and
|
12
25
|
# traverses the internal parse tree created by ruby.
|
@@ -28,7 +41,7 @@ require 'inline'
|
|
28
41
|
|
29
42
|
class ParseTree
|
30
43
|
|
31
|
-
VERSION = '
|
44
|
+
VERSION = '2.0.0'
|
32
45
|
|
33
46
|
##
|
34
47
|
# Front end translation method.
|
@@ -109,7 +122,17 @@ class ParseTree
|
|
109
122
|
# protected methods are included in instance_methods, go figure!
|
110
123
|
|
111
124
|
method_names.sort.each do |m|
|
112
|
-
|
125
|
+
r = parse_tree_for_method(klass, m.to_sym)
|
126
|
+
p m => r if r == [nil]
|
127
|
+
code << r
|
128
|
+
end
|
129
|
+
|
130
|
+
klass.modules.each do |mod| # TODO: add a test for this damnit
|
131
|
+
mod.instance_methods.each do |m|
|
132
|
+
r = parse_tree_for_method(mod, m.to_sym)
|
133
|
+
p m => r if r == [nil]
|
134
|
+
code << r
|
135
|
+
end
|
113
136
|
end
|
114
137
|
|
115
138
|
klass.singleton_methods(false).sort.each do |m|
|
@@ -131,7 +154,6 @@ class ParseTree
|
|
131
154
|
def parse_tree_for_method(klass, method, is_cls_meth=false)
|
132
155
|
$stderr.puts "** parse_tree_for_method(#{klass}, #{method}):" if $DEBUG
|
133
156
|
r = parse_tree_for_meth(klass, method.to_sym, @include_newlines, is_cls_meth)
|
134
|
-
r[1] = :"self.#{r[1]}" if is_cls_meth
|
135
157
|
r
|
136
158
|
end
|
137
159
|
|
@@ -306,11 +328,11 @@ class ParseTree
|
|
306
328
|
##
|
307
329
|
# add_to_parse_tree(ary, node, include_newlines, local_variables)
|
308
330
|
|
309
|
-
builder.
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
331
|
+
builder.prefix %Q@
|
332
|
+
void add_to_parse_tree(VALUE ary,
|
333
|
+
NODE * n,
|
334
|
+
VALUE newlines,
|
335
|
+
ID * locals) {
|
314
336
|
NODE * volatile node = n;
|
315
337
|
NODE * volatile contnode = NULL;
|
316
338
|
VALUE old_ary = Qnil;
|
@@ -544,7 +566,11 @@ again_no_block:
|
|
544
566
|
{
|
545
567
|
struct BLOCK *data;
|
546
568
|
Data_Get_Struct(node->nd_cval, struct BLOCK, data);
|
547
|
-
|
569
|
+
if (data->var == 0 || data->var == (NODE *)1 || data->var == (NODE *)2) {
|
570
|
+
rb_ary_push(current, Qnil);
|
571
|
+
} else {
|
572
|
+
add_to_parse_tree(current, data->var, newlines, locals);
|
573
|
+
}
|
548
574
|
add_to_parse_tree(current, data->body, newlines, locals);
|
549
575
|
break;
|
550
576
|
}
|
@@ -773,7 +799,11 @@ again_no_block:
|
|
773
799
|
|
774
800
|
if (arg_count > 0) {
|
775
801
|
// *arg name
|
776
|
-
VALUE sym =
|
802
|
+
VALUE sym = rb_str_new2("*");
|
803
|
+
if (locals[i + 3]) {
|
804
|
+
rb_str_concat(sym, rb_str_new2(rb_id2name(locals[i + 3])));
|
805
|
+
}
|
806
|
+
sym = rb_str_intern(sym);
|
777
807
|
rb_ary_push(current, sym);
|
778
808
|
} else if (arg_count == 0) {
|
779
809
|
// nothing to do in this case, empty list
|
@@ -878,8 +908,9 @@ again_no_block:
|
|
878
908
|
break;
|
879
909
|
|
880
910
|
case NODE_CFUNC:
|
881
|
-
|
882
|
-
rb_ary_push(current,
|
911
|
+
case NODE_IFUNC:
|
912
|
+
rb_ary_push(current, INT2NUM((long)node->nd_cfnc));
|
913
|
+
rb_ary_push(current, INT2NUM(node->nd_argc));
|
883
914
|
break;
|
884
915
|
|
885
916
|
#{if_version :<, "1.9", "#if 0"}
|
@@ -895,7 +926,6 @@ again_no_block:
|
|
895
926
|
// I think these are all runtime only... not positive but...
|
896
927
|
case NODE_MEMO: // enum.c zip
|
897
928
|
case NODE_CREF:
|
898
|
-
case NODE_IFUNC:
|
899
929
|
// #defines:
|
900
930
|
// case NODE_LMASK:
|
901
931
|
// case NODE_LSHIFT:
|
@@ -942,7 +972,10 @@ static VALUE parse_tree_for_meth(VALUE klass, VALUE method, VALUE newlines, VALU
|
|
942
972
|
}
|
943
973
|
if (st_lookup(RCLASS(klass)->m_tbl, id, &n)) {
|
944
974
|
node = (NODE*)n;
|
945
|
-
rb_ary_push(result, ID2SYM(rb_intern("defn")));
|
975
|
+
rb_ary_push(result, ID2SYM(rb_intern(is_cls_meth ? "defs": "defn")));
|
976
|
+
if (is_cls_meth) {
|
977
|
+
rb_ary_push(result, rb_ary_new3(1, ID2SYM(rb_intern("self"))));
|
978
|
+
}
|
946
979
|
rb_ary_push(result, ID2SYM(id));
|
947
980
|
add_to_parse_tree(result, node->nd_body, newlines, NULL);
|
948
981
|
} else {
|
data/lib/sexp.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
require 'parse_tree'
|
2
|
+
|
3
|
+
$TESTING ||= false # unless defined $TESTING
|
4
|
+
|
1
5
|
##
|
2
6
|
# Sexps are the basic storage mechanism of SexpProcessor. Sexps have
|
3
7
|
# a +type+ (to be renamed +node_type+) which is the first element of
|
4
8
|
# the Sexp. The type is used by SexpProcessor to determine whom to
|
5
9
|
# dispatch the Sexp to for processing.
|
6
10
|
|
7
|
-
$TESTING ||= false # unless defined $TESTING
|
8
|
-
|
9
11
|
class Sexp < Array # ZenTest FULL
|
10
12
|
|
11
13
|
@@array_types = [ :array, :args, ]
|
@@ -17,6 +19,28 @@ class Sexp < Array # ZenTest FULL
|
|
17
19
|
super(args)
|
18
20
|
end
|
19
21
|
|
22
|
+
##
|
23
|
+
# Creates a new Sexp for +klass+ or +method+ in +klass+.
|
24
|
+
#
|
25
|
+
# If +walk_ancestors+ is true and +method+ is provided, walks the ancestors
|
26
|
+
# of +klass+ until a method definition is found.
|
27
|
+
|
28
|
+
def self.for(klass, method = nil, walk_ancestors = false)
|
29
|
+
sexp = if walk_ancestors and method then
|
30
|
+
klass.ancestors.each do |klass|
|
31
|
+
sexp = ParseTree.translate klass, method
|
32
|
+
break sexp unless sexp == [nil]
|
33
|
+
end
|
34
|
+
else
|
35
|
+
ParseTree.translate klass, method
|
36
|
+
end
|
37
|
+
|
38
|
+
Sexp.from_array sexp
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Creates a new Sexp from Array +a+, typically from ParseTree::translate.
|
43
|
+
|
20
44
|
def self.from_array(a)
|
21
45
|
ary = Array === a ? a : [a]
|
22
46
|
|
@@ -44,6 +68,9 @@ class Sexp < Array # ZenTest FULL
|
|
44
68
|
end
|
45
69
|
end
|
46
70
|
|
71
|
+
##
|
72
|
+
# Returns true if this Sexp's pattern matches +sexp+.
|
73
|
+
|
47
74
|
def ===(sexp)
|
48
75
|
return nil unless Sexp === sexp
|
49
76
|
pattern = self # this is just for my brain
|
@@ -57,6 +84,9 @@ class Sexp < Array # ZenTest FULL
|
|
57
84
|
return nil
|
58
85
|
end
|
59
86
|
|
87
|
+
##
|
88
|
+
# Returns true if this Sexp matches +pattern+. (Opposite of #===.)
|
89
|
+
|
60
90
|
def =~(pattern)
|
61
91
|
return pattern === self
|
62
92
|
end
|
@@ -97,6 +127,9 @@ class Sexp < Array # ZenTest FULL
|
|
97
127
|
end
|
98
128
|
end
|
99
129
|
|
130
|
+
##
|
131
|
+
# Replaces all Sexps matching +pattern+ with Sexp +repl+.
|
132
|
+
|
100
133
|
def gsub(pattern, repl)
|
101
134
|
return repl if pattern == self
|
102
135
|
|
@@ -117,6 +150,9 @@ class Sexp < Array # ZenTest FULL
|
|
117
150
|
return "s(#{sexp_str})"
|
118
151
|
end
|
119
152
|
|
153
|
+
##
|
154
|
+
# Returns the node named +node+, deleting it if +delete+ is true.
|
155
|
+
|
120
156
|
def method_missing(meth, delete=false)
|
121
157
|
matches = find_all { | sexp | Sexp === sexp and sexp.first == meth }
|
122
158
|
|
@@ -155,7 +191,7 @@ class Sexp < Array # ZenTest FULL
|
|
155
191
|
end if $DEBUG or $TESTING
|
156
192
|
|
157
193
|
##
|
158
|
-
#
|
194
|
+
# Returns the bare bones structure of the sexp.
|
159
195
|
# s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))
|
160
196
|
|
161
197
|
def structure
|
@@ -171,6 +207,9 @@ class Sexp < Array # ZenTest FULL
|
|
171
207
|
result
|
172
208
|
end
|
173
209
|
|
210
|
+
##
|
211
|
+
# Replaces the Sexp matching +pattern+ with +repl+.
|
212
|
+
|
174
213
|
def sub(pattern, repl)
|
175
214
|
return repl.dup if pattern == self
|
176
215
|
|
@@ -213,6 +252,10 @@ end
|
|
213
252
|
class SexpMatchSpecial < Sexp; end
|
214
253
|
|
215
254
|
class SexpAny < SexpMatchSpecial
|
255
|
+
def ==(o)
|
256
|
+
Sexp === o
|
257
|
+
end
|
258
|
+
|
216
259
|
def ===(o)
|
217
260
|
return Sexp === o
|
218
261
|
end
|
data/lib/sexp_processor.rb
CHANGED
@@ -164,25 +164,16 @@ class SexpProcessor
|
|
164
164
|
end
|
165
165
|
|
166
166
|
def rewrite(exp)
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
if Array === sub_exp then
|
179
|
-
result << rewrite(sub_exp)
|
180
|
-
else
|
181
|
-
result << sub_exp
|
182
|
-
end
|
183
|
-
end
|
184
|
-
result
|
185
|
-
end
|
167
|
+
exp.map! { |sub| Array === sub ? rewrite(sub) : sub }
|
168
|
+
|
169
|
+
type = exp.first
|
170
|
+
begin
|
171
|
+
meth = @rewriters[type]
|
172
|
+
exp = self.send(meth, exp) if meth
|
173
|
+
old_type, type = type, exp.first
|
174
|
+
end until old_type == type
|
175
|
+
|
176
|
+
exp
|
186
177
|
end
|
187
178
|
|
188
179
|
##
|
@@ -221,6 +212,11 @@ class SexpProcessor
|
|
221
212
|
|
222
213
|
exp = self.rewrite(exp) if @process_level == 1
|
223
214
|
|
215
|
+
if @debug.has_key? type then
|
216
|
+
str = exp.inspect
|
217
|
+
puts "// DEBUG (rewritten): #{str}" if str =~ @debug[type]
|
218
|
+
end
|
219
|
+
|
224
220
|
# now do a pass with the real processor (or generic)
|
225
221
|
meth = @processors[type] || @default_method
|
226
222
|
if meth then
|