Model command alias prefix rewriting
This commit is contained in:
143
internal/alias/machine.go
Normal file
143
internal/alias/machine.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package alias
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"gitea.maximumdirect.net/eric/CommandKit/internal/commandpath"
|
||||
)
|
||||
|
||||
type symbolKind uint8
|
||||
|
||||
const (
|
||||
literalSymbol symbolKind = iota
|
||||
otherSymbol
|
||||
endSymbol
|
||||
boundarySymbol
|
||||
)
|
||||
|
||||
type symbol struct {
|
||||
kind symbolKind
|
||||
value string
|
||||
}
|
||||
|
||||
type transitionKey struct {
|
||||
control int
|
||||
read symbol
|
||||
}
|
||||
|
||||
type transition struct {
|
||||
next int
|
||||
push []symbol
|
||||
rule int
|
||||
}
|
||||
|
||||
// Machine is a finite-control pushdown rewrite model compiled from a resolver.
|
||||
// Its transitions replace the stack's first symbol with zero to two symbols.
|
||||
type Machine struct {
|
||||
start int
|
||||
halt int
|
||||
transitions map[transitionKey]transition
|
||||
alphabet []symbol
|
||||
literals map[string]struct{}
|
||||
}
|
||||
|
||||
// Compile builds a finite stack machine with explicit lookup, restoration,
|
||||
// and rewrite transitions for the mappings.
|
||||
func Compile(mappings []Mapping) (*Resolver, *Machine, error) {
|
||||
resolver, err := NewResolver(mappings)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resolver, compileMachine(resolver), nil
|
||||
}
|
||||
|
||||
func compileMachine(resolver *Resolver) *Machine {
|
||||
// Trie controls only move to child nodes. Helper controls prepend a fixed
|
||||
// number of saved symbols and then halt or return to the trie root after a
|
||||
// selected mapping has been completed.
|
||||
machine := &Machine{
|
||||
start: 0,
|
||||
halt: len(resolver.trie),
|
||||
transitions: make(map[transitionKey]transition),
|
||||
literals: make(map[string]struct{}),
|
||||
}
|
||||
for _, mapping := range resolver.mappings {
|
||||
for _, path := range []commandpath.Path{mapping.Source, mapping.Target} {
|
||||
for _, component := range path {
|
||||
machine.literals[component] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
for literal := range machine.literals {
|
||||
machine.alphabet = append(machine.alphabet, symbol{kind: literalSymbol, value: literal})
|
||||
}
|
||||
sort.Slice(machine.alphabet, func(i, j int) bool { return machine.alphabet[i].value < machine.alphabet[j].value })
|
||||
machine.alphabet = append(machine.alphabet, symbol{kind: otherSymbol}, symbol{kind: endSymbol}, symbol{kind: boundarySymbol})
|
||||
|
||||
nextControl := machine.halt + 1
|
||||
for nodeIndex, node := range resolver.trie {
|
||||
for _, input := range machine.alphabet {
|
||||
if input.kind == literalSymbol {
|
||||
if childIndex, exists := node.children[input.value]; exists {
|
||||
machine.transitions[transitionKey{control: nodeIndex, read: input}] = transition{next: childIndex, rule: -1}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
word, next, rule := machine.lookupOutcome(resolver, nodeIndex, input)
|
||||
machine.addWordTransition(nodeIndex, input, word, next, rule, &nextControl)
|
||||
}
|
||||
}
|
||||
return machine
|
||||
}
|
||||
|
||||
func (machine *Machine) lookupOutcome(resolver *Resolver, nodeIndex int, input symbol) ([]symbol, int, int) {
|
||||
node := resolver.trie[nodeIndex]
|
||||
word := make([]symbol, 0, len(node.path)+1)
|
||||
next := machine.halt
|
||||
rule := -1
|
||||
if node.best >= 0 {
|
||||
mapping := resolver.mappings[node.best]
|
||||
matchedLength := len(mapping.Source)
|
||||
for _, component := range mapping.Target {
|
||||
word = append(word, symbol{kind: literalSymbol, value: component})
|
||||
}
|
||||
for _, component := range node.path[matchedLength:] {
|
||||
word = append(word, symbol{kind: literalSymbol, value: component})
|
||||
}
|
||||
next = machine.start
|
||||
rule = node.best
|
||||
} else {
|
||||
for _, component := range node.path {
|
||||
word = append(word, symbol{kind: literalSymbol, value: component})
|
||||
}
|
||||
}
|
||||
word = append(word, input)
|
||||
return word, next, rule
|
||||
}
|
||||
|
||||
func (machine *Machine) addWordTransition(control int, read symbol, word []symbol, next, rule int, nextControl *int) {
|
||||
key := transitionKey{control: control, read: read}
|
||||
if len(word) <= 2 {
|
||||
machine.transitions[key] = transition{next: next, push: append([]symbol(nil), word...), rule: rule}
|
||||
return
|
||||
}
|
||||
|
||||
helper := *nextControl
|
||||
*nextControl = *nextControl + 1
|
||||
machine.transitions[key] = transition{next: helper, push: append([]symbol(nil), word[len(word)-2:]...), rule: rule}
|
||||
for index := len(word) - 3; index >= 0; index-- {
|
||||
following := next
|
||||
if index > 0 {
|
||||
following = *nextControl
|
||||
*nextControl = *nextControl + 1
|
||||
}
|
||||
expectedTop := word[index+1]
|
||||
machine.transitions[transitionKey{control: helper, read: expectedTop}] = transition{
|
||||
next: following,
|
||||
push: []symbol{word[index], expectedTop},
|
||||
rule: rule,
|
||||
}
|
||||
helper = following
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user