diff options
-rw-r--r-- | subex/main.go | 60 | ||||
-rw-r--r-- | subex/subexast.go | 16 | ||||
-rw-r--r-- | subex/subexstate.go | 211 |
3 files changed, 119 insertions, 168 deletions
diff --git a/subex/main.go b/subex/main.go index 2a5f5ad..4451a00 100644 --- a/subex/main.go +++ b/subex/main.go @@ -29,25 +29,58 @@ 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 - // Output so far in this branch - output []walk.Atom + // 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 { - states := pair.state.eat(pair.store, char) - for i := range states { - states[i].output = walk.ConcatData(pair.output, states[i].output) - } - return states + return pair.state.eat(pair.store, pair.outputStack, char) } -func (pair SubexBranch) accepting() [][]walk.Atom { - return pair.state.accepting(pair.store) +func (pair SubexBranch) accepting() []OutputStack { + return pair.state.accepting(pair.store, pair.outputStack) } func equalStates(left SubexBranch, right SubexBranch) bool { @@ -73,7 +106,7 @@ func pruneStates(states []SubexBranch) (newStates []SubexBranch) { func RunTransducer(transducer SubexState, input <-chan walk.Atom) (output []walk.Atom, err bool) { states := []SubexBranch{{ state: transducer, - output: nil, + outputStack: OutputStackNil{}.push(nil), store: make(Store), }} for piece := range input { @@ -84,9 +117,10 @@ func RunTransducer(transducer SubexState, input <-chan walk.Atom) (output []walk states = pruneStates(newStates) } for _, state := range states { - outputEnds := state.accepting() - for _, outputEnd := range outputEnds { - return walk.ConcatData(state.output, outputEnd), false + acceptingStacks := state.accepting() + for _, stack := range acceptingStacks { + output, _ := stack.pop() + return output, false } } return nil, true diff --git a/subex/subexast.go b/subex/subexast.go index 9e7067b..cec75f7 100644 --- a/subex/subexast.go +++ b/subex/subexast.go @@ -27,10 +27,11 @@ type SubexASTStore struct { slot rune } func (ast SubexASTStore) compileWith(next SubexState) SubexState { - return &SubexStoreState { - match: ast.match.compileWith(&SubexNoneState{}), - slot: ast.slot, - next: next, + return &SubexStoreBeginState { + next: ast.match.compileWith(&SubexStoreEndState { + slot: ast.slot, + next: next, + }), } } func (ast SubexASTStore) String() string { @@ -188,8 +189,9 @@ type SubexASTSum struct { content SubexAST } func (ast SubexASTSum) compileWith(next SubexState) SubexState { - return &SubexSumState { - inputState: ast.content.compileWith(&SubexNoneState{}), - next: next, + return &SubexSumBeginState { + next: ast.content.compileWith(&SubexSumEndState { + next: next, + }), } } diff --git a/subex/subexstate.go b/subex/subexstate.go index 855d44a..dbc8340 100644 --- a/subex/subexstate.go +++ b/subex/subexstate.go @@ -9,106 +9,48 @@ import ( // A state of execution for the transducer type SubexState interface { // Eat a Atom and transition to any number of new states - eat(store Store, char walk.Atom) []SubexBranch + eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch // Find accepting states reachable through epsilon transitions and return their outputs - accepting(store Store) [][]walk.Atom -} - -type SubexParentState interface { - SubexState - // Get the child - child() SubexState - // The child outputted output, what should be passed as accumulator data into the next version of the parent state - nextAcc(output []walk.Atom) interface{} - // Given the final accumulated data, run the next state after the parent, immutably borrows store - feedNext(acc interface{}, store Store, char walk.Atom) []SubexBranch - // Given the final accumulated data, get the accepted outputs from the next state, immutably borrows store - acceptNext(acc interface{}, store Store) [][]walk.Atom - // Given the next child and next accumulator data, generate the next parent - nextParent(child SubexState, acc interface{}) SubexState + accepting(store Store, outputStack OutputStack) []OutputStack } // Try first, if it fails then try second type SubexGroupState struct { first, second SubexState } -func (state SubexGroupState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexGroupState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { otherStore := store.clone() - return append(state.first.eat(store, char), state.second.eat(otherStore, char)...) + return append(state.first.eat(store, outputStack, char), state.second.eat(otherStore, outputStack, char)...) } -func (state SubexGroupState) accepting(store Store) [][]walk.Atom { - return append(state.first.accepting(store), state.second.accepting(store)...) +func (state SubexGroupState) accepting(store Store, outputStack OutputStack) []OutputStack { + return append(state.first.accepting(store, outputStack), state.second.accepting(store, outputStack)...) } -// Helper so states that are actually collections of states distinguished by a child state -// can pass eaten characters on to their children more easily -func feedChild(parent SubexParentState, store Store, char walk.Atom) (nextStates []SubexBranch) { - child := parent.child() - accepteds := child.accepting(store) - for _, accepted := range accepteds { - acc := parent.nextAcc(accepted) - nextStates = append(nextStates, parent.feedNext(acc, store, char)...) - } - nextChildren := child.eat(store, char) - for _, nextChild := range nextChildren { - acc := parent.nextAcc(nextChild.output) - nextStates = append(nextStates, SubexBranch{ - state: parent.nextParent(nextChild.state, acc), - output: nil, - store: nextChild.store, - }) - } - return nextStates +// Push an empty value onto the OutputStack and epsilon transition to next +// This value will be added to until SubexStoreEndState is reached when it will be stored +type SubexStoreBeginState struct { + next SubexState } - -func acceptChild(parent SubexParentState, store Store) (outputs [][]walk.Atom) { - child := parent.child() - accepteds := child.accepting(store) - for _, accepted := range accepteds { - acc := parent.nextAcc(accepted) - outputs = append(outputs, parent.acceptNext(acc, store)...) - } - return outputs +func (state SubexStoreBeginState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { + return state.next.eat(store, outputStack.push(nil), char) +} +func (state SubexStoreBeginState) accepting(store Store, outputStack OutputStack) []OutputStack { + return state.next.accepting(store, outputStack.push(nil)) } -// Run the match machine and store the output in a slot for later use -// Output nothing -type SubexStoreState struct { - match SubexState +// Pop the top of the OutputStack which contains the stuff outputted since the start of the store +// This outputted data gets stored in a slot +type SubexStoreEndState struct { slot rune next SubexState - toStore []walk.Atom -} -func (state SubexStoreState) child() SubexState { - return state.match -} -func (state SubexStoreState) nextAcc(output []walk.Atom) interface{} { - return walk.ConcatData(state.toStore, output) -} -func (state SubexStoreState) feedNext(acc interface{}, store Store, char walk.Atom) []SubexBranch { - toStore := acc.([]walk.Atom) - nextStore := store.withValue(state.slot, toStore) - return state.next.eat(nextStore, char) -} -func (state SubexStoreState) acceptNext(acc interface{}, store Store) [][]walk.Atom { - toStore := acc.([]walk.Atom) - nextStore := store.withValue(state.slot, toStore) - return state.next.accepting(nextStore) -} -func (state SubexStoreState) nextParent(match SubexState, acc interface{}) SubexState { - toStore := acc.([]walk.Atom) - return &SubexStoreState { - match: match, - slot: state.slot, - next: state.next, - toStore: toStore, - } } -func (state SubexStoreState) eat(store Store, char walk.Atom) (nextStates []SubexBranch) { - return feedChild(state, store, char) +func (state SubexStoreEndState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { + toStore, newStack := outputStack.pop() + return state.next.eat(store.withValue(state.slot, toStore), newStack, char) } -func (state SubexStoreState) accepting(store Store) (outputs [][]walk.Atom) { - return acceptChild(state, store) +func (state SubexStoreEndState) accepting(store Store, outputStack OutputStack) []OutputStack { + toStore, newStack := outputStack.pop() + return state.next.accepting(store.withValue(state.slot, toStore), newStack) } // A part of an output literal, either an Atom or a slot from which to load @@ -146,38 +88,32 @@ func (state SubexOutputState) build(store Store) []walk.Atom { } return result } -func (state SubexOutputState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexOutputState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { content := state.build(store) - nextStates := state.next.eat(store, char) - for i := range nextStates { - nextStates[i].output = walk.ConcatData(content, nextStates[i].output) - } + nextStates := state.next.eat(store, topAppend(outputStack, content), char) return nextStates } -func (state SubexOutputState) accepting(store Store) [][]walk.Atom { +func (state SubexOutputState) accepting(store Store, outputStack OutputStack) []OutputStack { content := state.build(store) - outputs := state.next.accepting(store) - for i := range outputs { - outputs[i] = walk.ConcatData(content, outputs[i]) - } - return outputs + outputStacks := state.next.accepting(store, topAppend(outputStack, content)) + return outputStacks } // A final state, transitions to nothing but is accepting type SubexNoneState struct {} -func (state SubexNoneState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexNoneState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { return nil } -func (state SubexNoneState) accepting(store Store) [][]walk.Atom { - return [][]walk.Atom{nil} +func (state SubexNoneState) accepting(store Store, outputStack OutputStack) []OutputStack { + return []OutputStack{outputStack} } // A dead end state, handy for making internals work nicer but technically redundant type SubexDeadState struct {} -func (state SubexDeadState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexDeadState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { return nil } -func (state SubexDeadState) accepting (store Store) [][]walk.Atom { +func (state SubexDeadState) accepting (store Store, outputStack OutputStack) []OutputStack { return nil } @@ -186,18 +122,18 @@ type SubexCopyAtomState struct { atom walk.Atom next SubexState } -func (state SubexCopyAtomState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexCopyAtomState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { // TODO can I compare Atom values with == ? if char == state.atom { return []SubexBranch{{ state: state.next, - output: []walk.Atom{char}, + outputStack: topAppend(outputStack, []walk.Atom{char}), store: store, }} } return nil } -func (state SubexCopyAtomState) accepting(store Store) [][]walk.Atom { +func (state SubexCopyAtomState) accepting(store Store, outputStack OutputStack) []OutputStack { return nil } @@ -205,14 +141,14 @@ func (state SubexCopyAtomState) accepting(store Store) [][]walk.Atom { type SubexCopyAnyState struct { next SubexState } -func (state SubexCopyAnyState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexCopyAnyState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { return []SubexBranch{{ state: state.next, - output: []walk.Atom{char}, + outputStack: topAppend(outputStack, []walk.Atom{char}), store: store, }} } -func (state SubexCopyAnyState) accepting(store Store) [][]walk.Atom { +func (state SubexCopyAnyState) accepting(store Store, outputStack OutputStack) []OutputStack { return nil } @@ -222,19 +158,19 @@ type SubexRangeState struct { parts map[walk.Atom]walk.Atom next SubexState } -func (state SubexRangeState) eat(store Store, char walk.Atom) []SubexBranch { +func (state SubexRangeState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { out, exists := state.parts[char] if !exists { return nil } else { return []SubexBranch{{ state: state.next, - output: []walk.Atom{out}, + outputStack: topAppend(outputStack, []walk.Atom{out}), store: store, }} } } -func (state SubexRangeState) accepting(store Store) [][]walk.Atom { +func (state SubexRangeState) accepting(store Store, outputStack OutputStack) []OutputStack { return nil } @@ -267,56 +203,35 @@ func sumValues(atoms []walk.Atom) (walk.ValueNumber, error) { return walk.ValueNumber(sum), nil } -// Run the inputState machine and sum any values output, output the sum -// Cast non numbers into numbers, branch dies if it is not castable -type SubexSumState struct { - inputState SubexState +// At the start of a sum, just pushes to the OutputStack allowing the end to capture what was output in the middle +// Tries to cast values to numbers to sum them and rejects if values are not castable +type SubexSumBeginState struct { next SubexState - acc []walk.Atom } -func (state SubexSumState) child() SubexState { - return state.inputState +func (state SubexSumBeginState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { + return state.next.eat(store, outputStack.push(nil), char) } -func (state SubexSumState) nextAcc(output []walk.Atom) interface{} { - return walk.ConcatData(state.acc, output) +func (state SubexSumBeginState) accepting(store Store, outputStack OutputStack) []OutputStack { + return state.next.accepting(store, outputStack.push(nil)) } -func (state SubexSumState) feedNext(acc interface{}, store Store, char walk.Atom) []SubexBranch { - childOutput := acc.([]walk.Atom) - total, err := sumValues(childOutput) + +// At the end of a sum, pops what has been output since the start, sums and outputs it +type SubexSumEndState struct { + next SubexState +} +func (state SubexSumEndState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { + toSum, newStack := outputStack.pop() + sum, err := sumValues(toSum) if err != nil { return nil } - output := []walk.Atom{total} - nextStates := state.next.eat(store.clone(), char) - for i := range nextStates { - nextStates[i].output = walk.ConcatData(output, nextStates[i].output) - } - return nextStates + return state.next.eat(store, topAppend(newStack, []walk.Atom{sum}), char) } -func (state SubexSumState) acceptNext(acc interface{}, store Store) [][]walk.Atom { - childOutput := acc.([]walk.Atom) - total, err := sumValues(childOutput) +func (state SubexSumEndState) accepting(store Store, outputStack OutputStack) []OutputStack { + toSum, newStack := outputStack.pop() + sum, err := sumValues(toSum) if err != nil { return nil } - output := []walk.Atom{total} - outputs := state.next.accepting(store.clone()) - for i := range outputs { - outputs[i] = walk.ConcatData(output, outputs[i]) - } - return outputs -} -func (state SubexSumState) nextParent(child SubexState, acc interface{}) SubexState { - childOutput := acc.([]walk.Atom) - return &SubexSumState { - inputState: child, - next: state.next, - acc: childOutput, - } -} -func (state SubexSumState) eat(store Store, char walk.Atom) (nextStates []SubexBranch) { - return feedChild(state, store, char) -} -func (state SubexSumState) accepting(store Store) (outputs [][]walk.Atom) { - return acceptChild(state, store) + return state.next.accepting(store, topAppend(newStack, []walk.Atom{sum})) } |