websocket base and command parser

This commit is contained in:
zomo
2026-01-06 23:43:14 -06:00
parent 81e8bca787
commit a6d932a560
5 changed files with 314 additions and 12 deletions

234
api/ws/commands.go Normal file
View File

@@ -0,0 +1,234 @@
package ws
import (
"errors"
"io"
"strings"
)
type Command struct {
Command string
Args []string
}
type parser struct {
cmd *Command
r *strings.Reader
arg string
args []string
}
type parserQuote rune
const (
parserQuoteSingle parserQuote = '\''
parserQuoteDouble parserQuote = '"'
)
type parserBracket rune
const (
parserBracketCurly parserBracket = '{'
parserBracketSquare parserBracket = '['
parserBracketTriangle parserBracket = '<'
)
var parserClosingBracket = map[parserBracket]rune{
parserBracketCurly: '}',
parserBracketSquare: ']',
parserBracketTriangle: '>',
}
func parseCommand(msg string) (*Command, error) {
cmd := &Command{}
r := strings.NewReader(msg)
parser := &parser{cmd: cmd, r: r}
return cmd, parser.parse()
}
func (p *parser) parse() error {
err := p.stateBase()
if err != nil {
return err
}
p.args = append(p.args, p.arg)
filteredArgs := []string{}
for _, arg := range p.args {
if arg != "" {
filteredArgs = append(filteredArgs, arg)
}
}
if len(p.args) >= 1 {
p.cmd.Command = p.args[0]
}
if len(p.args) >= 2 {
p.cmd.Args = p.args[1:]
}
return nil
}
func (p *parser) consume() (rune, bool, error) {
b := make([]byte, 1)
_, err := p.r.Read(b)
if err == io.EOF {
return 0, true, nil
}
if err != nil {
return 0, false, err
}
return rune(b[0]), false, nil
}
func (p *parser) push(r rune) {
p.arg += string(r)
}
func (p *parser) flush() {
p.args = append(p.args, p.arg)
p.arg = ""
}
func (p *parser) stateBase() error {
for {
ch, eof, err := p.consume()
if err != nil {
return err
}
if eof {
return nil
}
switch ch {
case '\\':
err := p.stateEscape()
if err != nil {
return err
}
case rune(parserQuoteDouble): fallthrough
case rune(parserQuoteSingle):
err := p.stateQuote(parserQuote(ch))
if err != nil {
return err
}
// special case, if the arg starts with a { then parse it with rough object mode
case rune(parserBracketCurly): fallthrough
case rune(parserBracketSquare): fallthrough
case rune(parserBracketTriangle):
if p.arg != "" {
p.push(ch)
} else {
err := p.stateBracket(parserBracket(ch))
if err != nil {
return err
}
}
case '\n': fallthrough
case '\t': fallthrough
case ' ':
p.flush()
default:
p.push(ch)
}
}
}
func (p *parser) stateQuote(quote parserQuote) error {
for {
ch, eof, err := p.consume()
if err != nil {
return err
}
if eof {
return errors.New("missing closing quote")
}
switch ch {
case '\\':
err := p.stateEscape()
if err != nil {
return err
}
case rune(quote):
return nil
default:
p.push(ch)
}
}
}
func (p *parser) stateBracket(bracket parserBracket) error {
p.push(rune(bracket))
for {
ch, eof, err := p.consume()
if err != nil {
return err
}
if eof {
return errors.New("missing closing bracket")
}
switch ch {
case '\\':
err := p.stateEscape()
if err != nil {
return err
}
case rune(parserQuoteDouble): fallthrough
case rune(parserQuoteSingle):
p.push(ch)
err := p.stateQuote(parserQuote(ch))
if err != nil {
return err
}
p.push(ch)
case rune(parserBracketCurly): fallthrough
case rune(parserBracketSquare): fallthrough
case rune(parserBracketTriangle):
err := p.stateBracket(parserBracket(ch))
if err != nil {
return err
}
case parserClosingBracket[bracket]:
p.push(ch)
return nil
default:
p.push(ch)
}
}
}
func (p *parser) stateEscape() error {
ch, eof, err := p.consume()
if err != nil {
return err
}
if eof {
return errors.New("invalid escape sequence before end of file")
}
switch ch {
case 'n':
p.arg += "\n"
case 't':
p.arg += "\t"
default:
p.push(ch)
}
return nil
}