diff --git a/server/command/builders.go b/server/command/builders.go new file mode 100644 index 0000000..9d859fb --- /dev/null +++ b/server/command/builders.go @@ -0,0 +1,139 @@ +package command + +func (g *Graph) AppendLiteral(child *Literal) *Graph { + g.nodes[0].Children = append(g.nodes[0].Children, child.index) + return g +} + +func (g *Graph) Literal(str string) LiteralBuilder { + index := len(g.nodes) + g.nodes = append(g.nodes, Node{ + g: g, + index: index, + flags: LiteralNode, + Name: str, + }) + return LiteralBuilder{g: g, current: index} +} + +func (g *Graph) Argument(name string, parser Parser) ArgumentBuilder { + index := len(g.nodes) + g.nodes = append(g.nodes, Node{ + g: g, + index: index, + flags: ArgumentNode, + Name: name, + Parser: parser, + }) + return ArgumentBuilder{g: g, current: index} +} + +type LiteralBuilder struct { + g *Graph + current int +} + +func (n LiteralBuilder) AppendLiteral(node *Literal) LiteralBuilderWithLiteral { + current := &n.g.nodes[n.current] + current.Children = append(current.Children, node.index) + return LiteralBuilderWithLiteral{n: n} +} + +func (n LiteralBuilder) AppendArgument(node *Argument) LiteralBuilderWithArgument { + current := &n.g.nodes[n.current] + current.Children = append(current.Children, node.index) + return LiteralBuilderWithArgument{n: n} +} + +func (n LiteralBuilder) HandleFunc(f HandlerFunc) *Literal { + current := &n.g.nodes[n.current] + current.Run = f + return (*Literal)(current) +} + +func (n LiteralBuilder) Unhandle() *Literal { + return n.HandleFunc(unhandledCmd) +} + +type ArgumentBuilder struct { + g *Graph + current int +} + +func (n ArgumentBuilder) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral { + current := &n.g.nodes[n.current] + current.Children = append(current.Children, node.index) + return ArgumentBuilderWithLiteral{n: n} +} + +func (n ArgumentBuilder) AppendArgument(node *Argument) ArgumentBuilderWithArgument { + current := &n.g.nodes[n.current] + current.Children = append(current.Children, node.index) + return ArgumentBuilderWithArgument{n: n} +} + +func (n ArgumentBuilder) HandleFunc(f HandlerFunc) *Argument { + current := &n.g.nodes[n.current] + current.Run = f + return (*Argument)(current) +} + +func (n ArgumentBuilder) Unhandle() *Argument { + return n.HandleFunc(unhandledCmd) +} + +type LiteralBuilderWithLiteral struct { + n LiteralBuilder +} + +func (n LiteralBuilderWithLiteral) AppendLiteral(node *Literal) LiteralBuilderWithLiteral { + return n.n.AppendLiteral(node) +} + +func (n LiteralBuilderWithLiteral) HandleFunc(f HandlerFunc) *Literal { + return n.n.HandleFunc(f) +} + +func (n LiteralBuilderWithLiteral) Unhandle() *Literal { + return n.n.Unhandle() +} + +type LiteralBuilderWithArgument struct { + n LiteralBuilder +} + +func (n LiteralBuilderWithArgument) HandleFunc(f HandlerFunc) *Literal { + return (*Literal)((*Argument)(n.n.HandleFunc(f))) +} + +func (n LiteralBuilderWithArgument) Unhandle() *Literal { + return (*Literal)((*Argument)(n.n.Unhandle())) +} + +type ArgumentBuilderWithLiteral struct { + n ArgumentBuilder +} + +func (n ArgumentBuilderWithLiteral) AppendLiteral(node *Literal) ArgumentBuilderWithLiteral { + return n.n.AppendLiteral(node) +} + +func (n ArgumentBuilderWithLiteral) HandleFunc(f HandlerFunc) *Argument { + return n.n.HandleFunc(f) +} + +func (n ArgumentBuilderWithLiteral) Unhandle() *Argument { + return n.n.Unhandle() +} + +type ArgumentBuilderWithArgument struct { + n ArgumentBuilder +} + +func (n ArgumentBuilderWithArgument) HandleFunc(f HandlerFunc) *Argument { + return n.n.HandleFunc(f) +} + +func (n ArgumentBuilderWithArgument) Unhandle() *Argument { + return n.n.Unhandle() +} diff --git a/server/command/command.go b/server/command/command.go index 08873ab..ff7ba90 100644 --- a/server/command/command.go +++ b/server/command/command.go @@ -1,223 +1,121 @@ package command import ( - "github.com/Tnze/go-mc/chat" + "errors" "strings" ) const ( - TypeRoot byte = iota - TypeLiteral - TypeArgument - TypeUnknown + RootNode = iota + LiteralNode + ArgumentNode ) -const ( - IsExecutable = 1 << (iota + 2) - HasRedirect - HasSuggestionsType -) - -type Node interface { - Then(nodes ...Node) Node - Runnable() HandleFunc - Parse(cmd string, store map[string]interface{}) (token string, next Node, err error) +type Graph struct { + nodes []Node } -type Root struct { - children map[string]Node -} - -type HandleFunc func(args map[string]interface{}) - -func NewGraph() *Root { - return &Root{children: make(map[string]Node)} -} - -func (r *Root) Then(nodes ...Node) Node { - for _, node := range nodes { - l, ok := node.(*Literal) - if !ok { - panic("you could only add Literal node to Root") - } - if _, ok := r.children[l.Name]; ok { - panic("the Literal(" + l.Name + ") is already exist") - } - r.children[l.Name] = node +func NewGraph() *Graph { + g := new(Graph) + root := Node{ + g: g, + flags: RootNode, } - return r + g.nodes = append(g.nodes, root) + return g } -func Lite(literal string) *Literal { - return &Literal{Name: literal} -} - -func Arg(name string, parser Parser) *Argument { - return &Argument{Name: name, Parser: parser} -} - -func (r *Root) Runnable() HandleFunc { - return nil -} - -func (r *Root) Parse(cmd string, _ map[string]interface{}) (token string, next Node, err error) { - token, value, err := StringParser(0).Parse(cmd) - if err != nil { - return "", nil, err - } - child, ok := r.children[value.(string)] - if !ok { - return "", nil, Error{chat.TranslateMsg("command.unknown.command")} - } - return "", child, nil -} - -func (r *Root) Run(cmd string) error { - var node Node = r - args := make(map[string]interface{}) +func (g *Graph) Run(cmd string) error { + var args []ParsedData + node := &g.nodes[0] // root for { - token, next, err := node.Parse(cmd, args) + left, value, next, err := node.parse(cmd) if err != nil { return err } - cmd = strings.TrimSpace(strings.TrimPrefix(cmd, token)) - if next == nil || len(cmd) == 0 { - f := node.Runnable() - if f == nil { - return Error{chat.TranslateMsg("command.unknown.command")} + args = append(args, value) + left = strings.TrimSpace(left) + if len(left) == 0 { + err := node.Run(args) + if err != nil { + return err } - f(args) return nil } - node = next + if next == 0 { + panic("root node can't be child") + } + + cmd = left + node = &g.nodes[next] } } -type Children struct { - Lite map[string]*Literal - Args *Argument -} +type ParsedData interface{} -func (c *Children) add(nodes []Node) { - for _, node := range nodes { - switch node.(type) { - case *Literal: - if c.Args != nil { - panic("this Node already has an Argument Node children, cannot Add Literal Node") +type HandlerFunc func(args []ParsedData) error + +type Node struct { + g *Graph + index int + flags byte + Name string + Children []int + Parser Parser + Run HandlerFunc +} +type Literal Node +type Argument Node + +func (n *Node) parse(cmd string) (left string, value ParsedData, next int, err error) { + switch n.flags & 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) + if err != nil { + return "", nil, 0, err + } + default: + panic("unreachable") + } + // find next + if len(n.Children) > 0 { + // look up the first child's type + switch n.g.nodes[n.Children[0]].flags & 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 "", nil, 0, err } - if c.Lite == nil { - c.Lite = make(map[string]*Literal) + literal := value.(string) + for _, i := range n.Children { + if n.g.nodes[i].Name == literal { + next = i + break + } } - child := node.(*Literal) - c.Lite[child.Name] = child - case *Argument: - if c.Lite != nil { - panic("this Node already has Literal Node children, cannot Add Argument Node") - } - if c.Args != nil { - panic("this Node already set an Argument Node child, cannot be duplicate") - } - c.Args = node.(*Argument) + case ArgumentNode: + next = n.Children[0] } } return } -type Handler struct { - Handler HandleFunc +func unhandledCmd([]ParsedData) error { + return errors.New("unhandled function") } -func (h *Handler) Handle(f HandleFunc) { - if h.Handler != nil { - panic("handler func set twice") - } - h.Handler = f -} - -func (h *Handler) Runnable() HandleFunc { - return h.Handler -} - -type Literal struct { - Name string - Children - Handler -} - -func (l *Literal) Then(nodes ...Node) Node { - l.add(nodes) - return l -} - -func (l *Literal) Parse(cmd string, store map[string]interface{}) (token string, next Node, err error) { - cmd = strings.TrimPrefix(cmd, l.Name) - store[l.Name] = nil - if l.Children.Lite != nil { - token, value, err := StringParser(0).Parse(strings.TrimSpace(cmd)) - if err != nil { - return token, nil, err - } - if next, ok := l.Children.Lite[value.(string)]; ok { - return l.Name, next, nil - } - return "", nil, Error{chat.TranslateMsg("command.unknown.command")} - } else if l.Children.Args != nil { - return l.Name, l.Children.Args, nil - } else { - return l.Name, nil, nil - } -} - -func (l *Literal) Handle(f HandleFunc) Node { - l.Handler.Handle(f) - return l -} - -type Argument struct { - Name string - Children - Parser Parser - Handler -} - -func (a *Argument) Parse(cmd string, store map[string]interface{}) (token string, next Node, err error) { - token, value, err := a.Parser.Parse(cmd) - if err != nil { - return "", nil, err - } - store[a.Name] = value - cmd = strings.TrimSpace(strings.TrimPrefix(cmd, token)) - - if a.Children.Lite != nil { - _, value, err := StringParser(0).Parse(strings.TrimSpace(cmd)) - if err != nil { - return token, nil, err - } - if next, ok := a.Children.Lite[value.(string)]; ok { - return token, next, nil - } - return "", nil, Error{chat.TranslateMsg("command.unknown.command")} - } else if a.Children.Args != nil { - return token, a.Children.Args, nil - } else { - return token, nil, nil - } -} - -func (a *Argument) Then(nodes ...Node) Node { - a.add(nodes) - return a -} - -func (a *Argument) Handle(f HandleFunc) Node { - a.Handler.Handle(f) - return a -} - -type Error struct { - msg chat.Message -} - -func (e Error) Error() string { - return e.msg.String() -} +type LiteralData string diff --git a/server/command/command_test.go b/server/command/command_test.go index d1a35df..b411048 100644 --- a/server/command/command_test.go +++ b/server/command/command_test.go @@ -3,37 +3,48 @@ package command import "testing" func TestRoot_Run(t *testing.T) { - g := NewGraph().Then( - Lite("whitelist").Then( - Lite("add").Then( - Arg("targets", StringParser(0)). - Handle(func(args map[string]interface{}) { - t.Logf("whitelist add: %v", args) - })), - Lite("remove").Then( - Arg("targets", StringParser(0)). - Handle(func(args map[string]interface{}) { - t.Logf("whitelist remove: %v", args) - })), - Lite("on").Handle(func(args map[string]interface{}) { + g := NewGraph() + g.AppendLiteral(g.Literal("whitelist"). + AppendLiteral(g.Literal("add"). + AppendArgument(g.Argument("targets", StringParser(0)). + HandleFunc(func(args []ParsedData) error { + t.Logf("whitelist add: %v", args) + return nil + })).Unhandle()). + AppendLiteral(g.Literal("remove"). + AppendArgument(g.Argument("targets", StringParser(0)). + HandleFunc(func(args []ParsedData) error { + t.Logf("whitelist remove: %v", args) + return nil + })).Unhandle()). + AppendLiteral(g.Literal("on"). + HandleFunc(func(args []ParsedData) error { t.Logf("whitelist on: %v", args) - }), - Lite("off").Handle(func(args map[string]interface{}) { + return nil + })). + AppendLiteral(g.Literal("off"). + HandleFunc(func(args []ParsedData) error { t.Logf("whitelist off: %v", args) - }), - ), - ).(*Root) + return nil + })). + Unhandle(), + ) - targetA := Arg("targetA", StringParser(0)) - targetB := Arg("targetB", StringParser(0)) - targetB.Handle(func(args map[string]interface{}) { - t.Logf("tp A B parsed: %v", args) - }) - tp := Lite("tp").Then(targetA.Then( - Lite("form").Then(targetB), - Lite("to").Then(targetB), - )) - g.Then(tp) + targetB := g.Argument("targetB", StringParser(0)). + HandleFunc(func(args []ParsedData) error { + t.Logf("tp A B parsed: %v", args) + return nil + }) + g.AppendLiteral(g.Literal("tp").AppendArgument( + g.Argument("targetA", StringParser(0)). + AppendLiteral(g.Literal("from"). + AppendArgument(targetB). + Unhandle()). + AppendLiteral(g.Literal("to"). + AppendArgument(targetB). + Unhandle()). + Unhandle(), + ).Unhandle()) err := g.Run("tp Tnze to Xi_Xi_Mi") if err != nil { diff --git a/server/command/parsers.go b/server/command/parsers.go index aee60b9..78fafa4 100644 --- a/server/command/parsers.go +++ b/server/command/parsers.go @@ -8,22 +8,23 @@ import ( pk "github.com/Tnze/go-mc/net/packet" ) -type StringParser struct { - Format int32 - V string +type Parser interface { + Parse(cmd string) (left string, value ParsedData, err error) } +type StringParser int32 + func (s StringParser) WriteTo(w io.Writer) (int64, error) { return pk.Tuple{ pk.Identifier("brigadier:string"), - pk.VarInt(s.Format), + pk.VarInt(s), }.WriteTo(w) } -func (s StringParser) Parse(cmd string) (token string, value interface{}, err error) { - switch s.Format { +func (s StringParser) Parse(cmd string) (left string, value ParsedData, err error) { + switch s { case 2: // Greedy Phrase - return cmd, cmd, nil + return "", cmd, nil case 1: // Quotable Phrase if len(cmd) > 0 && cmd[0] == '"' { var sb strings.Builder @@ -45,7 +46,7 @@ func (s StringParser) Parse(cmd string) (token string, value interface{}, err er sb.WriteRune(v) } } - return "", nil, ParseErr{ + return cmd, nil, ParseErr{ Pos: len(cmd) - 1, Err: "expected '\"'", } @@ -54,11 +55,11 @@ func (s StringParser) Parse(cmd string) (token string, value interface{}, err er case 0: // Single Word i := strings.IndexAny(cmd, "\t\n\v\f\r ") if i == -1 { - return cmd, cmd, nil + return "", cmd, nil } - return cmd[:i], cmd[:i], nil + return cmd[i:], cmd[:i], nil default: - panic("StringParser: unknown format 0x" + strconv.FormatInt(int64(s.Format), 16)) + panic("StringParser: unknown format 0x" + strconv.FormatInt(int64(s), 16)) } }