88 lines
2.8 KiB
Text
88 lines
2.8 KiB
Text
import Lean.Data.Trie
|
||
|
||
/-!
|
||
|
||
# Tests for the trie data structure
|
||
|
||
This test tests the `Lean.Parser.Trie` data structure by bisimulation with a simple `Array String`:
|
||
It performs a sequence of trie creation steps, and after each steps checks
|
||
whether the trie is operationally equivalent to the array of strings.
|
||
|
||
This test does not bother with values that are different than they `String` they are stored under.
|
||
This test does not test `upsert`; since `Trie.insert` goes through it, it should be sufficient
|
||
(and it would make this test approach more complicated.)
|
||
-/
|
||
|
||
open Lean.Data
|
||
|
||
/-- These keys used in `T.check` below. Also include keys for negative lookup tests here! -/
|
||
def keys : Array String := #[
|
||
"",
|
||
"h",
|
||
"hello",
|
||
"helloo",
|
||
"hellooo",
|
||
"helloooooo",
|
||
"hella",
|
||
"hellx",
|
||
"hö",
|
||
"hü",
|
||
"hä",
|
||
"💩"
|
||
]
|
||
|
||
/-- A trie together with a reference value as an array of values -/
|
||
def T := Trie String × Array String
|
||
|
||
def T.empty : T := (.empty, .empty)
|
||
|
||
def T.insert : T → String → T := fun (t,a) s =>
|
||
(t.insert s s, if a.contains s then a else a.push s)
|
||
|
||
/-- A convenience function for use in this test case -/
|
||
def Array.sorted : Array String → Array String := fun a =>
|
||
a.qsort (fun s1 s2 => s1 < s2)
|
||
|
||
/-- The intended semanics of `Trie.findPrefix` -/
|
||
def Array.findPrefix : Array String → String → Array String := fun a s =>
|
||
a.filter (fun s' => s.isPrefixOf s')
|
||
|
||
/-- The intended semanics of `Trie.matchPrefix`: Longest prefix found in trie -/
|
||
def Array.matchPrefix : Array String → String → Option String := fun a s => Id.run do
|
||
for i in List.reverse (List.range (s.length + 1)) do
|
||
let pfix := s.take i
|
||
if let some _ := a.find? (· == pfix) then
|
||
return some pfix
|
||
return none
|
||
|
||
|
||
def T.check : T → IO Unit := fun (t,a) => do
|
||
-- Check lookup equivalence
|
||
keys.forM fun s => do
|
||
unless t.find? s = a.find? (· == s) do
|
||
IO.println s!"find? differs: key = {s}"
|
||
-- Check findPrefix equivalence
|
||
keys.forM fun s => do
|
||
unless (t.findPrefix s).sorted = (a.findPrefix s).sorted do
|
||
IO.println s!"findPrefix differs: key = {s}"
|
||
-- Check matchPrefix equivalence
|
||
keys.forM fun s => do
|
||
unless t.matchPrefix s 0 = a.matchPrefix s do
|
||
IO.println s!"matchPrefix differs: key = {s}, got: {t.matchPrefix s 0} exp: {a.matchPrefix s} "
|
||
let s' := "somePrefix" ++ s
|
||
unless t.matchPrefix s' ((0 : String.Pos) + "somePrefix") = a.matchPrefix s do
|
||
IO.println s!"matchPrefix differs (with prefix): key = {s}"
|
||
|
||
def main : IO Unit := do
|
||
-- Add tricky insert sequences here:
|
||
for seq in #[
|
||
#["hello", "hella", "hellooo", "h", "hö", "hü", "💩", "", "hü"],
|
||
#["", "helooooo"]
|
||
] do
|
||
IO.println "Resetting trie"
|
||
let mut t : T := T.empty
|
||
t.check
|
||
for s in seq do
|
||
IO.println s!"Inserting {s}"
|
||
t := t.insert s
|
||
t.check
|