/
pattern.rb
117 lines (101 loc) · 3.47 KB
/
pattern.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# Matches trees against expressions. Trees are formed by arrays and hashes
# for expressing membership and sequence. The leafs of the tree are other
# classes.
#
# A tree issued by the parslet library might look like this:
#
# {
# :function_call => {
# :name => 'foobar',
# :args => [1, 2, 3]
# }
# }
#
# A pattern that would match against this tree would be:
#
# { :function_call => { :name => simple(:name), :args => sequence(:args) }}
#
# Note that Parslet::Pattern only matches at a given subtree; it wont try
# to match recursively. To do that, please use Parslet::Transform.
#
class Parslet::Pattern
autoload :Context, 'parslet/pattern/context'
def initialize(pattern)
@pattern = pattern
end
# Decides if the given subtree matches this pattern. Returns the bindings
# made on a successful match or nil if the match fails.
#
def match(subtree)
bindings = {}
return bindings if element_match(subtree, @pattern, bindings)
end
# Executes the block on the bindings obtained by #match, if such a match
# can be made. Contains the logic that will switch to instance variables
# depending on the arity of the block.
#
#---
# TODO This method should be in Transform.
#
def call_on_match(bindings, block)
if block
if block.arity == 1
return block.call(bindings)
else
context = Context.new(bindings)
return context.instance_eval(&block)
end
end
end
# Returns true if the tree element given by +tree+ matches the expression
# given by +exp+. This match must respect bindings already made in
# +bindings+. Note that bindings is carried along and modified.
#
def element_match(tree, exp, bindings)
# p [:elm, tree, exp]
case [tree, exp].map { |e| e.class }
when [Hash,Hash]
return element_match_hash(tree, exp, bindings)
when [Array,Array]
return element_match_ary_single(tree, exp, bindings)
else
# If elements match exactly, then that is good enough in all cases
return true if tree == exp
# If exp is a bind variable: Check if the binding matches
if exp.respond_to?(:can_bind?) && exp.can_bind?(tree)
return element_match_binding(tree, exp, bindings)
end
# Otherwise: No match (we don't know anything about the element
# combination)
return false
end
end
def element_match_binding(tree, exp, bindings) # :nodoc:
var_name = exp.variable_name
# TODO test for the hidden :_ feature.
if var_name && bound_value = bindings[var_name]
return bound_value == tree
end
# New binding:
bindings.store var_name, tree
return true
end
def element_match_ary_single(sequence, exp, bindings) # :nodoc:
return false if sequence.size != exp.size
return sequence.zip(exp).all? { |elt, subexp|
element_match(elt, subexp, bindings) }
end
def element_match_hash(tree, exp, bindings)
# Early failure when one hash is bigger than the other
return false unless exp.size == tree.size
# We iterate over expected pattern, since we demand that the keys that
# are there should be in tree as well.
exp.each do |expected_key, expected_value|
return false unless tree.has_key? expected_key
# Recurse into the value and stop early on failure
value = tree[expected_key]
return false unless element_match(value, expected_value, bindings)
end
return true
end
end