Skip to content

Commit b9da161

Browse files
committed
added matching capapbility for optional operation with a tree of expressions
1 parent 66026d6 commit b9da161

File tree

3 files changed

+102
-62
lines changed

3 files changed

+102
-62
lines changed

src/matchers.jl

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ function matcher(val::Any)
1111
if iscall(val)
1212
# if has two arguments and one of them is a DefSlot, create a term matcher with defslot
1313
if length(arguments(val)) == 2 && any(x -> isa(x, DefSlot), arguments(val))
14-
return term_matcher_defslot(val)
14+
return defslot_term_matcher_constructor(val)
1515
# else return a normal term matcher
1616
else
17-
return term_matcher(val)
17+
return term_matcher_constructor(val)
1818
end
1919
end
2020

@@ -40,18 +40,11 @@ function matcher(slot::Slot)
4040
end
4141
end
4242

43+
# this is called only when defslot_term_matcher finds the operation and tries
44+
# to match it, so no default value used. So the same function as slot_matcher
45+
# can be used
4346
function matcher(defslot::DefSlot)
44-
function defslot_matcher(next, data, bindings)
45-
!islist(data) && return
46-
val = get(bindings, defslot.name, nothing)
47-
if val !== nothing
48-
if isequal(val, car(data))
49-
return next(bindings, 1)
50-
end
51-
elseif defslot.predicate(car(data))
52-
next(assoc(bindings, defslot.name, car(data)), 1)
53-
end
54-
end
47+
matcher(Slot(defslot.name, defslot.predicate))
5548
end
5649

5750
# returns n == offset, 0 if failed
@@ -110,7 +103,7 @@ function matcher(segment::Segment)
110103
end
111104
end
112105

113-
function term_matcher(term)
106+
function term_matcher_constructor(term)
114107
matchers = (matcher(operation(term)), map(matcher, arguments(term))...,)
115108

116109
function term_matcher(success, data, bindings)
@@ -129,7 +122,7 @@ function term_matcher(term)
129122
end
130123
# explenation of above 3 lines:
131124
# car(matchers′)(b,n -> loop(drop_n(term, n), b, cdr(matchers′)), term, bindings′)
132-
# ------- next(b,n) -----------------------------
125+
# <------ next(b,n) ---------------------------->
133126
# car = first element of list, cdr = rest of the list, drop_n = drop first n elements of list
134127
# Calls the first matcher, with the "next" function being loop again but with n terms dropepd from term
135128
# Term is a linked list (a list and a index). drop n advances the index. when the index sorpasses
@@ -140,25 +133,43 @@ function term_matcher(term)
140133
end
141134
end
142135

143-
144-
# ~x + ~!y
145-
function term_matcher_defslot(term)
146-
matchers = (matcher(operation(term)), map(matcher, arguments(term))...) # create matchers for the operation and arguments of the term
147-
148-
function term_matcher(success, data, bindings)
149-
150-
!islist(data) && return nothing # if data is not a list, return nothing
151-
if !iscall(car(data))
152-
a = arguments(term)
153-
slot = a[findfirst(x -> isa(x, Slot), a)] # find the first slot in the term
154-
defslot = a[findfirst(x -> isa(x, DefSlot), a)] # find the first defslot in the term
155-
156-
bindings = assoc(bindings, slot.name, car(data))
157-
bindings = assoc(bindings, defslot.name, defslot.default)
158-
159-
return success(bindings, 1) # if first element is not a call, return success with bindings and 1
136+
# creates a matcher for a term containing a defslot, such as:
137+
# (~x + ...complicated pattern...) * ~!y
138+
# normal part (can bee a tree) operation defslot part
139+
140+
# defslot_term_matcher works like this:
141+
# checks wether data starts with the default operation.
142+
# if yes (1): continues like term_matcher
143+
# if no checks wether data matches the normal part
144+
# if no returns nothing, rule is not applied
145+
# if yes (2): adds the pair (default value name, default value) to the found bindings and
146+
# calls the success function like term_matcher would do
147+
148+
function defslot_term_matcher_constructor(term)
149+
a = arguments(term) # lenght two bc defslot term matcher is allowed only with +,* and ^, that accept two arguments
150+
matchers = (matcher(operation(term)), map(matcher, a)...) # create matchers for the operation and the two arguments of the term
151+
152+
defslot_index = findfirst(x -> isa(x, DefSlot), a) # find the defslot in the term
153+
defslot = a[defslot_index]
154+
155+
function defslot_term_matcher(success, data, bindings)
156+
# if data is not a list, return nothing
157+
!islist(data) && return nothing
158+
# if data (is not a tree and is just a symbol) or (is a tree not starting with the default operation)
159+
if !iscall(car(data)) || (istree(car(data)) && string(defslot.operation) != string(operation(car(data))))
160+
other_part_matcher = matchers[defslot_index==2 ? 2 : 3] # find the matcher of the normal part
161+
162+
# checks wether it matches the normal part
163+
# <-----------------(2)------------------------------->
164+
bindings = other_part_matcher((b,n) -> assoc(b, defslot.name, defslot.defaultValue), data, bindings)
165+
166+
if bindings === nothing
167+
return nothing
168+
end
169+
return success(bindings, 1)
160170
end
161171

172+
# (1)
162173
function loop(term, bindings′, matchers′) # Get it to compile faster
163174
if !islist(matchers′)
164175
if !islist(term)

src/rule.jl

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
@inline alwaystrue(x) = true
33

4-
# Matcher patterns with Slot and Segment
4+
# Matcher patterns with Slot, DefSlot and Segment
55

