furnace 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,37 +1,71 @@
1
1
  module Furnace::AST
2
- class Matcher
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
- yield elem, result if block_given? && result
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 |subpattern, index|
50
- if array[index].nil?
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 subpattern
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 MatcherSpecial
58
- if subpattern.type == :subset
59
- all_submatches = true
60
-
61
- subpattern.params.each do |pattern_case|
62
- submatches = false
63
- array[index..-1].each do |subset_elem|
64
- submatches ||= genmatch(subset_elem, pattern_case)
65
- break if submatches
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
- matches &&= all_submatches
121
+ all_submatches &&= sub_matches
122
+ break unless all_submatches
73
123
  end
74
- when Array
75
- matches &&= genmatch(array[index], subpattern)
124
+
125
+ matches &&= all_submatches
76
126
  else
77
- matches &&= array[index] == subpattern
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
- # if astlet.respond_to? :to_sexp
88
- # puts "match #{astlet.to_sexp} of #{pattern}"
89
- # else
90
- # puts "match #{astlet} of #{pattern}"
91
- # end
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, :params
3
+ attr_reader :type, :param
4
4
 
5
- def initialize(type, params=nil)
6
- @type, @params = type, params
5
+ def initialize(type, param=nil)
6
+ @type, @param = type, param
7
7
  end
8
8
 
9
- def self.define(type)
10
- lambda { |*args| new(type, args) }
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Furnace
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
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: 148493004
4
+ hash: 246344429
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 3
10
- version: 0.0.3
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-26 00:00:00 Z
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.