package subex import ( "main/walk" "strconv" "errors" ) // A state of execution for the transducer type SubexState interface { // Eat a Atom and transition to any number of new states eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch // Find accepting states reachable through epsilon transitions and return their outputs 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, outputStack OutputStack, char walk.Atom) []SubexBranch { otherStore := store.clone() return append(state.first.eat(store, outputStack, char), state.second.eat(otherStore, outputStack, char)...) } func (state SubexGroupState) accepting(store Store, outputStack OutputStack) []OutputStack { return append(state.first.accepting(store, outputStack), state.second.accepting(store, outputStack)...) } // 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 (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)) } // 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 } 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 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 type OutputContent interface { // Given the current store, return the []Atom produced by the TransducerOutput build(Store) []walk.Atom } // An OutputContent which is just an Atom literal type OutputAtomLiteral struct { atom walk.Atom } func (replacement OutputAtomLiteral) build(store Store) []walk.Atom { return []walk.Atom{replacement.atom} } // An OutputContent which is a slot that is loaded from type OutputLoad struct { slot rune } func (replacement OutputLoad) build(store Store) []walk.Atom { return store[replacement.slot] } // Don't read in anything, just output the series of data and slots specified type SubexOutputState struct { content []OutputContent next SubexState } // Given a store, return what is outputted by an epsilon transition from this state func (state SubexOutputState) build(store Store) []walk.Atom { var result []walk.Atom for _, part := range state.content { result = append(result, part.build(store)...) } return result } func (state SubexOutputState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { content := state.build(store) nextStates := state.next.eat(store, topAppend(outputStack, content), char) return nextStates } func (state SubexOutputState) accepting(store Store, outputStack OutputStack) []OutputStack { content := state.build(store) 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, outputStack OutputStack, char walk.Atom) []SubexBranch { return 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, outputStack OutputStack, char walk.Atom) []SubexBranch { return nil } func (state SubexDeadState) accepting (store Store, outputStack OutputStack) []OutputStack { return nil } // Read in a specific Atom and output it type SubexCopyAtomState struct { atom walk.Atom next SubexState } 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, outputStack: topAppend(outputStack, []walk.Atom{char}), store: store, }} } return nil } func (state SubexCopyAtomState) accepting(store Store, outputStack OutputStack) []OutputStack { return nil } // Read in any Atom and output it type SubexCopyAnyState struct { next SubexState } func (state SubexCopyAnyState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { return []SubexBranch{{ state: state.next, outputStack: topAppend(outputStack, []walk.Atom{char}), store: store, }} } func (state SubexCopyAnyState) accepting(store Store, outputStack OutputStack) []OutputStack { return nil } // Read in an Atom and apply a map to generate an Atom to output // If the input isn't in the map transition to nothing type SubexRangeState struct { parts map[walk.Atom]walk.Atom next SubexState } 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, outputStack: topAppend(outputStack, []walk.Atom{out}), store: store, }} } } func (state SubexRangeState) accepting(store Store, outputStack OutputStack) []OutputStack { return nil } func sumValues(atoms []walk.Atom) (walk.WalkValue, error) { allBools := true var sum float64 = 0 var any bool = false values, err := walk.MemoryCompound(atoms) if err != nil { return walk.ValueNull{}, err } for _, value := range values { switch v := value.(type) { case walk.ValueNull: allBools = false case walk.ValueBool: if (bool(v)) { sum += 1 any = true } case walk.ValueNumber: allBools = false sum += float64(v) case walk.ValueString: allBools = false num, err := strconv.ParseFloat(string(v), 64) if err == nil { sum += num } else { return walk.ValueNull{}, errors.New("Tried to sum non-castable string") } default: return walk.ValueNull{}, errors.New("Tried to sum non-number") } } if allBools { return walk.ValueBool(any), nil } else { return walk.ValueNumber(sum), nil } } // 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 } func (state SubexSumBeginState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { return state.next.eat(store, outputStack.push(nil), char) } func (state SubexSumBeginState) accepting(store Store, outputStack OutputStack) []OutputStack { return state.next.accepting(store, outputStack.push(nil)) } // 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 } return state.next.eat(store, topAppend(newStack, []walk.Atom{sum}), char) } func (state SubexSumEndState) accepting(store Store, outputStack OutputStack) []OutputStack { toSum, newStack := outputStack.pop() sum, err := sumValues(toSum) if err != nil { return nil } return state.next.accepting(store, topAppend(newStack, []walk.Atom{sum})) }