Skip to content

Commit 66026d6

Browse files
committed
first prototype of default value rules implemented
1 parent a95aea2 commit 66026d6

File tree

3 files changed

+162
-23
lines changed

3 files changed

+162
-23
lines changed

src/matchers.jl

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,51 @@
55
# 2. Dictionary
66
# 3. Callback: takes arguments Dictionary × Number of elements matched
77
#
8+
89
function matcher(val::Any)
9-
iscall(val) && return term_matcher(val)
10+
# if val is a call (like an operation) creates a term matcher or term matcher with defslot
11+
if iscall(val)
12+
# if has two arguments and one of them is a DefSlot, create a term matcher with defslot
13+
if length(arguments(val)) == 2 && any(x -> isa(x, DefSlot), arguments(val))
14+
return term_matcher_defslot(val)
15+
# else return a normal term matcher
16+
else
17+
return term_matcher(val)
18+
end
19+
end
20+
1021
function literal_matcher(next, data, bindings)
22+
# car data is the first element of data
1123
islist(data) && isequal(car(data), val) ? next(bindings, 1) : nothing
1224
end
1325
end
1426

1527
function matcher(slot::Slot)
1628
function slot_matcher(next, data, bindings)
17-
!islist(data) && return
29+
!islist(data) && return nothing
1830
val = get(bindings, slot.name, nothing)
31+
# if slot name already is in bindings, check if it matches
1932
if val !== nothing
2033
if isequal(val, car(data))
2134
return next(bindings, 1)
2235
end
23-
else
24-
if slot.predicate(car(data))
25-
next(assoc(bindings, slot.name, car(data)), 1)
36+
# elseif the first element of data matches the slot predicate, add it to bindings and call next
37+
elseif slot.predicate(car(data))
38+
next(assoc(bindings, slot.name, car(data)), 1)
39+
end
40+
end
41+
end
42+
43+
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)
2650
end
51+
elseif defslot.predicate(car(data))
52+
next(assoc(bindings, defslot.name, car(data)), 1)
2753
end
2854
end
2955
end
@@ -86,10 +112,52 @@ end
86112

87113
function term_matcher(term)
88114
matchers = (matcher(operation(term)), map(matcher, arguments(term))...,)
115+
89116
function term_matcher(success, data, bindings)
117+
!islist(data) && return nothing # if data is not a list, return nothing
118+
!iscall(car(data)) && return nothing # if first element is not a call, return nothing
90119

91-
!islist(data) && return nothing
92-
!iscall(car(data)) && return nothing
120+
function loop(term, bindings′, matchers′) # Get it to compile faster
121+
if !islist(matchers′)
122+
if !islist(term)
123+
return success(bindings′, 1)
124+
end
125+
return nothing
126+
end
127+
car(matchers′)(term, bindings′) do b, n
128+
loop(drop_n(term, n), b, cdr(matchers′))
129+
end
130+
# explenation of above 3 lines:
131+
# car(matchers′)(b,n -> loop(drop_n(term, n), b, cdr(matchers′)), term, bindings′)
132+
# ------- next(b,n) -----------------------------
133+
# car = first element of list, cdr = rest of the list, drop_n = drop first n elements of list
134+
# Calls the first matcher, with the "next" function being loop again but with n terms dropepd from term
135+
# Term is a linked list (a list and a index). drop n advances the index. when the index sorpasses
136+
# the length of the list, is considered empty
137+
end
138+
139+
loop(car(data), bindings, matchers) # Try to eat exactly one term
140+
end
141+
end
142+
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
160+
end
93161

94162
function loop(term, bindings′, matchers′) # Get it to compile faster
95163
if !islist(matchers′)

src/rule.jl

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,71 @@ Base.isequal(s1::Slot, s2::Slot) = s1.name == s2.name
1616

1717
Base.show(io::IO, s::Slot) = (print(io, "~"); print(io, s.name))
1818

