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{} mappings []Mapping } // 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. // End and boundary symbols are only restored as the final item below any // emitted command tokens. No transition places a marker above ordinary // input, so each remains the unique bottom marker of a valid stack. machine := &Machine{ start: 0, halt: len(resolver.trie), transitions: make(map[transitionKey]transition), literals: make(map[string]struct{}), mappings: resolver.mappings, } 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 } }