diff options
author | Charlie Stanton <charlie@shtanton.xyz> | 2024-03-29 09:49:26 +0000 |
---|---|---|
committer | Charlie Stanton <charlie@shtanton.xyz> | 2024-03-29 09:49:26 +0000 |
commit | 080a24e894f125d4f1741cfdcba7cb722304d209 (patch) | |
tree | 78c12af110a8a239b6a3b1f828e4f193fcb8cd32 | |
parent | 510a8c95ce112617c33f8dfb865e752db0716cb1 (diff) | |
download | stred-go-080a24e894f125d4f1741cfdcba7cb722304d209.tar |
Completely remove the path space
The new design uses deeply nested values in the value space instead.
-rw-r--r-- | main/command.go | 66 | ||||
-rw-r--r-- | main/main.go | 20 | ||||
-rw-r--r-- | main/parse.go | 17 | ||||
-rw-r--r-- | subex/arithmetic.go | 94 | ||||
-rw-r--r-- | subex/filter.go | 16 | ||||
-rw-r--r-- | subex/main.go | 297 | ||||
-rw-r--r-- | subex/main_test.go | 464 | ||||
-rw-r--r-- | subex/parse.go | 162 | ||||
-rw-r--r-- | subex/subexast.go | 287 | ||||
-rw-r--r-- | subex/subexast_test.go | 19 | ||||
-rw-r--r-- | subex/subexstate.go | 249 | ||||
-rw-r--r-- | subex/subexstate_test.go | 4 | ||||
-rw-r--r-- | walk/read.go | 4 | ||||
-rw-r--r-- | walk/walk.go | 49 |
14 files changed, 766 insertions, 982 deletions
diff --git a/main/command.go b/main/command.go index 5a898e2..1d089ee 100644 --- a/main/command.go +++ b/main/command.go @@ -13,12 +13,11 @@ type Command interface { type PrintValueCommand struct {} func (cmd PrintValueCommand) exec(state *ProgramState) { - err := state.out.Write(walk.WalkItem { - Path: state.path, - Value: state.value, - }) - if err != nil { - panic("Error while outputting") + for _, value := range state.value { + err := state.out.Write(value) + if err != nil { + panic("Error while outputting") + } } state.pc++ } @@ -32,8 +31,7 @@ func (cmd NextCommand) exec(state *ProgramState) { if err != nil { panic("Missing next value") } - state.value = nextItem.Value - state.path = nextItem.Path + state.value = []walk.Value{nextItem.Value} state.pc++ } func (cmd NextCommand) String() string { @@ -46,8 +44,7 @@ func (cmd AppendNextCommand) exec(state *ProgramState) { if err != nil { panic("Missing next value") } - state.value = append(state.value, nextItem.Value...) - state.path = nextItem.Path + state.value = append(state.value, nextItem.Value) state.pc++ } func (cmd AppendNextCommand) String() string { @@ -63,16 +60,7 @@ func (cmd DeleteValueCommand) String() string { return "d" } -type DeletePathCommand struct {} -func (cmd DeletePathCommand) exec(state *ProgramState) { - state.path = nil - state.pc++ -} -func (cmd DeletePathCommand) String() string { - return "D" -} - -func runSubex(state subex.Transducer, in walk.ValueList) (walk.ValueList, bool) { +func runSubex(state subex.Transducer, in []walk.Value) ([]walk.Value, bool) { out, error := subex.RunTransducer(state, in) if error { return nil, true @@ -96,22 +84,6 @@ func (cmd SubstituteValueCommand) String() string { return "s/.../" } -type SubstitutePathCommand struct { - subex subex.Transducer -} -func (cmd SubstitutePathCommand) exec(state *ProgramState) { - newPath, err := runSubex(cmd.subex, state.path) - if err { - state.pc++ - } else { - state.pc += 2 - state.path = newPath - } -} -func (cmd SubstitutePathCommand) String() string { - return "S/.../" -} - type NoopCommand struct {} func (cmd NoopCommand) exec(state *ProgramState) { state.pc++ @@ -180,26 +152,6 @@ func (cmd AppendZRegCommand) String() string { return "Z" } -type SwapPathCommand struct {} -func (cmd SwapPathCommand) exec(state *ProgramState) { - v := state.value - state.value = state.path - state.path = v - state.pc++ -} -func (cmd SwapPathCommand) String() string { - return "k" -} - -type AppendPathCommand struct {} -func (cmd AppendPathCommand) exec(state *ProgramState) { - state.path = append(state.path, state.value...) - state.pc++ -} -func (cmd AppendPathCommand) String() string { - return "K" -} - type JumpCommand struct { destination int } @@ -220,4 +172,4 @@ func (cmd BranchPlaceholderCommand) exec(state *ProgramState) { } func (cmd BranchPlaceholderCommand) String() string { return fmt.Sprintf("b%c", cmd.label) -}
\ No newline at end of file +} diff --git a/main/main.go b/main/main.go index 8e8c369..b7ef568 100644 --- a/main/main.go +++ b/main/main.go @@ -7,10 +7,8 @@ import ( "main/json" ) -type Program []Command - type ProgramState struct { - path, value, xreg, yreg, zreg walk.ValueList + value, xreg, yreg, zreg []walk.Value in walk.StredReader out walk.StredWriter program []Command @@ -55,23 +53,21 @@ func main() { if err != nil { break } - state.value = walkItem.Value - state.path = walkItem.Path + state.value = []walk.Value{walkItem.Value} state.pc = 0 for state.pc < len(state.program) { state.program[state.pc].exec(&state) } if !quiet { - err := state.out.Write(walk.WalkItem { - Path: state.path, - Value: state.value, - }) - if err != nil { - panic("Error while outputting") + for _, value := range state.value { + err := state.out.Write(value) + if err != nil { + panic("Error while outputting") + } } } } state.in.AssertDone() state.out.AssertDone() -}
\ No newline at end of file +} diff --git a/main/parse.go b/main/parse.go index 141ae7e..9c7a437 100644 --- a/main/parse.go +++ b/main/parse.go @@ -65,23 +65,14 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma return append(commands, PrintValueCommand{}) case 'd': return append(commands, DeleteValueCommand{}) - case 'D': - return append(commands, DeletePathCommand{}) case 'n': return append(commands, NextCommand{}) case 'N': return append(commands, AppendNextCommand{}) - case 's', 'S': + case 's': ast := p.parseSubex() subex := subex.CompileTransducer(ast) - switch commandChar { - case 's': - return append(commands, SubstituteValueCommand {subex}, JumpCommand {len(commands) + 3}) - case 'S': - return append(commands, SubstitutePathCommand {subex}, JumpCommand {len(commands) + 3}) - default: - panic("Unreachable!?!?") - } + return append(commands, SubstituteValueCommand {subex}, JumpCommand {len(commands) + 3}) case 'o': return append(commands, NoopCommand{}) case 'x': @@ -96,10 +87,6 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma return append(commands, SwapZRegCommand{}) case 'Z': return append(commands, AppendZRegCommand{}) - case 'k': - return append(commands, SwapPathCommand{}) - case 'K': - return append(commands, AppendPathCommand{}) case ':': labelToken := p.next() if labelToken.typ != TokenLabel { diff --git a/subex/arithmetic.go b/subex/arithmetic.go index 4cbc9db..4c87d5f 100644 --- a/subex/arithmetic.go +++ b/subex/arithmetic.go @@ -6,23 +6,23 @@ import ( "strconv" ) -func sumValues(values walk.ValueList) (walk.ValueList, error) { +func sumValues(values []walk.Value) ([]walk.Value, error) { allBools := true var sum float64 = 0 var any bool = false for _, value := range values { switch v := value.(type) { - case walk.NullScalar: + case walk.NullValue: allBools = false - case walk.BoolScalar: + case walk.BoolValue: if v { sum += 1 any = true } - case walk.NumberScalar: + case walk.NumberValue: allBools = false sum += float64(v) - case walk.StringStructure: + case walk.StringValue: allBools = false num, err := strconv.ParseFloat(string(v), 64) if err == nil { @@ -35,31 +35,31 @@ func sumValues(values walk.ValueList) (walk.ValueList, error) { } } if allBools { - return walk.ValueList{walk.BoolScalar(any)}, nil + return []walk.Value{walk.BoolValue(any)}, nil } else { - return walk.ValueList{walk.NumberScalar(sum)}, nil + return []walk.Value{walk.NumberValue(sum)}, nil } } // Compounds atoms into values, if all values are booleans, does AND, if not, tries to cast to numbers and multiply -func multiplyValues(values walk.ValueList) (walk.ValueList, error) { +func multiplyValues(values []walk.Value) ([]walk.Value, error) { allBools := true var product float64 = 1 var all bool = false for _, value := range values { switch v := value.(type) { - case walk.NullScalar: + case walk.NullValue: allBools = false product *= 0 - case walk.BoolScalar: + case walk.BoolValue: if !v { product *= 0 all = false } - case walk.NumberScalar: + case walk.NumberValue: allBools = false product *= float64(v) - case walk.StringStructure: + case walk.StringValue: allBools = false num, err := strconv.ParseFloat(string(v), 64) if err == nil { @@ -72,31 +72,31 @@ func multiplyValues(values walk.ValueList) (walk.ValueList, error) { } } if allBools { - return walk.ValueList{walk.BoolScalar(all)}, nil + return []walk.Value{walk.BoolValue(all)}, nil } else { - return walk.ValueList{walk.NumberScalar(product)}, nil + return []walk.Value{walk.NumberValue(product)}, nil } } // Does tries to cast all to numbers and negates them -func negateValues(values walk.ValueList) (walk.ValueList, error) { - var negatedNumbers walk.ValueList +func negateValues(values []walk.Value) ([]walk.Value, error) { + var negatedNumbers []walk.Value for _, value := range values { switch v := value.(type) { - case walk.NullScalar: - negatedNumbers = append(negatedNumbers, walk.NumberScalar(0)) - case walk.BoolScalar: + case walk.NullValue: + negatedNumbers = append(negatedNumbers, walk.NumberValue(0)) + case walk.BoolValue: if v { - negatedNumbers = append(negatedNumbers, walk.NumberScalar(-1)) + negatedNumbers = append(negatedNumbers, walk.NumberValue(-1)) } else { - negatedNumbers = append(negatedNumbers, walk.NumberScalar(0)) + negatedNumbers = append(negatedNumbers, walk.NumberValue(0)) } - case walk.NumberScalar: - negatedNumbers = append(negatedNumbers, walk.NumberScalar(-float64(v))) - case walk.StringStructure: + case walk.NumberValue: + negatedNumbers = append(negatedNumbers, walk.NumberValue(-float64(v))) + case walk.StringValue: num, err := strconv.ParseFloat(string(v), 64) if err == nil { - negatedNumbers = append(negatedNumbers, walk.NumberScalar(-num)) + negatedNumbers = append(negatedNumbers, walk.NumberValue(-num)) } else { return nil, errors.New("Tried to negate non-castable string") } @@ -109,24 +109,24 @@ func negateValues(values walk.ValueList) (walk.ValueList, error) { // If all are castable to numbers, takes reciprocals of all and returns them // Else errors -func reciprocalValues(values walk.ValueList) (walk.ValueList, error) { - var reciprocals walk.ValueList +func reciprocalValues(values []walk.Value) ([]walk.Value, error) { + var reciprocals []walk.Value for _, value := range values { switch v := value.(type) { - case walk.NullScalar: + case walk.NullValue: return nil, errors.New("Tried to take reciprocal of null") - case walk.BoolScalar: + case walk.BoolValue: if v { - reciprocals = append(reciprocals, walk.NumberScalar(1)) + reciprocals = append(reciprocals, walk.NumberValue(1)) } else { return nil, errors.New("Tried to take reciprocal of false") } - case walk.NumberScalar: - reciprocals = append(reciprocals, walk.NumberScalar(1 / float64(v))) - case walk.StringStructure: + case walk.NumberValue: + reciprocals = append(reciprocals, walk.NumberValue(1 / float64(v))) + case walk.StringValue: num, err := strconv.ParseFloat(string(v), 64) if err == nil { - reciprocals = append(reciprocals, walk.NumberScalar(1 / num)) + reciprocals = append(reciprocals, walk.NumberValue(1 / num)) } else { return nil, errors.New("Tried to take reciprocal of non-castable string") } @@ -139,17 +139,17 @@ func reciprocalValues(values walk.ValueList) (walk.ValueList, error) { // If all are castable to booleans, NOTs all and returns them // Else errors -func notValues(values walk.ValueList) (notted walk.ValueList, err error) { +func notValues(values []walk.Value) (notted []walk.Value, err error) { for _, value := range values { switch v := value.(type) { - case walk.NullScalar: - notted = append(notted, walk.BoolScalar(true)) - case walk.BoolScalar: - notted = append(notted, walk.BoolScalar(!bool(v))) - case walk.NumberScalar: - notted = append(notted, walk.BoolScalar(v == 0)) - case walk.StringStructure: - notted = append(notted, walk.BoolScalar(len(v) == 0)) + case walk.NullValue: + notted = append(notted, walk.BoolValue(true)) + case walk.BoolValue: + notted = append(notted, walk.BoolValue(!bool(v))) + case walk.NumberValue: + notted = append(notted, walk.BoolValue(v == 0)) + case walk.StringValue: + notted = append(notted, walk.BoolValue(len(v) == 0)) default: return nil, errors.New("Tried to NOT non-boolean") } @@ -158,16 +158,16 @@ func notValues(values walk.ValueList) (notted walk.ValueList, err error) { } // Returns true if all values are equal, false if not -func equalValues(values walk.ValueList) (walk.ValueList, error) { +func equalValues(values []walk.Value) ([]walk.Value, error) { if len(values) == 0 { - return walk.ValueList{walk.BoolScalar(true)}, nil + return []walk.Value{walk.BoolValue(true)}, nil } first := values[0] for _, value := range values[1:] { // TODO: Refine the equality check if value != first { - return walk.ValueList{walk.BoolScalar(false)}, nil + return []walk.Value{walk.BoolValue(false)}, nil } } - return walk.ValueList{walk.BoolScalar(true)}, nil + return []walk.Value{walk.BoolValue(true)}, nil } diff --git a/subex/filter.go b/subex/filter.go index 1a1b6db..dce0f0e 100644 --- a/subex/filter.go +++ b/subex/filter.go @@ -17,13 +17,13 @@ func (scalar selectScalarFilter) valueFilter(value walk.Value) bool { type anyNumberFilter struct {} func (_ anyNumberFilter) valueFilter(value walk.Value) bool { - _, isNumber := value.(walk.NumberScalar) + _, isNumber := value.(walk.NumberValue) return isNumber } type anyBoolFilter struct {} func (_ anyBoolFilter) valueFilter(value walk.Value) bool { - _, isBool := value.(walk.BoolScalar) + _, isBool := value.(walk.BoolValue) return isBool } @@ -34,29 +34,29 @@ func (_ anyValueFilter) valueFilter(value walk.Value) bool { type anyArrayFilter struct {} func (_ anyArrayFilter) valueFilter(value walk.Value) bool { - _, isArray := value.(walk.ArrayStructure) + _, isArray := value.(walk.ArrayValue) return isArray } type anyStringFilter struct {} func (_ anyStringFilter) valueFilter(value walk.Value) bool { - _, isString := value.(walk.StringStructure) + _, isString := value.(walk.StringValue) return isString } type runeFilter interface { - runeFilter(r walk.StringRuneAtom) bool + runeFilter(r rune) bool } type anyRuneFilter struct {} -func (_ anyRuneFilter) runeFilter(r walk.StringRuneAtom) bool { +func (_ anyRuneFilter) runeFilter(r rune) bool { return true } type selectRuneFilter struct { r rune } -func (f selectRuneFilter) runeFilter(r walk.StringRuneAtom) bool { - return f.r == rune(r) +func (f selectRuneFilter) runeFilter(r rune) bool { + return f.r == r } diff --git a/subex/main.go b/subex/main.go index e2cec0b..86a8d41 100644 --- a/subex/main.go +++ b/subex/main.go @@ -5,50 +5,96 @@ import ( ) type Transducer struct { - storeSize int + storeSize NextSlotIds initialState SubexState } -type StoreItem struct {} // Where slots are stored -type Store []walk.OutputList +type Store struct { + values [][]walk.Value + runes [][]rune +} + // Return a new store with all the data from this one func (store Store) clone() Store { - newStore := make([]walk.OutputList, len(store)) - copy(newStore, store) + newStore := Store{ + values: make([][]walk.Value, len(store.values)), + runes: make([][]rune, len(store.runes)), + } + copy(newStore.values, store.values) + copy(newStore.runes, store.runes) return newStore } + // Return a copy of this store but with an additional slot set -func (store Store) withValue(key int, value walk.OutputList) Store { +func (store Store) withValue(key int, value []walk.Value) Store { newStore := store.clone() - newStore[key] = value + newStore.values[key] = value return newStore } +func (store Store) withRunes(key int, runes []rune) Store { + newStore := store.clone() + newStore.runes[key] = runes + return newStore +} + +type SlotId struct { + id int + typ Type +} + +type NextSlotIds struct { + values int + runes int +} + type SlotMap struct { - nextId int - ids map[rune]int + next NextSlotIds + ids map[rune]SlotId } + func (m *SlotMap) getId(slot rune) int { id, exists := m.ids[slot] if exists { - return id + if id.typ != ValueType { + panic("Slot with wrong type used") + } + return id.id + } + id.id = m.next.values + id.typ = ValueType + m.next.values++ + m.ids[slot] = id + return id.id +} +func (m *SlotMap) getRuneId(slot rune) int { + id, exists := m.ids[slot] + if exists { + if id.typ != RuneType { + panic("Slot with wrong type used") + } + return id.id } - id = m.nextId - m.nextId++ + id.id = m.next.runes + id.typ = RuneType + m.next.runes++ m.ids[slot] = id - return id + return id.id } // Compile the SubexAST into a transducer SubexState that can be run func CompileTransducer(transducerAst SubexAST) Transducer { - slotMap := SlotMap { - nextId: 0, - ids: make(map[rune]int), + slotMap := SlotMap{ + next: NextSlotIds{ + values: 0, + runes: 0, + }, + ids: make(map[rune]SlotId), } - initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, false) - return Transducer { - storeSize: slotMap.nextId, + initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, ValueType, ValueType) + return Transducer{ + storeSize: slotMap.next, initialState: initial, } } @@ -58,46 +104,44 @@ type OutputStack struct { head walk.OutputList tail *OutputStack } -func (stack OutputStack) pop() (walk.OutputList, OutputStack) { - return stack.head, *stack.tail + +func (stack OutputStack) pop() ([]walk.Value, OutputStack) { + return stack.head.(walk.OutputValueList), *stack.tail } -func (stack OutputStack) push(atoms walk.OutputList) OutputStack { - return OutputStack { - head: atoms, +func (stack OutputStack) push(atoms []walk.Value) OutputStack { + return OutputStack{ + head: walk.OutputValueList(atoms), tail: &stack, } } -func (stack OutputStack) replace(atoms walk.OutputList) OutputStack { - return OutputStack { - head: atoms, +func (stack OutputStack) replace(atoms []walk.Value) OutputStack { + return OutputStack{ + head: walk.OutputValueList(atoms), tail: stack.tail, } } -func (stack OutputStack) peek() walk.OutputList { - return stack.head +func (stack OutputStack) peek() []walk.Value { + return stack.head.(walk.OutputValueList) } func topAppend(outputStack OutputStack, values []walk.Value) OutputStack { - head, isValues := outputStack.peek().(walk.ValueList) - if !isValues { - panic("Tried to append a value to an output of non-values") - } - head = append(walk.ValueList{}, head...) + head := outputStack.peek() + head = append([]walk.Value{}, head...) head = append(head, values...) return outputStack.replace(head) } -func topAppendRunes(outputStack OutputStack, runes walk.RuneList) OutputStack { - head, isRunes := outputStack.peek().(walk.RuneList) - if !isRunes { - panic("Tried to append runes to a non-rune list") - } - head = append(walk.RuneList{}, head...) +func topAppendRune(outputStack OutputStack, runes []rune) OutputStack { + head := outputStack.head.(walk.OutputRuneList) + head = append([]rune{}, head...) head = append(head, runes...) - return outputStack.replace(head) + return OutputStack{ + head: head, + tail: outputStack.tail, + } } -// Addition state that goes along with a subex state in an execution branch +// Additional state that goes along with a subex state in an execution branch type auxiliaryState struct { // Content of slots in this branch store Store @@ -114,29 +158,49 @@ func (aux auxiliaryState) cloneStore() auxiliaryState { return aux } -func (aux auxiliaryState) withValue(slot int, value walk.OutputList) auxiliaryState { +func (aux auxiliaryState) withValue(slot int, value []walk.Value) auxiliaryState { aux.store = aux.store.withValue(slot, value) return aux } -func (aux auxiliaryState) pushOutput(data walk.OutputList) auxiliaryState { +func (aux auxiliaryState) pushOutput(data []walk.Value) auxiliaryState { aux.outputStack = aux.outputStack.push(data) return aux } -func (aux auxiliaryState) popOutput() (walk.OutputList, auxiliaryState) { +func (aux auxiliaryState) pushOutputRunes(runes []rune) auxiliaryState { + tail := aux.outputStack + aux.outputStack = OutputStack{ + head: walk.OutputRuneList(runes), + tail: &tail, + } + return aux +} + +func (aux auxiliaryState) popDiscardOutput() auxiliaryState { + aux.outputStack = *aux.outputStack.tail + return aux +} + +func (aux auxiliaryState) popOutput() ([]walk.Value, auxiliaryState) { data, output := aux.outputStack.pop() aux.outputStack = output return data, aux } -func (aux auxiliaryState) topAppend(values walk.OutputList) auxiliaryState { - switch output := values.(type) { - case walk.ValueList: - aux.outputStack = topAppend(aux.outputStack, output) - case walk.RuneList: - aux.outputStack = topAppendRunes(aux.outputStack, output) - } +func (aux auxiliaryState) popOutputRunes() ([]rune, auxiliaryState) { + runes := aux.outputStack.head.(walk.OutputRuneList) + aux.outputStack = *aux.outputStack.tail + return runes, aux +} + +func (aux auxiliaryState) topAppend(values []walk.Value) auxiliaryState { + aux.outputStack = topAppend(aux.outputStack, values) + return aux +} + +func (aux auxiliaryState) topAppendRune(runes []rune) auxiliaryState { + aux.outputStack = topAppendRune(aux.outputStack, runes) return aux } @@ -150,31 +214,38 @@ func (aux auxiliaryState) decNest() auxiliaryState { return aux } -// One branch of subex execution type SubexBranch struct { - // State in this branch state SubexState + aux auxiliaryState +} + +// One branch of subex execution +type SubexEatBranch struct { + // State in this branch + state SubexEatState // Axiliary state aux auxiliaryState } + // Read a single character and return all the branches resulting from this branch consuming it -func (pair SubexBranch) eat(char walk.Edible) []SubexBranch { - return pair.state.eat(pair.aux, char) +func (pair SubexEatBranch) eat(edible walk.Edible) []SubexBranch { + return pair.state.eat(pair.aux, edible) } -func (pair SubexBranch) accepting() []OutputStack { +func (pair SubexEatBranch) accepting() []OutputStack { return pair.state.accepting(pair.aux) } -func equalStates(left SubexBranch, right SubexBranch) bool { +func equalStates(left SubexEatBranch, right SubexEatBranch) bool { // Only care about if they are the same pointer return left.state == right.state && left.aux.nesting == right.aux.nesting } // 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) []SubexBranch { +func pruneStates(states []SubexEatBranch) []SubexEatBranch { uniqueStates := 0 - outer: for _, state := range states { +outer: + for _, state := range states { for i := 0; i < uniqueStates; i++ { if equalStates(state, states[i]) { continue outer @@ -186,68 +257,90 @@ func pruneStates(states []SubexBranch) []SubexBranch { return states[:uniqueStates] } -func processInput(states []SubexBranch, input walk.StructureIter, nesting int) []SubexBranch { - if len(states) == 0 { - return states - } - var tmp []SubexBranch - newStates := make([]SubexBranch, 0, 2) - for { - piece := input.Next() - if piece == nil { - break +func addStates(curStates []SubexEatBranch, newStates []SubexBranch) []SubexEatBranch { + for _, state := range newStates { + switch s := state.state.(type) { + case SubexEpsilonState: + curStates = addStates(curStates, s.epsilon(state.aux)) + case SubexEatState: + curStates = append(curStates, SubexEatBranch{ + state: s, + aux: state.aux, + }) } + } + return curStates +} - // TODO: break if there are no states at this nesting level left - // TODO: What to do if there are remaining nested states after all pieces have been used? - for _, state := range states { - if state.aux.nesting == nesting { - newStates = append(newStates, state.eat(piece)...) - } else { - newStates = append(newStates, state) - } - } +func processInput(states []SubexEatBranch, input walk.Edible, nesting int) []SubexEatBranch { + newStates := make([]SubexEatBranch, 0, 2) - structure, isStructure := piece.(walk.Structure) - if isStructure { - iter := structure.Iter() - newStates = processInput(newStates, iter, nesting + 1) + for _, state := range states { + // TODO: What if nesting is changed by an epsilon state? + if state.aux.nesting == nesting { + newStates = addStates(newStates, state.eat(input)) + } else if state.aux.nesting < nesting { + newStates = append(newStates, state) } + } - tmp = states - states = pruneStates(newStates) - newStates = tmp[:0] - if len(states) == 0 { - return states + switch input := input.(type) { + case walk.StringValue: + for _, r := range input { + newStates = processInput(newStates, walk.RuneEdible(r), nesting+1) } + newStates = processInput(newStates, walk.StringEnd, nesting+1) + case walk.ArrayValue: + for _, el := range input { + newStates = processInput(newStates, walk.NumberValue(el.Index), nesting+1) + newStates = processInput(newStates, el.Value, nesting+1) + } + newStates = processInput(newStates, walk.ArrayEnd, nesting+1) + case walk.MapValue: + for _, el := range input { + newStates = processInput(newStates, walk.StringValue(el.Key), nesting+1) + newStates = processInput(newStates, el.Value, nesting+1) + } + newStates = processInput(newStates, walk.MapEnd, nesting+1) } - return states + + newStates = pruneStates(newStates) + + return newStates } // Run the subex transducer -func RunTransducer(transducer Transducer, input walk.ValueList) (output []walk.Value, err bool) { - states := []SubexBranch{{ +func RunTransducer(transducer Transducer, input []walk.Value) (output []walk.Value, err bool) { + states := addStates(nil, []SubexBranch{{ state: transducer.initialState, - aux: auxiliaryState { - outputStack: OutputStack { - head: walk.ValueList{}, + aux: auxiliaryState{ + outputStack: OutputStack{ + head: walk.OutputValueList(nil), tail: nil, }, - store: make([]walk.OutputList, transducer.storeSize), + store: Store{ + values: make([][]walk.Value, transducer.storeSize.values), + runes: make([][]rune, transducer.storeSize.runes), + }, nesting: 0, }, - }} + }}) + + for _, value := range input { + if len(states) == 0 { + break + } - states = processInput(states, walk.NewValueIter(input), 0) + states = processInput(states, value, 0) + } for _, state := range states { + if state.aux.nesting > 0 { + continue + } acceptingStacks := state.accepting() for _, stack := range acceptingStacks { - output, isValues := stack.head.(walk.ValueList) - if !isValues { - panic("Tried to output a non-values list") - } - return output, false + return stack.head.(walk.OutputValueList), false } } return nil, true diff --git a/subex/main_test.go b/subex/main_test.go index f0350d2..0b4ee1b 100644 --- a/subex/main_test.go +++ b/subex/main_test.go @@ -1,10 +1,10 @@ package subex import ( - "testing" "main/walk" - "fmt" + "reflect" "strings" + "testing" ) func buildTransducer(subex string) Transducer { @@ -14,429 +14,77 @@ func buildTransducer(subex string) Transducer { return transducer } -func fatalMismatch(t *testing.T, path walk.ValueList, message string) { - var sep string +func fmtValueList(values []walk.Value) string { var builder strings.Builder - for _, segment := range path { - builder.WriteString(sep) - builder.WriteString(segment.Debug()) - sep = "." - } - builder.WriteString(": ") - builder.WriteString(message) - t.Fatal(builder.String()) -} - -func expectEqual(t *testing.T, path walk.ValueList, output walk.Value, expected walk.Value) { - switch expected := expected.(type) { - case walk.NullScalar: - _, isNull := output.(walk.NullScalar) - if !isNull { - fatalMismatch(t, path, fmt.Sprintf("expected null, found %s", output.Debug())) - } - case walk.BoolScalar: - b, isBool := output.(walk.BoolScalar) - if !isBool { - fatalMismatch(t, path, fmt.Sprintf("expected boolean, found %s", output.Debug())) - } - if expected != b { - fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), b.Debug())) - } - case walk.NumberScalar: - n, isNumber := output.(walk.NumberScalar) - if !isNumber { - fatalMismatch(t, path, fmt.Sprintf("expected number, found %s", output.Debug())) - } - if expected != n { - fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), n.Debug())) - } - case walk.StringStructure: - s, isString := output.(walk.StringStructure) - if !isString { - fatalMismatch(t, path, fmt.Sprintf("expected string, found %s", output.Debug())) - } - if s != expected { - fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), s.Debug())) - } - case walk.ArrayStructure: - array, isArray := output.(walk.ArrayStructure) - if !isArray { - fatalMismatch(t, path, fmt.Sprintf("expected array, found %s", output.Debug())) - } - if len(array) != len(expected) { - fatalMismatch(t, path, fmt.Sprintf("Expected array length %d, found %d", len(expected), len(array))) - } - for i, value := range expected { - expectEqual(t, append(path, walk.NumberScalar(i)), array[i], value) - } - case walk.MapStructure: - m, isMap := output.(walk.MapStructure) - if !isMap { - fatalMismatch(t, path, fmt.Sprintf("expected map, found %s", output.Debug())) + builder.WriteRune('[') + for i, value := range values { + if i != 0 { + builder.WriteString(", ") } - for key, expected := range expected { - value, hasValue := m[key] - if !hasValue { - fatalMismatch(t, path, fmt.Sprintf("expected map to have key %s, but it doesn't", key)) - } - expectEqual(t, append(path, walk.StringStructure(key)), value, expected) - } - for key := range m { - _, hasValue := expected[key] - if !hasValue { - fatalMismatch(t, path, fmt.Sprintf("Didn't expect map to have key %s, but it does", key)) - } - } - default: - panic("Expected contains an invalid value") + builder.WriteString(value.Debug()) } + builder.WriteRune(']') + return builder.String() } -func expectOutput(t *testing.T, transducer Transducer, input walk.ValueList, expected walk.ValueList) { - output, err := RunTransducer(transducer, input) - - if err { - t.Fatalf("Error") - } - - if len(output) != len(expected) { - t.Fatalf("Output has incorrect length. Expected %d, got %d", len(expected), len(output)) - } - - for i, value := range output { - expectEqual(t, walk.ValueList{walk.NumberScalar(i)}, value, expected[i]) +func TestSubexMain(t *testing.T) { + type test struct { + subex string + input []walk.Value + expected []walk.Value } -} - -func expectReject(t *testing.T, transducer Transducer, input walk.ValueList) { - _, err := RunTransducer(transducer, input) - - if !err { - t.Fatalf("Expected transducer to error, but it accepted input: %v", input) - } -} - -func TestSimpleProcessInput(t *testing.T) { - states := []SubexBranch{{ - state: SubexCopyState { - next: SubexNoneState{}, - filter: anyValueFilter{}, - }, - aux: auxiliaryState { - outputStack: OutputStack { - head: walk.ValueList{}, - tail: nil, - }, - store: nil, - nesting: 0, - }, - }} - - input := walk.ValueList{ - walk.NumberScalar(2), - } - - states = processInput(states, walk.NewValueIter(input), 0) - - if len(states) != 1 { - t.Fatalf("States has wrong length") - } - - accepting := states[0].accepting() - - if len(accepting) != 1 { - t.Fatalf("Wrong number of accepting branches") - } - - values, isValues := accepting[0].head.(walk.ValueList) - - if !isValues { - t.Fatalf("Output is not a value list") - } - - if len(values) != 1 { - t.Fatalf("Output has wrong length") - } - - if values[0] != walk.NumberScalar(2) { - t.Fatalf("Outputted the wrong value") - } -} - -func TestTopAppendFromEmpty(t *testing.T) { - output := OutputStack { - head: walk.ValueList{}, - tail: nil, - } - - output = topAppend(output, []walk.Value{walk.NumberScalar(1), walk.NumberScalar(2)}) - - values, isValues := output.head.(walk.ValueList) - - if !isValues { - t.Fatalf("head is not values") - } - - if len(values) != 2 { - t.Fatalf("values has the wrong length") - } - - if values[0] != walk.NumberScalar(1) || values[1] != walk.NumberScalar(2) { - t.Fatalf("output has the wrong values") - } -} - -func TestArrayPriority1(t *testing.T) { - expectOutput( - t, - buildTransducer(":[.$_]|."), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(5), - }, - }, - walk.ValueList{ - walk.ArrayStructure{}, - }, - ) -} -func TestArrayPriority2(t *testing.T) { - expectOutput( - t, - buildTransducer(".|:[.$_]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(5), + tests := []test { + { + subex: `..+`, + input: []walk.Value { + walk.NumberValue(12), + walk.NumberValue(34), }, - }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(5), + expected: []walk.Value { + walk.NumberValue(46), }, }, - ) -} - -func TestDropSecondArrayElement(t *testing.T) { - expectOutput( - t, - buildTransducer(":[.(.$_)(.{-0})]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), + { + subex: `.`, + input: []walk.Value { + walk.MapValue {{ + Key: "a", + Value: walk.StringValue("b"), + }}, }, - }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(3), - walk.NumberScalar(4), + expected: []walk.Value { + walk.MapValue {{ + Key: "a", + Value: walk.StringValue("b"), + }}, }, }, - ) -} - -func TestDropSecondElement(t *testing.T) { - expectOutput( - t, - buildTransducer(".(.$_)(.{-0})"), - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), - }, - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(3), - walk.NumberScalar(4), - }, - ) -} - -func TestCopyManyValues(t *testing.T) { - expectOutput( - t, - buildTransducer(".{-0}"), - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), - }, - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), - }, - ) -} - -func TestCopyTwoValues(t *testing.T) { - expectOutput( - t, - buildTransducer(".."), - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - }, - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - }, - ) -} - -func TestCopyValue(t *testing.T) { - expectOutput( - t, - buildTransducer("."), - walk.ValueList{ - walk.NumberScalar(1), - }, - walk.ValueList{ - walk.NumberScalar(1), - }, - ) -} - -func TestSimpleArrayEntry(t *testing.T) { - expectOutput( - t, - buildTransducer(":[..]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(2), + { + subex: `~(.$_(.{-0}))~`, + input: []walk.Value { + walk.StringValue("hello"), }, - }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(2), + expected: []walk.Value { + walk.StringValue("ello"), }, }, - ) -} - -func TestArrayEntrySum(t *testing.T) { - expectOutput( - t, - buildTransducer(":[%{-0}+]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(7), - walk.NumberScalar(8), - walk.NumberScalar(3), - }, - }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(19), - }, - }, - ) -} - -func TestStringEmptyMatch(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"\""), - walk.ValueList{ - walk.StringStructure(""), - }, - walk.ValueList{ - walk.StringStructure(""), - }, - ) -} - -func TestStringSimpleMatch(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"hello\""), - walk.ValueList{ - walk.StringStructure("hello"), - }, - walk.ValueList{ - walk.StringStructure("hello"), - }, - ) -} - -func TestDiscardString(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"test\"$_."), - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - }, - walk.ValueList{ - walk.NumberScalar(2), - }, - ) -} + } -func TestStringThenValue(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"test\"."), - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - }, - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - }, - ) -} + for _, test := range tests { + lexer := NewStringRuneReader(test.subex) + ast := Parse(lexer) + transducer := CompileTransducer(ast) + output, err := RunTransducer(transducer, test.input) -func TestCutStringFromStart(t *testing.T) { - //transducer := buildTransducer("~\"test\"$_(.{-0})") - lexer := NewStringRuneReader("~\"test\"$_(.{-0})") - ast := Parse(lexer) - t.Log(ast) - transducer := CompileTransducer(ast) + if err { + t.Errorf("Subex %q rejected input %v", test.subex, fmtValueList(test.input)) + t.Logf("AST: %v", ast) + continue + } - expectOutput( - t, - transducer, - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - walk.StringStructure("test"), - }, - walk.ValueList{ - walk.NumberScalar(2), - walk.StringStructure("test"), - }, - ) - expectOutput( - t, - transducer, - walk.ValueList{ - walk.StringStructure("test"), - }, - walk.ValueList{}, - ) - expectReject( - t, - transducer, - walk.ValueList{ - walk.StringStructure("yeet"), - }, - ) - expectReject( - t, - transducer, - walk.ValueList{}, - ) + if !reflect.DeepEqual(output, test.expected) { + t.Errorf("Subex %q transformed input %s to output %s", test.subex, fmtValueList(test.input), fmtValueList(output)) + } + } } diff --git a/subex/parse.go b/subex/parse.go index 35baaa2..fa98ecc 100644 --- a/subex/parse.go +++ b/subex/parse.go @@ -6,6 +6,12 @@ import ( "strings" ) +type Type int +const ( + ValueType Type = iota + RuneType +) + type RuneReader interface { Next() rune Rewind() @@ -45,24 +51,24 @@ func parseScalarLiteral(l RuneReader) (walk.Scalar, bool) { if err != nil { panic("Invalid number literal") } - return walk.NumberScalar(number), true + return walk.NumberValue(number), true } switch r { case 'n': if accept(l, "u") && accept(l, "l") && accept(l, "l") { - return walk.NullScalar{}, true + return walk.NullValue{}, true } else { panic("Invalid literal") } case 't': if accept(l, "r") && accept(l, "u") && accept(l, "e") { - return walk.BoolScalar(true), true + return walk.BoolValue(true), true } else { panic("Invalid literal") } case 'f': if accept(l, "a") && accept(l, "l") && accept(l, "s") && accept(l, "e") { - return walk.BoolScalar(false), true + return walk.BoolValue(false), true } else { panic("Invalid literal") } @@ -133,34 +139,53 @@ func parseRepeatRange(l RuneReader) (output []ConvexRange) { return output } -// TODO: Consider if it's worth making better use of the go type system to enforce output being all runes or all values -func parseReplacement(l RuneReader, runic bool) (output []OutputContentAST) { +func parseValueReplacement(l RuneReader) (output []OutputValueAST) { // TODO escaping // TODO add arrays, maps and strings loop: for { r := l.Next() switch r { - case eof: - panic("Missing closing `") - case '`': - break loop - case '$': - slot := l.Next() - if slot == eof { - panic("Missing slot character") - } - output = append(output, OutputLoadAST{slot: slot}) - default: - if runic { - output = append(output, OutputRuneLiteralAST {walk.StringRuneAtom(r)}) - } else { - l.Rewind() - scalar, ok := parseScalarLiteral(l) - if !ok { - panic("Invalid scalar literal") - } - output = append(output, OutputValueLiteralAST {scalar}) - } + case eof: + panic("Missing closing `") + case ' ': + case '`': + break loop + case '$': + slot := l.Next() + if slot == eof { + panic("Missing slot character") + } + output = append(output, OutputValueLoadAST {slot: slot}) + default: + l.Rewind() + scalar, ok := parseScalarLiteral(l) + if !ok { + panic("Invalid scalar literal") + } + output = append(output, OutputValueLiteralAST {scalar}) + } + } + return output +} + +func parseRuneReplacement(l RuneReader) (output []OutputRuneAST) { + // TODO escaping + // TODO add arrays, maps and strings + loop: for { + r := l.Next() + switch r { + case eof: + panic("Missing closing `") + case '`': + break loop + case '$': + slot := l.Next() + if slot == eof { + panic("Missing slot character") + } + output = append(output, OutputRuneLoadAST {slot: slot}) + default: + output = append(output, OutputRuneLiteralAST {r}) } } return output @@ -245,17 +270,29 @@ func parseReplacement(l RuneReader, runic bool) (output []OutputContentAST) { // return parts // } -func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { +func parseSubex(l RuneReader, minPower int, inType Type, outType Type) SubexAST { var lhs SubexAST r := l.Next() switch r { case eof: return nil case '(': - lhs = parseSubex(l, 0, runic) + lhs = parseSubex(l, 0, inType, outType) if !accept(l, ")") { panic("Missing matching )") } + case '~': + if !accept(l, "(") { + panic("Missing ( after ~") + } + lhs = parseSubex(l, 0, RuneType, RuneType) + if !accept(l, ")") { + panic("Missing matching )") + } + if !accept(l, "~") { + panic("Missing matching ~") + } + lhs = SubexASTEnterString {lhs} // TODO // case '[': // rangeParts := parseRangeSubex(l) @@ -278,7 +315,10 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { // ) // lhs = SubexASTOutput {replacement} case '.': - if runic { + if inType != outType { + panic("Copying value changes type!") + } + if inType == RuneType { lhs = SubexASTCopyAnyRune{} } else { lhs = SubexASTCopyAnyValue{} @@ -287,32 +327,8 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { lhs = SubexASTCopyBool{} case '%': lhs = SubexASTCopyNumber{} - case ':': - if runic { - lhs = SubexASTCopyRune {':'} - } else { - if !accept(l, "[") { - panic("Missing [ after :") - } - lhs = SubexASTEnterArray {parseSubex(l, 0, runic)} - if !accept(l, "]") { - panic("Missing matching ]") - } - } case '`': - lhs = SubexASTOutput {parseReplacement(l, runic)} - case '~': - if runic { - lhs = SubexASTCopyRune {'~'} - } else { - if !accept(l, "\"") { - panic("Missing \" after ~") - } - lhs = SubexASTEnterString {parseSubex(l, 0, true)} - if !accept(l, "\"") { - panic("Missing matching \"") - } - } + lhs = SubexASTOutputValues {parseValueReplacement(l)} // TODO // case '_': // lhs = SubexASTCopyStringAtom{} @@ -322,15 +338,11 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { // lhs = SubexASTCopyValue{} // case '"': // lhs = SubexASTCopyScalar {walk.NewAtomStringTerminal()} - // case '~': - // literals := parseNonStringLiteral(l) - // var replacement []OutputContentAST - // for _, literal := range literals { - // replacement = append(replacement, OutputValueLiteralAST {literal}) - // } - // lhs = SubexASTOutput {replacement} default: - if runic { + if inType != outType { + panic("inType and outType don't match in copy") + } + if inType == RuneType { lhs = SubexASTCopyRune {r} } else { l.Rewind() @@ -343,7 +355,7 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { } loop: for { if minPower <= 20 { - next := parseSubex(l, 21, runic) + next := parseSubex(l, 21, inType, outType) if next != nil && (next != SubexASTEmpty{}) { lhs = SubexASTConcat{lhs, next} continue loop @@ -362,12 +374,12 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { lhs = SubexASTProduct {lhs} case r == '-' && minPower <= 4: lhs = SubexASTNegate {lhs} - case r == '/' && minPower <= 4: - lhs = SubexASTReciprocal {lhs} + // case r == '/' && minPower <= 4: + // lhs = SubexASTReciprocal {lhs} case r == '!' && minPower <= 4: lhs = SubexASTNot {lhs} - case r == '=' && minPower <= 4: - lhs = SubexASTEqual {lhs} + // case r == '=' && minPower <= 4: + // lhs = SubexASTEqual {lhs} case r == '$' && minPower <= 4: slot := l.Next() if slot == eof { @@ -376,26 +388,26 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { if slot == '_' { lhs = SubexASTDiscard {lhs} } else { - lhs = SubexASTStore{ + lhs = SubexASTStoreValues { Match: lhs, Slot: slot, } } case r == '|' && minPower <= 8: - rhs := parseSubex(l, 9, runic) + rhs := parseSubex(l, 9, inType, outType) if rhs == nil { panic("Missing subex after |") } lhs = SubexASTOr{lhs, rhs} - case r == ';' && minPower <= 10: - rhs := parseSubex(l, 11, runic) + /*case r == ';' && minPower <= 10: + rhs := parseSubex(l, 11, inType, outType) if rhs == nil { panic("Missing subex after ;") } - lhs = SubexASTJoin{ + lhs = SubexASTJoin { Content: lhs, Delimiter: rhs, - } + }*/ default: l.Rewind() break loop @@ -405,7 +417,7 @@ func parseSubex(l RuneReader, minPower int, runic bool) SubexAST { } func Parse(l RuneReader) SubexAST { - ast := parseSubex(l, 0, false) + ast := parseSubex(l, 0, ValueType, ValueType) if ast == nil { return SubexASTEmpty{} } diff --git a/subex/subexast.go b/subex/subexast.go index e02091d..cc7313b 100644 --- a/subex/subexast.go +++ b/subex/subexast.go @@ -7,43 +7,42 @@ import ( // A node in the AST of a subex type SubexAST interface { - compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState + compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState } // Process the first subex, then the second, splitting the input text in two type SubexASTConcat struct { First, Second SubexAST } -func (ast SubexASTConcat) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return ast.First.compileWith(ast.Second.compileWith(next, slotMap, runic), slotMap, runic) +func (ast SubexASTConcat) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + return ast.First.compileWith( + ast.Second.compileWith(next, slotMap, inType, outType), + slotMap, + inType, + outType, + ) } func (ast SubexASTConcat) String() string { return fmt.Sprintf("(%v)(%v)", ast.First, ast.Second) } // Processing a subex and storing the output in a slot instead of outputting it -type SubexASTStore struct { +type SubexASTStoreValues struct { Match SubexAST Slot rune } -func (ast SubexASTStore) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTStoreValues) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { id := slotMap.getId(ast.Slot) newNext := ast.Match.compileWith(&SubexStoreEndState { slot: id, next: next, - }, slotMap, runic) + }, slotMap, inType, ValueType) - if !runic { - return &SubexCaptureBeginState { - next: newNext, - } - } else { - return &SubexCaptureRunesBeginState { - next: newNext, - } + return &SubexCaptureBeginState { + next: newNext, } } -func (ast SubexASTStore) String() string { +func (ast SubexASTStoreValues) String() string { return fmt.Sprintf("$%c(%v)", ast.Slot, ast.Match) } @@ -51,10 +50,10 @@ func (ast SubexASTStore) String() string { type SubexASTOr struct { First, Second SubexAST } -func (ast SubexASTOr) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTOr) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { return &SubexGroupState { - ast.First.compileWith(next, slotMap, runic), - ast.Second.compileWith(next, slotMap, runic), + ast.First.compileWith(next, slotMap, inType, outType), + ast.Second.compileWith(next, slotMap, inType, outType), } } func (ast SubexASTOr) String() string { @@ -84,19 +83,24 @@ func (cr ConvexRange) decrement() ConvexRange { return ConvexRange{cr.Start - 1, cr.End - 1} } } -func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { min, _ := cr.minmax() if min != 0 { - return content.compileWith(cr.decrement().compile(content, next, slotMap, runic), slotMap, runic) + return content.compileWith( + cr.decrement().compile(content, next, slotMap, inType, outType), + slotMap, + inType, + outType, + ) } if cr.Start == -1 { state := &SubexGroupState {nil, next} - state.first = content.compileWith(state, slotMap, runic) + state.first = content.compileWith(state, slotMap, inType, outType) return state } if cr.End == -1 { state := &SubexGroupState {next, nil} - state.second = content.compileWith(state, slotMap, runic) + state.second = content.compileWith(state, slotMap, inType, outType) return state } @@ -104,7 +108,7 @@ func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMa state := next; for i := 0; i < cr.Start; i += 1 { state = &SubexGroupState { - content.compileWith(state, slotMap, runic), + content.compileWith(state, slotMap, inType, outType), next, } } @@ -114,7 +118,7 @@ func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMa for i := 0; i < cr.End; i += 1 { state = &SubexGroupState { next, - content.compileWith(state, slotMap, runic), + content.compileWith(state, slotMap, inType, outType), } } return state @@ -127,10 +131,13 @@ type SubexASTRepeat struct { Content SubexAST Acceptable []ConvexRange } -func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != outType { + panic("Invalid types") + } var state SubexState = &SubexDeadState{} for _, convex := range ast.Acceptable { - state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap, runic)} + state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap, inType, outType)} } return state } @@ -142,7 +149,10 @@ func (ast SubexASTRepeat) String() string { type SubexASTCopyScalar struct { Scalar walk.Scalar } -func (ast SubexASTCopyScalar) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyScalar) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCopyState{ filter: selectScalarFilter {ast.Scalar}, next: next, @@ -153,17 +163,26 @@ func (ast SubexASTCopyScalar) String() string { } type SubexASTCopyAnyRune struct {} -func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != RuneType || outType != RuneType { + panic("Invalid types for SubexASTNot") + } return &SubexCopyRuneState { next: next, filter: anyRuneFilter{}, } } +func (ast SubexASTCopyAnyRune) String() string { + return "." +} type SubexASTCopyRune struct { rune rune } -func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != RuneType || outType != RuneType { + panic("Invalid types for SubexASTNot") + } return &SubexCopyRuneState { next: next, filter: selectRuneFilter {ast.rune}, @@ -172,7 +191,10 @@ func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, runic // Read in a single atom that must be a boolean and output it unchanged type SubexASTCopyBool struct {} -func (ast SubexASTCopyBool) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyBool) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCopyState { next: next, filter: anyBoolFilter{}, @@ -184,7 +206,10 @@ func (ast SubexASTCopyBool) String() string { // Read in a single atom that must be a number and output it unchanged type SubexASTCopyNumber struct {} -func (ast SubexASTCopyNumber) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyNumber) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCopyState { next: next, filter: anyNumberFilter{}, @@ -194,34 +219,12 @@ func (ast SubexASTCopyNumber) String() string { return "%" } -// Read in a full string value and copy it out unchanged -// # is equivalent to "_{-0}" -// TODO -// type SubexASTCopyString struct {} -// func (ast SubexASTCopyString) compileWith(next SubexState, slotMap *SlotMap) SubexState { -// stringAtomState := &SubexCopyStringAtomState { -// next: nil, -// } -// stringContentState := &SubexGroupState { -// &SubexCopyScalarState { -// scalar: walk.NewAtomStringTerminal(), -// next: next, -// }, -// stringAtomState, -// } -// stringAtomState.next = stringContentState -// return &SubexCopyScalarState { -// scalar: walk.NewAtomStringTerminal(), -// next: stringContentState, -// } -// } -// func (ast SubexASTCopyString) String() string { -// return "#" -// } - // Read in any single Atom and output it unchanged type SubexASTCopyAnyValue struct {} -func (ast SubexASTCopyAnyValue) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyAnyValue) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCopyState { next: next, filter: anyValueFilter{}, @@ -231,6 +234,7 @@ func (ast SubexASTCopyAnyValue) String() string { return "." } +/* type OutputContentAST interface { compile(slotMap *SlotMap) OutputContent } @@ -273,25 +277,66 @@ func (ast SubexASTOutput) compileWith(next SubexState, slotMap *SlotMap, runic b func (ast SubexASTOutput) String() string { return "=...=" } +*/ -// Read in a repeated subex separated by a delimiter. Greedy -type SubexASTJoin struct { - Content, Delimiter SubexAST +type OutputValueAST interface { + compile(slotMap *SlotMap) OutputValue +} + +type OutputValueLoadAST struct { + slot rune } -func (ast SubexASTJoin) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - afterContentState := &SubexGroupState { - nil, - next, +func (ast OutputValueLoadAST) compile(slotMap *SlotMap) OutputValue { + return OutputValueLoad { + slotMap.getId(ast.slot), } - manyContentsState := ast.Content.compileWith(afterContentState, slotMap, runic) - afterContentState.first = ast.Delimiter.compileWith(manyContentsState, slotMap, runic) - return &SubexGroupState { - manyContentsState, - next, +} + +type OutputValueLiteralAST struct { + scalar walk.Scalar +} +func (ast OutputValueLiteralAST) compile(slotMap *SlotMap) OutputValue { + return OutputValueLiteral { + ast.scalar, + } +} + +type SubexASTOutputValues struct { + Replacement []OutputValueAST +} +func (ast SubexASTOutputValues) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if outType != ValueType { + panic("Invalid outType") + } + var content []OutputValue + for _, el := range ast.Replacement { + content = append(content, el.compile(slotMap)) + } + return &SubexOutputValuesState { + content: content, + next: next, } } -func (ast SubexASTJoin) String() string { - return fmt.Sprintf("(%v);(%v)", ast.Content, ast.Delimiter) + +type OutputRuneAST interface { + compile(slotMap *SlotMap) OutputRune +} + +type OutputRuneLoadAST struct { + slot rune +} +func (ast OutputRuneLoadAST) compile(slotMap *SlotMap) OutputRune { + return OutputRuneLoad {slotMap.getRuneId(ast.slot)} +} + +type OutputRuneLiteralAST struct { + r rune +} +func (ast OutputRuneLiteralAST) compile (slotMap *SlotMap) OutputRune { + return OutputRuneLiteral {ast.r} +} + +type SubexASTOutputRunes struct { } // Run each input Atom through a map to produce an output Atom @@ -314,12 +359,15 @@ func (ast SubexASTJoin) String() string { type SubexASTSum struct { Content SubexAST } -func (ast SubexASTSum) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTSum) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCaptureBeginState { next: ast.Content.compileWith(&SubexArithmeticEndState { next: next, calculate: sumValues, - }, slotMap, runic), + }, slotMap, inType, outType), } } func (ast SubexASTSum) String() string { @@ -330,12 +378,15 @@ func (ast SubexASTSum) String() string { type SubexASTProduct struct { Content SubexAST } -func (ast SubexASTProduct) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTProduct) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCaptureBeginState { next: ast.Content.compileWith(&SubexArithmeticEndState { next: next, calculate: multiplyValues, - }, slotMap, runic), + }, slotMap, inType, outType), } } func (ast SubexASTProduct) String() string { @@ -347,12 +398,15 @@ func (ast SubexASTProduct) String() string { type SubexASTNegate struct { Content SubexAST } -func (ast SubexASTNegate) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTNegate) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCaptureBeginState { next: ast.Content.compileWith(&SubexArithmeticEndState { next: next, calculate: negateValues, - }, slotMap, runic), + }, slotMap, inType, outType), } } func (ast SubexASTNegate) String() string { @@ -360,58 +414,29 @@ func (ast SubexASTNegate) String() string { } // Runs the content Subex and collects the output -// If it is a list of atoms castable to numbers, it takes the reciprocal of them all and outputs them -// Else it rejects -type SubexASTReciprocal struct { - Content SubexAST -} -func (ast SubexASTReciprocal) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: reciprocalValues, - }, slotMap, runic), - } -} -func (ast SubexASTReciprocal) String() string { - return fmt.Sprintf("(%v)/", ast.Content) -} - -// Runs the content Subex and collects the output // Maps over the values in the output, casting each to a boolean, notting each and then outputs them // Rejects if it cannot cast to boolean type SubexASTNot struct { Content SubexAST } -func (ast SubexASTNot) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTNot) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTNot") + } return &SubexCaptureBeginState { next: ast.Content.compileWith(&SubexArithmeticEndState { next: next, calculate: notValues, - }, slotMap, runic), + }, slotMap, ValueType, ValueType), } } func (ast SubexASTNot) String() string { return fmt.Sprintf("(%v)!", ast.Content) } -// Runs the content Subex and collects the output -// Replaces it with true if all output values are equal and false otherwise -type SubexASTEqual struct { - Content SubexAST -} -func (ast SubexASTEqual) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: equalValues, - }, slotMap, runic), - } -} - // Does nothing type SubexASTEmpty struct {} -func (ast SubexASTEmpty) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTEmpty) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { return next } func (ast SubexASTEmpty) String() string { @@ -422,9 +447,9 @@ func (ast SubexASTEmpty) String() string { type SubexASTDiscard struct { Content SubexAST } -func (ast SubexASTDiscard) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, runic) - if !runic { +func (ast SubexASTDiscard) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, inType, outType) + if inType == ValueType { return &SubexCaptureBeginState { next: newNext, } @@ -442,7 +467,10 @@ func (ast SubexASTDiscard) String() string { type SubexASTEnterArray struct { Content SubexAST } -func (ast SubexASTEnterArray) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTEnterArray) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTEnterArray") + } return &SubexCaptureBeginState { next: &SubexIncrementNestState { next: &SubexCopyState { @@ -451,13 +479,14 @@ func (ast SubexASTEnterArray) compileWith(next SubexState, slotMap *SlotMap, run next: &SubexCaptureBeginState { next: ast.Content.compileWith( &SubexDiscardTerminalState { - terminal: walk.ArrayEndTerminal{}, + terminal: walk.ArrayEnd, next: &SubexDecrementNestState { next: &SubexConstructArrayState {next: next}, }, }, slotMap, - runic, + ValueType, + ValueType, ), }, }, @@ -469,22 +498,26 @@ func (ast SubexASTEnterArray) compileWith(next SubexState, slotMap *SlotMap, run type SubexASTEnterString struct { Content SubexAST } -func (ast SubexASTEnterString) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTEnterString) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTEnterString") + } return &SubexCaptureBeginState { - next: &SubexIncrementNestState { - next: &SubexCopyState { - filter: anyStringFilter{}, - next: &SubexDiscardState { + next: &SubexCopyState { + filter: anyStringFilter{}, + next: &SubexDiscardState { + next: &SubexIncrementNestState { next: &SubexCaptureRunesBeginState { next: ast.Content.compileWith( - &SubexDecrementNestState { - next: &SubexDiscardTerminalState { - terminal: walk.StringEndTerminal{}, + &SubexDiscardTerminalState { + terminal: walk.StringEnd, + next: &SubexDecrementNestState { next: &SubexConstructStringState {next: next}, }, }, slotMap, - true, + RuneType, + RuneType, ), }, }, diff --git a/subex/subexast_test.go b/subex/subexast_test.go deleted file mode 100644 index 156162e..0000000 --- a/subex/subexast_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package subex - -import ( - "testing" -) - -func expectASTEqual(t *testing.T, ast SubexAST, expected SubexAST) { - if ast == expected { - return - } - - t.Fatalf("Expected %v, found %v", expected, ast) -} - -func expectAST(t *testing.T, subex string, expected SubexAST) { - lexer := NewStringRuneReader(subex) - ast := Parse(lexer) - expectASTEqual(t, ast, expected) -} diff --git a/subex/subexstate.go b/subex/subexstate.go index 0b21c93..4de8ae2 100644 --- a/subex/subexstate.go +++ b/subex/subexstate.go @@ -4,13 +4,24 @@ package subex // e.g. Combine all of the copy states into a single type that has a filter function import ( + "fmt" "main/walk" + "strings" ) -// A state of execution for the transducer type SubexState interface { +} + +type SubexEpsilonState interface { + SubexState + epsilon(aux auxiliaryState) []SubexBranch +} + +// A state of execution for the transducer +type SubexEatState interface { + SubexState // Eat a Atom and transition to any number of new states - eat(aux auxiliaryState, char walk.Edible) []SubexBranch + eat(aux auxiliaryState, edible walk.Edible) []SubexBranch // Find accepting states reachable through epsilon transitions and return their outputs accepting(aux auxiliaryState) []OutputStack } @@ -19,13 +30,18 @@ type SubexState interface { type SubexGroupState struct { first, second SubexState } -func (state SubexGroupState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - otherAux := aux.cloneStore() - return append(state.first.eat(aux, char), state.second.eat(otherAux, char)...) -} -func (state SubexGroupState) accepting(aux auxiliaryState) []OutputStack { +func (state SubexGroupState) epsilon(aux auxiliaryState) []SubexBranch { otherAux := aux.cloneStore() - return append(state.first.accepting(aux), state.second.accepting(otherAux)...) + return []SubexBranch { + { + state: state.first, + aux: aux, + }, + { + state: state.second, + aux: otherAux, + }, + } } type SubexCopyState struct { @@ -39,7 +55,7 @@ func (state SubexCopyState) eat(aux auxiliaryState, edible walk.Edible) []SubexB } return []SubexBranch{{ state: state.next, - aux: aux.topAppend(walk.ValueList{value}), + aux: aux.topAppend([]walk.Value{value}), }} } func (state SubexCopyState) accepting(aux auxiliaryState) []OutputStack { @@ -51,29 +67,32 @@ type SubexCopyRuneState struct { filter runeFilter } func (state SubexCopyRuneState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - r, isRune := edible.(walk.StringRuneAtom) - if !isRune || !state.filter.runeFilter(r) { + r, isRune := edible.(walk.RuneEdible) + if !isRune || !state.filter.runeFilter(rune(r)) { return nil } return []SubexBranch{{ state: state.next, - aux: aux.topAppend(walk.RuneList{r}), + aux: aux.topAppendRune([]rune{rune(r)}), }} } func (state SubexCopyRuneState) accepting(aux auxiliaryState) []OutputStack { return nil } +func (state SubexCopyRuneState) String() string { + return fmt.Sprintf("SubexCopyRuneState[%v]", state.filter) +} // Just pushes to the OutputStack and hands over to the next state // Used to capture the output of the state being handed over to type SubexCaptureBeginState struct { next SubexState } -func (state SubexCaptureBeginState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - return state.next.eat(aux.pushOutput(walk.ValueList{}), char) -} -func (state SubexCaptureBeginState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.pushOutput(walk.ValueList{})) +func (state SubexCaptureBeginState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.pushOutput(nil), + }} } func (state SubexCaptureBeginState) String() string { return "CaptureBeginState" @@ -82,24 +101,22 @@ func (state SubexCaptureBeginState) String() string { type SubexCaptureRunesBeginState struct { next SubexState } -func (state SubexCaptureRunesBeginState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - return state.next.eat(aux.pushOutput(walk.RuneList{}), char) -} -func (state SubexCaptureRunesBeginState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.pushOutput(walk.RuneList{})) +func (state SubexCaptureRunesBeginState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.pushOutputRunes(nil), + }} } // Discard the top of the OutputStack type SubexDiscardState struct { next SubexState } -func (state SubexDiscardState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - _, newAux := aux.popOutput() - return state.next.eat(newAux, char) -} -func (state SubexDiscardState) accepting(aux auxiliaryState) []OutputStack { - _, newAux := aux.popOutput() - return state.next.accepting(newAux) +func (state SubexDiscardState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.popDiscardOutput(), + }} } // Pop the top of the OutputStack which contains the stuff outputted since the start of the store @@ -108,17 +125,15 @@ type SubexStoreEndState struct { slot int next SubexState } -func (state SubexStoreEndState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexStoreEndState) epsilon(aux auxiliaryState) []SubexBranch { toStore, aux := aux.popOutput() - aux = aux.withValue(state.slot, toStore) - return state.next.eat(aux, char) -} -func (state SubexStoreEndState) accepting(aux auxiliaryState) []OutputStack { - toStore, aux := aux.popOutput() - aux = aux.withValue(state.slot, toStore) - return state.next.accepting(aux) + return []SubexBranch {{ + state: state.next, + aux: aux.withValue(state.slot, toStore), + }} } +/* // 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 ValueList produced by the TransducerOutput @@ -184,7 +199,7 @@ func (state SubexOutputState) build(store Store) walk.ValueList { } return result } -func (state SubexOutputState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexOutputState) eat(aux auxiliaryState, char walk.Value) []SubexBranch { content := state.build(aux.store) nextStates := state.next.eat(aux.topAppend(content), char) return nextStates @@ -194,10 +209,55 @@ func (state SubexOutputState) accepting(aux auxiliaryState) []OutputStack { outputStacks := state.next.accepting(aux.topAppend(content)) return outputStacks } +*/ + +type OutputValue interface { + build(store Store) []walk.Value +} + +type OutputValueLoad struct { + slot int +} +func (ov OutputValueLoad) build(store Store) []walk.Value { + return store.values[ov.slot] +} + +type OutputValueLiteral struct { + scalar walk.Scalar +} +func (ov OutputValueLiteral) build(store Store) []walk.Value { + return []walk.Value{ov.scalar} +} + +type SubexOutputValuesState struct { + content []OutputValue + next SubexState +} +func (state SubexOutputValuesState) epsilon(aux auxiliaryState) []SubexBranch { + var content []walk.Value + for _, el := range state.content { + content = append(content, el.build(aux.store)...) + } + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend(content), + }} +} + +type OutputRune interface { +} + +type OutputRuneLoad struct { + slot int +} + +type OutputRuneLiteral struct { + r rune +} // A final state, transitions to nothing but is accepting type SubexNoneState struct {} -func (state SubexNoneState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexNoneState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { return nil } func (state SubexNoneState) accepting(aux auxiliaryState) []OutputStack { @@ -206,7 +266,7 @@ func (state SubexNoneState) accepting(aux auxiliaryState) []OutputStack { // A dead end state, handy for making internals work nicer but technically redundant type SubexDeadState struct {} -func (state SubexDeadState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexDeadState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { return nil } func (state SubexDeadState) accepting (aux auxiliaryState) []OutputStack { @@ -239,31 +299,18 @@ func (state SubexDeadState) accepting (aux auxiliaryState) []OutputStack { type SubexArithmeticEndState struct { next SubexState - calculate func(walk.ValueList) (walk.ValueList, error) + calculate func([]walk.Value) ([]walk.Value, error) } -func (state SubexArithmeticEndState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - toCompute, aux := aux.popOutput() - values, isValues := toCompute.(walk.ValueList) - if !isValues { - panic("Tried to do arithmetic on non-values") - } - result, err := state.calculate(values) - if err != nil { - return nil - } - return state.next.eat(aux.topAppend(result), char) -} -func (state SubexArithmeticEndState) accepting(aux auxiliaryState) []OutputStack { - toCompute, aux := aux.popOutput() - values, isValues := toCompute.(walk.ValueList) - if !isValues { - panic("Tried to do arithmetic on non-values") - } +func (state SubexArithmeticEndState) epsilon(aux auxiliaryState) []SubexBranch { + values, aux := aux.popOutput() result, err := state.calculate(values) if err != nil { return nil } - return state.next.accepting(aux.topAppend(result)) + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend(result), + }} } type SubexDiscardTerminalState struct { @@ -286,55 +333,57 @@ func (state SubexDiscardTerminalState) accepting(aux auxiliaryState) []OutputSta type SubexConstructArrayState struct { next SubexState } -func (state SubexConstructArrayState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - outputs, aux := aux.popOutput() - values, isValues := outputs.(walk.ValueList) - if !isValues { - panic("Tried to create an array from non-values") +func (state SubexConstructArrayState) epsilon(aux auxiliaryState) []SubexBranch { + values, aux := aux.popOutput() + var array walk.ArrayValue + if len(values) % 2 != 0 { + panic("Tried to construct array with odd length input") } - array := walk.ArrayStructure(values) - return state.next.eat(aux.topAppend(walk.ValueList{array}), edible) -} -func (state SubexConstructArrayState) accepting(aux auxiliaryState) []OutputStack { - outputs, aux := aux.popOutput() - values, isValues := outputs.(walk.ValueList) - if !isValues { - panic("Tried to create an array from non-values") + for i := 0; i < len(values); i += 2 { + index, isNum := values[i].(walk.NumberValue) + if !isNum { + panic("Tried to construct array with non-numeric index") + } + array = append(array, walk.ArrayElement { + Index: int(index), + Value: values[i + 1], + }) } - array := walk.ArrayStructure(values) - return state.next.accepting(aux.topAppend(walk.ValueList{array})) + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend([]walk.Value {array}), + }} } type SubexConstructStringState struct { next SubexState } -func (state SubexConstructStringState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - outputs, aux := aux.popOutput() - runes, isRunes := outputs.(walk.RuneList) - if !isRunes { - panic("Tried to create a string from non-runes") +func (state SubexConstructStringState) construct(runes []rune) []walk.Value { + var builder strings.Builder + for _, r := range runes { + builder.WriteRune(r) } - s := walk.StringStructure(runes) - return state.next.eat(aux.topAppend(walk.ValueList{s}), edible) + return []walk.Value{walk.StringValue(builder.String())} } -func (state SubexConstructStringState) accepting(aux auxiliaryState) []OutputStack { - outputs, aux := aux.popOutput() - runes, isRunes := outputs.(walk.RuneList) - if !isRunes { - panic("Tried to create a string from non-runes") - } - s := walk.StringStructure(runes) - return state.next.accepting(aux.topAppend(walk.ValueList{s})) +func (state SubexConstructStringState) epsilon(aux auxiliaryState) []SubexBranch { + runes, aux := aux.popOutputRunes() + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend(state.construct(runes)), + }} +} +func (state SubexConstructStringState) String() string { + return "SubexConstructStringState" } type SubexIncrementNestState struct { next SubexState } -func (state SubexIncrementNestState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - return state.next.eat(aux.incNest(), edible) -} -func (state SubexIncrementNestState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.incNest()) +func (state SubexIncrementNestState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.incNest(), + }} } func (state SubexIncrementNestState) String() string { return "IncrementNestState" @@ -343,9 +392,9 @@ func (state SubexIncrementNestState) String() string { type SubexDecrementNestState struct { next SubexState } -func (state SubexDecrementNestState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - return state.next.eat(aux.decNest(), edible) -} -func (state SubexDecrementNestState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.decNest()) +func (state SubexDecrementNestState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.decNest(), + }} } diff --git a/subex/subexstate_test.go b/subex/subexstate_test.go deleted file mode 100644 index 4eb8969..0000000 --- a/subex/subexstate_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package subex - -import ( -) diff --git a/walk/read.go b/walk/read.go index f25109c..5aedafb 100644 --- a/walk/read.go +++ b/walk/read.go @@ -6,6 +6,6 @@ type StredReader interface { } type StredWriter interface { - Write(WalkItem) error + Write(Value) error AssertDone() -}
\ No newline at end of file +} diff --git a/walk/walk.go b/walk/walk.go index fc9e9de..3fba62f 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -7,7 +7,41 @@ import ( type PathSegment interface {} +type OutputList interface { + outputList() +} + +type OutputValueList []Value +func (_ OutputValueList) outputList() {} + +type OutputRuneList []rune +func (_ OutputRuneList) outputList() {} + +// Can be passed to .eat on a state +type Edible interface { + edible() +} + +type RuneEdible rune +func (_ RuneEdible) edible() {} + +type Terminal int +const ( + ArrayEnd Terminal = iota + MapEnd + StringEnd +) +func (_ Terminal) edible() {} + +// Simple value +// Has a literal +type Scalar interface { + Value + scalar() +} + type Value interface { + Edible value() Debug() string } @@ -17,6 +51,8 @@ func (_ NullValue) value() {} func (_ NullValue) Debug() string { return "null" } +func (_ NullValue) edible() {} +func (_ NullValue) scalar() {} type BoolValue bool func (_ BoolValue) value() {} @@ -26,24 +62,23 @@ func (b BoolValue) Debug() string { } return "false" } +func (_ BoolValue) edible() {} +func (_ BoolValue) scalar() {} type NumberValue float64 func (_ NumberValue) value() {} func (n NumberValue) Debug() string { return fmt.Sprintf("%v", float64(n)) } - -type RuneValue rune -func (_ RuneValue) value() {} -func (r RuneValue) Debug() string { - return string(r) -} +func (_ NumberValue) edible() {} +func (_ NumberValue) scalar() {} type StringValue string func (_ StringValue) value() {} func (s StringValue) Debug() string { return fmt.Sprintf("%q", string(s)) } +func (_ StringValue) edible() {} type ArrayElement struct { Index int @@ -66,6 +101,7 @@ func (array ArrayValue) Debug() string { builder.WriteRune(']') return builder.String() } +func (_ ArrayValue) edible() {} type MapElement struct { Key string @@ -88,6 +124,7 @@ func (m MapValue) Debug() string { builder.WriteRune('}') return builder.String() } +func (_ MapValue) edible() {} type WalkItem struct { Value Value |