furnace 0.3.0.beta1 → 0.3.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/furnace.rb CHANGED
@@ -1,13 +1,11 @@
1
1
  module Furnace
2
- end
2
+ require "furnace/version"
3
3
 
4
- require "furnace/version"
4
+ require "furnace/ast"
5
+ require "furnace/cfg"
5
6
 
6
- require "furnace/ast"
7
- require "furnace/cfg"
8
- require "furnace/code"
7
+ require "furnace/transform/pipeline"
8
+ require "furnace/transform/iterative_process"
9
9
 
10
- require "furnace/transform/pipeline"
11
- require "furnace/transform/iterative_process"
12
-
13
- require "furnace/graphviz"
10
+ require "furnace/graphviz"
11
+ end
data/test/ast_test.rb ADDED
@@ -0,0 +1,166 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe AST::Node do
4
+ class MetaNode < AST::Node
5
+ attr_reader :meta
6
+ end
7
+
8
+ before do
9
+ @node = AST::Node.new(:node, [ 0, 1 ])
10
+ @metanode = MetaNode.new(:node, [ 0, 1 ], meta: 'value')
11
+ end
12
+
13
+ it 'should have accessors for type and children' do
14
+ @node.type.should.equal :node
15
+ @node.children.should.equal [0, 1]
16
+ end
17
+
18
+ it 'should set metadata' do
19
+ @metanode.meta.should.equal 'value'
20
+ end
21
+
22
+ it 'should be frozen' do
23
+ @node.frozen?.should.be.true
24
+ @node.children.frozen?.should.be.true
25
+ end
26
+
27
+ it 'should not allow duping' do
28
+ -> { @node.dup }.should.raise NoMethodError
29
+ end
30
+
31
+ it 'should return an updated node, but only if needed' do
32
+ @node.updated().should.be.identical_to @node
33
+ @node.updated(:node).should.be.identical_to @node
34
+ @node.updated(nil, [0, 1]).should.be.identical_to @node
35
+
36
+ updated = @node.updated(:other_node)
37
+ updated.should.not.be.identical_to @node
38
+ updated.type.should.equal :other_node
39
+ updated.children.should.equal @node.children
40
+
41
+ updated.frozen?.should.be.true
42
+
43
+ updated = @node.updated(nil, [1, 1])
44
+ updated.should.not.be.identical_to @node
45
+ updated.type.should.equal @node.type
46
+ updated.children.should.equal [1, 1]
47
+
48
+ updated = @metanode.updated(nil, nil, meta: 'other_value')
49
+ updated.meta.should.equal 'other_value'
50
+ end
51
+
52
+ it 'should use fancy type in to_s' do
53
+ node = AST::Node.new(:ast_node)
54
+ node.to_s.should.equal '(ast-node ...)'
55
+ end
56
+
57
+ it 'should format to_sexp correctly' do
58
+ AST::Node.new(:a, [ :sym, [ 1, 2 ] ]).to_sexp.should.equal '(a :sym [1, 2])'
59
+ AST::Node.new(:a, [ :sym, @node ]).to_sexp.should.equal "(a :sym\n (node 0 1))"
60
+ AST::Node.new(:a, [ :sym,
61
+ AST::Node.new(:b, [ @node, @node ])
62
+ ]).to_sexp.should.equal "(a :sym\n (b\n (node 0 1)\n (node 0 1)))"
63
+ end
64
+
65
+ it 'should return self in to_ast' do
66
+ @node.to_ast.should.be.identical_to @node
67
+ end
68
+
69
+ it 'should only use type and children in comparisons' do
70
+ @node.should.equal @node
71
+ @node.should.equal @metanode
72
+ @node.should.not.equal :foo
73
+
74
+ mock_node = Object.new.tap do |obj|
75
+ def obj.to_ast
76
+ self
77
+ end
78
+
79
+ def obj.type
80
+ :node
81
+ end
82
+
83
+ def obj.children
84
+ [ 0, 1 ]
85
+ end
86
+ end
87
+ @node.should.equal mock_node
88
+ end
89
+ end
90
+
91
+ describe AST::Processor do
92
+ def have_sexp(text)
93
+ text = text.lines.map { |line| line.sub /^ +\|(.+)/, '\1' }.join.rstrip
94
+ lambda { |ast| ast.to_sexp == text }
95
+ end
96
+
97
+ class MockProcessor
98
+ include AST::Processor
99
+
100
+ attr_reader :counts
101
+
102
+ def initialize
103
+ @counts = Hash.new(0)
104
+ end
105
+
106
+ def on_root(node)
107
+ count_node(node)
108
+ node.updated(nil, process_all(node.children))
109
+ end
110
+ alias on_body on_root
111
+
112
+ def on_def(node)
113
+ count_node(node)
114
+ name, arglist, body = node.children
115
+ node.updated(:def, [ name, process(arglist), process(body) ])
116
+ end
117
+
118
+ def handler_missing(node)
119
+ count_node(node)
120
+ end
121
+
122
+ def count_node(node)
123
+ @counts[node.type] += 1; nil
124
+ end
125
+ end
126
+
127
+ before do
128
+ @ast = AST::Node.new(:root, [
129
+ AST::Node.new(:def, [ :func,
130
+ AST::Node.new(:arglist, [ :foo, :bar ]),
131
+ AST::Node.new(:body, [
132
+ AST::Node.new(:invoke, [ :puts, "Hello world" ])
133
+ ])
134
+ ]),
135
+ AST::Node.new(:invoke, [ :func ])
136
+ ])
137
+
138
+ @processor = MockProcessor.new
139
+ end
140
+
141
+ it 'should visit every node' do
142
+ @processor.process(@ast).should.equal @ast
143
+ @processor.counts.should.equal({
144
+ root: 1,
145
+ def: 1,
146
+ arglist: 1,
147
+ body: 1,
148
+ invoke: 2
149
+ })
150
+ end
151
+
152
+ it 'should be able to replace inner nodes' do
153
+ def @processor.on_arglist(node)
154
+ node.updated(:new_fancy_arglist)
155
+ end
156
+
157
+ @processor.process(@ast).should have_sexp(<<-SEXP)
158
+ |(root
159
+ | (def :func
160
+ | (new-fancy-arglist :foo :bar)
161
+ | (body
162
+ | (invoke :puts "Hello world")))
163
+ | (invoke :func))
164
+ SEXP
165
+ end
166
+ end
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'furnace'
4
+ include Furnace
5
+
6
+ module Bacon
7
+ module ColoredOutput
8
+ def handle_specification(name)
9
+ puts spaces + name
10
+ yield
11
+ puts if Counter[:context_depth] == 1
12
+ end
13
+
14
+ def handle_requirement(description)
15
+ print spaces
16
+
17
+ error = yield
18
+
19
+ print error.empty? ? "\e[32m" : "\e[1;31m"
20
+ print " - #{description}"
21
+ puts error.empty? ? "\e[0m" : " [#{error}]\e[0m"
22
+ end
23
+
24
+ def handle_summary
25
+ print ErrorLog if Backtraces
26
+ puts "%d specifications (%d requirements), %d failures, %d errors" %
27
+ Counter.values_at(:specifications, :requirements, :failed, :errors)
28
+ end
29
+
30
+ def spaces
31
+ " " * (Counter[:context_depth] - 1)
32
+ end
33
+ end
34
+
35
+ extend ColoredOutput
36
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: furnace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0.beta1
4
+ version: 0.3.0.beta2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,40 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-31 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2012-11-08 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bacon
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.1'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.1'
14
46
  description: Furnace is a static code analysis framework for dynamic languages, aimed
