diff options
Diffstat (limited to 'proto/protocol.go')
| -rw-r--r-- | proto/protocol.go | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/proto/protocol.go b/proto/protocol.go new file mode 100644 index 0000000..b59cd70 --- /dev/null +++ b/proto/protocol.go @@ -0,0 +1,342 @@ +package proto + +import ( + "bufio" + "strings" + "errors" +) + +type Object struct { + Kind string + Id string + Fields map[string]string +} + +type Command struct { + Kind string + Target string + Args []Object +} + +type Line struct { + Kind rune + RequestId string + Cmd Command +} + +func NewCmd(kind string, target string, args ...Object) Command { + return Command {kind, target, args} +} + +var SyntaxError = errors.New("invalid syntax") + +func ReadLiteral(b *bufio.Reader) (string, error) { + var sb strings.Builder + for { + c, _, err := b.ReadRune() + if strings.IndexRune(",;\n", c) != -1 { + b.UnreadRune() + break + } + if c == '\\' { + c, _, err = b.ReadRune() + } + if err != nil { + return "", err + } + sb.WriteRune(c) + } + return sb.String(), nil +} + +func ReadIdentifier(b *bufio.Reader) (string, error) { + var sb strings.Builder + for { + c, _, err := b.ReadRune() + if strings.IndexRune("?!*<>:\\,#@~;\n\t ", c) != -1 { + b.UnreadRune() + break + } + if err != nil { + return "", err + } + sb.WriteRune(c) + } + return sb.String(), nil +} + +func ReadObject(b *bufio.Reader) (Object, error) { + var o Object + var err error + o.Kind, err = ReadIdentifier(b) + if err != nil { + return o, err + } + + o.Fields = make(map[string]string) + + c, _, err := b.ReadRune() + if err != nil { + return o, err + } + + if c == '#' { + o.Id, err = ReadIdentifier(b) + if err != nil { + return o, err + } + + c, _, err = b.ReadRune() + if err != nil { + return o, err + } + } + + switch c { + case ' ': + for { + key, err := ReadIdentifier(b) + if err != nil { + return o, err + } + c, _, err := b.ReadRune() + if err != nil { + return o, err + } else if c != ':' { + return o, SyntaxError + } + value, err := ReadLiteral(b) + if err != nil { + return o, err + } + o.Fields[key] = value + c, _, err = b.ReadRune(); + if c == ';' || c == '\n' { + b.UnreadRune() + break + } + } + case ';': + b.UnreadRune() + default: + return o, SyntaxError + } + return o, nil +} + +func ReadLine(b *bufio.Reader) (Line, error) { + var l Line + var err error + l.Kind, _, err = b.ReadRune() + if err != nil { + return l, err + } + switch l.Kind { + case '?', '!': + l.RequestId, err = ReadIdentifier(b) + if err != nil { + return l, err + } + c, _, err := b.ReadRune() + if err != nil { + return l, err + } else if c != ' ' { + return l, SyntaxError + } + case '*': + default: + return l, SyntaxError + } + + l.Cmd.Kind, err = ReadIdentifier(b) + if err != nil { + return l, err + } + + c, _, err := b.ReadRune() + if err != nil { + return l, err + } + + if c == '>' { + l.Cmd.Target, err = ReadIdentifier(b) + if err != nil { + return l, err + } + + c, _, err = b.ReadRune() + if err != nil { + return l, err + } + } + + if c == ' ' { + for { + o, err := ReadObject(b) + if err != nil { + return l, err + } + l.Cmd.Args = append(l.Cmd.Args, o) + + c, _, err := b.ReadRune() + if err != nil { + return l, err + } + if c == ';' { + break + } + } + } else if c != ';' { + return l, SyntaxError + } + + c, _, err = b.ReadRune() + if err != nil { + return l, err + } else if c != '\n' { + return l, SyntaxError + } + + return l, nil +} + +func WriteLiteral(b *bufio.Writer, s string) error { + for _, c := range s { + if strings.IndexRune(",;\n\\", c) != -1 { + _, err := b.WriteRune('\\'); + if err != nil { + return err + } + } + _, err := b.WriteRune(c); + if err != nil { + return err + } + } + return nil +} + +func WriteIdentifier(b *bufio.Writer, s string) error { + for _, c := range s { + if strings.IndexRune("?!*<>:\\,#@~;\n\t ", c) != -1 { + panic("invalid character in identifier") + } + _, err := b.WriteRune(c); + if err != nil { + return err + } + } + return nil +} + +func WriteObject(b *bufio.Writer, o Object) error { + err := WriteIdentifier(b, o.Kind) + if err != nil { + return err + } + if o.Id != "" { + b.WriteRune('#') + err := WriteIdentifier(b, o.Id) + if err != nil { + return err + } + } + if len(o.Fields) != 0 { + b.WriteRune(' ') + } + comma := false; + for k, v := range o.Fields { + if comma { + b.WriteRune(',') + } + err := WriteIdentifier(b, k) + if err != nil { + return err + } + b.WriteRune(':') + err = WriteLiteral(b, v) + if err != nil { + return err + } + comma = true + } + return nil +} + +func WriteLine(b *bufio.Writer, l Line) error { + _, err := b.WriteRune(l.Kind) + if err != nil { + return err + } + switch l.Kind { + case '!', '?': + err := WriteIdentifier(b, l.RequestId) + if err != nil { + return err + } + b.WriteRune(' ') + case '*': + } + + err = WriteIdentifier(b, l.Cmd.Kind) + if err != nil { + return err + } + if l.Cmd.Target != "" { + b.WriteRune('>') + err = WriteIdentifier(b, l.Cmd.Target) + if err != nil { + return err + } + } + + if len(l.Cmd.Args) != 0 { + b.WriteRune(' ') + } + for i, o := range l.Cmd.Args { + err := WriteObject(b, o) + if err != nil { + return err + } + if i < len(l.Cmd.Args) - 1 { + b.WriteRune('\n') + } + } + _, err = b.WriteString(";\n") + if err != nil { + return err + } + return b.Flush() +} + +func ReadLines(b *bufio.Reader) (<-chan Line, <-chan error) { + out, errChan := make(chan Line, 1), make(chan error, 1) + go func() { + for { + l, err := ReadLine(b) + if err != nil { + errChan <- err + close(out) + return + } else { + out <- l + } + } + }() + return out, errChan +} + +func WriteLines(b *bufio.Writer, lines <-chan Line) <-chan error { + errChan := make(chan error, 1) + go func() { + for l := range lines { + err := WriteLine(b, l) + if err != nil { + errChan <- err + return + } + } + close(errChan) + }() + return errChan +} |
