furnace 0.3.0.beta2 → 0.3.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
data/.yardopts CHANGED
@@ -1 +1 @@
1
- -m markdown --protected
1
+ -r {Furnace} -m markdown --protected
data/Rakefile CHANGED
@@ -9,4 +9,13 @@ task :test do
9
9
  Dir["test/**/*_test.rb"].each do |file|
10
10
  load file
11
11
  end
12
+ end
13
+
14
+ PAGES_REPO = 'git@github.com:whitequark/furnace'
15
+
16
+ task :pages do
17
+ system "git clone #{PAGES_REPO} gh-temp/ -b gh-pages; rm gh-temp/* -rf" or abort
18
+ system "yardoc -o gh-temp/; cd gh-temp/; git add -A; git commit -m 'Updated pages.'" or abort
19
+ system "cd gh-temp/; git push -f origin gh-pages" or abort
20
+ FileUtils.rm_rf 'gh-temp'
12
21
  end
@@ -19,4 +19,6 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_development_dependency 'rake'
21
21
  s.add_development_dependency 'bacon', '~> 1.1'
22
+ s.add_development_dependency 'yard'
23
+ s.add_development_dependency 'redcarpet'
22
24
  end
@@ -1,3 +1,15 @@
1
+ # Furnace is a set of tools for writing compilers, translators or
2
+ # static analyzers--any programs which read, manipulate or transform
3
+ # other programs.
4
+ #
5
+ # Currently it provides three independent modules, grouped by the main
6
+ # data structure being used:
7
+ #
8
+ # * Abstract syntax trees: {AST}
9
+ # * Control flow graphs: {CFG}
10
+ # * Transformations: {Transform}
11
+ #
12
+ # Additionally, a simple {Graphviz} adapter is provided.
1
13
  module Furnace
2
14
  require "furnace/version"
3
15
 
@@ -8,11 +8,12 @@ module Furnace
8
8
  # garbage collector, but completely eliminates all concurrency
9
9
  # and aliasing problems.
10
10
  #
11
- # See also {Node} and {Processor} for additional
11
+ # See also {Node}, {Processor} and {Sexp} for additional
12
12
  # recommendations and design patterns.
13
13
  module AST
14
14
  end
15
15
 
16
16
  require_relative "ast/node"
17
17
  require_relative "ast/processor"
18
+ require_relative "ast/sexp"
18
19
  end
@@ -67,10 +67,14 @@ module Furnace::AST
67
67
  # attribute readers for such variables. The values passed in the hash
68
68
  # are not frozen or whitelisted; such behavior can also be implemented\
69
69
  # by subclassing Node and overriding this method.
70
+ #
71
+ # @return [nil]
70
72
  def assign_properties(properties)
71
73
  properties.each do |name, value|
72
74
  instance_variable_set :"@#{name}", value
73
75
  end
76
+
77
+ nil
74
78
  end
75
79
  protected :assign_properties
76
80
 
@@ -84,6 +88,11 @@ module Furnace::AST
84
88
  # yield `(foo)`.
85
89
  #
86
90
  # If the resulting node would be identical to `self`, does nothing.
91
+ #
92
+ # @param [Symbol, nil] type
93
+ # @param [Array, nil] children
94
+ # @param [Hash, nil] properties
95
+ # @return [AST::Node]
87
96
  def updated(type=nil, children=nil, properties=nil)
88
97
  new_type = type || @type
89
98
  new_children = children || @children
@@ -100,6 +109,8 @@ module Furnace::AST
100
109
 
101
110
  # Compares `self` to `other`, possibly converting with `to_ast`. Only
102
111
  # `type` and `children` are compared; metadata is deliberately ignored.
112
+ #
113
+ # @return [Boolean]
103
114
  def ==(other)
104
115
  if equal?(other)
105
116
  true
@@ -113,11 +124,16 @@ module Furnace::AST
113
124
  end
114
125
 
115
126
  # Converts `self` to a concise s-expression, omitting any children.
127
+ #
128
+ # @return [String]
116
129
  def to_s
117
130
  "(#{fancy_type} ...)"
118
131
  end
119
132
 
120
133
  # Converts `self` to a pretty-printed s-expression.
134
+ #
135
+ # @param [Integer] indent Base indentation level.
136
+ # @return [String]
121
137
  def to_sexp(indent=0)
122
138
  sexp = "#{" " * indent}(#{fancy_type}"
123
139
 
@@ -139,7 +155,7 @@ module Furnace::AST
139
155
  end
140
156
  alias :inspect :to_sexp
141
157
 
142
- # Returns `self`.
158
+ # @return [AST::Node] self
143
159
  def to_ast
144
160
  self
145
161
  end
@@ -149,6 +165,8 @@ module Furnace::AST
149
165
  # Returns `@type` with all underscores replaced by dashes. This allows
