furnace 0.3.0.beta2 → 0.3.0.beta3

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/.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: