furnace 0.3.0.beta1 → 0.3.0.beta2
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/.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
|