package walk import ( "strings" "math" "unicode/utf8" ) // int or string type PathSegment interface {} 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 } 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 }