19+
# for when the slot is a symbol, like `~x`
20+
makeslot(s::Symbol, keys) = (push!(keys, s); Slot(s))
21+
22+
# for when the slot is an expression, like `~x::predicate`
23+
function makeslot(s::Expr, keys)
24+
if !(s.head == :(::))
25+
error("Syntax for specifying a slot is ~x::\$predicate, where predicate is a boolean function")
26+
end
27+
28+
name = s.args[1]
29+
30+
push!(keys, name)
31+
:(Slot($(QuoteNode(name)), $(esc(s.args[2]))))
32+
end
33+
34+
# matches one term with built in default value.
35+
# syntax: ~!x
36+
# Example usage:
37+
# (~!x + ~y) can match (a + b) but also just "a" and x takes default value of zero.
38+
# (~!x)*(~y) can match a*b but also just "a", and x takes default value of one.
39+
# (~x + ~y)^(~!z) can match (a + b)^c but also just "a + b", and z takes default value of one.
40+
# 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.
42+
43+
struct DefSlot{P, D}
44+
name::Symbol
45+
predicate::P
46+
default::D
47+
end
48+
49+
DefSlot(s) = DefSlot(s, alwaystrue, nothing)
50+
Base.isequal(s1::DefSlot, s2::DefSlot) = s1.name == s2.name
51+
Base.show(io::IO, s::DefSlot) = (print(io, "~!"); print(io, s.name))
52+
53+
makeDefSlot(s::Symbol, keys, default) = (push!(keys, s); DefSlot(s, alwaystrue, default))
54+
55+
function makeDefSlot(s::Expr, keys, default)
56+
if !(s.head == :(::))
57+
error("Syntax for specifying a default slot is ~!x::\$predicate, where predicate is a boolean function")
58+
end
59+
60+
name = s.args[1]
61+
62+
push!(keys, name)
63+
:(DefSlot($(QuoteNode(name)), $(esc(s.args[2])), $(esc(default))))
64+
end
65+
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
78+
79+
return nothing # no default value for this call
80+
end
81+
82+
83+
1984
# matches zero or more terms
2085
# syntax: ~~x
2186
struct Segment{F}
@@ -37,37 +102,29 @@ function makesegment(s::Expr, keys)
37102
end
38103

39104
name = s.args[1]
40-
105+
41106
push!(keys, name)
42107
:(Segment($(QuoteNode(name)), $(esc(s.args[2]))))
43108
end
44109

45-
makeslot(s::Symbol, keys) = (push!(keys, s); Slot(s))
46-
47-
function makeslot(s::Expr, keys)
48-
if !(s.head == :(::))
49-
error("Syntax for specifying a slot is ~x::\$predicate, where predicate is a boolean function")
50-
end
51-
52-
name = s.args[1]
53-
54-
push!(keys, name)
55-
:(Slot($(QuoteNode(name)), $(esc(s.args[2]))))
56-
end
57-
58-
function makepattern(expr, keys)
110+
# parent call is needed to know which default value to give if any default slots are present
111+
function makepattern(expr, keys, parentCall=nothing)
59112
if expr isa Expr
60113
if expr.head === :call
61114
if expr.args[1] === :(~)
62115
if expr.args[2] isa Expr && expr.args[2].args[1] == :(~)
63116
# matches ~~x::predicate
64117
makesegment(expr.args[2].args[2], keys)
118+
elseif expr.args[2] isa Expr && expr.args[2].args[1] == :(!)
119+
# matches ~!x::predicate
120+
makeDefSlot(expr.args[2].args[2], keys, defaultValOfCall(parentCall))
65121
else
66122
# matches ~x::predicate
67123
makeslot(expr.args[2], keys)
68124
end
69125
else
70-
:(term($(map(x->makepattern(x, keys), expr.args)...); type=Any))
126+
# make a pattern for every argument of the expr.
127+
:(term($(map(x->makepattern(x, keys, operation(expr)), expr.args)...); type=Any))
71128
end
72129
elseif expr.head === :ref
73130
:(term(getindex, $(map(x->makepattern(x, keys), expr.args)...); type=Any))

test/rewrite.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ end
4747
@eqtest @rule(+(~~x,~y,~~x) => (~~x, ~y, ~~x))(term(+,6,type=Any)) == ([], 6, [])
4848
end
4949

50+
@testset "Slot matcher with default value" begin
51+
r_sum = @rule (~x + ~!y)^2 => ~y
52+
@test r_sum((a + b)^2) === b
53+
@test r_sum(b^2) === 0
54+
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
62+
end
63+
5064
using SymbolicUtils: @capture
5165

5266
@testset "Capture form" begin

0 commit comments

Comments
 (0)