/
match.jl
114 lines (94 loc) · 2.6 KB
/
match.jl
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
mutable struct MatchError
pat
ex
end
nomatch(pat, ex) = throw(MatchError(pat, ex))
function store!(env, name, ex)
haskey(env, name) && !(env[name] == ex) && nomatch(name, ex)
assoc!(env, name, ex)
end
isbinding(s) = false
isbinding(s::Symbol) = occursin(r"[^_]_(_str)?$", string(s))
function bname(s::Symbol)
Symbol(Base.match(r"^@?(.*?)_+(_str)?$", string(s)).captures[1])
end
function match_inner(pat, ex, env)
pat == ex || nomatch(pat, ex)
return env
end
match_inner(pat::QuoteNode, ex::QuoteNode, env) =
match(pat.value, ex.value, env)
isslurp(s) = false
isslurp(s::Symbol) = s == :__ || occursin(r"[^_]__$", string(s))
function slurprange(pat)
slurps = length(filter(isslurp, pat))
slurps == 0 && return 0,0
slurps > 1 && error("Pattern may only contain one slurp.")
left, right = 1, 1
while !isslurp(pat[left]) left += 1 end
while !isslurp(pat[end+1-right]) right += 1 end
return left, right
end
inrange(i, range, len) =
range ≠ (0,0) && i ≥ range[1] && i ≤ len+1-range[2]
function match_inner(pat::Expr, ex::Expr, env)
match(pat.head, ex.head, env)
pat, ex = rmlines(pat), rmlines(ex)
sr = slurprange(pat.args)
slurp = Any[]
i = 1
for p in pat.args
i > length(ex.args) &&
(isslurp(p) ? store!(env, bname(p), slurp) : nomatch(pat, ex))
while inrange(i, sr, length(ex.args))
push!(slurp, ex.args[i])
i += 1
end
if isslurp(p)
p ≠ :__ && store!(env, bname(p), slurp)
else
match(p, ex.args[i], env)
i += 1
end
end
i == length(ex.args)+1 || nomatch(pat, ex)
return env
end
blockunify(a, b) =
isexpr(a, :block) && !isexpr(b, :block) ? (a, Expr(:block, b)) :
!isexpr(a, :block) && isexpr(b, :block) ? (Expr(:block, a), b) :
(a, b)
function normalise(ex)
ex = unblock(ex)
isexpr(ex, :inert) && (ex = Expr(:quote, ex.args[1]))
isa(ex, QuoteNode) && (ex = Expr(:quote, ex.value))
isexpr(ex, :kw) && (ex = Expr(:(=), ex.args...))
return ex
end
# No longer removed from macro calls
match(::LineNumberNode, ::LineNumberNode, _) = nothing
function match(pat, ex, env)
pat = normalise(pat)
pat == :_ && return env
isbinding(pat) && return store!(env, bname(pat), ex)
ex = normalise(ex)
pat, ex = blockunify(pat, ex)
isslurp(pat) && return store!(env, bname(pat), Any[ex])
return match_inner(pat, ex, env)
end
match(pat, ex) = match(pat, ex, Dict())
function ismatch(pat, ex)
try
match(pat, ex)
return true
catch e
isa(e, MatchError) ? (return false) : rethrow()
end
end
function trymatch(pat, ex)
try
match(pat, ex)
catch e
isa(e, MatchError) ? (return) : rethrow()
end
end