commit 2d3ad194d64315fb37df48a6c4c67ec591f9b2aa Author: Miroslav Vasilev Date: Mon Jun 16 14:59:51 2025 +0300 Initial diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a7c43c0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceFolder}/src/Server/main.go" + }, + ] +} \ No newline at end of file diff --git a/src/CommandLib/arg.go b/src/CommandLib/arg.go new file mode 100644 index 0000000..8feba73 --- /dev/null +++ b/src/CommandLib/arg.go @@ -0,0 +1,86 @@ +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 +} diff --git a/src/CommandLib/arg_validation.go b/src/CommandLib/arg_validation.go new file mode 100644 index 0000000..1b54e78 --- /dev/null +++ b/src/CommandLib/arg_validation.go @@ -0,0 +1,49 @@ +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 +} diff --git a/src/CommandLib/arg_value.go b/src/CommandLib/arg_value.go new file mode 100644 index 0000000..5b3d14f --- /dev/null +++ b/src/CommandLib/arg_value.go @@ -0,0 +1,15 @@ +package commandlib + +type argValue struct { + value any +} + +func CreateArgValue(val any) *argValue { + return &argValue{ + value: val, + } +} + +func (aVal *argValue) Value() any { + return aVal.value +} diff --git a/src/CommandLib/command.go b/src/CommandLib/command.go new file mode 100644 index 0000000..7f6aa19 --- /dev/null +++ b/src/CommandLib/command.go @@ -0,0 +1,71 @@ +package commandlib + +type ArgumentBase interface { + Name() string + ArgType() ArgType + IsOptional() bool + Validate(value any) (valid bool, feedback []error) +} + +type ArgumentValue interface { + Value() any +} + +type command struct { + name string + altname string + + args []ArgumentBase + + work func(argValues []ArgumentValue) (err error) +} + +func CreateCommand( + name string, + altname string, + work func(argValues []ArgumentValue) (err error), + arguments ...ArgumentBase, +) (cmd *command, err error) { + var onlyAcceptingOptionals = false + + 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 + + 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) DoWork(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) +} diff --git a/src/CommandLib/command_registry.go b/src/CommandLib/command_registry.go new file mode 100644 index 0000000..90d206e --- /dev/null +++ b/src/CommandLib/command_registry.go @@ -0,0 +1,10 @@ +package commandlib + +type Command interface { + Name() string + DoWork(argValues []ArgumentValue) (err error) +} + +type commandRegistry struct { + commands []Command +} diff --git a/src/CommandLib/error.go b/src/CommandLib/error.go new file mode 100644 index 0000000..7ae83f4 --- /dev/null +++ b/src/CommandLib/error.go @@ -0,0 +1,19 @@ +package commandlib + +import "fmt" + +type commandLibError struct { + cmdName string + message string +} + +func CreateCommandLibError(cmdName string, msg string, msgArgs ...any) *commandLibError { + return &commandLibError{ + cmdName: cmdName, + message: fmt.Sprintf(msg, msgArgs...), + } +} + +func (cmdErr *commandLibError) Error() string { + return "Error with command '" + cmdErr.cmdName + "': " + cmdErr.message +} diff --git a/src/CommandLib/go.mod b/src/CommandLib/go.mod new file mode 100644 index 0000000..61b9aed --- /dev/null +++ b/src/CommandLib/go.mod @@ -0,0 +1,3 @@ +module code.haedhutner.dev/mvv/LastMUD/CommandLib + +go 1.24.4 diff --git a/src/CommandLib/lastmud.w3c-ebnf b/src/CommandLib/lastmud.w3c-ebnf new file mode 100644 index 0000000..b8e64b5 --- /dev/null +++ b/src/CommandLib/lastmud.w3c-ebnf @@ -0,0 +1,22 @@ +// BNF of the command language used to interact with the LastMUD server + +name ::= letter ( letter | digit )* ; + +message ::= word ( space word )* ; + +decimal ::= number "." number ; // 1.0, 2.0, 132.183, etc. + +number ::= digit ( digit )* ; // 123, 12, 97401, etc. + +word ::= letter+; + +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" + | "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" ; + +digit ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; + +space ::= " " ; \ No newline at end of file diff --git a/src/CommandLib/tokenizer.go b/src/CommandLib/tokenizer.go new file mode 100644 index 0000000..dd7dcc8 --- /dev/null +++ b/src/CommandLib/tokenizer.go @@ -0,0 +1,30 @@ +package commandlib + +import ( + "strconv" + "strings" +) + +func Tokenize(commandMsg string) []any { + split := strings.Split(commandMsg, " ") + + tokens := []any{} + + for _, v := range split { + valInt, err := strconv.ParseInt(v, 10, 32) + + if err == nil { + tokens = append(tokens, valInt) + } + + valFloat, err := strconv.ParseFloat(v, 32) + + if err == nil { + tokens = append(tokens, valFloat) + } + + tokens = append(tokens, v) + } + + return tokens +} diff --git a/src/CoreLib/go.mod b/src/CoreLib/go.mod new file mode 100644 index 0000000..27a45be --- /dev/null +++ b/src/CoreLib/go.mod @@ -0,0 +1,6 @@ +module code.haedhutner.dev/mvv/LastMUD/CoreLib + +require code.haedhutner.dev/mvv/LastMUD/CommandLib v0.0.0 +replace code.haedhutner.dev/mvv/LastMUD/CommandLib => ../CommandLib + +go 1.24.4 diff --git a/src/Server/go.mod b/src/Server/go.mod new file mode 100644 index 0000000..a2f390c --- /dev/null +++ b/src/Server/go.mod @@ -0,0 +1,13 @@ +module code.haedhutner.dev/mvv/LastMUD/Server + +require ( + code.haedhutner.dev/mvv/LastMUD/CommandLib v0.0.0 + code.haedhutner.dev/mvv/LastMUD/CoreLib v0.0.0 +) + +replace ( + code.haedhutner.dev/mvv/LastMUD/CommandLib => ../CommandLib + code.haedhutner.dev/mvv/LastMUD/CoreLib => ../CoreLib +) + +go 1.24.4 diff --git a/src/Server/main.go b/src/Server/main.go new file mode 100644 index 0000000..e591613 --- /dev/null +++ b/src/Server/main.go @@ -0,0 +1,76 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "net" + "strings" + + commandlib "code.haedhutner.dev/mvv/LastMUD/CommandLib" +) + +type Command interface { + Name() string +} + +type argValue struct { + value string +} + +func main() { + testcmd, err := commandlib.CreateCommand( + "test", + "t", + func(argValues []commandlib.ArgumentValue) (err error) { + err = nil + return + }, + commandlib.CreateStringArg("test", "test message"), + ) + + ln, err := net.Listen("tcp", ":8000") + + if err != nil { + log.Fatal(err) + } + + fmt.Println("Listening on port 8000") + + conn, err := ln.Accept() + + if err != nil { + log.Fatal(err) + } + + for { + message, err := bufio.NewReader(conn).ReadString('\n') + response := "" + + if err != nil { + log.Fatal(err) + } + + 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) + + if err != nil { + fmt.Print(err.Error()) + } + } else { + fmt.Print("Message Received: ", string(message)) + + response = strings.ToUpper(message) + } + + conn.Write([]byte(response + "\n> ")) + } + +}