package json_tokens import ( "fmt" "strings" "bufio" "main/walk" ) func stringPathSegment(segment walk.PathSegment) string { return fmt.Sprintf("%v", segment) } type JSONOutStructure int const ( JSONOutRoot JSONOutStructure = iota JSONOutMap JSONOutArray JSONOutString JSONOutValueEnd ) type JSONOut struct { structure []JSONOutStructure writer *bufio.Writer } func (out *JSONOut) indent(adjust int) { fmt.Fprint(out.writer, strings.Repeat("\t", len(out.structure) - 1 + adjust)) } func (out *JSONOut) atomOut(key string, atom walk.Atom) { state := out.structure[len(out.structure) - 1] switch state { case JSONOutRoot, JSONOutMap, JSONOutArray: switch atom.Typ { case walk.AtomNull, walk.AtomBool, walk.AtomNumber: out.indent(0) if state == JSONOutMap { fmt.Fprintf(out.writer, "%q: ", key) } fmt.Fprint(out.writer, atom.String()) out.structure = append(out.structure, JSONOutValueEnd) case walk.AtomStringTerminal: out.indent(0) if state == JSONOutMap { fmt.Fprintf(out.writer, "%q: ", key) } fmt.Fprint(out.writer, "\"") out.structure = append(out.structure, JSONOutString) case walk.AtomTerminal: switch atom.Terminal() { case walk.MapBegin: out.indent(0) if state == JSONOutMap { fmt.Fprintf(out.writer, "%q: ", key) } fmt.Fprint(out.writer, "{\n") out.structure = append(out.structure, JSONOutMap) case walk.ArrayBegin: out.indent(0) if state == JSONOutMap { fmt.Fprintf(out.writer, "%q: ", key) } fmt.Fprint(out.writer, "[\n") out.structure = append(out.structure, JSONOutArray) case walk.MapEnd: out.indent(-1) if state != JSONOutMap { panic("Map ended while not inside a map") } fmt.Fprint(out.writer, "}") out.structure[len(out.structure) - 1] = JSONOutValueEnd case walk.ArrayEnd: out.indent(-1) if state != JSONOutArray { panic("Array ended while not inside a array") } fmt.Fprint(out.writer, "]") out.structure[len(out.structure) - 1] = JSONOutValueEnd default: panic("Invalid TerminalValue") } default: panic("Invalid AtomType in root value") } case JSONOutValueEnd: out.structure = out.structure[:len(out.structure) - 1] underState := out.structure[len(out.structure) - 1] if underState == JSONOutMap && atom.Typ == walk.AtomTerminal && atom.Terminal() == walk.MapEnd { fmt.Fprint(out.writer, "\n") out.indent(-1) fmt.Fprint(out.writer, "}") out.structure[len(out.structure) - 1] = JSONOutValueEnd } else if underState == JSONOutArray && atom.Typ == walk.AtomTerminal && atom.Terminal() == walk.ArrayEnd { fmt.Fprint(out.writer, "\n") out.indent(-1) fmt.Fprint(out.writer, "]") out.structure[len(out.structure) - 1] = JSONOutValueEnd } else if underState == JSONOutRoot { panic("Tried to output JSON after root value has concluded") } else { fmt.Fprint(out.writer, ",\n") out.atomOut(key, atom) } case JSONOutString: if atom.Typ == walk.AtomStringTerminal { fmt.Fprint(out.writer, "\"") out.structure[len(out.structure) - 1] = JSONOutValueEnd } else { fmt.Fprint(out.writer, atom.String()) } default: panic("Invalid JSONOutState") } } func (out *JSONOut) Print(path walk.Path, values []walk.Atom) { var segment walk.PathSegment if len(path) > 0 { segment = path[len(path) - 1] } segmentString := stringPathSegment(segment) for _, atom := range values { out.atomOut(segmentString, atom) } } func (out *JSONOut) Write(item walk.WalkItem) error { pathValues, err := walk.Compound(item.Path) if err != nil { return err } path := walk.PathFromWalkValues(pathValues) out.Print(path, item.Value) return nil } func (out *JSONOut) AssertDone() { out.writer.Flush() if len(out.structure) != 2 || out.structure[0] != JSONOutRoot || out.structure[1] != JSONOutValueEnd { panic("Program ended with incomplete JSON output") } } func NewJSONOut(writer *bufio.Writer) *JSONOut { return &JSONOut { structure: []JSONOutStructure{JSONOutRoot}, writer: writer, } }