Files
commandkit/internal/alias/machine.go

144 lines
4.1 KiB
Go

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
}
}