Files
go-mc/server/command/command.go
2022-11-26 20:37:57 +08:00

134 lines
2.5 KiB
Go

package command
import (
"context"
"errors"
"strings"
)
const (
RootNode = iota
LiteralNode
ArgumentNode
)
// Graph is a directed graph with a root node, representing all commands and how they are parsed.
type Graph struct {
// List of all nodes. The first element is the root node
nodes []*Node
}
func NewGraph() *Graph {
var g Graph
g.nodes = append(g.nodes,
&Node{g: &g, kind: RootNode},
)
return &g
}
func (g *Graph) Execute(ctx context.Context, cmd string) error {
var args []ParsedData
node := g.nodes[0] // root
for {
// parser command
left, value, err := node.parse(cmd)
if err != nil {
return err
}
args = append(args, value)
left = strings.TrimSpace(left)
if len(left) == 0 {
return node.Run(ctx, args)
}
// find next node
next, err := node.next(left)
if err != nil {
return err
}
if next == 0 {
return errors.New("command contains extra text: " + left)
}
cmd = left
node = g.nodes[next]
}
}
type ParsedData interface{}
type HandlerFunc func(ctx context.Context, args []ParsedData) error
// Node is the node of the Graph. There are 3 kinds of node: Root, Literal and Argument.
type Node struct {
g *Graph
index int32
kind byte
Name string
Children []int32
SuggestionsType string
Parser Parser
Run HandlerFunc
}
type (
Literal Node
Argument Node
)
func (n *Node) parse(cmd string) (left string, value ParsedData, err error) {
switch n.kind & 0x03 {
case RootNode:
left = cmd
value = nil
err = nil
case LiteralNode:
if !strings.HasPrefix(cmd, n.Name) {
panic("expect " + cmd + " prefixed with " + n.Name)
}
left = strings.TrimPrefix(cmd, n.Name)
value = LiteralData(n.Name)
case ArgumentNode:
left, value, err = n.Parser.Parse(cmd)
default:
panic("unreachable")
}
return
}
func (n *Node) next(left string) (next int32, err error) {
if len(n.Children) == 0 {
return 0, nil
}
// look up the first child's type
switch n.g.nodes[n.Children[0]].kind & 0x03 {
case RootNode:
panic("root node can't be child")
default:
panic("unreachable")
case LiteralNode:
_, value, err := StringParser(0).Parse(strings.TrimSpace(left))
if err != nil {
return 0, err
}
literal := value.(string)
for _, i := range n.Children {
if n.g.nodes[i].Name == literal {
next = i
break
}
}
case ArgumentNode:
next = n.Children[0]
}
return
}
func unhandledCmd(context.Context, []ParsedData) error {
return errors.New("unhandled function")
}
type LiteralData string