diff options
| author | Charlie Stanton <charlie@shtanton.xyz> | 2023-07-19 11:57:59 +0100 | 
|---|---|---|
| committer | Charlie Stanton <charlie@shtanton.xyz> | 2023-07-19 11:57:59 +0100 | 
| commit | 8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b (patch) | |
| tree | 7a16883c17c2bdcc49b2f9d4f333dfc76c66248f /subex | |
| parent | 3c34366bdd5d817a184d6b1c901d03a16b6faa4b (diff) | |
| download | stred-go-8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b.tar | |
Huge refactor to a more value based system, doing away with terminals. Also introduces unit testing
Diffstat (limited to 'subex')
| -rw-r--r-- | subex/arithmetic.go | 114 | ||||
| -rw-r--r-- | subex/filter.go | 62 | ||||
| -rw-r--r-- | subex/main.go | 167 | ||||
| -rw-r--r-- | subex/main_test.go | 442 | ||||
| -rw-r--r-- | subex/parse.go | 295 | ||||
| -rw-r--r-- | subex/subexast.go | 296 | ||||
| -rw-r--r-- | subex/subexast_test.go | 19 | ||||
| -rw-r--r-- | subex/subexstate.go | 362 | ||||
| -rw-r--r-- | subex/subexstate_test.go | 4 | 
9 files changed, 1254 insertions, 507 deletions
| diff --git a/subex/arithmetic.go b/subex/arithmetic.go index 1ebd1a6..9e5e530 100644 --- a/subex/arithmetic.go +++ b/subex/arithmetic.go @@ -6,27 +6,23 @@ import (  	"strconv"  ) -func sumValues(atoms []walk.Atom) ([]walk.Atom, error) { +func sumValues(values walk.ValueList) (walk.ValueList, error) {  	allBools := true  	var sum float64 = 0  	var any bool = false -	values, err := walk.Compound(atoms) -	if err != nil { -		return nil, err -	}  	for _, value := range values {  		switch v := value.(type) { -			case walk.ValueNull: +			case walk.NullScalar:  				allBools = false -			case walk.ValueBool: -				if bool(v) { +			case walk.BoolScalar: +				if v {  					sum += 1  					any = true  				} -			case walk.ValueNumber: +			case walk.NumberScalar:  				allBools = false  				sum += float64(v) -			case walk.ValueString: +			case walk.StringStructure:  				allBools = false  				num, err := strconv.ParseFloat(string(v), 64)  				if err == nil { @@ -39,35 +35,31 @@ func sumValues(atoms []walk.Atom) ([]walk.Atom, error) {  		}  	}  	if allBools { -		return []walk.Atom{walk.NewAtomBool(any)}, nil +		return walk.ValueList{walk.BoolScalar(any)}, nil  	} else { -		return []walk.Atom{walk.NewAtomNumber(sum)}, nil +		return walk.ValueList{walk.NumberScalar(sum)}, nil  	}  }  // Compounds atoms into values, if all values are booleans, does AND, if not, tries to cast to numbers and multiply -func multiplyValues(atoms []walk.Atom) ([]walk.Atom, error) { +func multiplyValues(values walk.ValueList) (walk.ValueList, error) {  	allBools := true  	var product float64 = 1  	var all bool = false -	values, err := walk.Compound(atoms) -	if err != nil { -		return nil, err -	}  	for _, value := range values {  		switch v := value.(type) { -			case walk.ValueNull: +			case walk.NullScalar:  				allBools = false  				product *= 0 -			case walk.ValueBool: -				if !bool(v) { +			case walk.BoolScalar: +				if !v {  					product *= 0  					all = false  				} -			case walk.ValueNumber: +			case walk.NumberScalar:  				allBools = false  				product *= float64(v) -			case walk.ValueString: +			case walk.StringStructure:  				allBools = false  				num, err := strconv.ParseFloat(string(v), 64)  				if err == nil { @@ -80,35 +72,31 @@ func multiplyValues(atoms []walk.Atom) ([]walk.Atom, error) {  		}  	}  	if allBools { -		return []walk.Atom{walk.NewAtomBool(all)}, nil +		return walk.ValueList{walk.BoolScalar(all)}, nil  	} else { -		return []walk.Atom{walk.NewAtomNumber(product)}, nil +		return walk.ValueList{walk.NumberScalar(product)}, nil  	}  }  // Does tries to cast all to numbers and negates them -func negateValues(atoms []walk.Atom) ([]walk.Atom, error) { -	var negatedNumbers []walk.Atom -	values, err := walk.Compound(atoms) -	if err != nil { -		return nil, err -	} +func negateValues(values walk.ValueList) (walk.ValueList, error) { +	var negatedNumbers walk.ValueList  	for _, value := range values {  		switch v := value.(type) { -			case walk.ValueNull: -				negatedNumbers = append(negatedNumbers, walk.NewAtomNumber(0)) -			case walk.ValueBool: -				if bool(v) { -					negatedNumbers = append(negatedNumbers, walk.NewAtomNumber(-1)) +			case walk.NullScalar: +				negatedNumbers = append(negatedNumbers, walk.NumberScalar(0)) +			case walk.BoolScalar: +				if v { +					negatedNumbers = append(negatedNumbers, walk.NumberScalar(-1))  				} else { -					negatedNumbers = append(negatedNumbers, walk.NewAtomNumber(0)) +					negatedNumbers = append(negatedNumbers, walk.NumberScalar(0))  				} -			case walk.ValueNumber: -				negatedNumbers = append(negatedNumbers, walk.NewAtomNumber(-float64(v))) -			case walk.ValueString: +			case walk.NumberScalar: +				negatedNumbers = append(negatedNumbers, walk.NumberScalar(-float64(v))) +			case walk.StringStructure:  				num, err := strconv.ParseFloat(string(v), 64)  				if err == nil { -					negatedNumbers = append(negatedNumbers, walk.NewAtomNumber(-num)) +					negatedNumbers = append(negatedNumbers, walk.NumberScalar(-num))  				} else {  					return nil, errors.New("Tried to negate non-castable string")  				} @@ -121,28 +109,24 @@ func negateValues(atoms []walk.Atom) ([]walk.Atom, error) {  // If all are castable to numbers, takes reciprocals of all and returns them  // Else errors -func reciprocalValues(atoms []walk.Atom) ([]walk.Atom, error) { -	var reciprocals []walk.Atom -	values, err := walk.Compound(atoms) -	if err != nil { -		return nil, err -	} +func reciprocalValues(values walk.ValueList) (walk.ValueList, error) { +	var reciprocals walk.ValueList  	for _, value := range values {  		switch v := value.(type) { -			case walk.ValueNull: +			case walk.NullScalar:  				return nil, errors.New("Tried to take reciprocal of null") -			case walk.ValueBool: -				if bool(v) { -					reciprocals = append(reciprocals, walk.NewAtomNumber(1)) +			case walk.BoolScalar: +				if v { +					reciprocals = append(reciprocals, walk.NumberScalar(1))  				} else {  					return nil, errors.New("Tried to take reciprocal of false")  				} -			case walk.ValueNumber: -				reciprocals = append(reciprocals, walk.NewAtomNumber(1 / float64(v))) -			case walk.ValueString: +			case walk.NumberScalar: +				reciprocals = append(reciprocals, walk.NumberScalar(1 / float64(v))) +			case walk.StringStructure:  				num, err := strconv.ParseFloat(string(v), 64)  				if err == nil { -					reciprocals = append(reciprocals, walk.NewAtomNumber(1 / num)) +					reciprocals = append(reciprocals, walk.NumberScalar(1 / num))  				} else {  					return nil, errors.New("Tried to take reciprocal of non-castable string")  				} @@ -155,21 +139,17 @@ func reciprocalValues(atoms []walk.Atom) ([]walk.Atom, error) {  // If all are castable to booleans, NOTs all and returns them  // Else errors -func notValues(atoms []walk.Atom) (notted []walk.Atom, err error) { -	values, err := walk.Compound(atoms) -	if err != nil { -		return nil, err -	} +func notValues(values walk.ValueList) (notted walk.ValueList, err error) {  	for _, value := range values {  		switch v := value.(type) { -			case walk.ValueNull: -				notted = append(notted, walk.NewAtomBool(true)) -			case walk.ValueBool: -				notted = append(notted, walk.NewAtomBool(!bool(v))) -			case walk.ValueNumber: -				notted = append(notted, walk.NewAtomBool(v == 0)) -			case walk.ValueString: -				notted = append(notted, walk.NewAtomBool(len(v) == 0)) +			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))  			default:  				return nil, errors.New("Tried to NOT non-boolean")  		} diff --git a/subex/filter.go b/subex/filter.go new file mode 100644 index 0000000..1a1b6db --- /dev/null +++ b/subex/filter.go @@ -0,0 +1,62 @@ +package subex + +import ( +	"main/walk" +) + +type valueFilter interface { +	valueFilter(value walk.Value) bool +} + +type selectScalarFilter struct { +	scalar walk.Scalar +} +func (scalar selectScalarFilter) valueFilter(value walk.Value) bool { +	return value == scalar.scalar +} + +type anyNumberFilter struct {} +func (_ anyNumberFilter) valueFilter(value walk.Value) bool { +	_, isNumber := value.(walk.NumberScalar) +	return isNumber +} + +type anyBoolFilter struct {} +func (_ anyBoolFilter) valueFilter(value walk.Value) bool { +	_, isBool := value.(walk.BoolScalar) +	return isBool +} + +type anyValueFilter struct {} +func (_ anyValueFilter) valueFilter(value walk.Value) bool { +	return true +} + +type anyArrayFilter struct {} +func (_ anyArrayFilter) valueFilter(value walk.Value) bool { +	_, isArray := value.(walk.ArrayStructure) +	return isArray +} + +type anyStringFilter struct {} +func (_ anyStringFilter) valueFilter(value walk.Value) bool { +	_, isString := value.(walk.StringStructure) +	return isString +} + + +type runeFilter interface { +	runeFilter(r walk.StringRuneAtom) bool +} + +type anyRuneFilter struct {} +func (_ anyRuneFilter) runeFilter(r walk.StringRuneAtom) bool { +	return true +} + +type selectRuneFilter struct { +	r rune +} +func (f selectRuneFilter) runeFilter(r walk.StringRuneAtom) bool { +	return f.r == rune(r) +} diff --git a/subex/main.go b/subex/main.go index ebd87cb..e2cec0b 100644 --- a/subex/main.go +++ b/subex/main.go @@ -11,15 +11,15 @@ type Transducer struct {  type StoreItem struct {}  // Where slots are stored -type Store [][]walk.Atom +type Store []walk.OutputList  // Return a new store with all the data from this one  func (store Store) clone() Store { -	newStore := make([][]walk.Atom, len(store)) +	newStore := make([]walk.OutputList, len(store))  	copy(newStore, store)  	return newStore  }  // Return a copy of this store but with an additional slot set -func (store Store) withValue(key int, value []walk.Atom) Store { +func (store Store) withValue(key int, value walk.OutputList) Store {  	newStore := store.clone()  	newStore[key] = value  	return newStore @@ -46,7 +46,7 @@ func CompileTransducer(transducerAst SubexAST) Transducer {  		nextId: 0,  		ids: make(map[rune]int),  	} -	initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap) +	initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, false)  	return Transducer {  		storeSize: slotMap.nextId,  		initialState: initial, @@ -55,54 +55,119 @@ func CompileTransducer(transducerAst SubexAST) Transducer {  // An immutable stack for outputting to  type OutputStack struct { -	head []walk.Atom +	head walk.OutputList  	tail *OutputStack  } -func (stack OutputStack) pop() ([]walk.Atom, OutputStack) { +func (stack OutputStack) pop() (walk.OutputList, OutputStack) {  	return stack.head, *stack.tail  } -func (stack OutputStack) push(atoms []walk.Atom) OutputStack { +func (stack OutputStack) push(atoms walk.OutputList) OutputStack {  	return OutputStack {  		head: atoms,  		tail: &stack,  	}  } -func (stack OutputStack) replace(atoms []walk.Atom) OutputStack { +func (stack OutputStack) replace(atoms walk.OutputList) OutputStack {  	return OutputStack {  		head: atoms,  		tail: stack.tail,  	}  } -func (stack OutputStack) peek() []walk.Atom { +func (stack OutputStack) peek() walk.OutputList {  	return stack.head  } -func topAppend(outputStack OutputStack, atoms []walk.Atom) OutputStack { -	head := outputStack.peek() -	return outputStack.replace(walk.ConcatData(head, atoms)) +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 = append(head, values...) +	return outputStack.replace(head)  } -// One branch of subex execution -type SubexBranch struct { +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...) +	head = append(head, runes...) +	return outputStack.replace(head) +} + +// Addition state that goes along with a subex state in an execution branch +type auxiliaryState struct {  	// Content of slots in this branch  	store Store -	// State in this branch -	state SubexState  	// The output stack. At the end of the program, whatever is on top of this will be output  	// States may push or pop to the stack as they wish, creating sort of a call stack that allows states to capture the output of other states  	outputStack OutputStack +	// How deeply nested the current execution is inside of the overall value +	// i.e. starts at zero, is incremented to one when entering an array +	nesting int +} + +func (aux auxiliaryState) cloneStore() auxiliaryState { +	aux.store = aux.store.clone() +	return aux +} + +func (aux auxiliaryState) withValue(slot int, value walk.OutputList) auxiliaryState { +	aux.store = aux.store.withValue(slot, value) +	return aux +} + +func (aux auxiliaryState) pushOutput(data walk.OutputList) auxiliaryState { +	aux.outputStack = aux.outputStack.push(data) +	return aux +} + +func (aux auxiliaryState) popOutput() (walk.OutputList, 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) +	} +	return aux +} + +func (aux auxiliaryState) incNest() auxiliaryState { +	aux.nesting++ +	return aux +} + +func (aux auxiliaryState) decNest() auxiliaryState { +	aux.nesting-- +	return aux +} + +// One branch of subex execution +type SubexBranch struct { +	// State in this branch +	state SubexState +	// 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.Atom) []SubexBranch { -	return pair.state.eat(pair.store, pair.outputStack, char) +func (pair SubexBranch) eat(char walk.Edible) []SubexBranch { +	return pair.state.eat(pair.aux, char)  }  func (pair SubexBranch) accepting() []OutputStack { -	return pair.state.accepting(pair.store, pair.outputStack) +	return pair.state.accepting(pair.aux)  }  func equalStates(left SubexBranch, right SubexBranch) bool {  	// Only care about if they are the same pointer -	return left.state == right.state +	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 @@ -121,33 +186,67 @@ func pruneStates(states []SubexBranch) []SubexBranch {  	return states[:uniqueStates]  } -// Run the subex transducer -func RunTransducer(transducer Transducer, input []walk.Atom) (output []walk.Atom, err bool) { -	states := []SubexBranch{{ -		state: transducer.initialState, -		outputStack: OutputStack { -			head: nil, -			tail: nil, -		}, -		store: make([][]walk.Atom, transducer.storeSize), -	}} +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 := range input { +	for { +		piece := input.Next() +		if piece == nil { +			break +		} + +		// 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 { -			newStates = append(newStates, state.eat(piece)...) +			if state.aux.nesting == nesting { +				newStates = append(newStates, state.eat(piece)...) +			} else { +				newStates = append(newStates, state) +			}  		} + +		structure, isStructure := piece.(walk.Structure) +		if isStructure { +			iter := structure.Iter() +			newStates = processInput(newStates, iter, nesting + 1) +		} +  		tmp = states  		states = pruneStates(newStates)  		newStates = tmp[:0]  		if len(states) == 0 { -			return nil, true +			return states  		}  	} +	return states +} + +// Run the subex transducer +func RunTransducer(transducer Transducer, input walk.ValueList) (output []walk.Value, err bool) { +	states := []SubexBranch{{ +		state: transducer.initialState, +		aux: auxiliaryState { +			outputStack: OutputStack { +				head: walk.ValueList{}, +				tail: nil, +			}, +			store: make([]walk.OutputList, transducer.storeSize), +			nesting: 0, +		}, +	}} + +	states = processInput(states, walk.NewValueIter(input), 0) +  	for _, state := range states {  		acceptingStacks := state.accepting()  		for _, stack := range acceptingStacks { -			output := stack.head +			output, isValues := stack.head.(walk.ValueList) +			if !isValues { +				panic("Tried to output a non-values list") +			}  			return output, false  		}  	} diff --git a/subex/main_test.go b/subex/main_test.go new file mode 100644 index 0000000..f0350d2 --- /dev/null +++ b/subex/main_test.go @@ -0,0 +1,442 @@ +package subex + +import ( +	"testing" +	"main/walk" +	"fmt" +	"strings" +) + +func buildTransducer(subex string) Transducer { +	lexer := NewStringRuneReader(subex) +	ast := Parse(lexer) +	transducer := CompileTransducer(ast) +	return transducer +} + +func fatalMismatch(t *testing.T, path walk.ValueList, message string) { +	var sep 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())) +		} +		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") +	} +} + +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 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), +			}, +		}, +		walk.ValueList{ +			walk.ArrayStructure{ +				walk.NumberScalar(5), +			}, +		}, +	) +} + +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), +			}, +		}, +		walk.ValueList{ +			walk.ArrayStructure{ +				walk.NumberScalar(1), +				walk.NumberScalar(3), +				walk.NumberScalar(4), +			}, +		}, +	) +} + +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), +			}, +		}, +		walk.ValueList{ +			walk.ArrayStructure{ +				walk.NumberScalar(1), +				walk.NumberScalar(2), +			}, +		}, +	) +} + +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), +		}, +	) +} + +func TestCutStringFromStart(t *testing.T) { +	//transducer := buildTransducer("~\"test\"$_(.{-0})") +	lexer := NewStringRuneReader("~\"test\"$_(.{-0})") +	ast := Parse(lexer) +	t.Log(ast) +	transducer := CompileTransducer(ast) + +	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{}, +	) +} diff --git a/subex/parse.go b/subex/parse.go index 746217b..a671e6d 100644 --- a/subex/parse.go +++ b/subex/parse.go @@ -22,7 +22,7 @@ func accept(l RuneReader, chars string) bool {  	return false  } -func expectBracket(l RuneReader, ifLeft walk.Atom, ifRight walk.Atom) walk.Atom { +func expectBracket(l RuneReader, ifLeft walk.AtomOLD, ifRight walk.AtomOLD) walk.AtomOLD {  	switch l.Next() {  		case '(':  			return ifLeft @@ -38,7 +38,7 @@ func isNumericRune(r rune) bool {  }  // Having just parsed a `, read until the next ` and parse the contents into a list of non-string atoms -func parseNonStringLiteral(l RuneReader) (literals []walk.Atom) { +func parseNonStringLiteral(l RuneReader) (literals []walk.Scalar) {  	for {  		r := l.Next()  		if isNumericRune(r) { @@ -57,7 +57,7 @@ func parseNonStringLiteral(l RuneReader) (literals []walk.Atom) {  			if err != nil {  				panic("Invalid number literal")  			} -			literals = append(literals, walk.NewAtomNumber(number)) +			literals = append(literals, walk.NumberScalar(number))  			continue  		}  		switch r { @@ -67,30 +67,22 @@ func parseNonStringLiteral(l RuneReader) (literals []walk.Atom) {  				continue  			case 'n':  				if accept(l, "u") && accept(l, "l") && accept(l, "l") { -					literals = append(literals, walk.NewAtomNull()) +					literals = append(literals, walk.NullScalar{})  				} else {  					panic("Invalid literal")  				}  			case 't':  				if accept(l, "r") && accept(l, "u") && accept(l, "e") { -					literals = append(literals, walk.NewAtomBool(true)) +					literals = append(literals, walk.BoolScalar(true))  				} else {  					panic("Invalid literal")  				}  			case 'f':  				if accept(l, "a") && accept(l, "l") && accept(l, "s") && accept(l, "e") { -					literals = append(literals, walk.NewAtomBool(false)) +					literals = append(literals, walk.BoolScalar(false))  				} else {  					panic("Invalid literal")  				} -			case '{': -				literals = append(literals, walk.NewAtomTerminal(walk.MapBegin)) -			case '}': -				literals = append(literals, walk.NewAtomTerminal(walk.MapEnd)) -			case '[': -				literals = append(literals, walk.NewAtomTerminal(walk.ArrayBegin)) -			case ']': -				literals = append(literals, walk.NewAtomTerminal(walk.ArrayEnd))  			default:  				panic("Invalid literal")  		} @@ -177,113 +169,113 @@ func parseReplacement(l RuneReader) (output []OutputContentAST) {  			case '`':  				literals := parseNonStringLiteral(l)  				for _, literal := range literals { -					output = append(output, OutputAtomLiteralAST {literal}) +					output = append(output, OutputValueLiteralAST {literal})  				} -			case '"': -				output = append(output, OutputAtomLiteralAST {walk.NewAtomStringTerminal()})  			default: -				output = append(output, OutputAtomLiteralAST{atom: walk.NewAtomStringRune(r)}) +				panic("Invalid value to insert") +				//output = append(output, OutputValueLiteralAST{atom: walk.NewAtomStringRune(r)})  		}  	}  	return output  }  // Parse the contents of a range subex [] into a map -func parseRangeSubex(l RuneReader) map[walk.Atom]walk.Atom { -	// TODO escaping -	parts := make(map[walk.Atom]walk.Atom) -	var froms []walk.Atom -	var hasTo bool -	for { -		fromsStart := l.Next() -		if fromsStart == ']' { -			hasTo = false -			break -		} else if fromsStart == '=' { -			hasTo = true -			break -		} else if fromsStart == '`' { -			literals := parseNonStringLiteral(l) -			froms = append(froms, literals...) -			continue -		} else if fromsStart == '"' { -			froms = append(froms, walk.NewAtomStringTerminal()) -			continue -		} -		if accept(l, "-") { -			fromsEnd := l.Next() -			if fromsEnd == ']' || fromsEnd == '=' { -				l.Rewind() -				fromsEnd = fromsStart -			} -			for i := fromsStart; i <= fromsEnd; i += 1 { -				froms = append(froms, walk.NewAtomStringRune(i)) -			} -		} else { -			froms = append(froms, walk.NewAtomStringRune(fromsStart)) -		} -	} -	if len(froms) == 0 { -		panic("Missing from part of range expression") -	} +// func parseRangeSubex(l RuneReader) map[walk.AtomOLD]walk.AtomOLD { +// 	// TODO escaping +// 	parts := make(map[walk.AtomOLD]walk.AtomOLD) +// 	var froms []walk.AtomOLD +// 	var hasTo bool +// 	for { +// 		fromsStart := l.Next() +// 		if fromsStart == ']' { +// 			hasTo = false +// 			break +// 		} else if fromsStart == '=' { +// 			hasTo = true +// 			break +// 		} else if fromsStart == '`' { +// 			literals := parseNonStringLiteral(l) +// 			froms = append(froms, literals...) +// 			continue +// 		} else if fromsStart == '"' { +// 			froms = append(froms, walk.NewAtomStringTerminal()) +// 			continue +// 		} +// 		if accept(l, "-") { +// 			fromsEnd := l.Next() +// 			if fromsEnd == ']' || fromsEnd == '=' { +// 				l.Rewind() +// 				fromsEnd = fromsStart +// 			} +// 			for i := fromsStart; i <= fromsEnd; i += 1 { +// 				froms = append(froms, walk.NewAtomStringRune(i)) +// 			} +// 		} else { +// 			froms = append(froms, walk.NewAtomStringRune(fromsStart)) +// 		} +// 	} +// 	if len(froms) == 0 { +// 		panic("Missing from part of range expression") +// 	} -	var tos []walk.Atom -	if hasTo { -		for { -			tosStart := l.Next() -			if tosStart == ']' { -				break -			} else if tosStart == '`' { -				literals := parseNonStringLiteral(l) -				tos = append(tos, literals...) -				continue -			} else if tosStart == '"' { -				tos = append(tos, walk.NewAtomStringTerminal()) -				continue -			} -			if accept(l, "-") { -				tosEnd := l.Next() -				if tosEnd == ']' { -					l.Rewind() -					tosEnd = tosStart -				} -				for i := tosStart; i <= tosEnd; i += 1 { -					tos = append(tos, walk.NewAtomStringRune(i)) -				} -			} else { -				tos = append(tos, walk.NewAtomStringRune(tosStart)) -			} -		} -	} else { -		tos = froms -	} -	if len(tos) == 0 { -		panic("Missing to part of range expression") -	} +// 	var tos []walk.AtomOLD +// 	if hasTo { +// 		for { +// 			tosStart := l.Next() +// 			if tosStart == ']' { +// 				break +// 			} else if tosStart == '`' { +// 				literals := parseNonStringLiteral(l) +// 				tos = append(tos, literals...) +// 				continue +// 			} else if tosStart == '"' { +// 				tos = append(tos, walk.NewAtomStringTerminal()) +// 				continue +// 			} +// 			if accept(l, "-") { +// 				tosEnd := l.Next() +// 				if tosEnd == ']' { +// 					l.Rewind() +// 					tosEnd = tosStart +// 				} +// 				for i := tosStart; i <= tosEnd; i += 1 { +// 					tos = append(tos, walk.NewAtomStringRune(i)) +// 				} +// 			} else { +// 				tos = append(tos, walk.NewAtomStringRune(tosStart)) +// 			} +// 		} +// 	} else { +// 		tos = froms +// 	} +// 	if len(tos) == 0 { +// 		panic("Missing to part of range expression") +// 	} -	for i, from := range froms { -		parts[from] = tos[i % len(tos)] -	} -	return parts -} +// 	for i, from := range froms { +// 		parts[from] = tos[i % len(tos)] +// 	} +// 	return parts +// } -func parseSubex(l RuneReader, minPower int) SubexAST { +func parseSubex(l RuneReader, minPower int, runic bool) SubexAST {  	var lhs SubexAST  	r := l.Next()  	switch r {  		case eof:  			return nil  		case '(': -			lhs = parseSubex(l, 0) +			lhs = parseSubex(l, 0, runic)  			if !accept(l, ")") {  				panic("Missing matching )")  			} -		case '[': -			rangeParts := parseRangeSubex(l) -			lhs = SubexASTRange {rangeParts} -		case ')', '|', ';', '{', '+', '-', '*', '/', '!', '$', ':': +		// TODO +		// case '[': +		// 	rangeParts := parseRangeSubex(l) +		// 	lhs = SubexASTRange {rangeParts} +		case ')', ']', '"', '|', ';', '{', '+', '-', '*', '/', '!', '$':  			l.Rewind() -			return nil +			return SubexASTEmpty{}  		case '=':  			replacement := parseReplacement(l)  			lhs = SubexASTOutput{replacement} @@ -291,47 +283,80 @@ func parseSubex(l RuneReader, minPower int) SubexAST {  			literals := parseNonStringLiteral(l)  			lhs = SubexASTEmpty{}  			for _, literal := range literals { -				lhs = SubexASTConcat {lhs, SubexASTCopyAtom {literal}} +				lhs = SubexASTConcat {lhs, SubexASTCopyScalar {literal}}  			} -		case '^': -			replacement := parseReplacement(l) -			replacement = append( -				[]OutputContentAST{OutputAtomLiteralAST {walk.NewAtomStringTerminal()}}, -				replacement... -			) -			replacement = append( -				replacement, -				OutputAtomLiteralAST {walk.NewAtomStringTerminal()}, -			) -			lhs = SubexASTOutput {replacement} +		// case '^': +		// 	replacement := parseReplacement(l) +		// 	replacement = append( +		// 		[]OutputContentAST{OutputValueLiteralAST {walk.NewAtomStringTerminal()}}, +		// 		replacement... +		// 	) +		// 	replacement = append( +		// 		replacement, +		// 		OutputValueLiteralAST {walk.NewAtomStringTerminal()}, +		// 	) +		// 	lhs = SubexASTOutput {replacement}  		case '.': -			lhs = SubexASTCopyAny{} +			if runic { +				lhs = SubexASTCopyAnyRune{} +			} else { +				lhs = SubexASTCopyAnyValue{} +			}  		case '?':  			lhs = SubexASTCopyBool{}  		case '%':  			lhs = SubexASTCopyNumber{} -		case '_': -			lhs = SubexASTCopyStringAtom{} -		case '#': -			lhs = SubexASTCopyString{} -		case ',': -			lhs = SubexASTCopyValue{} -		case '"': -			lhs = SubexASTCopyAtom {walk.NewAtomStringTerminal()} +		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 '~': -			literals := parseNonStringLiteral(l) -			var replacement []OutputContentAST -			for _, literal := range literals { -				replacement = append(replacement, OutputAtomLiteralAST {literal}) +			if runic { +				lhs = SubexASTCopyRune {'~'} +			} else { +				if !accept(l, "\"") { +					panic("Missing \" after ~") +				} +				lhs = SubexASTEnterString {parseSubex(l, 0, true)} +				if !accept(l, "\"") { +					panic("Missing matching \"") +				}  			} -			lhs = SubexASTOutput {replacement} +		// TODO +		// case '_': +		// 	lhs = SubexASTCopyStringAtom{} +		// case '#': +		// 	lhs = SubexASTCopyString{} +		// case ',': +		// 	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: -			lhs = SubexASTCopyAtom{Atom: walk.NewAtomStringRune(r)} +			if runic { +				lhs = SubexASTCopyRune {r} +			} else { +				panic("Tried to match rune outside of string") +			}  	}  	loop: for {  		if minPower <= 20 { -			next := parseSubex(l, 21) -			if next != nil { +			next := parseSubex(l, 21, runic) +			if next != nil && (next != SubexASTEmpty{}) {  				lhs = SubexASTConcat{lhs, next}  				continue loop  			} @@ -366,20 +391,14 @@ func parseSubex(l RuneReader, minPower int) SubexAST {  						Slot: slot,  					}  				} -			case r == ':' && minPower <= 4: -				replacement := parseReplacement(l) -				lhs = SubexASTConcat { -					SubexASTDiscard {lhs}, -					SubexASTOutput {replacement}, -				}  			case r == '|' && minPower <= 8: -				rhs := parseSubex(l, 9) +				rhs := parseSubex(l, 9, runic)  				if rhs == nil {  					panic("Missing subex after |")  				}  				lhs = SubexASTOr{lhs, rhs}  			case r == ';' && minPower <= 10: -				rhs := parseSubex(l, 11) +				rhs := parseSubex(l, 11, runic)  				if rhs == nil {  					panic("Missing subex after ;")  				} @@ -396,7 +415,7 @@ func parseSubex(l RuneReader, minPower int) SubexAST {  }  func Parse(l RuneReader) SubexAST { -	ast := parseSubex(l, 0) +	ast := parseSubex(l, 0, false)  	if ast == nil {  		return SubexASTEmpty{}  	} diff --git a/subex/subexast.go b/subex/subexast.go index f4088fe..31c77ba 100644 --- a/subex/subexast.go +++ b/subex/subexast.go @@ -7,15 +7,15 @@ import (  // A node in the AST of a subex  type SubexAST interface { -	compileWith(next SubexState, slotMap *SlotMap) SubexState +	compileWith(next SubexState, slotMap *SlotMap, runic bool) 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) SubexState { -	return ast.First.compileWith(ast.Second.compileWith(next, slotMap), slotMap) +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) String() string {  	return fmt.Sprintf("(%v)(%v)", ast.First, ast.Second) @@ -26,13 +26,21 @@ type SubexASTStore struct {  	Match SubexAST  	Slot rune  } -func (ast SubexASTStore) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTStore) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	id := slotMap.getId(ast.Slot) -	return &SubexCaptureBeginState { -		next: ast.Match.compileWith(&SubexStoreEndState { -			slot: id, -			next: next, -		}, slotMap), +	newNext := ast.Match.compileWith(&SubexStoreEndState { +		slot: id, +		next: next, +	}, slotMap, runic) + +	if !runic { +		return &SubexCaptureBeginState { +			next: newNext, +		} +	} else { +		return &SubexCaptureRunesBeginState { +			next: newNext, +		}  	}  }  func (ast SubexASTStore) String() string { @@ -43,10 +51,10 @@ func (ast SubexASTStore) String() string {  type SubexASTOr struct {  	First, Second SubexAST  } -func (ast SubexASTOr) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTOr) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return &SubexGroupState { -		ast.First.compileWith(next, slotMap), -		ast.Second.compileWith(next, slotMap), +		ast.First.compileWith(next, slotMap, runic), +		ast.Second.compileWith(next, slotMap, runic),  	}  }  func (ast SubexASTOr) String() string { @@ -76,19 +84,19 @@ func (cr ConvexRange) decrement() ConvexRange {  		return ConvexRange{cr.Start - 1, cr.End - 1}  	}  } -func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMap) SubexState { +func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMap, runic bool) SubexState {  	min, _ := cr.minmax()  	if min != 0 { -		return content.compileWith(cr.decrement().compile(content, next, slotMap), slotMap) +		return content.compileWith(cr.decrement().compile(content, next, slotMap, runic), slotMap, runic)  	}  	if cr.Start == -1 {  		state := &SubexGroupState {nil, next} -		state.first = content.compileWith(state, slotMap) +		state.first = content.compileWith(state, slotMap, runic)  		return state  	}  	if cr.End == -1 {  		state := &SubexGroupState {next, nil} -		state.second = content.compileWith(state, slotMap) +		state.second = content.compileWith(state, slotMap, runic)  		return state  	} @@ -96,7 +104,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), +				content.compileWith(state, slotMap, runic),  				next,  			}  		} @@ -106,7 +114,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), +				content.compileWith(state, slotMap, runic),  			}  		}  		return state @@ -119,10 +127,10 @@ type SubexASTRepeat struct {  	Content SubexAST  	Acceptable []ConvexRange  } -func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	var state SubexState = &SubexDeadState{}  	for _, convex := range ast.Acceptable { -		state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap)} +		state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap, runic)}  	}  	return state  } @@ -131,23 +139,44 @@ func (ast SubexASTRepeat) String() string {  }  // Read in a single specific Atom and output it unchanged -type SubexASTCopyAtom struct { -	Atom walk.Atom +type SubexASTCopyScalar struct { +	Scalar walk.Scalar  } -func (ast SubexASTCopyAtom) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	return &SubexCopyAtomState{ -		atom: ast.Atom, +func (ast SubexASTCopyScalar) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCopyState{ +		filter: selectScalarFilter {ast.Scalar},  		next: next,  	}  } -func (ast SubexASTCopyAtom) String() string { +func (ast SubexASTCopyScalar) String() string {  	return fmt.Sprintf("a")  } +type SubexASTCopyAnyRune struct {} +func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCopyRuneState { +		next: next, +		filter: anyRuneFilter{}, +	} +} + +type SubexASTCopyRune struct { +	rune rune +} +func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCopyRuneState { +		next: next, +		filter: selectRuneFilter {ast.rune}, +	} +} +  // 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) SubexState { -	return &SubexCopyBoolState {next} +func (ast SubexASTCopyBool) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCopyState { +		next: next, +		filter: anyBoolFilter{}, +	}  }  func (ast SubexASTCopyBool) String() string {  	return "?" @@ -155,65 +184,50 @@ 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) SubexState { -	return &SubexCopyNumberState {next} +func (ast SubexASTCopyNumber) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCopyState { +		next: next, +		filter: anyNumberFilter{}, +	}  }  func (ast SubexASTCopyNumber) String() string {  	return "%"  } -// Read in a single atom that must be a string atom and output it unchanged -type SubexASTCopyStringAtom struct {} -func (ast SubexASTCopyStringAtom) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	return &SubexCopyStringAtomState {next} -} -func (ast SubexASTCopyStringAtom) String() string { -	return "_" -} -  // Read in a full string value and copy it out unchanged  // # is equivalent to "_{-0}" -type SubexASTCopyString struct {} -func (ast SubexASTCopyString) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	stringAtomState := &SubexCopyStringAtomState { -		next: nil, -	} -	stringContentState := &SubexGroupState { -		&SubexCopyAtomState { -			atom: walk.NewAtomStringTerminal(), -			next: next, -		}, -		stringAtomState, -	} -	stringAtomState.next = stringContentState -	return &SubexCopyAtomState { -		atom: walk.NewAtomStringTerminal(), -		next: stringContentState, -	} -} -func (ast SubexASTCopyString) String() string { -	return "#" -} - -// Read in a non-terminal value and copy it out unchanged -// , is equivalent to `null`|?|%|# -type SubexASTCopyValue struct {} -func (ast SubexASTCopyValue) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	return &SubexGroupState { -		SubexASTCopyString{}.compileWith(next, slotMap), -		&SubexCopyNonStringNonTerminalAtomState {next}, -	} -} -func (ast SubexASTCopyValue) String() string { -	return "," -} +// 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 SubexASTCopyAny struct {} -func (ast SubexASTCopyAny) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	return &SubexCopyAnyState{next} +type SubexASTCopyAnyValue struct {} +func (ast SubexASTCopyAnyValue) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCopyState { +		next: next, +		filter: anyValueFilter{}, +	}  } -func (ast SubexASTCopyAny) String() string { +func (ast SubexASTCopyAnyValue) String() string {  	return "."  } @@ -228,10 +242,10 @@ func (ast OutputLoadAST) compile(slotMap *SlotMap) OutputContent {  	return OutputLoad {slotMap.getId(ast.slot)}  } -type OutputAtomLiteralAST struct { -	atom walk.Atom +type OutputValueLiteralAST struct { +	atom walk.Value  } -func (ast OutputAtomLiteralAST) compile(slotMap *SlotMap) OutputContent { +func (ast OutputValueLiteralAST) compile(slotMap *SlotMap) OutputContent {  	return OutputAtomLiteral {ast.atom}  } @@ -239,7 +253,7 @@ func (ast OutputAtomLiteralAST) compile(slotMap *SlotMap) OutputContent {  type SubexASTOutput struct {  	Replacement []OutputContentAST  } -func (ast SubexASTOutput) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTOutput) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	var content []OutputContent  	for _, el := range ast.Replacement {  		content = append(content, el.compile(slotMap)) @@ -257,13 +271,13 @@ func (ast SubexASTOutput) String() string {  type SubexASTJoin struct {  	Content, Delimiter SubexAST  } -func (ast SubexASTJoin) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTJoin) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	afterContentState := &SubexGroupState {  		nil,  		next,  	} -	manyContentsState := ast.Content.compileWith(afterContentState, slotMap) -	afterContentState.first = ast.Delimiter.compileWith(manyContentsState, slotMap) +	manyContentsState := ast.Content.compileWith(afterContentState, slotMap, runic) +	afterContentState.first = ast.Delimiter.compileWith(manyContentsState, slotMap, runic)  	return &SubexGroupState {  		manyContentsState,  		next, @@ -275,30 +289,30 @@ func (ast SubexASTJoin) String() string {  // Run each input Atom through a map to produce an output Atom  // Atoms not in the map cause this to not match -type SubexASTRange struct { -	Parts map[walk.Atom]walk.Atom -} -func (ast SubexASTRange) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	return &SubexRangeState { -		parts: ast.Parts, -		next: next, -	} -} -func (ast SubexASTRange) String() string { -	return fmt.Sprintf("[abc=xyz]") -} +// type SubexASTRange struct { +// 	Parts map[walk.Atom]walk.Atom +// } +// func (ast SubexASTRange) compileWith(next SubexState, slotMap *SlotMap) SubexState { +// 	return &SubexRangeState { +// 		parts: ast.Parts, +// 		next: next, +// 	} +// } +// func (ast SubexASTRange) String() string { +// 	return fmt.Sprintf("[abc=xyz]") +// }  // Run content, if content is a list of booleans, OR them, if all values are castable to numbers, sum them and output the total  // Reject if neither of these cases match  type SubexASTSum struct {  	Content SubexAST  } -func (ast SubexASTSum) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTSum) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return &SubexCaptureBeginState {  		next: ast.Content.compileWith(&SubexArithmeticEndState {  			next: next,  			calculate: sumValues, -		}, slotMap), +		}, slotMap, runic),  	}  }  func (ast SubexASTSum) String() string { @@ -309,12 +323,12 @@ func (ast SubexASTSum) String() string {  type SubexASTProduct struct {  	Content SubexAST  } -func (ast SubexASTProduct) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTProduct) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return &SubexCaptureBeginState {  		next: ast.Content.compileWith(&SubexArithmeticEndState {  			next: next,  			calculate: multiplyValues, -		}, slotMap), +		}, slotMap, runic),  	}  }  func (ast SubexASTProduct) String() string { @@ -326,12 +340,12 @@ func (ast SubexASTProduct) String() string {  type SubexASTNegate struct {  	Content SubexAST  } -func (ast SubexASTNegate) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTNegate) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return &SubexCaptureBeginState {  		next: ast.Content.compileWith(&SubexArithmeticEndState {  			next: next,  			calculate: negateValues, -		}, slotMap), +		}, slotMap, runic),  	}  }  func (ast SubexASTNegate) String() string { @@ -344,12 +358,12 @@ func (ast SubexASTNegate) String() string {  type SubexASTReciprocal struct {  	Content SubexAST  } -func (ast SubexASTReciprocal) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTReciprocal) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return &SubexCaptureBeginState {  		next: ast.Content.compileWith(&SubexArithmeticEndState {  			next: next,  			calculate: reciprocalValues, -		}, slotMap), +		}, slotMap, runic),  	}  }  func (ast SubexASTReciprocal) String() string { @@ -362,12 +376,12 @@ func (ast SubexASTReciprocal) String() string {  type SubexASTNot struct {  	Content SubexAST  } -func (ast SubexASTNot) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTNot) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return &SubexCaptureBeginState {  		next: ast.Content.compileWith(&SubexArithmeticEndState {  			next: next,  			calculate: notValues, -		}, slotMap), +		}, slotMap, runic),  	}  }  func (ast SubexASTNot) String() string { @@ -376,7 +390,7 @@ func (ast SubexASTNot) String() string {  // Does nothing  type SubexASTEmpty struct {} -func (ast SubexASTEmpty) compileWith(next SubexState, slotMap *SlotMap) SubexState { +func (ast SubexASTEmpty) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState {  	return next  }  func (ast SubexASTEmpty) String() string { @@ -387,11 +401,73 @@ func (ast SubexASTEmpty) String() string {  type SubexASTDiscard struct {  	Content SubexAST  } -func (ast SubexASTDiscard) compileWith(next SubexState, slotMap *SlotMap) SubexState { -	return &SubexCaptureBeginState { -		next: ast.Content.compileWith(&SubexDiscardState {next}, slotMap), +func (ast SubexASTDiscard) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, runic) +	if !runic { +		return &SubexCaptureBeginState { +			next: newNext, +		} +	} else { +		return &SubexCaptureRunesBeginState { +			next: newNext, +		}  	}  }  func (ast SubexASTDiscard) String() string {  	return fmt.Sprintf("(%v)$_", ast.Content)  } + +// Go into an array, pass the content each of the values in the array to eat and then leave the array +type SubexASTEnterArray struct { +	Content SubexAST +} +func (ast SubexASTEnterArray) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCaptureBeginState { +		next: &SubexIncrementNestState { +			next: &SubexCopyState { +				filter: anyArrayFilter{}, +				next: &SubexDiscardState { +					next: &SubexCaptureBeginState { +						next: ast.Content.compileWith( +							&SubexDiscardTerminalState { +								terminal: walk.ArrayEndTerminal{}, +								next: &SubexDecrementNestState { +									next: &SubexConstructArrayState {next: next}, +								}, +							}, +							slotMap, +							runic, +						), +					}, +				}, +			}, +		}, +	} +} + +type SubexASTEnterString struct { +	Content SubexAST +} +func (ast SubexASTEnterString) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +	return &SubexCaptureBeginState { +		next: &SubexIncrementNestState { +			next: &SubexCopyState { +				filter: anyStringFilter{}, +				next: &SubexDiscardState { +					next: &SubexCaptureRunesBeginState { +						next: ast.Content.compileWith( +							&SubexDecrementNestState { +								next: &SubexDiscardTerminalState { +									terminal: walk.StringEndTerminal{}, +									next: &SubexConstructStringState {next: next}, +								}, +							}, +							slotMap, +							true, +						), +					}, +				}, +			}, +		}, +	} +} diff --git a/subex/subexast_test.go b/subex/subexast_test.go new file mode 100644 index 0000000..156162e --- /dev/null +++ b/subex/subexast_test.go @@ -0,0 +1,19 @@ +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 7ecff0c..7ffd592 100644 --- a/subex/subexstate.go +++ b/subex/subexstate.go @@ -1,5 +1,8 @@  package subex +// TODO: Simplify this implementation by combining similar states into one type +// e.g. Combine all of the copy states into a single type that has a filter function +  import (  	"main/walk"  ) @@ -7,21 +10,58 @@ import (  // A state of execution for the transducer  type SubexState interface {  	// Eat a Atom and transition to any number of new states -	eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch +	eat(aux auxiliaryState, char walk.Edible) []SubexBranch  	// Find accepting states reachable through epsilon transitions and return their outputs -	accepting(store Store, outputStack OutputStack) []OutputStack +	accepting(aux auxiliaryState) []OutputStack  }  // Try first, if it fails then try second  type SubexGroupState struct {  	first, second SubexState  } -func (state SubexGroupState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	otherStore := store.clone() -	return append(state.first.eat(store, outputStack, char), state.second.eat(otherStore, outputStack, char)...) +func (state SubexGroupState) 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 { +	otherAux := aux.cloneStore() +	return append(state.first.accepting(aux), state.second.accepting(otherAux)...) +} + +type SubexCopyState struct { +	next SubexState +	filter valueFilter +} +func (state SubexCopyState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { +	value, isValue := edible.(walk.Value) +	if !isValue || !state.filter.valueFilter(value) { +		return nil +	} +	return []SubexBranch{{ +		state: state.next, +		aux: aux.topAppend(walk.ValueList{value}), +	}} +} +func (state SubexCopyState) accepting(aux auxiliaryState) []OutputStack { +	return nil +} + +type SubexCopyRuneState struct { +	next SubexState +	filter runeFilter +} +func (state SubexCopyRuneState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { +	r, isRune := edible.(walk.StringRuneAtom) +	if !isRune || !state.filter.runeFilter(r) { +		return nil +	} +	return []SubexBranch{{ +		state: state.next, +		aux: aux.topAppend(walk.RuneList{r}), +	}}  } -func (state SubexGroupState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return append(state.first.accepting(store, outputStack), state.second.accepting(store, outputStack)...) +func (state SubexCopyRuneState) accepting(aux auxiliaryState) []OutputStack { +	return nil  }  // Just pushes to the OutputStack and hands over to the next state @@ -29,24 +69,37 @@ func (state SubexGroupState) accepting(store Store, outputStack OutputStack) []O  type SubexCaptureBeginState struct {  	next SubexState  } -func (state SubexCaptureBeginState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	return state.next.eat(store, outputStack.push(nil), char) +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) accepting(store Store, outputStack OutputStack) []OutputStack { -	return state.next.accepting(store, outputStack.push(nil)) +func (state SubexCaptureBeginState) String() string { +	return "CaptureBeginState" +} + +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{}))  }  // Discard the top of the OutputStack  type SubexDiscardState struct {  	next SubexState  } -func (state SubexDiscardState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	_, newStack := outputStack.pop() -	return state.next.eat(store, newStack, char) +func (state SubexDiscardState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +	_, newAux := aux.popOutput() +	return state.next.eat(newAux, char)  } -func (state SubexDiscardState) accepting(store Store, outputStack OutputStack) []OutputStack { -	_, newStack := outputStack.pop() -	return state.next.accepting(store, newStack) +func (state SubexDiscardState) accepting(aux auxiliaryState) []OutputStack { +	_, newAux := aux.popOutput() +	return state.next.accepting(newAux)  }  // Pop the top of the OutputStack which contains the stuff outputted since the start of the store @@ -55,35 +108,41 @@ type SubexStoreEndState struct {  	slot int  	next SubexState  } -func (state SubexStoreEndState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	toStore, newStack := outputStack.pop() -	return state.next.eat(store.withValue(state.slot, toStore), newStack, char) +func (state SubexStoreEndState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +	toStore, aux := aux.popOutput() +	aux = aux.withValue(state.slot, toStore) +	return state.next.eat(aux, char)  } -func (state SubexStoreEndState) accepting(store Store, outputStack OutputStack) []OutputStack { -	toStore, newStack := outputStack.pop() -	return state.next.accepting(store.withValue(state.slot, toStore), newStack) +func (state SubexStoreEndState) accepting(aux auxiliaryState) []OutputStack { +	toStore, aux := aux.popOutput() +	aux = aux.withValue(state.slot, toStore) +	return state.next.accepting(aux)  }  // A part of an output literal, either an Atom or a slot from which to load  type OutputContent interface {  	// Given the current store, return the []Atom produced by the TransducerOutput -	build(Store) []walk.Atom +	build(Store) walk.ValueList  }  // An OutputContent which is just an Atom literal  type OutputAtomLiteral struct { -	atom walk.Atom +	atom walk.Value  } -func (replacement OutputAtomLiteral) build(store Store) []walk.Atom { -	return []walk.Atom{replacement.atom} +func (replacement OutputAtomLiteral) build(store Store) walk.ValueList { +	return walk.ValueList{replacement.atom}  }  // An OutputContent which is a slot that is loaded from  type OutputLoad struct {  	slot int  } -func (replacement OutputLoad) build(store Store) []walk.Atom { -	return store[replacement.slot] +func (replacement OutputLoad) build(store Store) walk.ValueList { +	values, isValues := store[replacement.slot].(walk.ValueList) +	if !isValues { +		panic("Tried to output non-values list") +	} +	return values  }  // Don't read in anything, just output the series of data and slots specified @@ -92,189 +151,176 @@ type SubexOutputState struct {  	next SubexState  }  // Given a store, return what is outputted by an epsilon transition from this state -func (state SubexOutputState) build(store Store) []walk.Atom { -	var result []walk.Atom +// TODO: separate into buildValues and buildRunes +func (state SubexOutputState) build(store Store) walk.ValueList { +	var result walk.ValueList  	for _, part := range state.content {  		result = append(result, part.build(store)...)  	}  	return result  } -func (state SubexOutputState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	content := state.build(store) -	nextStates := state.next.eat(store, topAppend(outputStack, content), char) +func (state SubexOutputState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +	content := state.build(aux.store) +	nextStates := state.next.eat(aux.topAppend(content), char)  	return nextStates  } -func (state SubexOutputState) accepting(store Store, outputStack OutputStack) []OutputStack { -	content := state.build(store) -	outputStacks := state.next.accepting(store, topAppend(outputStack, content)) +func (state SubexOutputState) accepting(aux auxiliaryState) []OutputStack { +	content := state.build(aux.store) +	outputStacks := state.next.accepting(aux.topAppend(content))  	return outputStacks  }  // A final state, transitions to nothing but is accepting  type SubexNoneState struct {} -func (state SubexNoneState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { +func (state SubexNoneState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch {  	return nil  } -func (state SubexNoneState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return []OutputStack{outputStack} +func (state SubexNoneState) accepting(aux auxiliaryState) []OutputStack { +	return []OutputStack{aux.outputStack}  }  // A dead end state, handy for making internals work nicer but technically redundant  type SubexDeadState struct {} -func (state SubexDeadState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { +func (state SubexDeadState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch {  	return nil  } -func (state SubexDeadState) accepting (store Store, outputStack OutputStack) []OutputStack { +func (state SubexDeadState) accepting (aux auxiliaryState) []OutputStack {  	return nil  } -// Read in a specific Atom and output it -type SubexCopyAtomState struct { -	atom walk.Atom -	next SubexState -} -func (state SubexCopyAtomState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	// TODO can I compare Atom values with == ? -	if char == state.atom { -		return []SubexBranch{{ -			state: state.next, -			outputStack: topAppend(outputStack, []walk.Atom{char}), -			store: store, -		}} -	} -	return nil -} -func (state SubexCopyAtomState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return nil -} +// Read in an Atom and apply a map to generate an Atom to output +// If the input isn't in the map transition to nothing +// TODO +// type SubexRangeState struct { +// 	parts map[walk.Atom]walk.Atom +// 	next SubexState +// } +// func (state SubexRangeState) eat(aux auxiliaryState, char walk.Atom) []SubexBranch { +// 	out, exists := state.parts[char] +// 	if !exists { +// 		return nil +// 	} else { +// 		return []SubexBranch{{ +// 			state: state.next, +// 			outputStack: topAppend(outputStack, []walk.Atom{out}), +// 			store: store, +// 		}} +// 	} +// } +// func (state SubexRangeState) accepting(aux auxiliaryState) []OutputStack { +// 	return nil +// } -// Read in a boolean atom and output it -type SubexCopyBoolState struct { -	next SubexState -} -func (state SubexCopyBoolState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	if char.Typ == walk.AtomBool { -		return []SubexBranch{{ -			state: state.next, -			outputStack: topAppend(outputStack, []walk.Atom{char}), -			store: store, -		}} -	} -	return nil -} -func (state SubexCopyBoolState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return nil -} -// Read in a number atom and output it -type SubexCopyNumberState struct { +type SubexArithmeticEndState struct {  	next SubexState +	calculate func(walk.ValueList) (walk.ValueList, error)  } -func (state SubexCopyNumberState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	if char.Typ == walk.AtomNumber { -		return []SubexBranch{{ -			state: state.next, -			outputStack: topAppend(outputStack, []walk.Atom{char}), -			store: store, -		}} +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")  	} -	return nil -} -func (state SubexCopyNumberState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return nil -} - -// Read in a string atom and output it -type SubexCopyStringAtomState struct { -	next SubexState -} -func (state SubexCopyStringAtomState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	if char.Typ == walk.AtomStringRune { -		return []SubexBranch{{ -			state: state.next, -			outputStack: topAppend(outputStack, []walk.Atom{char}), -			store: store, -		}} +	result, err := state.calculate(values) +	if err != nil { +		return nil  	} -	return nil +	return state.next.eat(aux.topAppend(result), char)  } -func (state SubexCopyStringAtomState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return nil +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") +	} +	result, err := state.calculate(values) +	if err != nil { +		return nil +	} +	return state.next.accepting(aux.topAppend(result))  } -// Read in an atom and copy it out as long as it is not part of a string -type SubexCopyNonStringNonTerminalAtomState struct { +type SubexDiscardTerminalState struct { +	terminal walk.Terminal  	next SubexState  } -func (state SubexCopyNonStringNonTerminalAtomState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	if char.Typ == walk.AtomStringRune || char.Typ == walk.AtomStringTerminal || char.Typ == walk.AtomTerminal { +func (state SubexDiscardTerminalState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { +	if edible != state.terminal {  		return nil  	}  	return []SubexBranch{{  		state: state.next, -		outputStack: topAppend(outputStack, []walk.Atom{char}), -		store: store, +		aux: aux,  	}}  } -func (state SubexCopyNonStringNonTerminalAtomState) accepting(store Store, outputStack OutputStack) []OutputStack { +func (state SubexDiscardTerminalState) accepting(aux auxiliaryState) []OutputStack {  	return nil  } -// Read in any Atom and output it -type SubexCopyAnyState struct { +type SubexConstructArrayState struct {  	next SubexState  } -func (state SubexCopyAnyState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	return []SubexBranch{{ -		state: state.next, -		outputStack: topAppend(outputStack, []walk.Atom{char}), -		store: store, -	}} -} -func (state SubexCopyAnyState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return nil +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") +	} +	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") +	} +	array := walk.ArrayStructure(values) +	return state.next.accepting(aux.topAppend(walk.ValueList{array}))  } -// Read in an Atom and apply a map to generate an Atom to output -// If the input isn't in the map transition to nothing -type SubexRangeState struct { -	parts map[walk.Atom]walk.Atom +type SubexConstructStringState struct {  	next SubexState  } -func (state SubexRangeState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	out, exists := state.parts[char] -	if !exists { -		return nil -	} else { -		return []SubexBranch{{ -			state: state.next, -			outputStack: topAppend(outputStack, []walk.Atom{out}), -			store: store, -		}} +func (state 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 SubexRangeState) accepting(store Store, outputStack OutputStack) []OutputStack { -	return nil +	s := walk.StringStructure(runes) +	return state.next.eat(aux.topAppend(walk.ValueList{s}), edible) +} +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}))  } +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) String() string { +	return "IncrementNestState" +} -type SubexArithmeticEndState struct { +type SubexDecrementNestState struct {  	next SubexState -	calculate func([]walk.Atom) ([]walk.Atom, error)  } -func (state SubexArithmeticEndState) eat(store Store, outputStack OutputStack, char walk.Atom) []SubexBranch { -	toCompute, newStack := outputStack.pop() -	result, err := state.calculate(toCompute) -	if err != nil { -		return nil -	} -	return state.next.eat(store, topAppend(newStack, result), char) +func (state SubexDecrementNestState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { +	return state.next.eat(aux.decNest(), edible)  } -func (state SubexArithmeticEndState) accepting(store Store, outputStack OutputStack) []OutputStack { -	toCompute, newStack := outputStack.pop() -	result, err := state.calculate(toCompute) -	if err != nil { -		return nil -	} -	return state.next.accepting(store, topAppend(newStack, result)) +func (state SubexDecrementNestState) accepting(aux auxiliaryState) []OutputStack { +	return state.next.accepting(aux.decNest())  } diff --git a/subex/subexstate_test.go b/subex/subexstate_test.go new file mode 100644 index 0000000..4eb8969 --- /dev/null +++ b/subex/subexstate_test.go @@ -0,0 +1,4 @@ +package subex + +import ( +) | 
