Command parsers plan 3
This commit is contained in:
139
server/command/builders.go
Normal file
139
server/command/builders.go
Normal 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()
|
||||
}
|
@ -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
|
||||
|
@ -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 <from/to> 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 <from/to> 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 {
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user