15
47
  at efficient type and behavior inference.
16
48
  email:
@@ -20,32 +52,25 @@ extensions: []
20
52
  extra_rdoc_files: []
21
53
  files:
22
54
  - .gitignore
55
+ - .yardopts
23
56
  - Gemfile
24
57
  - LICENSE
25
58
  - Rakefile
26
59
  - furnace.gemspec
27
60
  - lib/furnace.rb
28
61
  - lib/furnace/ast.rb
29
- - lib/furnace/ast/matcher.rb
30
- - lib/furnace/ast/matcher/dsl.rb
31
- - lib/furnace/ast/matcher/special.rb
32
62
  - lib/furnace/ast/node.rb
33
- - lib/furnace/ast/strict_visitor.rb
34
- - lib/furnace/ast/visitor.rb
63
+ - lib/furnace/ast/processor.rb
35
64
  - lib/furnace/cfg.rb
65
+ - lib/furnace/cfg/algorithms.rb
36
66
  - lib/furnace/cfg/graph.rb
37
67
  - lib/furnace/cfg/node.rb
38
- - lib/furnace/code.rb
39
- - lib/furnace/code/newline_token.rb
40
- - lib/furnace/code/nonterminal_token.rb
41
- - lib/furnace/code/separated_token.rb
42
- - lib/furnace/code/surrounded_token.rb
43
- - lib/furnace/code/terminal_token.rb
44
- - lib/furnace/code/token.rb
45
68
  - lib/furnace/graphviz.rb