150
166
  # to write symbol literals without quotes in Ruby sources and yet have
151
167
  # nicely looking s-expressions.
168
+ #
169
+ # @return [String]
152
170
  def fancy_type
153
171
  @type.to_s.gsub('_', '-')
154
172
  end
@@ -1,5 +1,234 @@
1
1
  module Furnace::AST
2
- module Processor
2
+ # Processor is a class which helps transforming one AST into another.
3
+ # In a nutshell, the {#process} method accepts a {Node} and dispatches
4
+ # it to a handler corresponding to its type, and returns a (possibly)
5
+ # updated variant of the node.
6
+ #
7
+ # Processor has a set of associated design patterns. They are best
8
+ # explained with a concrete example. Let's define a simple arithmetic
9
+ # language and an AST format for it:
10
+ #
11
+ # Terminals (AST nodes which do not have other AST nodes inside):
12
+ #
13
+ # * `(integer <int-literal>)`,
14
+ #
15
+ # Nonterminals (AST nodes with other nodes as children):
16
+ #
17
+ # * `(add <node> <node>)`,
18
+ # * `(multiply <node> <node>)`,
19
+ # * `(divide <node> <node>)`,
20
+ # * `(negate <node>)`,
21
+ # * `(store <node> <string-literal>)`: stores value of `<node>` into a variable named `<string-literal>`,
22
+ # * `(load <string-literal>)`: loads value of a variable named `<string-literal>`,
23
+ # * `(each <node> ...): computes each of the `<node>`s and prints the result.
24
+ #
25
+ # Furnace AST nodes all have the same Ruby class, and therefore they don't
26
+ # know how to traverse themselves. (A solution which dynamically checks the
27
+ # type of children is possible, but is slow and error-prone.) So, a subclass
28
+ # of Processor which knows how to traverse the entire tree should be defined.
29
+ # Such subclass has a handler for each nonterminal node which recursively
30
+ # processes children nodes:
31
+ #
32
+ # require 'furnace'
33
+ # include Furnace
34
+ #
35
+ # class ArithmeticsProcessor < AST::Processor
36
+ # # This method traverses any binary operators such as (add) or (multiply).
37
+ # def process_binary_op(node)
38
+ # # Children aren't decomposed automatically; it is suggested to use Ruby
39
+ # # multiple assignment expansion, as it is very convenient here.
40
+ # left_expr, right_expr = node.children
41
+ #
42
+ # # AST::Node#updated won't change node type if nil is passed as a first
43
+ # # argument, which allows to reuse the same handler for multiple node types
44
+ # # using `alias' (below).
45
+ # node.updated(nil, [
46
+ # process(left_expr),
47
+ # process(right_expr)
48
+ # ])
49
+ # end
50
+ # alias on_add process_binary_op
51
+ # alias on_multiply process_binary_op
52
+ # alias on_divide process_binary_op
53
+ #
54
+ # def on_negate(node)
55
+ # # It is also possible to use #process_all for more compact code
56
+ # # if every child is a Node.
57
+ # node.updated(nil, process_all(node.children))
58
+ # end
59
+ #
60
+ # def on_store(node)
61
+ # expr, variable_name = node.children
62
+ #
63
+ # # Note that variable_name is not a Node and thus isn't passed to #process.
64
+ # node.updated(nil, [
65
+ # process(expr),
66
+ # variable_name
67
+ # ])
68
+ # end
69
+ #
70
+ # # (load) is effectively a terminal node, and so it does not need
71
+ # # an explicit handler, as the following is the default behavior.
72
+ # def on_load(node)
73
+ # nil
74
+ # end
75
+ #
76
+ # def on_each(node)
77
+ # node.updated(nil, process_all(node.children))
78
+ # end
79
+ # end
80
+ #
81
+ # Let's test our ArithmeticsProcessor:
82
+ #
83
+ # include AST::Sexp
84
+ # expr = s(:add, s(:integer, 2), s(:integer, 2))
85
+ #
86
+ # p ArithmeticsProcessor.new.process(expr) == expr # => true
87
+ #
88
+ # As expected, it does not change anything at all. This isn't actually
89
+ # very useful, so let's now define a Calculator, which will compute the
90
+ # expression values:
91
+ #
92
+ # # This Processor folds nonterminal nodes and returns an (integer)
93
+ # # terminal node.
94
+ # class ArithmeticsCalculator < ArithmeticsProcessor
95
+ # def compute_op(node)
96
+ # # First, node children are processed and then unpacked to local
97
+ # # variables.
98
+ # nodes = process_all(node.children)
99
+ #
100
+ # if nodes.all? { |node| node.type == :integer }
101
+ # # If each of those nodes represents a literal, we can fold this
102
+ # # node!
103
+ # values = nodes.map { |node| node.children.first }
104
+ # AST::Node.new(:integer, [
105
+ # yield(values)
106
+ # ])
107
+ # else
108
+ # # Otherwise, we can just leave the current node in the tree and
109
+ # # only update it with processed children nodes, which can be
110
+ # # partially folded.
111
+ # node.updated(nil, nodes)
112
+ # end
113
+ # end
114
+ #
115
+ # def on_add(node)
116
+ # compute_op(node) { |left, right| left + right }
117
+ # end
118
+ #
119
+ # def on_multiply(node)
120
+ # compute_op(node) { |left, right| left * right }
121
+ # end
122
+ # end
123
+ #
124
+ # Let's check:
125
+ #
126
+ # p ArithmeticsCalculator.new.process(expr) # => (integer 4)
127
+ #
128
+ # Excellent, the calculator works! Now, a careful reader could notice that
129
+ # the ArithmeticsCalculator does not know how to divide numbers. What if we
130
+ # pass an expression with division to it?
131
+ #
132
+ # expr_with_division = \
133
+ # s(:add,
134
+ # s(:integer, 1),
135
+ # s(:divide,
136
+ # s(:add, s(:integer, 8), s(:integer, 4)),
137
+ # s(:integer, 3))) # 1 + (8 + 4) / 3
138
+ #
139
+ # folded_expr_with_division = ArithmeticsCalculator.new.process(expr_with_division)
140
+ # p folded_expr_with_division
141
+ # # => (add
142
+ # # (integer 1)
143
+ # # (divide
144
+ # # (integer 12)
145
+ # # (integer 3)))
146
+ #
147
+ # As you can see, the expression was folded _partially_: the inner `(add)` node which
148
+ # could be computed was folded to `(integer 12)`, the `(divide)` node is left as-is
149
+ # because there is no computing handler for it, and the root `(add)` node was also left
150
+ # as it is because some of its children were not literals.
151
+ #
152
+ # Note that this partial folding is only possible because the _data_ format, i.e.
153
+ # the format in which the computed values of the nodes are represented, is the same as
154
+ # the AST itself.
155
+ #
156
+ # Let's extend our ArithmeticsCalculator class further.
157
+ #
158
+ # class ArithmeticsCalculator
159
+ # def on_divide(node)
160
+ # compute_op(node) { |left, right| left / right }
161
+ # end
162
+ #
163
+ # def on_negate(node)
164
+ # # Note how #compute_op works regardless of the operator arity.
165
+ # compute_op(node) { |value| -value }
166
+ # end
167
+ # end
168
+ #
169
+ # Now, let's apply our renewed ArithmeticsCalculator to a partial result of previous
170
+ # evaluation:
171
+ #
172
+ # p ArithmeticsCalculator.new.process(expr_with_division) # => (integer 5)
173
+ #
174
+ # Five! Excellent. This is also pretty much how CRuby 1.8 executed its programs.
175
+ #
176
+ # Now, let's do some automated bug searching. Division by zero is an error, right?
177
+ # So if we could detect that someone has divided by zero before the program is even
178
+ # run, that could save some debugging time.
179
+ #
180
+ # class DivisionByZeroVerifier < ArithmeticsProcessor
181
+ # class VerificationFailure < Exception; end
182
+ #
183
+ # def on_divide(node)
184
+ # # You need to process the children to handle nested divisions
185
+ # # such as:
186
+ # # (divide
187
+ # # (integer 1)
188
+ # # (divide (integer 1) (integer 0))
189
+ # left, right = process_all(node.children)
190
+ #
191
+ # if right.type == :integer &&
192
+ # right.children.first == 0
193
+ # raise VerificationFailure, "Ouch! This code divides by zero."
194
+ # end
195
+ # end
196
+ #
197
+ # def divides_by_zero?(ast)
198
+ # process(ast)
199
+ # false
200
+ # rescue VerificationFailure
201
+ # true
202
+ # end
203
+ # end
204
+ #
205
+ # nice_expr = \
206
+ # s(:divide,
207
+ # s(:add, s(:integer, 10), s(:integer, 2)),
208
+ # s(:integer, 4))
209
+ #
210
+ # p DivisionByZeroVerifier.new.divides_by_zero?(nice_expr)
211
+ # # => false. Good.
212
+ #
213
+ # bad_expr = \
214
+ # s(:add, s(:integer, 10),
215
+ # s(:divide, s(:integer, 1), s(:integer, 0)))
216
+ #
217
+ # p DivisionByZeroVerifier.new.divides_by_zero?(bad_expr)
218
+ # # => true. WHOOPS. DO NOT RUN THIS.
219
+ #
220
+ # Of course, this won't detect more complex cases... unless you use some partial
221
+ # evaluation before! The possibilites are endless. Have fun.
222
+ class Processor
223
+ # Dispatches `node`. If a node has type `:foo`, then a handler named
224
+ # `on_foo` is invoked with one argument, the `node`; if there isn't
225
+ # such a handler, {#handler_missing} is invoked with the same argument.
226
+ #
227
+ # If the handler returns `nil`, `node` is returned; otherwise, the return
228
+ # value of the handler is passed along.
229
+ #
230
+ # @param [AST::Node, nil] node
231
+ # @return [AST::Node]
3
232
  def process(node)
