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 }