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 /subex | |
| 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.
Diffstat (limited to 'subex')
| -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 | 
9 files changed, 702 insertions, 890 deletions
| 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 ( -) | 
