/* * The MIT License (MIT) * * Copyright (c) 2022 Chance Dinkins * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, Subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or Substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Package index contains a trie index used by StdConverter to lookup Replacements. package index import ( "github.com/chanced/caps/token" ) // IndexedReplacement is a node in an Index // created from a Replacement type IndexedReplacement struct { Screaming string Camel string Lower string } // IsEmpty reports whether or not ir is empty func (ir IndexedReplacement) IsEmpty() bool { return len(ir.Screaming) == 0 } func (ir IndexedReplacement) HasValue() bool { return !ir.IsEmpty() } // Index is a trie index used by Converter to lookup Replacements. type Index struct { value IndexedReplacement nodes map[rune]*Index lastMatch IndexedReplacement partialMatches []rune caser token.Caser } // Clone creates a copy of the Index func (idx *Index) Clone() Index { return Index{ value: idx.value, nodes: idx.nodes, lastMatch: idx.lastMatch, partialMatches: idx.partialMatches, caser: idx.caser, } } // NewIndex creates a new Index of Replacements, // internally represented as Trie // // reverseIndexes indicates whether or not func New(caser token.Caser) *Index { return &Index{ nodes: make(map[rune]*Index), caser: token.CaserOrDefault(caser), } } func (idx Index) Value() IndexedReplacement { return idx.value } func (idx Index) Contains(s string) bool { _, ok := idx.Get(s) return ok } // PartialMatches returns func (idx Index) PartialMatches() string { return string(idx.partialMatches) } func (idx Index) PartialMatchRunes() []rune { return idx.partialMatches } func (idx Index) HasPartialMatches() bool { return len(idx.partialMatches) > 0 } func (idx Index) HasMatched() bool { return !idx.lastMatch.IsEmpty() } func (idx Index) LastMatch() IndexedReplacement { return idx.lastMatch } // HasValue reports true if the current node has a value func (idx Index) HasValue() bool { return !idx.value.IsEmpty() } // Match searches the index for the given token, returning an Index node. // // If t is empty, the root node is returned. // // # If the Index does not contain the node, an empty Index is returned func (idx Index) Match(s string) (Index, bool) { var ok bool if len(s) == 0 { return idx, false } next := &idx for _, r := range token.ToLower(idx.caser, s) { if next, ok = next.nodes[r]; !ok || next == nil { return Index{ partialMatches: idx.partialMatches, lastMatch: idx.lastMatch, caser: idx.caser, }, false } idx = Index{ nodes: next.nodes, value: next.value, lastMatch: idx.lastMatch, partialMatches: idx.partialMatches, caser: idx.caser, } if next.HasValue() { idx.lastMatch = next.value idx.partialMatches = nil } else { if idx.partialMatches == nil { idx.partialMatches = make([]rune, 0, 8) } idx.partialMatches = append(idx.partialMatches, r) } } return idx, true } // Get searches the index for the t, returning the IndexedReplacement and true // if found. // // To GetForward a reversed value, use GetReverse. func (idx *Index) Get(s string) (IndexedReplacement, bool) { if len(s) == 0 { return idx.value, idx.value.HasValue() } node := idx var ok bool for _, r := range token.ToLower(idx.caser, s) { if node, ok = node.nodes[r]; !ok { return IndexedReplacement{}, false } } return node.value, node.value.HasValue() } // Nodes returns all nodes in the Index func (idx *Index) Nodes() []Index { nodes := make([]Index, 0, len(idx.nodes)) nodes = append(nodes, *idx) for _, node := range idx.nodes { nodes = append(nodes, node.Nodes()...) } return nodes } // Values returns all IndexedReplacements in the Index func (idx *Index) Values() []IndexedReplacement { nodes := idx.Nodes() values := make([]IndexedReplacement, 0, len(nodes)) for _, n := range nodes { if n.HasValue() { values = append(values, n.value) } } return values } // Add inserts r into the Index, indexed by the lowercase variant of r.Camel AND // r.Screaming. // // If the Index does not contains the IndexedReplacement // repesentation of r, one is created, inserted, and true is returned. // Otherwise, the previous value is replaced with a new IndexedReplacement. // // If idx.IsReversed is true, the IndexedReplacement is inserted into the // Index with the key in reverse order (e.g. AnExample -> elpmaxena). func (idx *Index) Add(camel string, screaming string) bool { ir := IndexedReplacement{ Screaming: screaming, Camel: camel, Lower: token.ToLower(idx.caser, camel), } var exists bool var er IndexedReplacement var ok bool if er, ok = idx.Get(ir.Screaming); ok { exists = true idx.Delete(er.Camel) idx.Delete(er.Screaming) } if er, ok = idx.Get(ir.Camel); ok { exists = true idx.Delete(er.Screaming) idx.Delete(er.Camel) } node := idx for _, r := range ir.Lower { if _, ok = node.nodes[r]; !ok { node.nodes[r] = &Index{ nodes: make(map[rune]*Index), caser: idx.caser, } } node = node.nodes[r] } node.value = ir skey := token.ToLower(idx.caser, ir.Screaming) if ir.Lower != skey { node = idx for _, r := range skey { if _, ok = node.nodes[r]; !ok { node.nodes[r] = &Index{ nodes: make(map[rune]*Index), caser: idx.caser, } } node = node.nodes[r] } node.value = ir } return exists } // Delete deletes the IndexedReplacement indexed by key from the Index. func (idx *Index) Delete(key string) bool { node := idx nodes := make([]*Index, len(key)) var ok bool var i int var r rune for i, r = range token.ToLower(idx.caser, key) { nodes[i] = node if node, ok = node.nodes[r]; !ok || node == nil { return false } } node.value = IndexedReplacement{} child := node k := []rune(key) for i := len(key) - 1; i >= 0; i-- { r = k[i] child = node node = nodes[i] if child.value.IsEmpty() && len(child.nodes) == 0 { // safe to delete delete(node.nodes, r) } else { break } } return true }