package walk import ( "fmt" "strings" "math" "unicode/utf8" "bufio" ) // int or string type PathSegment interface {} func stringPathSegment(segment PathSegment) string { return fmt.Sprintf("%v", segment) } type Path []PathSegment func (path Path) ToWalkValues() []Value { var values []Value for _, segment := range path { switch s := segment.(type) { case int: values = append(values, ValueNumber(s)) case string: values = append(values, ValueString(s)) default: panic("Invalid PathSegment") } } return values } func PathFromWalkValues(values []Value) Path { var segments []PathSegment for _, value := range values { switch v := value.(type) { case ValueNumber: segments = append(segments, int(math.Round(float64(v)))) case ValueString: segments = append(segments, string(v)) default: panic("Invalid value in path") } } return segments } type WalkItem struct { Value []Atom Path []Atom } 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 Atom) { state := out.structure[len(out.structure) - 1] switch state { case JSONOutRoot, JSONOutMap, JSONOutArray: switch atom.Typ { case AtomNull, AtomBool, 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 AtomStringTerminal: out.indent(0) if state == JSONOutMap { fmt.Fprintf(out.writer, "%q: ", key) } fmt.Fprint(out.writer, "\"") out.structure = append(out.structure, JSONOutString) case AtomTerminal: switch ValueTerminal(atom.data) { case 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 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 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 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 == AtomTerminal && ValueTerminal(atom.data) == 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 == AtomTerminal && ValueTerminal(atom.data) == 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 == 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 Path, values []Atom) { var segment PathSegment if len(path) > 0 { segment = path[len(path) - 1] } segmentString := stringPathSegment(segment) for _, atom := range values { out.atomOut(segmentString, atom) } } 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, } } func ConcatData(first []Atom, second []Atom) []Atom { res := make([]Atom, 0, len(first) + len(second)) res = append(res, first...) res = append(res, second...) return res } func Atomise(in []Value) (out []Atom) { numAtoms := 0 for _, value := range in { switch v := value.(type) { case ValueTerminal, ValueNull, ValueBool, ValueNumber: numAtoms++ case ValueString: numAtoms += utf8.RuneCountInString(string(v)) + 2 default: panic("Invalid WalkValue") } } out = make([]Atom, 0, numAtoms) for _, value := range in { out = value.Atomise(out) } return out } type CompoundError int const ( CompoundRuneOutsideString CompoundError = iota CompoundUnknownAtom CompoundMissingEnd CompoundInvalidStringAtom ) func (err CompoundError) Error() string { switch err { case CompoundRuneOutsideString: return "Compound Error: Rune Outside String" case CompoundUnknownAtom: return "Compound Error: Unknown Atom" case CompoundMissingEnd: return "Compound Error: Missing End" case CompoundInvalidStringAtom: return "Compound Error: Invalid String Atom" default: panic("Invalid CompoundError") } } type CompoundResult struct { value Value error error } func Compound(in []Atom) (out []Value, error error) { numValues := 0 i := 0 inString := false for _, atom := range in { switch atom.Typ { case AtomNull, AtomBool, AtomNumber, AtomTerminal: if !inString { numValues++ } case AtomStringTerminal: if inString { numValues++ } inString = !inString } } i = 0 out = make([]Value, 0, numValues) for { if i >= len(in) { break } atom := in[i] i++ switch atom.Typ { case AtomNull: out = append(out, ValueNull{}) continue case AtomBool: out = append(out, ValueBool(atom.data != 0)) continue case AtomNumber: out = append(out, ValueNumber(math.Float64frombits(atom.data))) continue case AtomTerminal: out = append(out, ValueTerminal(atom.data)) continue case AtomStringRune: return nil, CompoundRuneOutsideString case AtomStringTerminal: default: return nil, CompoundUnknownAtom } // Handle string start var builder strings.Builder for { if i >= len(in) { return nil, CompoundMissingEnd } atom := in[i] i++ if atom.Typ == AtomStringTerminal { break } builder.WriteString(atom.String()) } out = append(out, ValueString(builder.String())) } return out, nil }