Initial
This commit is contained in:
commit
2d3ad194d6
13 changed files with 415 additions and 0 deletions
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
|
@ -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"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
86
src/CommandLib/arg.go
Normal file
86
src/CommandLib/arg.go
Normal file
|
@ -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
|
||||||
|
}
|
49
src/CommandLib/arg_validation.go
Normal file
49
src/CommandLib/arg_validation.go
Normal file
|
@ -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
|
||||||
|
}
|
15
src/CommandLib/arg_value.go
Normal file
15
src/CommandLib/arg_value.go
Normal file
|
@ -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
|
||||||
|
}
|
71
src/CommandLib/command.go
Normal file
71
src/CommandLib/command.go
Normal file
|
@ -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)
|
||||||
|
}
|
10
src/CommandLib/command_registry.go
Normal file
10
src/CommandLib/command_registry.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package commandlib
|
||||||
|
|
||||||
|
type Command interface {
|
||||||
|
Name() string
|
||||||
|
DoWork(argValues []ArgumentValue) (err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type commandRegistry struct {
|
||||||
|
commands []Command
|
||||||
|
}
|
19
src/CommandLib/error.go
Normal file
19
src/CommandLib/error.go
Normal file
|
@ -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
|
||||||
|
}
|
3
src/CommandLib/go.mod
Normal file
3
src/CommandLib/go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module code.haedhutner.dev/mvv/LastMUD/CommandLib
|
||||||
|
|
||||||
|
go 1.24.4
|
22
src/CommandLib/lastmud.w3c-ebnf
Normal file
22
src/CommandLib/lastmud.w3c-ebnf
Normal file
|
@ -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 ::= " " ;
|
30
src/CommandLib/tokenizer.go
Normal file
30
src/CommandLib/tokenizer.go
Normal file
|
@ -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
|
||||||
|
}
|
6
src/CoreLib/go.mod
Normal file
6
src/CoreLib/go.mod
Normal file
|
@ -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
|
13
src/Server/go.mod
Normal file
13
src/Server/go.mod
Normal file
|
@ -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
|
76
src/Server/main.go
Normal file
76
src/Server/main.go
Normal file
|
@ -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> "))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue