144 lines
4.1 KiB
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
|
||
|
|
}
|
||
|
|
}
|