4
233
  if node
5
234
  # Invoke a specific handler
@@ -16,12 +245,20 @@ module Furnace::AST
16
245
  node
17
246
  end
18
247
 
248
+ # {#process}es each node from `nodes` and returns an array of results.
249
+ #
250
+ # @param [Array<AST::Node>] nodes
251
+ # @return [Array<AST::Node>]
19
252
  def process_all(nodes)
20
253
  nodes.map do |node|
21
254
  process node
22
255
  end
23
256
  end
24
257
 
258
+ # Default handler. Does nothing.
259
+ #
260
+ # @param [AST::Node] node
261
+ # @return [AST::Node, nil]
25
262
  def handler_missing(node)
26
263
  end
27
264
  end
@@ -0,0 +1,30 @@
1
+ module Furnace::AST
2
+ # This simple module is very useful in the cases where one needs
3
+ # to define deeply nested ASTs from Ruby code, for example, in
4
+ # tests. It should be used like this:
5
+ #
6
+ # describe YourLanguage::AST do
7
+ # include Sexp
8
+ #
9
+ # it "should correctly parse expressions" do
10
+ # YourLanguage.parse("1 + 2 * 3").should ==
11
+ # s(:add,
12
+ # s(:integer, 1),
13
+ # s(:multiply,
14
+ # s(:integer, 2),
15
+ # s(:integer, 3)))
16
+ # end
17
+ # end
18
+ #
19
+ # This way the amount of boilerplate code is greatly reduced.
20
+ module Sexp
21
+ # Creates a {Node} with type `type` and children `children`.
22
+ # Note that the resulting node is of the type AST::Node and not a
23
+ # subclass.
24
+ # This would not pose a problem with comparisons, as {Node#==}
25
+ # ignores metadata.
26
+ def s(type, *children)
27
+ Node.new(type, children)
28
+ end
29
+ end
30
+ end
@@ -1,3 +1,3 @@
1
1
  module Furnace
