CommandLib more or less finished
This commit is contained in:
parent
1b0564b39a
commit
82082b128f
14 changed files with 203 additions and 323 deletions
|
@ -1,86 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
type ArgType byte
|
||||
|
||||
const (
|
||||
StringArg = iota
|
||||
IntArg
|
||||
FloatArg
|
||||
)
|
||||
|
||||
func (a ArgType) String() string {
|
||||
switch a {
|
||||
case StringArg:
|
||||
return "word"
|
||||
case IntArg:
|
||||
return "number"
|
||||
case FloatArg:
|
||||
return "decimal number"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type arg struct {
|
||||
name string
|
||||
help string
|
||||
optional bool
|
||||
|
||||
argType ArgType
|
||||
validators []ArgValidator
|
||||
}
|
||||
|
||||
func CreateArg(
|
||||
name string,
|
||||
help string,
|
||||
optional bool,
|
||||
argType ArgType,
|
||||
validators ...ArgValidator,
|
||||
) (res *arg, err error) {
|
||||
res = &arg{
|
||||
name: name,
|
||||
help: help,
|
||||
argType: argType,
|
||||
validators: validators,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CreateStringArg(name string, help string, validators ...ArgValidator) (res *arg) {
|
||||
res = &arg{
|
||||
name: name,
|
||||
help: help,
|
||||
argType: StringArg,
|
||||
validators: append([]ArgValidator{StringArgTypeValidator}, validators...),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (arg *arg) ArgType() ArgType {
|
||||
return arg.argType
|
||||
}
|
||||
|
||||
func (arg *arg) Name() string {
|
||||
return arg.name
|
||||
}
|
||||
|
||||
func (arg *arg) IsOptional() bool {
|
||||
return arg.optional
|
||||
}
|
||||
|
||||
func (arg *arg) Validate(value any) (valid bool, feedback []error) {
|
||||
feedback = []error{}
|
||||
|
||||
for _, validate := range arg.validators {
|
||||
err := validate(value)
|
||||
|
||||
if err != nil {
|
||||
valid = false
|
||||
feedback = append(feedback, err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ArgValidator = func(value any) (err error)
|
||||
|
||||
type argValidationError struct {
|
||||
message string
|
||||
}
|
||||
|
||||
func CreateArgValidationError(template string, args ...any) *argValidationError {
|
||||
return &argValidationError{
|
||||
message: fmt.Sprintf(template, args...),
|
||||
}
|
||||
}
|
||||
|
||||
func (err *argValidationError) Error() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
func StringArgTypeValidator(value any) (err error) {
|
||||
_, valid := value.(string)
|
||||
|
||||
if !valid {
|
||||
err = CreateArgValidationError("Invalid argument type, expected %v", StringArg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func IntArgTypeValidator(value any) (err error) {
|
||||
_, valid := value.(int32)
|
||||
|
||||
if !valid {
|
||||
err = CreateArgValidationError("Invalid type, expected %v", IntArg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func FloatArgTypeValidator(value any) (err error) {
|
||||
_, valid := value.(float32)
|
||||
|
||||
if !valid {
|
||||
err = CreateArgValidationError("Invalid type, expected %v", FloatArg)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
type argValue struct {
|
||||
value any
|
||||
}
|
||||
|
||||
func CreateArgValue(val any) *argValue {
|
||||
return &argValue{
|
||||
value: val,
|
||||
}
|
||||
}
|
||||
|
||||
func (aVal *argValue) Value() any {
|
||||
return aVal.value
|
||||
}
|
|
@ -1,71 +1,70 @@
|
|||
package commandlib
|
||||
|
||||
type ArgumentBase interface {
|
||||
Name() string
|
||||
ArgType() ArgType
|
||||
IsOptional() bool
|
||||
Validate(value any) (valid bool, feedback []error)
|
||||
type Command struct {
|
||||
commandDefinition CommandDefinition
|
||||
params []Parameter
|
||||
}
|
||||
|
||||
type ArgumentValue interface {
|
||||
Value() any
|
||||
func CreateCommand(cmdDef CommandDefinition, parameters []Parameter) Command {
|
||||
return Command{
|
||||
commandDefinition: cmdDef,
|
||||
params: parameters,
|
||||
}
|
||||
}
|
||||
|
||||
type command struct {
|
||||
name string
|
||||
altname string
|
||||
|
||||
args []ArgumentBase
|
||||
|
||||
work func(argValues []ArgumentValue) (err error)
|
||||
func (cmd Command) Execute() (err error) {
|
||||
return cmd.commandDefinition.work(cmd.params...)
|
||||
}
|
||||
|
||||
func CreateCommand(
|
||||
name string,
|
||||
altname string,
|
||||
work func(argValues []ArgumentValue) (err error),
|
||||
arguments ...ArgumentBase,
|
||||
) (cmd *command, err error) {
|
||||
var onlyAcceptingOptionals = false
|
||||
type commandContextError struct {
|
||||
err string
|
||||
}
|
||||
|
||||
for _, v := range arguments {
|
||||
if !v.IsOptional() && onlyAcceptingOptionals {
|
||||
// Optional arguments can only be placed after non-optional ones
|
||||
err = CreateCommandLibError(name, "Cannot define non-optional arguments after optional ones.")
|
||||
cmd = nil
|
||||
func createCommandContextError(err string) *commandContextError {
|
||||
return &commandContextError{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func (cce *commandContextError) Error() string {
|
||||
return cce.err
|
||||
}
|
||||
|
||||
type CommandContext struct {
|
||||
commandString string
|
||||
tokens []Token
|
||||
|
||||
command Command
|
||||
}
|
||||
|
||||
func CreateCommandContext(commandRegistry *CommandRegistry, commandString string) (ctx *CommandContext, err error) {
|
||||
tokenizer := CreateTokenizer()
|
||||
|
||||
tokens, tokenizerError := tokenizer.Tokenize(commandString)
|
||||
|
||||
if tokenizerError != nil {
|
||||
err = tokenizerError
|
||||
return
|
||||
}
|
||||
|
||||
commandDef := commandRegistry.Match(tokens)
|
||||
|
||||
if commandDef == nil {
|
||||
err = createCommandContextError("Unknown command")
|
||||
return
|
||||
}
|
||||
|
||||
params := commandDef.ParseParameters(tokens)
|
||||
|
||||
ctx = &CommandContext{
|
||||
commandString: commandString,
|
||||
tokens: tokens,
|
||||
command: CreateCommand(*commandDef, params),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if v.IsOptional() {
|
||||
onlyAcceptingOptionals = true
|
||||
}
|
||||
}
|
||||
|
||||
cmd = new(command)
|
||||
|
||||
cmd.name = name
|
||||
cmd.altname = altname
|
||||
cmd.work = work
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (cmd *command) Name() string {
|
||||
return cmd.name
|
||||
}
|
||||
|
||||
func (cmd *command) Execute(argValues []ArgumentValue) (err error) {
|
||||
|
||||
for i, v := range cmd.args {
|
||||
if i > len(argValues)-1 {
|
||||
if !v.IsOptional() {
|
||||
return CreateCommandLibError(cmd.name, "Not enough arguments, found %d, expected more", len(argValues))
|
||||
} else {
|
||||
break // There are no more arg values to process, and the remaining arguments are all optional anyway
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.work(argValues)
|
||||
func (ctx *CommandContext) ExecuteCommand() (err error) {
|
||||
return ctx.command.Execute()
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
type commandContext struct {
|
||||
commandString string
|
||||
tokens []Token
|
||||
|
||||
command Command
|
||||
}
|
||||
|
||||
func CreateCommandContext(commandString string) (ctx *commandContext, err error) {
|
||||
tokenizer := CreateTokenizer()
|
||||
|
||||
tokens, tokenizerError := tokenizer.Tokenize(commandString)
|
||||
|
||||
if tokenizerError != nil {
|
||||
err = tokenizerError
|
||||
return
|
||||
}
|
||||
|
||||
ctx = &commandContext{
|
||||
commandString: commandString,
|
||||
tokens: tokens,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ctx *commandContext) Execute() (err error) {
|
||||
ctx.command.Execute()
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
type CommandDefinition struct {
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
type Command interface {
|
||||
Name() string
|
||||
DoWork(argValues []ArgumentValue) (err error)
|
||||
}
|
||||
|
||||
type commandRegistry struct {
|
||||
commands []Command
|
||||
}
|
|
@ -2,18 +2,18 @@ package commandlib
|
|||
|
||||
import "fmt"
|
||||
|
||||
type commandLibError struct {
|
||||
type commandError struct {
|
||||
cmdName string
|
||||
message string
|
||||
}
|
||||
|
||||
func CreateCommandLibError(cmdName string, msg string, msgArgs ...any) *commandLibError {
|
||||
return &commandLibError{
|
||||
func createCommandError(cmdName string, msg string, msgArgs ...any) *commandError {
|
||||
return &commandError{
|
||||
cmdName: cmdName,
|
||||
message: fmt.Sprintf(msg, msgArgs...),
|
||||
}
|
||||
}
|
||||
|
||||
func (cmdErr *commandLibError) Error() string {
|
||||
func (cmdErr *commandError) Error() string {
|
||||
return "Error with command '" + cmdErr.cmdName + "': " + cmdErr.message
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package commandlib
|
||||
|
||||
type Parameter interface {
|
||||
Value() any
|
||||
}
|
||||
|
||||
type Command interface {
|
||||
Name() string
|
||||
Parameters() []Parameter
|
||||
}
|
|
@ -45,7 +45,7 @@ look ::= "look" [ "around" | direction | "at" identifier ] ;
|
|||
|
||||
move ::= "move" direction | "go" direction ;
|
||||
|
||||
// [Player Name], [Item Name], [Place Name] or just Played Name, Item Name, Place Name
|
||||
// [Player Name], [Item Name], [Place Name] or just Player Name, Item Name, Place Name
|
||||
// brackets may be useful in situations where there are multiple identifiers
|
||||
identifier ::= "[" name "]" | name ;
|
||||
|
||||
|
@ -57,12 +57,10 @@ number ::= digit ( digit )* ; // 123, 12, 97401, etc.
|
|||
|
||||
word ::= letter+;
|
||||
|
||||
chatMessage ::= ( letter | punctuation | digit | space )+ ;
|
||||
chatMessage ::= ( letter | digit | space )+ ;
|
||||
|
||||
direction ::= "east" | "west" | "north" | "up" | "down" ;
|
||||
|
||||
punctuation ::= "," | "." | "!" | "?" | "'" | "/" | '"' | ":" | ";" | "-" | "(" | ")" | "[" | "]" ;
|
||||
|
||||
letter ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
|
||||
| "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
|
||||
| "u" | "v" | "w" | "x" | "y" | "z" ;
|
||||
|
|
25
src/CommandLib/parameter.go
Normal file
25
src/CommandLib/parameter.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package commandlib
|
||||
|
||||
import "strconv"
|
||||
|
||||
type Parameter struct {
|
||||
value string
|
||||
}
|
||||
|
||||
func CreateParameter(value string) Parameter {
|
||||
return Parameter{
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (p Parameter) AsString() (res string, err error) {
|
||||
return p.value, nil
|
||||
}
|
||||
|
||||
func (p Parameter) AsInteger() (res int, err error) {
|
||||
return strconv.Atoi(p.value)
|
||||
}
|
||||
|
||||
func (p Parameter) AsDecimal() (res float64, err error) {
|
||||
return strconv.ParseFloat(p.value, 32)
|
||||
}
|
71
src/CommandLib/registry.go
Normal file
71
src/CommandLib/registry.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package commandlib
|
||||
|
||||
import "log"
|
||||
|
||||
type TokenMatcher func(tokens []Token) bool
|
||||
|
||||
type ParameterParser func(tokens []Token) []Parameter
|
||||
|
||||
type CommandWork func(parameters ...Parameter) (err error)
|
||||
|
||||
type CommandDefinition struct {
|
||||
name string
|
||||
tokenMatcher TokenMatcher
|
||||
parameterParser ParameterParser
|
||||
work CommandWork
|
||||
}
|
||||
|
||||
func CreateCommandDefinition(
|
||||
name string,
|
||||
tokenMatcher TokenMatcher,
|
||||
parameterParser ParameterParser,
|
||||
work CommandWork,
|
||||
) CommandDefinition {
|
||||
return CommandDefinition{
|
||||
name: name,
|
||||
tokenMatcher: tokenMatcher,
|
||||
parameterParser: parameterParser,
|
||||
work: work,
|
||||
}
|
||||
}
|
||||
|
||||
func (def CommandDefinition) Name() string {
|
||||
return def.name
|
||||
}
|
||||
|
||||
func (def CommandDefinition) Match(tokens []Token) bool {
|
||||
return def.tokenMatcher(tokens)
|
||||
}
|
||||
|
||||
func (def CommandDefinition) ParseParameters(tokens []Token) []Parameter {
|
||||
return def.parameterParser(tokens)
|
||||
}
|
||||
|
||||
func (def CommandDefinition) ExecuteFunc() CommandWork {
|
||||
return def.work
|
||||
}
|
||||
|
||||
type CommandRegistry struct {
|
||||
commandDefinitions []CommandDefinition
|
||||
}
|
||||
|
||||
func CreateCommandRegistry(commandDefinitions ...CommandDefinition) *CommandRegistry {
|
||||
return &CommandRegistry{
|
||||
commandDefinitions: commandDefinitions,
|
||||
}
|
||||
}
|
||||
|
||||
func (comReg *CommandRegistry) Register(newCommandDefinitions ...CommandDefinition) {
|
||||
comReg.commandDefinitions = append(comReg.commandDefinitions, newCommandDefinitions...)
|
||||
}
|
||||
|
||||
func (comReg *CommandRegistry) Match(tokens []Token) (comDef *CommandDefinition) {
|
||||
for _, v := range comReg.commandDefinitions {
|
||||
if v.Match(tokens) {
|
||||
log.Println("Found match", v.Name())
|
||||
return &v
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -20,7 +20,6 @@ const (
|
|||
|
||||
TokenDirection
|
||||
TokenCommand
|
||||
TokenSayCommand
|
||||
TokenSelf
|
||||
|
||||
TokenWhitespace
|
||||
|
@ -46,8 +45,6 @@ func (tt TokenType) String() string {
|
|||
return "Direction"
|
||||
case TokenCommand:
|
||||
return "Command"
|
||||
case TokenSayCommand:
|
||||
return "SayCommand"
|
||||
case TokenSelf:
|
||||
return "Self"
|
||||
case TokenWhitespace:
|
||||
|
@ -92,12 +89,15 @@ type tokenPattern struct {
|
|||
pattern string
|
||||
}
|
||||
|
||||
type tokenizer struct {
|
||||
// Used to tokenize a string input.
|
||||
// This is the starting point for parsing a command string.
|
||||
// Create with [CreateTokenizer]
|
||||
type Tokenizer struct {
|
||||
tokenPatterns []tokenPattern
|
||||
}
|
||||
|
||||
func CreateTokenizer() *tokenizer {
|
||||
return &tokenizer{
|
||||
func CreateTokenizer() *Tokenizer {
|
||||
return &Tokenizer{
|
||||
tokenPatterns: []tokenPattern{
|
||||
{tokenType: TokenDecimal, pattern: `\b\d+\.\d+\b`},
|
||||
{tokenType: TokenNumber, pattern: `\b\d+\b`},
|
||||
|
@ -111,15 +111,16 @@ func CreateTokenizer() *tokenizer {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *tokenizer) Tokenize(commandMsg string) (tokens []Token, err error) {
|
||||
// Tokenize a command string
|
||||
func (t *Tokenizer) Tokenize(commandString string) (tokens []Token, err error) {
|
||||
tokens = []Token{}
|
||||
pos := 0
|
||||
inputLen := len(commandMsg)
|
||||
inputLen := len(commandString)
|
||||
|
||||
// Continue iterating until we reach the end of the input
|
||||
for pos < inputLen {
|
||||
matched := false
|
||||
remaining := commandMsg[pos:]
|
||||
remaining := commandString[pos:]
|
||||
|
||||
// Iterate through each token type and test its pattern
|
||||
for _, pattern := range t.tokenPatterns {
|
||||
|
@ -133,7 +134,7 @@ func (t *tokenizer) Tokenize(commandMsg string) (tokens []Token, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// If the loc isn't nil, that means we've found a match
|
||||
// If the location of the match isn't nil, that means we've found a match
|
||||
if loc := re.FindStringIndex(remaining); loc != nil {
|
||||
lexeme := remaining[loc[0]:loc[1]]
|
||||
|
||||
|
@ -145,9 +146,9 @@ func (t *tokenizer) Tokenize(commandMsg string) (tokens []Token, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Unknown tokens are still added, except carriage return (\r) and newline (\n)
|
||||
// Unknown tokens are still added
|
||||
if !matched {
|
||||
tokens = append(tokens, CreateToken(TokenUnknown, commandMsg[pos:pos+1], pos))
|
||||
tokens = append(tokens, CreateToken(TokenUnknown, commandString[pos:pos+1], pos))
|
||||
pos++
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,6 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
commandlib "code.haedhutner.dev/mvv/LastMUD/CommandLib"
|
||||
)
|
||||
|
||||
type Command interface {
|
||||
|
@ -15,18 +12,6 @@ type Command interface {
|
|||
}
|
||||
|
||||
func main() {
|
||||
// testcmd, err := commandlib.CreateCommand(
|
||||
// "test",
|
||||
// "t",
|
||||
// func(argValues []commandlib.ArgumentValue) (err error) {
|
||||
// err = nil
|
||||
// return
|
||||
// },
|
||||
// commandlib.CreateStringArg("test", "test message"),
|
||||
// )
|
||||
|
||||
tokenizer := commandlib.CreateTokenizer()
|
||||
|
||||
ln, err := net.Listen("tcp", ":8000")
|
||||
|
||||
if err != nil {
|
||||
|
@ -41,47 +26,52 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// cmdRegistry := commandlib.CreateCommandRegistry(
|
||||
// commandlib.CreateCommandDefinition(
|
||||
// "exit",
|
||||
// func(tokens []commandlib.Token) bool {
|
||||
// return tokens[0].Lexeme() == "exit"
|
||||
// },
|
||||
// func(tokens []commandlib.Token) []commandlib.Parameter {
|
||||
// return nil
|
||||
// },
|
||||
// func(parameters ...commandlib.Parameter) (err error) {
|
||||
// err = conn.Close()
|
||||
// return
|
||||
// },
|
||||
// ),
|
||||
// )
|
||||
|
||||
for {
|
||||
message, err := bufio.NewReader(conn).ReadString('\n')
|
||||
message, _ := bufio.NewReader(conn).ReadString('\n')
|
||||
response := ""
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// if err != nil {
|
||||
|
||||
// if err == io.EOF {
|
||||
// fmt.Println("Client disconnected")
|
||||
// break
|
||||
// }
|
||||
|
||||
// log.Println("Read error:", err)
|
||||
|
||||
// continue
|
||||
// }
|
||||
|
||||
conn.Write([]byte(message + "\n"))
|
||||
|
||||
tokens, err := tokenizer.Tokenize(message)
|
||||
|
||||
if err != nil {
|
||||
response = err.Error()
|
||||
} else {
|
||||
lines := make([]string, len(tokens))
|
||||
|
||||
for i, tok := range tokens {
|
||||
lines[i] = tok.String()
|
||||
}
|
||||
|
||||
response = strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// if strings.HasPrefix(message, testcmd.Name()) {
|
||||
// tokens := commandlib.Tokenize(message)
|
||||
// args := []commandlib.ArgumentValue{}
|
||||
|
||||
// for _, v := range tokens[1:] {
|
||||
// args = append(args, commandlib.CreateArgValue(v))
|
||||
// }
|
||||
|
||||
// err := testcmd.DoWork(args)
|
||||
// cmdContext, err := commandlib.CreateCommandContext(cmdRegistry, message)
|
||||
|
||||
// if err != nil {
|
||||
// fmt.Print(err.Error())
|
||||
// }
|
||||
// log.Println(err)
|
||||
// response = err.Error()
|
||||
// } else {
|
||||
// fmt.Print("Message Received: ", string(message))
|
||||
// // err = cmdContext.ExecuteCommand()
|
||||
|
||||
// response = strings.ToUpper(message)
|
||||
// // if err != nil {
|
||||
// // log.Println(err)
|
||||
// // response = err.Error()
|
||||
// // }
|
||||
// }
|
||||
|
||||
conn.Write([]byte(response + "\n> "))
|
||||
|
|
Loading…
Add table
Reference in a new issue