package subex import ( "os" "fmt" "bufio" "main/walk" "strings" ) // A part of an insertion, either an Atom or a slot from which to load // TODO rename this type TransducerOutput interface { // Given the current store, return the []Atom produced by the TransducerOutput build(Store) []walk.Atom } // A TransducerOutput which is just an Atom literal type TransducerReplacementAtom struct { atom walk.Atom } func (replacement TransducerReplacementAtom) build(store Store) []walk.Atom { return []walk.Atom{replacement.atom} } // A TransducerOutput which is a slot that is loaded from type TransducerReplacementLoad struct { slot rune } func (replacement TransducerReplacementLoad) build(store Store) []walk.Atom { return store[replacement.slot] } // 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{}) } // One branch of subex execution type SubexBranch struct { // Content of slots in this branch store Store // State in this branch state SubexState // Output so far in this branch output []walk.Atom } // Read a single character and return all the branches resulting from this branch consuming it func (pair SubexBranch) eat(char walk.Atom) []SubexBranch { states := pair.state.eat(pair.store, char) for i := range states { states[i].output = walk.ConcatData(pair.output, states[i].output) } return states } func (pair SubexBranch) accepting() [][]walk.Atom { return pair.state.accepting(pair.store) } 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, output: 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 { outputEnds := state.accepting() for _, outputEnd := range outputEnds { return walk.ConcatData(state.output, outputEnd), false } } return nil, true } func Main() { if len(os.Args) != 2 { panic("Expected: program [subex]") } stdin := bufio.NewReader(os.Stdin); jsonStream := walk.Json(stdin); var tokens []walk.WalkValue; for token := range jsonStream { tokens = append(tokens, token.Value); } program := os.Args[1] ast := Parse(program) transducer := CompileTransducer(ast) pieces := make(chan walk.Atom) go func(out chan<- walk.Atom, input []walk.WalkValue) { for _, value := range input { value.Pieces(out) } close(out) }(pieces, tokens) output, err := RunTransducer(transducer, pieces) if !err { dataIn := make(chan walk.Atom) go func(out chan<- walk.Atom, in []walk.Atom) { for _, atom := range in { out<-atom } close(out) }(dataIn, output) valueOut := make(chan walk.WalkValue) go func(out chan<- walk.WalkValue, in <-chan walk.Atom) { for { atom, hasAtom := <-in if !hasAtom { break } switch v := atom.(type) { case walk.TerminalValue: out<-v continue case walk.ValueNull: out<-v continue case walk.ValueBool: out<-v continue case walk.ValueNumber: out<-v continue case rune: panic("Error! Rune output by subex but not in a string") case walk.EndString: panic("Error! subex output an EndString before BeginString") case walk.StartString: default: panic("Unknown atom type") } // Handle string start var builder strings.Builder loop: for { atom, hasAtom := <-in if !hasAtom { panic("Missing EndString") } switch v := atom.(type) { case walk.EndString: break loop case rune: builder.WriteRune(v) default: panic("Invalid atom in string") } } out<-walk.ValueString(builder.String()) } close(out) }(valueOut, dataIn) for value := range valueOut { fmt.Println(value) } } else { fmt.Println("Error") } }