<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod3
-rw-r--r--main/command.go38
-rw-r--r--main/filter.go34
-rw-r--r--main/json.go299
-rw-r--r--main/main.go139
-rw-r--r--main/pathfilter.go78
6 files changed, 591 insertions, 0 deletions
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..6967dfd
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module main
+
+go 1.18
diff --git a/main/command.go b/main/command.go
new file mode 100644
index 0000000..560d3c3
--- /dev/null
+++ b/main/command.go
@@ -0,0 +1,38 @@
+package main
+
+type PrintValueCommand struct {}
+func (cmd PrintValueCommand) exec(state *ProgramState) {
+ state.out <- state.space
+}
+
+type ToggleTerminalCommand struct {}
+func (cmd ToggleTerminalCommand) exec(state *ProgramState) {
+ terminal, isTerminal := state.space.value.(TerminalValue)
+ if !isTerminal {
+ return
+ }
+ switch terminal {
+ case ArrayBegin:
+ state.space.value = MapBegin
+ case ArrayEnd:
+ state.space.value = MapEnd
+ case MapBegin:
+ state.space.value = ArrayBegin
+ case MapEnd:
+ state.space.value = ArrayEnd
+ }
+}
+
+type FilteredCommand struct {
+ filter Filter
+ command Command
+}
+func (cmd FilteredCommand) exec(state *ProgramState) {
+ if cmd.filter.exec(state) {
+ cmd.command.exec(state)
+ }
+}
+
+type Command interface {
+ exec(*ProgramState)
+} \ No newline at end of file
diff --git a/main/filter.go b/main/filter.go
new file mode 100644
index 0000000..95e6d82
--- /dev/null
+++ b/main/filter.go
@@ -0,0 +1,34 @@
+package main
+
+type PathFilter struct {
+ initial PathFilterState
+}
+func (filter PathFilter) exec(state *ProgramState) bool {
+ pathFilterState := make(map[PathFilterState]struct{})
+ pathFilterState[filter.initial] = struct{}{}
+ for _, segment := range state.space.path {
+ nextPathFilterState := make(map[PathFilterState]struct{})
+ for curState := range pathFilterState {
+ for nextState := range curState.eat(segment) {
+ nextPathFilterState[nextState] = struct{}{}
+ }
+ }
+ pathFilterState = nextPathFilterState
+ }
+ for pathState := range pathFilterState {
+ if pathState.accept() {
+ return true
+ }
+ }
+ return false
+}
+
+type RangeFilter struct {
+ start Filter
+ end Filter
+ active bool
+}
+
+type Filter interface {
+ exec(*ProgramState) bool
+} \ No newline at end of file
diff --git a/main/json.go b/main/json.go
new file mode 100644
index 0000000..66ca5d5
--- /dev/null
+++ b/main/json.go
@@ -0,0 +1,299 @@
+package main
+
+import (
+ "io"
+ "encoding/json"
+ "fmt"
+)
+
+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 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", token.value)
+ 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
+ }
+ 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")
+}
diff --git a/main/main.go b/main/main.go
new file mode 100644
index 0000000..31e46c6
--- /dev/null
+++ b/main/main.go
@@ -0,0 +1,139 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "bufio"
+)
+
+type PathSegment interface {}
+type Path []PathSegment
+
+type TerminalValue int
+const (
+ ArrayBegin TerminalValue = iota
+ ArrayEnd
+ MapBegin
+ MapEnd
+)
+type ValueNull struct {}
+type ValueBool bool
+type ValueNumber float64
+type ValueString string
+
+type WalkValue interface {}
+
+type Program []Command
+
+type ProgramState struct {
+ space WalkItem
+ in chan WalkItem
+ out chan WalkItem
+ program []Command
+}
+
+type StringSegmentPathFilterAST struct {
+ index string
+}
+func (ast StringSegmentPathFilterAST) compileWith(next PathFilterState) PathFilterState {
+ return StringSegmentPathFilter {
+ index: ast.index,
+ next: next,
+ }
+}
+
+type RepeatPathFilterAST struct {
+ content PathFilterAST
+}
+func (ast RepeatPathFilterAST) compileWith(next PathFilterState) PathFilterState {
+ nextGroup := &GroupPathFilter{}
+ repeatStart := ast.content.compileWith(nextGroup)
+ nextGroup.filters = []PathFilterState{next, repeatStart}
+ return nextGroup
+}
+
+type SequencePathFilterAST struct {
+ sequence []PathFilterAST
+}
+func (ast SequencePathFilterAST) compileWith(next PathFilterState) PathFilterState {
+ for i := len(ast.sequence) - 1; i >= 0; i -= 1 {
+ next = ast.sequence[i].compileWith(next)
+ }
+ return next
+}
+
+type AnySegmentPathFilterAST struct {}
+func (ast AnySegmentPathFilterAST) compileWith(next PathFilterState) PathFilterState {
+ return AnySegmentPathFilter{next: next}
+}
+
+type PathFilterAST interface {
+ compileWith(PathFilterState) PathFilterState
+}
+
+func compilePathFilterAST(ast PathFilterAST) PathFilter {
+ return PathFilter{
+ initial: ast.compileWith(NonePathFilter{}),
+ }
+}
+
+func main() {
+ if len(os.Args) < 2 {
+ fmt.Println("Missing program arg")
+ return
+ }
+ //input := os.Args[1]
+ //tokens := Lex(input)
+ //program := Parse(tokens)
+
+ stdin := bufio.NewReader(os.Stdin)
+ dataStream := Json(stdin)
+
+ var allRemainingPathFilter AnySegmentPathFilter
+ {
+ g := GroupPathFilter {
+ filters: []PathFilterState{NonePathFilter{}},
+ }
+ allRemainingPathFilter = AnySegmentPathFilter {
+ next: PathFilterState(&g),
+ }
+ g.filters = append(g.filters, PathFilterState(&allRemainingPathFilter))
+ }
+
+ state := ProgramState {
+ in: dataStream,
+ out: make(chan WalkItem),
+ program: []Command {
+ FilteredCommand {
+ filter: compilePathFilterAST(
+ StringSegmentPathFilterAST {"people"},
+ ),
+ command: PrintValueCommand{},
+ },
+ FilteredCommand {
+ filter: compilePathFilterAST(
+ SequencePathFilterAST {
+ []PathFilterAST{
+ StringSegmentPathFilterAST {"people"},
+ AnySegmentPathFilterAST{},
+ StringSegmentPathFilterAST {"age"},
+ },
+ },
+ ),
+ command: PrintValueCommand{},
+ },
+ },
+ }
+
+ go func () {
+ for walkItem := range dataStream {
+ state.space = walkItem
+ for _, cmd := range state.program {
+ cmd.exec(&state)
+ }
+ }
+ close(state.out)
+ }()
+
+ JsonOut(state.out)
+}
diff --git a/main/pathfilter.go b/main/pathfilter.go
new file mode 100644
index 0000000..7b6c64f
--- /dev/null
+++ b/main/pathfilter.go
@@ -0,0 +1,78 @@
+package main
+
+type MapTerminalFilter struct {}
+func (filter MapTerminalFilter) exec(state *ProgramState) bool {
+ terminal, isTerminal := state.space.value.(TerminalValue)
+ if !isTerminal {
+ return false
+ }
+ return terminal == MapBegin || terminal == MapEnd
+}
+
+type NonTerminalFilter struct {}
+func (filter NonTerminalFilter) exec(state *ProgramState) bool {
+ _, isTerminal := state.space.value.(TerminalValue)
+ return !isTerminal
+}
+
+type AnySegmentPathFilter struct {
+ next PathFilterState
+}
+func (filter AnySegmentPathFilter) eat(segment PathSegment) map[PathFilterState]struct{} {
+ res := make(map[PathFilterState]struct{})
+ res[filter.next] = struct{}{}
+ return res
+}
+func (filter AnySegmentPathFilter) accept() bool {
+ return false
+}
+
+type GroupPathFilter struct {
+ filters []PathFilterState
+}
+func (filter GroupPathFilter) eat(segment PathSegment) map[PathFilterState]struct{} {
+ res := make(map[PathFilterState]struct{})
+ for _, f := range filter.filters {
+ for r := range f.eat(segment) {
+ res[r] = struct{}{}
+ }
+ }
+ return res
+}
+func (filter GroupPathFilter) accept() bool {
+ for _, f := range filter.filters {
+ if f.accept() {
+ return true
+ }
+ }
+ return false
+}
+
+type NonePathFilter struct {}
+func (filter NonePathFilter) eat(segment PathSegment) map[PathFilterState]struct{} {
+ return make(map[PathFilterState]struct{})
+}
+func (filter NonePathFilter) accept() bool {
+ return true
+}
+
+type StringSegmentPathFilter struct {
+ index string
+ next PathFilterState
+}
+func (filter StringSegmentPathFilter) eat(segment PathSegment) map[PathFilterState]struct{} {
+ s, isString := segment.(string)
+ res := make(map[PathFilterState]struct{})
+ if isString && s == filter.index {
+ res[filter.next] = struct{}{}
+ }
+ return res
+}
+func (filter StringSegmentPathFilter) accept() bool {
+ return false
+}
+
+type PathFilterState interface {
+ eat(PathSegment) map[PathFilterState]struct{}
+ accept() bool
+}