46
69
  - lib/furnace/transform/iterative_process.rb
47
70
  - lib/furnace/transform/pipeline.rb
48
71
  - lib/furnace/version.rb
72
+ - test/ast_test.rb
73
+ - test/test_helper.rb
49
74
  homepage: http://github.com/whitequark/furnace
50
75
  licenses: []
51
76
  post_install_message:
@@ -58,6 +83,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
58
83
  - - ! '>='
59
84
  - !ruby/object:Gem::Version
60
85
  version: '0'
86
+ segments:
87
+ - 0
88
+ hash: 1541160448532291935
61
89
  required_rubygems_version: !ruby/object:Gem::Requirement
62
90
  none: false
63
91
  requirements:
@@ -1,50 +0,0 @@
1
- module Furnace::AST
2
- class MatcherDSL
3
- SpecialAny = MatcherSpecial.new(:any)
4
- SpecialSkip = MatcherSpecial.new(:skip)
5
- SpecialEach = MatcherSpecial.define(:each)
6
- SpecialEither = MatcherSpecial.define(:either)
7
- SpecialEitherMulti = MatcherSpecial.define(:either_multi)
8
- SpecialMaybe = MatcherSpecial.define(:maybe)
9
-
10
- def any
11
- SpecialAny
12
- end
13
-
14
- def skip
15
- SpecialSkip
16
- end
17
-
18
- def each
19
- SpecialEach
20
- end
21
-
22
- def either
23
- SpecialEither
24
- end
25
-
26
- def either_multi
27
- SpecialEitherMulti
28
- end
29
-
30
- def maybe
31
- SpecialMaybe
32
- end
33
-
34
- def map(name)
35
- ->(hash) { MatcherSpecial.new(:map, [name, hash]) }
36
- end
37
-
38
- def capture(name)
39
- MatcherSpecial.new(:capture, name)
40
- end
41
-
42
- def capture_rest(name)
43
- MatcherSpecial.new(:capture_rest, name)
44
- end
45
-
46
- def backref(name)
47
- MatcherSpecial.new(:backref, name)
48
- end
49
- end
50
- end
@@ -1,20 +0,0 @@
1
- module Furnace::AST
2
- class MatcherSpecial
3
- attr_reader :type, :param
4
-
5
- def initialize(type, param=nil)
6
- @type, @param = type, param
7
- end
8
-
9
- class << self
10
- def define(type)
11
- lambda { |*args| new(type, args) }
12
- end
13
-
14
- def kind(type)
15
- @kind_lambdas ||= {}
16
- @kind_lambdas[type] ||= lambda { |m| m.is_a?(MatcherSpecial) && m.type == type }
17
- end
18
- end
19
- end
20
- end
@@ -1,198 +0,0 @@
1
- module Furnace::AST
2
- class MatcherError < StandardError; end
3
-
4
- class Matcher
5
- def initialize(&block)
6
- @pattern = MatcherDSL.new.instance_exec(&block)
7
- end
8
-
9
- def match(object, captures={})
10
- if genmatch(object.to_astlet, @pattern, captures)
11
- captures
12
- else
13
- nil
14
- end
15
- end
16
-
17
- def find_one(collection, initial_captures={})
18
- collection.find do |elem|
19
- result = match elem, initial_captures.dup
20
-
21
- if block_given? && result
22
- yield elem, result
23
- else
24
- result
25
- end
26
- end
27
- end
28
-
29
- def find_one!(collection, initial_captures={})
30
- found = nil
31
-
32
- collection.each do |elem|
33
- result = match elem, initial_captures.dup
34
-
35
- if result
36
- raise MatcherError, "already matched" if found
37
-
38
- found = elem
39
- yield elem, result if block_given?
40
- end
41
- end
42
-
43
- raise MatcherError, "no match found" unless found
44
-
45
- found
46
- end
47
-
48
- def find_all(collection, initial_captures={})
49
- collection.select do |elem|
50
- result = match elem, initial_captures.dup
51
- yield elem, result if block_given? && result
52
- result
53
- end
54
- end
55
-
56
- protected
57
-
58
- def submatch(array, pattern, captures)
59
- matches = true
60
- nested_captures = captures.dup
61
- index = 0
62
-
63
- pattern.each_with_index do |nested_pattern|
64
- return nil if index > array.length
65
-
66
- case nested_pattern
67
- when Array
68
- matches &&= genmatch(array[index], nested_pattern, nested_captures)
69
- index += 1
70
- when MatcherDSL::SpecialAny
71
- # it matches
72
- index += 1
73
- when MatcherDSL::SpecialSkip
74
- # it matches all remaining elements
75
- index = array.length
76
- when MatcherSpecial.kind(:capture)
77
- # it matches and captures
78
- nested_captures[nested_pattern.param] = array[index]
79
- index += 1
80
- when MatcherSpecial.kind(:capture_rest)
81
- # it matches and captures all remaining
82
- nested_captures[nested_pattern.param] = array[index..-1]
83
- index = array.length
84
- when MatcherSpecial.kind(:backref)
85
- matches &&= (nested_captures[nested_pattern.param] == array[index])
86
- index += 1
87
- when MatcherSpecial.kind(:maybe)
88
- if advance = submatch(array[index..-1], nested_pattern.param, nested_captures)
89
- index += advance
90
- end
91
- when MatcherSpecial.kind(:each)
92
- all_submatches = true
93
-
94
- workset = Set.new array[index..-1]
95
-
96
- nested_pattern.param.each do |subset_pattern|
97
- sub_matches = false
98
-
99
- workset.each do |subset_elem|
100
- sub_matches ||= genmatch(subset_elem, subset_pattern, nested_captures)
101
-
102
- if sub_matches
103
- workset.delete subset_elem
104
- break
105
- end
106
- end
107
-
108
- all_submatches &&= sub_matches
109
- break unless all_submatches
110
- end
111
-
112
- index += 1
113
- matches &&= all_submatches
114
- when MatcherSpecial.kind(:either)
115
- sub_found = false
116
-
117
- nested_pattern.param.each do |subset_pattern|
118
- if genmatch(array[index], subset_pattern, nested_captures)
119
- sub_found = true
120
- index += 1
121
- break
122
- end
123
- end
124
-
125
- matches &&= sub_found
126
- when MatcherSpecial.kind(:either_multi)
127
- sub_found = false
128
-
129
- nested_pattern.param.each do |subset_pattern|
130
- if matched = genmatch(array[index..-1], subset_pattern, nested_captures)
131
- sub_found = true
132
- index += matched
133
- break
134
- end
135
- end
136
-
137
- matches &&= sub_found
138
- when MatcherSpecial.kind(:map)
139
- captures_key, patterns = nested_pattern.param
140
- nested_captures[captures_key] = []
141
-
142
- while index < array.length
143
- sub_found = false
144
-
145
- patterns.each do |subset_key, subset_pattern|
146
- subset_captures = captures.dup
147
-
148
- if matched = submatch(array[index..-1], subset_pattern, subset_captures)
149
- if subset_key
150
- nested_captures[captures_key].push [subset_key, subset_captures]
151
- end
152
-
153
- sub_found = true
154
- index += matched
155
-
156
- break
157
- end
158
- end
159
-
160
- matches &&= sub_found
161
- break unless matches
162
- end
163
- else
164
- matches &&= (nested_pattern === array[index])
165
- index += 1
166
- end
167
-
168
- break unless matches
169
- end
170
-
171
- captures.replace(nested_captures) if matches
172
-
173
- index if matches
174
- end
175
-
176
- def genmatch(astlet, pattern, captures)
177
- if $DEBUG
178
- if astlet.respond_to? :to_sexp
179
- puts "match #{astlet.to_sexp} of #{pattern}"
180
- else
181
- puts "match #{astlet} of #{pattern}"
182
- end
183
- end
184
-
185
- if pattern === astlet
186
- true
187
- elsif pattern.is_a? MatcherSpecial
188
- submatch([astlet], [pattern], captures)
189
- elsif astlet.is_a? Node
190
- submatch([astlet.type].concat(astlet.children), pattern, captures)
191
- elsif astlet.is_a? Array
192
- submatch(astlet, pattern, captures)
193
- else
194
- false
195
- end
196
- end
197
- end
198
- end
@@ -1,50 +0,0 @@
1
- module Furnace::AST
2
- module Visitor
3
- def visit(node)
4
- replacements = {}
5
-
6
- node.children.each_with_index do |child, index|
7
- if child.is_a? Node
8
- visit child
9
-
10
- if child.type == :expand
11
- replacements[index] = child.children
12
- end
13
- end
14
- end
15
-
16
- if replacements.any?
17
- new_children = []
18
-
19
- node.children.each_with_index do |child, index|
20
- if replacements[index]
21
- new_children.concat replacements[index]
22
- else
23
- new_children.push child
24
- end
25
- end
26
-
27
- node.children.replace new_children
28
- end
29
-
30
- node.children.delete_if do |child|
31
- if child.is_a? Node
32
- child.type == :remove
33
- end
34
- end
35
-
36
- # Invoke a specific handler
37
- on_handler = :"on_#{node.type}"
38
- if respond_to? on_handler
39
- send on_handler, node
40
- end
41
-
42
- # Invoke a generic handler
43
- if respond_to? :on_any
44
- send :on_any, node
45
- end
46
-
47
- node
48
- end
49
- end
50
- end
@@ -1,9 +0,0 @@
1
- module Furnace
2
- module Code
3
- class NewlineToken < TerminalToken
4
- def to_text
5
- "\n"
6
- end
7
- end
8
- end
9
- end
@@ -1,16 +0,0 @@
1
- module Furnace
2
- module Code
3
- class NonterminalToken < Token
4
- attr_reader :children
5
-
6
- def initialize(origin, children, options={})
7
- super(origin, options)
8
- @children = children.compact
9
- end
10
-
11
- def to_text
12
- children.map(&:to_text).join
13
- end
14
- end
15
- end
16
- end
@@ -1,17 +0,0 @@
1
- module Furnace
2
- module Code
3
- class SeparatedToken < SurroundedToken
4
- def text_between
5
- ""
6
- end
7
-
8
- def to_text
9
- "#{text_before}#{children.map(&:to_text).join(text_between)}#{text_after}"
10
- end
11
-
12
- def to_structure(options={})
13
- structurize "#{text_before} #{([text_between] * 3).join(" ")} #{text_after}", options
14
- end
15
- end
16
- end
17
- end
@@ -1,21 +0,0 @@
1
- module Furnace
2
- module Code
3
- class SurroundedToken < NonterminalToken
4
- def text_before
5
- ""
6
- end
7
-
8
- def text_after
9
- ""
10
- end
11
-
12
- def to_text
13
- "#{text_before}#{children.map(&:to_text).join}#{text_after}"
14
- end
15
-
16
- def to_structure(options={})
17
- structurize "#{text_before} ... #{text_after}", options
18
- end
19
- end
20
- end
21
- end