package walk import ( "io" "encoding/json" "fmt" "strings" ) type PathSegment interface {} type Path []PathSegment type TerminalValue int const ( ArrayBegin TerminalValue = iota ArrayEnd MapBegin MapEnd ) func (value TerminalValue) Pieces(out chan<- Atom) { out<-value } func (value TerminalValue) String() string { switch value { case ArrayBegin: return "[" case ArrayEnd: return "]" case MapBegin: return "{" case MapEnd: return "}" default: panic("Unknown TerminalValue") } } type ValueNull struct {} func (value ValueNull) Pieces(out chan<- Atom) { out<-value } func (value ValueNull) String() string { return "null" } type ValueBool bool func (value ValueBool) Pieces(out chan<- Atom) { out<-value } func (value ValueBool) String() string { if value { return "true" } else { return "false" } } type ValueNumber float64 func (value ValueNumber) Pieces(out chan<- Atom) { out<-value } func (value ValueNumber) String() string { v := float64(value) return fmt.Sprintf("%f", v) } type StartString struct {} type EndString struct {} type ValueString string func (value ValueString) Pieces(out chan<- Atom) { out<-StartString{} for _, char := range value { out<-char } out<-EndString{} } func (value ValueString) String() string { return fmt.Sprintf("\"%s\"", string(value)) } type Atom interface {} type WalkValue interface { Pieces(out chan<- Atom) String() string } type WalkItem struct { Value WalkValue Path Path } type WalkItemStream struct { channel chan WalkItem rewinds []WalkItem } func (stream *WalkItemStream) next() (WalkItem, bool) { if len(stream.rewinds) == 0 { item, hasItem := <- stream.channel return item, hasItem } item := stream.rewinds[len(stream.rewinds)-1] stream.rewinds = stream.rewinds[0:len(stream.rewinds)-1] return item, true } func (stream *WalkItemStream) rewind(item WalkItem) { stream.rewinds = append(stream.rewinds, item) } func (stream *WalkItemStream) peek() (WalkItem, bool) { item, hasItem := stream.next() if !hasItem { return item, false } stream.rewind(item) return item, true } func tokenToValue(token json.Token) WalkValue { switch token.(type) { case nil: return ValueNull {} case bool: return ValueBool(token.(bool)) case float64: return ValueNumber(token.(float64)) case string: return ValueString(token.(string)) default: panic("Can't convert JSON token to value") } } func readValue(dec *json.Decoder, path Path, out chan WalkItem) bool { if !dec.More() { return true } t, err := dec.Token() if err == io.EOF { return true } else if err != nil { panic("Invalid JSON") } switch t.(type) { case nil, string, float64, bool: v := tokenToValue(t) out <- WalkItem {v, path} return false case json.Delim: switch rune(t.(json.Delim)) { case '[': out <- WalkItem {ArrayBegin, path} index := 0 for dec.More() { empty := readValue(dec, append(path, index), out) if empty { break } index += 1 } t, err := dec.Token() if err != nil { panic("Invalid JSON") } delim, isDelim := t.(json.Delim) if !isDelim || delim != ']' { panic("Expected ] in JSON") } out <- WalkItem{ArrayEnd, path} return false case '{': out <- WalkItem {MapBegin, path} for dec.More() { t, _ := dec.Token() key, keyIsString := t.(string) if !keyIsString { panic("Invalid JSON") } empty := readValue(dec, append(path, key), out) if empty { panic("Invalid JSON") } } t, err := dec.Token() if err != nil { panic("Invalid JSON") } delim, isDelim := t.(json.Delim) if !isDelim || delim != '}' { panic("Expected } in JSON") } out <- WalkItem {MapEnd, path} return false default: panic("Error parsing JSON") } default: panic("Invalid JSON token") } } func startWalk(dec *json.Decoder, out chan WalkItem) { isEmpty := readValue(dec, nil, out) if isEmpty { panic("Missing JSON input") } close(out) } func Json(r io.Reader) chan WalkItem { dec := json.NewDecoder(r) out := make(chan WalkItem) go startWalk(dec, out) return out } func printIndent(indent int) { for i := 0; i < indent; i += 1 { fmt.Print("\t") } } func jsonOutArray(in *WalkItemStream, indent int) { fmt.Println("[") token, hasToken := in.next() if !hasToken { panic("Missing ] in output JSON") } terminal, isTerminal := token.Value.(TerminalValue) if isTerminal && terminal == ArrayEnd { fmt.Print("\n") printIndent(indent) fmt.Print("]") return } in.rewind(token) for { valueToken := jsonOutValue(in, indent + 1, true) if valueToken != nil { panic("Missing value in output JSON array") } token, hasToken := in.next() if !hasToken { panic("Missing ] in output JSON") } terminal, isTerminal := token.Value.(TerminalValue) if isTerminal && terminal == ArrayEnd { fmt.Print("\n") printIndent(indent) fmt.Print("]") return } in.rewind(token) fmt.Println(",") } } func jsonOutMap(in *WalkItemStream, indent int) { fmt.Println("{") token, hasToken := in.next() if !hasToken { panic("Missing } in output JSON") } terminal, isTerminal := token.Value.(TerminalValue) if isTerminal && terminal == MapEnd { fmt.Print("\n") printIndent(indent) fmt.Print("}") return } in.rewind(token) for { keyToken, hasKeyToken := in.peek() if !hasKeyToken { panic("Missing map element") } printIndent(indent + 1) if len(keyToken.Path) == 0 { panic("Map element missing key") } key := keyToken.Path[len(keyToken.Path)-1] switch key.(type) { case int: fmt.Print(key.(int)) case string: fmt.Printf("%q", key.(string)) default: panic("Invalid path segment") } fmt.Print(": ") valueToken := jsonOutValue(in, indent + 1, false) if valueToken != nil { panic("Missing value int output JSON map") } token, hasToken := in.next() if !hasToken { panic("Missing } in output JSON") } terminal, isTerminal := token.Value.(TerminalValue) if isTerminal && terminal == MapEnd { fmt.Print("\n") printIndent(indent) fmt.Print("}") return } in.rewind(token) fmt.Println(",") } } func jsonOutValue(in *WalkItemStream, indent int, doIndent bool) WalkValue { token, hasToken := in.next() if !hasToken { panic("Missing JSON token in output") } switch v := token.Value.(type) { case ValueNull: if doIndent { printIndent(indent) } fmt.Printf("null") return nil case ValueBool: if doIndent { printIndent(indent) } if token.Value.(ValueBool) { fmt.Print("true") } else { fmt.Print("false") } return nil case ValueNumber: if doIndent { printIndent(indent) } fmt.Printf("%v", token.Value) return nil case ValueString: if doIndent { printIndent(indent) } fmt.Printf("%q", string(v)) return nil case TerminalValue: switch token.Value.(TerminalValue) { case ArrayBegin: if doIndent { printIndent(indent) } jsonOutArray(in, indent) return nil case MapBegin: if doIndent { printIndent(indent) } jsonOutMap(in, indent) return nil default: return token.Value } default: panic("Invalid WalkValue") } } func JsonOut(in chan WalkItem) { stream := WalkItemStream { channel: in, rewinds: nil, } if jsonOutValue(&stream, 0, true) != nil { panic("Invalid output JSON") } fmt.Print("\n") } func ConcatData(first []Atom, second []Atom) []Atom { return append(append([]Atom(nil), first...), second...) } func Atomise(in <-chan WalkValue) <-chan Atom { out := make(chan Atom) go func(out chan<- Atom, input <-chan WalkValue) { for value := range input { value.Pieces(out) } close(out) }(out, in) return out } func Compound(in <-chan Atom) <-chan WalkValue { out := make(chan WalkValue) go func(out chan<- WalkValue, in <-chan Atom) { for { atom, hasAtom := <-in if !hasAtom { break } switch v := atom.(type) { case TerminalValue: out<-v continue case ValueNull: out<-v continue case ValueBool: out<-v continue case ValueNumber: out<-v continue case rune: panic("Error! Rune output by subex but not in a string") case EndString: panic("Error! subex output an EndString before BeginString") case StartString: default: panic("Unknown atom type") } // Handle string start var builder strings.Builder loop: for { atom, hasAtom := <-in if !hasAtom { panic("Missing EndString") } switch v := atom.(type) { case EndString: break loop case rune: builder.WriteRune(v) default: panic("Invalid atom in string") } } out<-ValueString(builder.String()) } close(out) }(out, in) return out } func MemoryCompound(in []Atom) (out []WalkValue) { inChan := make(chan Atom) go func(in []Atom, out chan<- Atom) { for _, atom := range in { out<-atom } close(out) }(in, inChan) outChan := Compound(inChan) for value := range outChan { out = append(out, value) } return out }