diff options
| author | citrons <citrons@mondecitronne.com> | 2025-01-26 01:56:53 -0600 |
|---|---|---|
| committer | citrons <citrons@mondecitronne.com> | 2025-01-26 01:56:53 -0600 |
| commit | 10b8a79389e7073f6bd65695c3d05c77b825bc33 (patch) | |
| tree | b7e6dbd84b5b2c960ab8aafc1f99c3950d679e44 /proto | |
initial commit
Diffstat (limited to 'proto')
| -rw-r--r-- | proto/fail.go | 11 | ||||
| -rw-r--r-- | proto/id.go | 28 | ||||
| -rw-r--r-- | proto/protocol.go | 342 |
3 files changed, 381 insertions, 0 deletions
diff --git a/proto/fail.go b/proto/fail.go new file mode 100644 index 0000000..0b6366b --- /dev/null +++ b/proto/fail.go @@ -0,0 +1,11 @@ +package proto + +type Fail Object + +func (f Fail) Cmd() Command { + return NewCmd("fail", "", Object(f)) +} + +func (f Fail) Error() string { + return f.Kind +} diff --git a/proto/id.go b/proto/id.go new file mode 100644 index 0000000..22bb395 --- /dev/null +++ b/proto/id.go @@ -0,0 +1,28 @@ +package proto + +import ( + "time" + "strconv" +) + +var counter = make(chan uint8) +func init() { + go func() { + var i uint8 + for i = 0; true; i++ { + counter <- i + } + }() +} + +var epoch = time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) + +func GenId() string { + t := time.Now().UnixMilli() - epoch.UnixMilli() + id := uint64(t << 6) | uint64(<-counter & 63) + return strconv.FormatUint(id, 36) +} + +func Timestamp() string { + return strconv.FormatInt(time.Now().Unix(), 10) +} 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 +} |