2
- VERSION = "0.3.0.beta2"
2
+ VERSION = "0.3.0.beta3"
3
3
  end
@@ -94,9 +94,7 @@ describe AST::Processor do
94
94
  lambda { |ast| ast.to_sexp == text }
95
95
  end
96
96
 
97
- class MockProcessor
98
- include AST::Processor
99
-
97
+ class MockProcessor < AST::Processor
100
98
  attr_reader :counts
101
99
 
102
100
  def initialize
@@ -163,4 +161,20 @@ describe AST::Processor do
163
161
  | (invoke :func))
164
162
  SEXP
165
163
  end
164
+
165
+ extend Furnace::AST::Sexp
166
+
167
+ it 'should build sexps' do
168
+ s(:add,
169
+ s(:integer, 1),
170
+ s(:multiply,
171
+ s(:integer, 2),
172
+ s(:integer, 3))).should have_sexp(<<-SEXP)
173
+ |(add
174
+ | (integer 1)
175
+ | (multiply
176
+ | (integer 2)
177
+ | (integer 3)))
178
+ SEXP
179
+ end
166
180
  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.beta2
4
+ version: 0.3.0.beta3
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-08 00:00:00.000000000 Z
12
+ date: 2012-11-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -43,6 +43,38 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: '1.1'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: redcarpet
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
46
78
  description: Furnace is a static code analysis framework for dynamic languages, aimed
47
79
  at efficient type and behavior inference.
48
80
  email:
@@ -61,6 +93,7 @@ files:
61
93
  - lib/furnace/ast.rb
62
94
  - lib/furnace/ast/node.rb
63
95
  - lib/furnace/ast/processor.rb
96
+ - lib/furnace/ast/sexp.rb
64
97
  - lib/furnace/cfg.rb
65
98
  - lib/furnace/cfg/algorithms.rb
66
99
  - lib/furnace/cfg/graph.rb
@@ -85,7 +118,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
118
  version: '0'
86
119
  segments:
87
120
  - 0
88
- hash: 1541160448532291935
121
+ hash: 4298627248929422388
89
122
  required_rubygems_version: !ruby/object:Gem::Requirement
90
123
  none: false
91
124
  requirements:
@@ -99,3 +132,4 @@ signing_key:
99
132
  specification_version: 3
100
133
  summary: A static code analysis framework
101
134
  test_files: []
135
+ has_rdoc: