Skip to content

Commit

Permalink
Fix #106 by removing accidental quadratic perf. (#108)
Browse files Browse the repository at this point in the history
* Fix #106 by removing accidental quadratic perf.
* Bump version number.
  • Loading branch information
gafter authored Nov 10, 2024
1 parent 356d365 commit 1785df4
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Match"
uuid = "7eb4fadd-790c-5f42-8a69-bfa0b872bfbf"
version = "2.1.1"
version = "2.1.2"
authors = ["Neal Gafter <[email protected]>", "Kevin Squire <[email protected]>"]

[deps]
Expand Down
49 changes: 27 additions & 22 deletions src/automaton.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,40 +92,45 @@ function Base.:(==)(a::DeduplicatedAutomatonNode, b::DeduplicatedAutomatonNode)
isequal(a.next, b.next)
end

struct DeduplicationMap
# A map to "intern" a dedulplicated node, returning a unique semantically-equivalent instance
intern::Dict{DeduplicatedAutomatonNode, DeduplicatedAutomatonNode}

# A map to turn an AutomatonNode into the equivalent DeduplicatedAutomatonNode.
# This lets us retrieve from the cache the (interned) mapping of successors.
map::Dict{AutomatonNode, DeduplicatedAutomatonNode}
function DeduplicationMap()
new(
Dict{DeduplicatedAutomatonNode, DeduplicatedAutomatonNode}(),
Dict{AutomatonNode, DeduplicatedAutomatonNode}())
end
end

#
# Deduplicate a code point, given the deduplications of the downstream code points.
# Has the side-effect of adding a mapping to the dict.
# Has the side-effect of adding mappings to the DeduplicationMap.
#
function dedup!(
dict::Dict{DeduplicatedAutomatonNode, DeduplicatedAutomatonNode},
node::AutomatonNode,
binder::BinderContext)
next = if node.next isa Tuple{}
node.next
elseif node.next isa Tuple{AutomatonNode}
(dedup!(dict, node.next[1], binder),)
elseif node.next isa Tuple{AutomatonNode, AutomatonNode}
t = dedup!(dict, node.next[1], binder)
f = dedup!(dict, node.next[2], binder)
(t, f)
else
error("Unknown next type: $(node.next)")
dedup::DeduplicationMap,
node::AutomatonNode)::DeduplicatedAutomatonNode
get!(dedup.map, node) do
next = tuple(map(succ -> dedup.map[succ], collect(node.next))...)

key = DeduplicatedAutomatonNode(node.action, next)
result = get!(dedup.intern, key, key)
get!(dedup.map, node, result)
end
key = DeduplicatedAutomatonNode(node.action, next)
result = get!(dict, key, key)
result
end

#
# Deduplicate the decision automaton by collapsing behaviorally identical nodes.
#
function deduplicate_automaton(entry::AutomatonNode, binder::BinderContext)
dedup_map = Dict{DeduplicatedAutomatonNode, DeduplicatedAutomatonNode}()
result = Vector{DeduplicatedAutomatonNode}()
function deduplicate_automaton(entry::AutomatonNode)
dedup_map = DeduplicationMap()
top_down_nodes = reachable_nodes(entry)
for e in Iterators.reverse(top_down_nodes)
_ = dedup!(dedup_map, e, binder)
_ = dedup!(dedup_map, e)
end
new_entry = dedup!(dedup_map, entry, binder)
new_entry = dedup!(dedup_map, entry)
return reachable_nodes(new_entry)
end
4 changes: 2 additions & 2 deletions src/match_cases_opt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ end
#
function build_deduplicated_automaton(location::LineNumberNode, mod::Module, value, body)
entry, predeclared_temps, binder = build_automaton(location::LineNumberNode, mod::Module, value, body)
top_down_nodes = deduplicate_automaton(entry, binder)
top_down_nodes = deduplicate_automaton(entry)
return top_down_nodes, predeclared_temps, binder
end

Expand Down Expand Up @@ -468,7 +468,7 @@ function handle_match_dump_verbose(__source__, __module__, io, value, body)
# print the dump of the decision automaton before deduplication
$dumpall($io, $top_down_nodes, $binder, true)
# but return the count of deduplicated nodes.
$length($deduplicate_automaton($entry, $binder))
$length($deduplicate_automaton($entry))
end)
end

Expand Down
7 changes: 7 additions & 0 deletions test/coverage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -550,4 +550,11 @@ end # of automaton
end) == 2
@test @__match__(1, 1 => 2) == 2
end

@testset "Test trivial conditions" begin
@test (@__match__ 1 begin
_ => 2
end) == 2
@test @__match__(1, _ => 2) == 2
end
end # @testset "Tests that add code coverage"
2 changes: 1 addition & 1 deletion test/rematch2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ end
function f2(a, b, c, d, e, f, g, h)
# For reference we use the brute-force implementation of pattern-matching that just
# performs the tests sequentially, like writing an if-elseif-else chain.
Match.@match (a, b, c, d, e, f, g, h) begin
Match.@__match__ (a, b, c, d, e, f, g, h) begin
(a, b, c, d, e, f, g, h) where (!(!((!a || !b) && (c || !d)) || !(!e || f) && (g || h))) => 1
(a, b, c, d, e, f, g, h) where (!((!a || b) && (c || d) || (e || !f) && (!g || !h))) => 2
(a, b, c, d, e, f, g, h) where (!((a || b) && !(!c || !d) || !(!(!e || f) && !(g || !h)))) => 3
Expand Down

0 comments on commit 1785df4

Please sign in to comment.