66
# matches one term
77
# syntax: ~x
@@ -31,53 +31,61 @@ function makeslot(s::Expr, keys)
3131
:(Slot($(QuoteNode(name)), $(esc(s.args[2]))))
3232
end
3333

34+
35+
36+
37+
38+
3439
# matches one term with built in default value.
3540
# syntax: ~!x
3641
# Example usage:
3742
# (~!x + ~y) can match (a + b) but also just "a" and x takes default value of zero.
3843
# (~!x)*(~y) can match a*b but also just "a", and x takes default value of one.
3944
# (~x + ~y)^(~!z) can match (a + b)^c but also just "a + b", and z takes default value of one.
4045
# only these three operations are supported for default values.
41-
# Note that the default value is not used in the consequent, it is only used to match the pattern.
4246

43-
struct DefSlot{P, D}
47+
struct DefSlot{P, O}
4448
name::Symbol
4549
predicate::P
46-
default::D
50+
operation::O
51+
defaultValue::Real
52+
end
53+
54+
# operation | default
55+
# + | 0
56+
# * | 1
57+
# ^ | 1
58+
function defaultValOfCall(call)
59+
if call == :+
60+
return 0
61+
elseif call == :*
62+
return 1
63+
elseif call == :^
64+
return 1
65+
end
66+
# else no default value for this call
67+
return nothing
4768
end
4869

49-
DefSlot(s) = DefSlot(s, alwaystrue, nothing)
70+
DefSlot(s) = DefSlot(s, alwaystrue, nothing, 0)
5071
Base.isequal(s1::DefSlot, s2::DefSlot) = s1.name == s2.name
5172
Base.show(io::IO, s::DefSlot) = (print(io, "~!"); print(io, s.name))
5273

53-
makeDefSlot(s::Symbol, keys, default) = (push!(keys, s); DefSlot(s, alwaystrue, default))
74+
makeDefSlot(s::Symbol, keys, op) = (push!(keys, s); DefSlot(s, alwaystrue, op, defaultValOfCall(op)))
5475

55-
function makeDefSlot(s::Expr, keys, default)
76+
function makeDefSlot(s::Expr, keys, op)
5677
if !(s.head == :(::))
5778
error("Syntax for specifying a default slot is ~!x::\$predicate, where predicate is a boolean function")
5879
end
5980

6081
name = s.args[1]
6182

6283
push!(keys, name)
63-
:(DefSlot($(QuoteNode(name)), $(esc(s.args[2])), $(esc(default))))
84+
tmp = defaultValOfCall(op)
85+
:(DefSlot($(QuoteNode(name)), $(esc(s.args[2])), $(esc(op))), $(esc(tmp)))
6486
end
6587

66-
# parent | default
67-
# + | 0
68-
# * | 1
69-
# ^ | 1
70-
function defaultValOfCall(call)
71-
if call == :+
72-
return 0
73-
elseif call == :*
74-
return 1
75-
elseif call == :^
76-
return 1
77-
end
7888

79-
return nothing # no default value for this call
80-
end
8189

8290

8391

@@ -117,7 +125,7 @@ function makepattern(expr, keys, parentCall=nothing)
117125
makesegment(expr.args[2].args[2], keys)
118126
elseif expr.args[2] isa Expr && expr.args[2].args[1] == :(!)
119127
# matches ~!x::predicate
120-
makeDefSlot(expr.args[2].args[2], keys, defaultValOfCall(parentCall))
128+
makeDefSlot(expr.args[2].args[2], keys, parentCall)
121129
else
122130
# matches ~x::predicate
123131
makeslot(expr.args[2], keys)

test/rewrite.jl

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,34 @@ end
5252
@test r_sum((a + b)^2) === b
5353
@test r_sum(b^2) === 0
5454

55-
r_mult = @rule (~x * ~!y + ~z) => ~y
56-
@test r_mult(c + a*b) === b
57-
@test r_mult(c + b) === 1
58-
59-
r_pow = @rule (~x + ~y)^(~!m) => ~m
60-
@test r_pow((a + b)^2) === 2
61-
@test r_pow(a + b) === 1
55+
r_mult = @rule ~x * ~!y => ~y
56+
@test r_mult(a * b) === b
57+
@test r_mult(a) === 1
58+
59+
r_mult2 = @rule (~x * ~!y + ~z) => ~y
60+
@test r_mult2(c + a*b) === b
61+
@test r_mult2(c + b) === 1
62+
63+
# here the "normal part" in the defslot_term_matcher is not a symbol but a tree
64+
r_mult3 = @rule (~!x)*(~y + ~z) => ~x
65+
@test r_mult3(a*(c+2)) === a
66+
@test r_mult3(2*(c+2)) === 2
67+
@test r_mult3(c+2) === 1
68+
69+
r_pow = @rule (~x)^(~!m) => ~m
70+
@test r_pow(a^(b+1)) === b+1
71+
@test r_pow(a) === 1
72+
@test r_pow(a+1) === 1
73+
74+
# here the "normal part" in the defslot_term_matcher is not a symbol but a tree
75+
r_pow2 = @rule (~x + ~y)^(~!m) => ~m
76+
@test r_pow2((a+b)^c) === c
77+
@test r_pow2(a+b) === 1
78+
79+
r_mix = @rule (~x + (~y)*(~!c))^(~!m) => ~m + ~c
80+
@test r_mix((a + b*c)^d) === c + d
81+
@test r_mix((a + b*c)) === 1 + c
82+
@test r_mix((a + b)) === 2 #1+1
6283
end
6384

6485
using SymbolicUtils: @capture

0 commit comments

Comments
 (0)