package subex import ( "os" "fmt" "bufio" "main/walk" ) // Where slots are stored type Store map[rune][]walk.Atom // Return a new store with all the data from this one func (store Store) clone() Store { newStore := make(Store) for key, val := range store { newStore[key] = val } return newStore } // Return a copy of this store but with an additional slot set func (store Store) withValue(key rune, value []walk.Atom) Store { newStore := store.clone() newStore[key] = value return newStore } // Compile the SubexAST into a transducer SubexState that can be run func CompileTransducer(transducerAst SubexAST) SubexState { return transducerAst.compileWith(&SubexNoneState{}) } // An immutable stack for outputting to type OutputStack interface { pop() ([]walk.Atom, OutputStack) push(atoms []walk.Atom) OutputStack } type OutputStackNil struct {} func (stack OutputStackNil) pop() ([]walk.Atom, OutputStack) { panic("Tried to pop from an empty OutputStack") } func (stack OutputStackNil) push(atoms []walk.Atom) OutputStack { return &OutputStackCons { head: atoms, tail: stack, } } type OutputStackCons struct { head []walk.Atom tail OutputStack } func (stack OutputStackCons) pop() ([]walk.Atom, OutputStack) { return stack.head, stack.tail } func (stack OutputStackCons) push(atoms []walk.Atom) OutputStack { return &OutputStackCons { head: atoms, tail: stack, } } func topAppend(outputStack OutputStack, atoms []walk.Atom) OutputStack { head, tail := outputStack.pop() return tail.push(walk.ConcatData(head, atoms)) } // One branch of subex execution type SubexBranch struct { // Content of slots in this branch store Store // State in this branch state SubexState // The output stack. At the end of the program, whatever is on top of this will be output // States may push or pop to the stack as they wish, creating sort of a call stack that allows states to capture the output of other states outputStack OutputStack } // Read a single character and return all the branches resulting from this branch consuming it func (pair SubexBranch) eat(char walk.Atom) []SubexBranch { return pair.state.eat(pair.store, pair.outputStack, char) } func (pair SubexBranch) accepting() []OutputStack { return pair.state.accepting(pair.store, pair.outputStack) } func equalStates(left SubexBranch, right SubexBranch) bool { // Only care about if they are the same pointer return left.state == right.state } // If two branches have the same state, only the first has a chance of being successful // This function removes all of the pointless execution branches to save execution time func pruneStates(states []SubexBranch) (newStates []SubexBranch) { outer: for _, state := range states { for _, newState := range newStates { if equalStates(state, newState) { continue outer } } newStates = append(newStates, state) } return newStates } // Run the subex transducer func RunTransducer(transducer SubexState, input <-chan walk.Atom) (output []walk.Atom, err bool) { states := []SubexBranch{{ state: transducer, outputStack: OutputStackNil{}.push(nil), store: make(Store), }} for piece := range input { var newStates []SubexBranch for _, state := range states { newStates = append(newStates, state.eat(piece)...) } states = pruneStates(newStates) } for _, state := range states { acceptingStacks := state.accepting() for _, stack := range acceptingStacks { output, _ := stack.pop() return output, false } } return nil, true } func Main() { if len(os.Args) != 2 { panic("Expected: program [subex]") } program := os.Args[1] ast := Parse(program) transducer := CompileTransducer(ast) stdin := bufio.NewReader(os.Stdin); jsonStream := walk.Json(stdin); tokenStream := make(chan walk.WalkValue) go func(in <-chan walk.WalkItem, out chan<- walk.WalkValue) { for item := range in { out<-item.Value } close(out) }(jsonStream, tokenStream) atoms := walk.Atomise(tokenStream) output, err := RunTransducer(transducer, atoms) if err { fmt.Println("Error") return } valueOut, error := walk.MemoryCompound(output) if error != nil { fmt.Println(error.Error()) return } for _, value := range valueOut { fmt.Println(value) } }