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