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/.gitignore +3 -2
- data/.yardopts +1 -0
- data/Gemfile +1 -0
- data/Rakefile +12 -1
- data/furnace.gemspec +3 -0
- data/lib/furnace/ast/node.rb +102 -17
- data/lib/furnace/ast/{strict_visitor.rb → processor.rb} +4 -4
- data/lib/furnace/ast.rb +17 -8
- data/lib/furnace/cfg/algorithms.rb +193 -0
- data/lib/furnace/cfg/graph.rb +5 -187
- data/lib/furnace/cfg.rb +5 -4
- data/lib/furnace/version.rb +1 -1
- data/lib/furnace.rb +7 -9
- data/test/ast_test.rb +166 -0
- data/test/test_helper.rb +36 -0
- metadata +43 -15
- data/lib/furnace/ast/matcher/dsl.rb +0 -50
- data/lib/furnace/ast/matcher/special.rb +0 -20
- data/lib/furnace/ast/matcher.rb +0 -198
- data/lib/furnace/ast/visitor.rb +0 -50
- data/lib/furnace/code/newline_token.rb +0 -9
- data/lib/furnace/code/nonterminal_token.rb +0 -16
- data/lib/furnace/code/separated_token.rb +0 -17
- data/lib/furnace/code/surrounded_token.rb +0 -21
- data/lib/furnace/code/terminal_token.rb +0 -9
- data/lib/furnace/code/token.rb +0 -73
- data/lib/furnace/code.rb +0 -10
data/lib/furnace.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
module Furnace
|
2
|
-
|
2
|
+
require "furnace/version"
|
3
3
|
|
4
|
-
require "furnace/
|
4
|
+
require "furnace/ast"
|
5
|
+
require "furnace/cfg"
|
5
6
|
|
6
|
-
require "furnace/
|
7
|
-
require "furnace/
|
8
|
-
require "furnace/code"
|
7
|
+
require "furnace/transform/pipeline"
|
8
|
+
require "furnace/transform/iterative_process"
|
9
9
|
|
10
|
-
require "furnace/
|
11
|
-
|
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
|
data/test/test_helper.rb
ADDED
@@ -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.
|
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-
|
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/
|
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
|
data/lib/furnace/ast/matcher.rb
DELETED
@@ -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
|
data/lib/furnace/ast/visitor.rb
DELETED
@@ -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,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
|