235 lines
3.7 KiB
Go
235 lines
3.7 KiB
Go
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
|
|
}
|