Command parsers plan 3

This commit is contained in:
Tnze
2022-01-03 12:36:57 +08:00
parent 57b5649f53
commit 9433dd98de
4 changed files with 277 additions and 228 deletions

139
server/command/builders.go Normal file
View File

@ -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()
}

View File

@ -1,223 +1,121 @@
package command package command
import ( import (
"github.com/Tnze/go-mc/chat" "errors"
"strings" "strings"
) )
const ( const (
TypeRoot byte = iota RootNode = iota
TypeLiteral LiteralNode
TypeArgument ArgumentNode
TypeUnknown
) )
const ( type Graph struct {
IsExecutable = 1 << (iota + 2) nodes []Node
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 Root struct { func NewGraph() *Graph {
children map[string]Node g := new(Graph)
} root := Node{
g: g,
type HandleFunc func(args map[string]interface{}) flags: RootNode,
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
} }
return r g.nodes = append(g.nodes, root)
return g
} }
func Lite(literal string) *Literal { func (g *Graph) Run(cmd string) error {
return &Literal{Name: literal} var args []ParsedData
} node := &g.nodes[0] // root
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{})
for { for {
token, next, err := node.Parse(cmd, args) left, value, next, err := node.parse(cmd)
if err != nil { if err != nil {
return err return err
} }
cmd = strings.TrimSpace(strings.TrimPrefix(cmd, token)) args = append(args, value)
if next == nil || len(cmd) == 0 { left = strings.TrimSpace(left)
f := node.Runnable() if len(left) == 0 {
if f == nil { err := node.Run(args)
return Error{chat.TranslateMsg("command.unknown.command")} if err != nil {
return err
} }
f(args)
return nil return nil
} }
node = next if next == 0 {
panic("root node can't be child")
}
cmd = left
node = &g.nodes[next]
} }
} }
type Children struct { type ParsedData interface{}
Lite map[string]*Literal
Args *Argument
}
func (c *Children) add(nodes []Node) { type HandlerFunc func(args []ParsedData) error
for _, node := range nodes {
switch node.(type) { type Node struct {
case *Literal: g *Graph
if c.Args != nil { index int
panic("this Node already has an Argument Node children, cannot Add Literal Node") 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 { literal := value.(string)
c.Lite = make(map[string]*Literal) for _, i := range n.Children {
if n.g.nodes[i].Name == literal {
next = i
break
}
} }
child := node.(*Literal) case ArgumentNode:
c.Lite[child.Name] = child next = n.Children[0]
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)
} }
} }
return return
} }
type Handler struct { func unhandledCmd([]ParsedData) error {
Handler HandleFunc return errors.New("unhandled function")
} }
func (h *Handler) Handle(f HandleFunc) { type LiteralData string
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()
}

View File

@ -3,37 +3,48 @@ package command
import "testing" import "testing"
func TestRoot_Run(t *testing.T) { func TestRoot_Run(t *testing.T) {
g := NewGraph().Then( g := NewGraph()
Lite("whitelist").Then( g.AppendLiteral(g.Literal("whitelist").
Lite("add").Then( AppendLiteral(g.Literal("add").
Arg("targets", StringParser(0)). AppendArgument(g.Argument("targets", StringParser(0)).
Handle(func(args map[string]interface{}) { HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist add: %v", args) t.Logf("whitelist add: %v", args)
})), return nil
Lite("remove").Then( })).Unhandle()).
Arg("targets", StringParser(0)). AppendLiteral(g.Literal("remove").
Handle(func(args map[string]interface{}) { AppendArgument(g.Argument("targets", StringParser(0)).
t.Logf("whitelist remove: %v", args) HandleFunc(func(args []ParsedData) error {
})), t.Logf("whitelist remove: %v", args)
Lite("on").Handle(func(args map[string]interface{}) { return nil
})).Unhandle()).
AppendLiteral(g.Literal("on").
HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist on: %v", args) t.Logf("whitelist on: %v", args)
}), return nil
Lite("off").Handle(func(args map[string]interface{}) { })).
AppendLiteral(g.Literal("off").
HandleFunc(func(args []ParsedData) error {
t.Logf("whitelist off: %v", args) t.Logf("whitelist off: %v", args)
}), return nil
), })).
).(*Root) Unhandle(),
)
targetA := Arg("targetA", StringParser(0)) targetB := g.Argument("targetB", StringParser(0)).
targetB := Arg("targetB", StringParser(0)) HandleFunc(func(args []ParsedData) error {
targetB.Handle(func(args map[string]interface{}) { t.Logf("tp A <from/to> B parsed: %v", args)
t.Logf("tp A <from/to> B parsed: %v", args) return nil
}) })
tp := Lite("tp").Then(targetA.Then( g.AppendLiteral(g.Literal("tp").AppendArgument(
Lite("form").Then(targetB), g.Argument("targetA", StringParser(0)).
Lite("to").Then(targetB), AppendLiteral(g.Literal("from").
)) AppendArgument(targetB).
g.Then(tp) Unhandle()).
AppendLiteral(g.Literal("to").
AppendArgument(targetB).
Unhandle()).
Unhandle(),
).Unhandle())
err := g.Run("tp Tnze to Xi_Xi_Mi") err := g.Run("tp Tnze to Xi_Xi_Mi")
if err != nil { if err != nil {

View File

@ -8,22 +8,23 @@ import (
pk "github.com/Tnze/go-mc/net/packet" pk "github.com/Tnze/go-mc/net/packet"
) )
type StringParser struct { type Parser interface {
Format int32 Parse(cmd string) (left string, value ParsedData, err error)
V string
} }
type StringParser int32
func (s StringParser) WriteTo(w io.Writer) (int64, error) { func (s StringParser) WriteTo(w io.Writer) (int64, error) {
return pk.Tuple{ return pk.Tuple{
pk.Identifier("brigadier:string"), pk.Identifier("brigadier:string"),
pk.VarInt(s.Format), pk.VarInt(s),
}.WriteTo(w) }.WriteTo(w)
} }
func (s StringParser) Parse(cmd string) (token string, value interface{}, err error) { func (s StringParser) Parse(cmd string) (left string, value ParsedData, err error) {
switch s.Format { switch s {
case 2: // Greedy Phrase case 2: // Greedy Phrase
return cmd, cmd, nil return "", cmd, nil
case 1: // Quotable Phrase case 1: // Quotable Phrase
if len(cmd) > 0 && cmd[0] == '"' { if len(cmd) > 0 && cmd[0] == '"' {
var sb strings.Builder var sb strings.Builder
@ -45,7 +46,7 @@ func (s StringParser) Parse(cmd string) (token string, value interface{}, err er
sb.WriteRune(v) sb.WriteRune(v)
} }
} }
return "", nil, ParseErr{ return cmd, nil, ParseErr{
Pos: len(cmd) - 1, Pos: len(cmd) - 1,
Err: "expected '\"'", Err: "expected '\"'",
} }
@ -54,11 +55,11 @@ func (s StringParser) Parse(cmd string) (token string, value interface{}, err er
case 0: // Single Word case 0: // Single Word
i := strings.IndexAny(cmd, "\t\n\v\f\r ") i := strings.IndexAny(cmd, "\t\n\v\f\r ")
if i == -1 { if i == -1 {
return cmd, cmd, nil return "", cmd, nil
} }
return cmd[:i], cmd[:i], nil return cmd[i:], cmd[:i], nil
default: default:
panic("StringParser: unknown format 0x" + strconv.FormatInt(int64(s.Format), 16)) panic("StringParser: unknown format 0x" + strconv.FormatInt(int64(s), 16))
} }
} }