experimental-cli/project/parser.go

168 lines
3.6 KiB
Go

package main
import (
"bufio"
"strconv"
"strings"
)
type pigeonBodyItem struct {
key string
value string
}
type pigeonMessage struct {
author string
depth int64
kind string
prev string
body []pigeonBodyItem
signature string
}
type parserMode int
const (
parsingHeader parserMode = iota
parsingBody parserMode = iota
parsingFooter parserMode = iota
parsingDone parserMode = iota
parsingError parserMode = iota
)
type parserState struct {
mode parserMode
scanner *bufio.Scanner
buffer pigeonMessage
results []pigeonMessage
error error
}
type parserOutput struct {
/** `messages` is an array of messages. The messages are SHALLOW
verified. That means the message has a valid signature and syntax,
but `depth` and `prev` have not been scrutinized for validity. */
messages []pigeonMessage
/** `blobIndex` is a hash where keys represent each unique blob
foud in a bundle. This is required to avoid ingesting unwanted
blobs to disk. */
blobIndex map[string]bool
}
func newState(message string) parserState {
return parserState{
mode: parsingHeader,
scanner: bufio.NewScanner(strings.NewReader(message)),
}
}
func maybeIndexBlob(index map[string]bool, input string) {
if isBlob(input) {
index[input] = true
}
}
func parseMessage(message string) (parserOutput, error) {
empty := parserOutput{
messages: []pigeonMessage{},
blobIndex: map[string]bool{},
}
state := newState(message)
for state.scanner.Scan() {
// Exit early if any step produces an error.
if state.error != nil {
return empty, state.error
}
switch state.mode {
case parsingDone:
maybeContinue(&state)
case parsingHeader:
parseHeader(&state)
case parsingBody:
parseBody(&state)
case parsingFooter:
parseFooter(&state)
case parsingError:
break
}
}
if state.mode == parsingError {
return empty, state.error
}
blobIndex := map[string]bool{}
for _, msg := range state.results {
if getPeerStatus(msg.author) == following {
for _, pair := range msg.body {
maybeIndexBlob(blobIndex, pair.key)
maybeIndexBlob(blobIndex, pair.value)
}
}
}
output := parserOutput{messages: state.results, blobIndex: blobIndex}
return output, nil
}
func parseHeader(state *parserState) {
t := state.scanner.Text()
chunks := strings.Split(t, " ")
switch chunks[0] {
case "":
state.mode = parsingBody
return
case "author":
state.buffer.author = chunks[1]
return
case "kind":
state.buffer.kind = chunks[1]
return
case "prev":
state.buffer.prev = chunks[1]
return
case "depth":
depth, err := strconv.ParseInt(chunks[1], 10, 32)
if err != nil {
tpl := "Parsing bad depth in message %d: %q"
panicf(tpl, len(state.results), chunks[1])
}
state.buffer.depth = depth
return
default:
panicf("BAD HEADER: %q", t)
}
}
func parseBody(state *parserState) {
t := state.scanner.Text()
chunks := strings.Split(t, ":")
if chunks[0] == "" {
state.mode = parsingFooter
return
}
pair := pigeonBodyItem{key: chunks[0], value: chunks[1]}
state.buffer.body = append(state.buffer.body, pair)
return
}
func parseFooter(state *parserState) {
t := state.scanner.Text()
chunks := strings.Split(t, " ")
state.buffer.signature = chunks[1]
state.mode = parsingDone
err := verifyShallow(&state.buffer)
if err != nil {
panicf("Message verification failed for %s. %s", state.buffer.signature, err)
}
state.results = append(state.results, state.buffer)
state.buffer.body = []pigeonBodyItem{}
state.buffer = pigeonMessage{}
}
func maybeContinue(state *parserState) {
t1 := state.scanner.Text()
if t1 == "" {
state.mode = parsingHeader
}
}