furnace 0.0.3 → 0.0.4
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/lib/furnace/ast/matcher.rb +95 -41
- data/lib/furnace/ast/matcher_special.rb +12 -5
- data/lib/furnace/ast/node.rb +10 -0
- data/lib/furnace/version.rb +1 -1
- metadata +4 -4
data/lib/furnace/ast/matcher.rb
CHANGED
@@ -1,37 +1,71 @@
|
|
1
1
|
module Furnace::AST
|
2
|
-
class
|
3
|
-
SpecialAny = MatcherSpecial.new(:any)
|
4
|
-
SpecialSubset = MatcherSpecial.define(:subset)
|
2
|
+
class MatcherError < StandardError; end
|
5
3
|
|
4
|
+
class Matcher
|
6
5
|
def initialize(&block)
|
7
6
|
@pattern = self.class.class_exec(&block)
|
8
7
|
end
|
9
8
|
|
10
|
-
def match(object)
|
11
|
-
genmatch(object.to_astlet, @pattern)
|
9
|
+
def match(object, captures={})
|
10
|
+
if genmatch(object.to_astlet, @pattern, captures)
|
11
|
+
captures
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
12
15
|
end
|
13
16
|
|
14
|
-
def find_one(collection)
|
17
|
+
def find_one(collection, initial_captures={})
|
15
18
|
collection.find do |elem|
|
16
|
-
result = match elem
|
17
|
-
|
18
|
-
result
|
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
|
19
41
|
end
|
42
|
+
|
43
|
+
raise MatcherError, "no match found" unless found
|
44
|
+
|
45
|
+
found
|
20
46
|
end
|
21
47
|
|
22
|
-
def find_all(collection)
|
48
|
+
def find_all(collection, initial_captures={})
|
23
49
|
collection.select do |elem|
|
24
|
-
result = match elem
|
50
|
+
result = match elem, initial_captures.dup
|
25
51
|
yield elem, result if block_given? && result
|
26
52
|
result
|
27
53
|
end
|
28
54
|
end
|
29
55
|
|
56
|
+
SpecialAny = MatcherSpecial.new(:any)
|
57
|
+
SpecialSkip = MatcherSpecial.new(:skip)
|
58
|
+
SpecialSubset = MatcherSpecial.define(:subset)
|
59
|
+
|
30
60
|
class << self
|
31
61
|
def any
|
32
62
|
SpecialAny
|
33
63
|
end
|
34
64
|
|
65
|
+
def skip
|
66
|
+
SpecialSkip
|
67
|
+
end
|
68
|
+
|
35
69
|
def subset
|
36
70
|
SpecialSubset
|
37
71
|
end
|
@@ -39,56 +73,76 @@ module Furnace::AST
|
|
39
73
|
def capture(name)
|
40
74
|
MatcherSpecial.new(:capture, name)
|
41
75
|
end
|
76
|
+
|
77
|
+
def backref(name)
|
78
|
+
MatcherSpecial.new(:backref, name)
|
79
|
+
end
|
42
80
|
end
|
43
81
|
|
44
82
|
protected
|
45
83
|
|
46
|
-
def submatch(array, pattern)
|
84
|
+
def submatch(array, pattern, captures)
|
47
85
|
matches = true
|
86
|
+
nested_captures = captures.dup
|
48
87
|
|
49
|
-
pattern.each_with_index do |
|
50
|
-
if array
|
51
|
-
return false
|
52
|
-
end
|
88
|
+
pattern.each_with_index do |nested_pattern, index|
|
89
|
+
return false if index > array.length
|
53
90
|
|
54
|
-
case
|
91
|
+
case nested_pattern
|
92
|
+
when Array
|
93
|
+
matches &&= genmatch(array[index], nested_pattern, nested_captures)
|
55
94
|
when SpecialAny
|
56
95
|
# it matches
|
57
|
-
when
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
96
|
+
when SpecialSkip
|
97
|
+
# it matches all remaining elements
|
98
|
+
break
|
99
|
+
when MatcherSpecial.kind(:capture)
|
100
|
+
# it matches and captures
|
101
|
+
nested_captures[nested_pattern.param] = array[index]
|
102
|
+
when MatcherSpecial.kind(:backref)
|
103
|
+
matches &&= (nested_captures[nested_pattern.param] == array[index])
|
104
|
+
when MatcherSpecial.kind(:subset)
|
105
|
+
all_submatches = true
|
106
|
+
|
107
|
+
workset = Set.new array[index..-1]
|
108
|
+
|
109
|
+
nested_pattern.param.each do |subset_pattern|
|
110
|
+
sub_matches = false
|
111
|
+
|
112
|
+
workset.each do |subset_elem|
|
113
|
+
sub_matches ||= genmatch(subset_elem, subset_pattern, nested_captures)
|
114
|
+
|
115
|
+
if sub_matches
|
116
|
+
workset.delete subset_elem
|
117
|
+
break
|
66
118
|
end
|
67
|
-
|
68
|
-
all_submatches &&= submatches
|
69
|
-
break unless all_submatches
|
70
119
|
end
|
71
120
|
|
72
|
-
|
121
|
+
all_submatches &&= sub_matches
|
122
|
+
break unless all_submatches
|
73
123
|
end
|
74
|
-
|
75
|
-
matches &&=
|
124
|
+
|
125
|
+
matches &&= all_submatches
|
76
126
|
else
|
77
|
-
matches &&= array[index] ==
|
127
|
+
matches &&= (array[index] == nested_pattern)
|
78
128
|
end
|
79
129
|
|
80
130
|
break unless matches
|
81
131
|
end
|
82
132
|
|
133
|
+
captures.replace(nested_captures) if matches
|
134
|
+
|
83
135
|
matches
|
84
136
|
end
|
85
137
|
|
86
|
-
def genmatch(astlet, pattern)
|
87
|
-
|
88
|
-
|
89
|
-
#
|
90
|
-
|
91
|
-
#
|
138
|
+
def genmatch(astlet, pattern, captures)
|
139
|
+
if $DEBUG
|
140
|
+
if astlet.respond_to? :to_sexp
|
141
|
+
puts "match #{astlet.to_sexp} of #{pattern}"
|
142
|
+
else
|
143
|
+
puts "match #{astlet} of #{pattern}"
|
144
|
+
end
|
145
|
+
end
|
92
146
|
|
93
147
|
if pattern.first.is_a?(Symbol)
|
94
148
|
# Match an astlet
|
@@ -96,7 +150,7 @@ module Furnace::AST
|
|
96
150
|
|
97
151
|
if astlet.is_a? Node
|
98
152
|
if astlet.type == type
|
99
|
-
submatch(astlet.children, rest)
|
153
|
+
submatch(astlet.children, rest, captures)
|
100
154
|
else
|
101
155
|
false
|
102
156
|
end
|
@@ -106,7 +160,7 @@ module Furnace::AST
|
|
106
160
|
else
|
107
161
|
# Match an array
|
108
162
|
if astlet.is_a? Array
|
109
|
-
submatch(astlet, pattern)
|
163
|
+
submatch(astlet, pattern, captures)
|
110
164
|
else
|
111
165
|
false
|
112
166
|
end
|
@@ -1,13 +1,20 @@
|
|
1
1
|
module Furnace::AST
|
2
2
|
class MatcherSpecial
|
3
|
-
attr_reader :type, :
|
3
|
+
attr_reader :type, :param
|
4
4
|
|
5
|
-
def initialize(type,
|
6
|
-
@type, @
|
5
|
+
def initialize(type, param=nil)
|
6
|
+
@type, @param = type, param
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
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
|
11
18
|
end
|
12
19
|
end
|
13
20
|
end
|
data/lib/furnace/ast/node.rb
CHANGED
@@ -42,6 +42,16 @@ module Furnace::AST
|
|
42
42
|
node
|
43
43
|
end
|
44
44
|
|
45
|
+
def ==(other)
|
46
|
+
if other.respond_to? :to_astlet
|
47
|
+
other = other.to_astlet
|
48
|
+
other.type == self.type &&
|
49
|
+
other.children == self.children
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
45
55
|
def index
|
46
56
|
parent.children.find_index(self)
|
47
57
|
end
|
data/lib/furnace/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: furnace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 246344429
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 4
|
10
|
+
version: 0.0.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Peter Zotov
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-01-
|
18
|
+
date: 2012-01-28 00:00:00 Z
|
19
19
|
dependencies: []
|
20
20
|
|
21
21
|
description: Furnace is a static code analysis framework for dynamic languages, aimed at efficient type and behavior inference.
|