<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharlie Stanton <charlie@shtanton.xyz>2023-07-19 11:57:59 +0100
committerCharlie Stanton <charlie@shtanton.xyz>2023-07-19 11:57:59 +0100
commit8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b (patch)
tree7a16883c17c2bdcc49b2f9d4f333dfc76c66248f
parent3c34366bdd5d817a184d6b1c901d03a16b6faa4b (diff)
downloadstred-go-8cf10efe3b5a1bcc70bc6e5590ee63fd5eb00c5b.tar
Huge refactor to a more value based system, doing away with terminals. Also introduces unit testing
-rw-r--r--json/read.go288
-rw-r--r--json/write.go202
-rw-r--r--json_array/read.go18
-rw-r--r--json_array/write.go27
-rw-r--r--json_tokens/read.go16
-rw-r--r--json_tokens/write.go4
-rw-r--r--main/command.go12
-rw-r--r--main/lex.go2
-rw-r--r--main/main.go8
-rw-r--r--main/parse.go54
-rw-r--r--subex/arithmetic.go114
-rw-r--r--subex/filter.go62
-rw-r--r--subex/main.go167
-rw-r--r--subex/main_test.go442
-rw-r--r--subex/parse.go295
-rw-r--r--subex/subexast.go296
-rw-r--r--subex/subexast_test.go19
-rw-r--r--subex/subexstate.go362
-rw-r--r--subex/subexstate_test.go4
-rw-r--r--walk/atom.go38
-rw-r--r--walk/value.go14
-rw-r--r--walk/walk.go259
-rw-r--r--walk/walk_test.go45
23 files changed, 2105 insertions, 643 deletions
diff --git a/json/read.go b/json/read.go
new file mode 100644
index 0000000..6a68467
--- /dev/null
+++ b/json/read.go
@@ -0,0 +1,288 @@
+package json
+
+import (
+ "bufio"
+ "main/walk"
+ "strings"
+ "strconv"
+ "errors"
+)
+
+func isWhitespace(r rune) bool {
+ for _, ws := range " \t\r\n" {
+ if r == ws {
+ return true
+ }
+ }
+ return false
+}
+
+func isNumberRune(r rune) bool {
+ return '0' <= r && r <= '9' || r == '.'
+}
+
+type JSONReaderStructure int
+const (
+ JSONReaderStructureArray JSONReaderStructure = iota
+ JSONReaderStructureMap
+)
+
+type JSONReaderState int
+const (
+ JSONReaderStateValue JSONReaderState = iota
+ JSONReaderStateValueEnd
+)
+
+func NewJSONReader(reader *bufio.Reader) *JSONReader {
+ return &JSONReader {
+ path: nil,
+ structure: nil,
+ state: JSONReaderStateValue,
+ reader: reader,
+ }
+}
+
+type JSONReader struct {
+ path []walk.Value
+ structure []JSONReaderStructure
+ state JSONReaderState
+ reader *bufio.Reader
+}
+
+func (reader *JSONReader) Read() (walk.WalkItem, error) {
+ switch reader.state {
+ case JSONReaderStateValue:
+ if len(reader.structure) == 0 {
+ path := reader.clonePath()
+ value, err := reader.readValue()
+ if err != nil {
+ panic("Missing JSON input")
+ }
+ return walk.WalkItem {
+ Path: path,
+ Value: []walk.Value{value},
+ }, nil
+ }
+ switch reader.structure[len(reader.structure) - 1] {
+ case JSONReaderStructureArray:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing rest of array")
+ }
+ if r == ']' {
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ reader.reader.UnreadRune()
+ prevIndex := reader.path[len(reader.path) - 1].(walk.NumberScalar)
+ reader.path[len(reader.path) - 1] = prevIndex + 1
+ path := reader.clonePath()
+ value, err := reader.readValue()
+ if err != nil {
+ panic("Missing value in array")
+ }
+ return walk.WalkItem {
+ Path: path,
+ Value: []walk.Value{value},
+ }, nil
+ case JSONReaderStructureMap:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Reached EOF inside JSON map")
+ }
+ if r == '}' {
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ if r != '"' {
+ panic("Expected key in map, found something else")
+ }
+ key := reader.readString()
+ reader.path[len(reader.path) - 1] = walk.StringStructure(key)
+ r, err = reader.nextNonWsRune()
+ if err != nil {
+ panic("Reached EOF after map key")
+ }
+ if r != ':' {
+ panic("Expected : after map key, found something else")
+ }
+ path := reader.clonePath()
+ value, err := reader.readValue()
+ if err != nil {
+ panic("Missing value in map")
+ }
+ return walk.WalkItem {
+ Path: path,
+ Value: []walk.Value{value},
+ }, nil
+ default:
+ panic("Invalid JSONReaderStructure")
+ }
+ case JSONReaderStateValueEnd:
+ if len(reader.structure) == 0 {
+ _, err := reader.nextNonWsRune()
+ if err == nil {
+ panic("input continues after JSON value")
+ }
+ return walk.WalkItem{}, errors.New("eof")
+ }
+ switch reader.structure[len(reader.structure) - 1] {
+ case JSONReaderStructureArray:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing end of array")
+ }
+ if r == ']' {
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ if r != ',' {
+ panic("Missing , after array value")
+ }
+ reader.state = JSONReaderStateValue
+ return reader.Read()
+ case JSONReaderStructureMap:
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing end of map")
+ }
+ if r == '}' {
+ reader.path = reader.path[:len(reader.path) - 1]
+ reader.structure = reader.structure[:len(reader.structure) - 1]
+ reader.state = JSONReaderStateValueEnd
+ return reader.Read()
+ }
+ if r != ',' {
+ panic("Missing , after map value")
+ }
+ reader.state = JSONReaderStateValue
+ return reader.Read()
+ default:
+ panic("Invalid JSONReaderStructure")
+ }
+ default:
+ panic("Invalid JSONReaderState")
+ }
+}
+
+func (reader *JSONReader) readValue() (walk.Value, error) {
+ r, err := reader.nextNonWsRune()
+ if err != nil {
+ panic("Missing value in JSON")
+ }
+ switch r {
+ case 'n':
+ reader.requireString("ull")
+ reader.state = JSONReaderStateValueEnd
+ return walk.NullScalar{}, nil
+ case 'f':
+ reader.requireString("alse")
+ reader.state = JSONReaderStateValueEnd
+ return walk.BoolScalar(false), nil
+ case 't':
+ reader.requireString("rue")
+ reader.state = JSONReaderStateValueEnd
+ return walk.BoolScalar(true), nil
+ case '"':
+ v := reader.readString()
+ reader.state = JSONReaderStateValueEnd
+ return walk.StringStructure(v), nil
+ case '{':
+ reader.state = JSONReaderStateValue
+ reader.structure = append(reader.structure, JSONReaderStructureMap)
+ reader.path = append(reader.path, walk.StringStructure(""))
+ return walk.MapStructure(make(map[string]walk.Value)), nil
+ case '[':
+ reader.state = JSONReaderStateValue
+ reader.structure = append(reader.structure, JSONReaderStructureArray)
+ reader.path = append(reader.path, walk.NumberScalar(-1))
+ return walk.ArrayStructure{}, nil
+ }
+ if isNumberRune(r) {
+ var builder strings.Builder
+ builder.WriteRune(r)
+ for {
+ r, _, err = reader.reader.ReadRune()
+ if err != nil {
+ break
+ }
+ if !isNumberRune(r) {
+ reader.reader.UnreadRune()
+ break
+ }
+ builder.WriteRune(r)
+ }
+ number, parseError := strconv.ParseFloat(builder.String(), 64)
+ if parseError != nil {
+ panic("Invalid number")
+ }
+ reader.state = JSONReaderStateValueEnd
+ return walk.NumberScalar(number), nil
+ }
+ panic("Invalid JSON value starting with: " + string(r))
+}
+
+func (reader *JSONReader) readString() string {
+ var builder strings.Builder
+ for {
+ r, _, err := reader.reader.ReadRune()
+ if err != nil {
+ panic("Missing rest of string")
+ }
+ if r == '"' {
+ break
+ }
+ if r == '\\' {
+ r, _, err = reader.reader.ReadRune()
+ if err != nil {
+ panic("Missing rune after \\")
+ }
+ builder.WriteRune(r)
+ continue
+ }
+ builder.WriteRune(r)
+ }
+ return builder.String()
+}
+
+func (reader *JSONReader) nextNonWsRune() (rune, error) {
+ for {
+ r, _, err := reader.reader.ReadRune()
+ if err != nil {
+ return 0, err
+ }
+ if !isWhitespace(r) {
+ return r, nil
+ }
+ }
+}
+
+func (reader *JSONReader) requireString(criteria string) {
+ for _, r := range criteria {
+ reader.require(r)
+ }
+}
+
+func (reader *JSONReader) require(criterion rune) {
+ r, _, err := reader.reader.ReadRune()
+ if err != nil {
+ panic("Error while reading required rune: " + err.Error())
+ }
+ if r != criterion {
+ panic("Required rune not read")
+ }
+}
+
+func (reader *JSONReader) clonePath() []walk.Value {
+ return append([]walk.Value{}, reader.path...)
+}
+
+func (reader *JSONReader) AssertDone() {
+ // TODO
+}
diff --git a/json/write.go b/json/write.go
new file mode 100644
index 0000000..d024a56
--- /dev/null
+++ b/json/write.go
@@ -0,0 +1,202 @@
+package json
+
+import (
+ "bufio"
+ "fmt"
+ "main/walk"
+)
+
+func isNumber(value walk.Value) bool {
+ _, isFloat := value.(walk.NumberScalar)
+ return isFloat
+}
+
+func isString(value walk.Value) bool {
+ _, isString := value.(walk.StringStructure)
+ return isString
+}
+
+func segmentEqual(left walk.Value, right walk.Value) bool {
+ switch left := left.(type) {
+ case walk.NumberScalar:
+ _, isNumber := right.(walk.NumberScalar)
+ return isNumber
+ case walk.StringStructure:
+ right, isString := right.(walk.StringStructure)
+ return isString && left == right
+ default:
+ panic("Invalid path segment type")
+ }
+}
+
+type JSONWriterState int
+const (
+ JSONWriterStateBeforeValue JSONWriterState = iota
+ JSONWriterStateAfterValue JSONWriterState = iota
+ JSONWriterStateInArray
+ JSONWriterStateInMap
+)
+
+func NewJSONWriter(writer *bufio.Writer) *JSONWriter {
+ return &JSONWriter {
+ path: nil,
+ writer: writer,
+ state: JSONWriterStateBeforeValue,
+ }
+}
+
+type JSONWriter struct {
+ path []walk.Value
+ writer *bufio.Writer
+ state JSONWriterState
+}
+
+func (writer *JSONWriter) Write(item walk.WalkItem) error {
+ path := item.Path
+ for _, value := range item.Value {
+ err := writer.write(path, value)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (writer *JSONWriter) indent(level int) {
+ for i := 0; i < level; i += 1 {
+ writer.writer.WriteRune('\t')
+ }
+}
+
+func (writer *JSONWriter) write(targetPath []walk.Value, value walk.Value) error {
+ diversionPoint := len(writer.path)
+ for diversionPoint < len(writer.path) && diversionPoint < len(targetPath) && segmentEqual(writer.path[diversionPoint], targetPath[diversionPoint]) {
+ diversionPoint += 1
+ }
+
+ switch writer.state {
+ case JSONWriterStateBeforeValue:
+ goto beforeValue
+ case JSONWriterStateAfterValue:
+ goto afterValue
+ case JSONWriterStateInMap:
+ goto inMap
+ case JSONWriterStateInArray:
+ goto inArray
+ default:
+ panic("Invalid JSONWriterState")
+ }
+
+ beforeValue: {
+ switch value := value.(type) {
+ case walk.NullScalar:
+ writer.writer.WriteString("null")
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.BoolScalar:
+ if value {
+ writer.writer.WriteString("true")
+ } else {
+ writer.writer.WriteString("false")
+ }
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.NumberScalar:
+ writer.writer.WriteString(fmt.Sprintf("%v", value))
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.StringStructure:
+ writer.writer.WriteString(fmt.Sprintf("%q", value))
+ writer.state = JSONWriterStateAfterValue
+ return nil
+ case walk.ArrayStructure:
+ // TODO: write the contents of the structures
+ writer.writer.WriteString("[\n")
+ writer.state = JSONWriterStateInArray
+ return nil
+ case walk.MapStructure:
+ writer.writer.WriteString("{\n")
+ writer.state = JSONWriterStateInMap
+ return nil
+ default:
+ panic("Invalid value type")
+ }
+ }
+
+ afterValue: {
+ if len(writer.path) == 0 {
+ writer.writer.WriteRune('\n')
+ goto beforeValue
+ }
+ switch writer.path[len(writer.path) - 1].(type) {
+ case walk.NumberScalar:
+ // TODO: second part of this condition might be redundant
+ if len(writer.path) - 1 <= diversionPoint && len(targetPath) >= len(writer.path) && isNumber(targetPath[len(writer.path) - 1]) {
+ writer.writer.WriteString(",\n")
+ writer.path = writer.path[:len(writer.path) - 1]
+ goto inArray
+ } else {
+ writer.writer.WriteString("\n")
+ writer.indent(len(writer.path) - 1)
+ writer.writer.WriteString("]")
+ writer.path = writer.path[:len(writer.path) - 1]
+ goto afterValue
+ }
+ case walk.StringStructure:
+ if len(writer.path) -1 <= diversionPoint && len(targetPath) >= len(writer.path) && isString(targetPath[len(writer.path) - 1]) {
+ writer.writer.WriteString(",\n")
+ writer.path = writer.path[:len(writer.path) - 1]
+ goto inMap
+ } else {
+ writer.writer.WriteString("\n")
+ writer.indent(len(writer.path) - 1)
+ writer.writer.WriteString("}")
+ writer.path = writer.path[:len(writer.path) - 1]
+ goto afterValue
+ }
+ default:
+ panic("Invalid path segment type")
+ }
+ }
+
+ inMap: {
+ if len(writer.path) <= diversionPoint && len(targetPath) > len(writer.path) && isString(targetPath[len(writer.path)]) {
+ writer.indent(len(writer.path) + 1)
+ writer.writer.WriteString(fmt.Sprintf("%q: ", targetPath[len(writer.path)].(walk.StringStructure)))
+ writer.path = append(writer.path, targetPath[len(writer.path)].(walk.StringStructure))
+ goto beforeValue
+ } else {
+ writer.writer.WriteString("\n}")
+ goto afterValue
+ }
+ }
+
+ inArray: {
+ if len(writer.path) <= diversionPoint && len(targetPath) > len(writer.path) && isNumber(targetPath[len(writer.path)]) {
+ writer.indent(len(writer.path) + 1)
+ writer.path = append(writer.path, walk.NumberScalar(0))
+ goto beforeValue
+ } else {
+ writer.writer.WriteString("\n]")
+ goto afterValue
+ }
+ }
+}
+
+func (writer *JSONWriter) AssertDone() {
+ for i := len(writer.path) - 1; i >= 0; i -= 1 {
+ switch writer.path[i].(type) {
+ case walk.NumberScalar:
+ writer.writer.WriteString("\n")
+ writer.indent(i)
+ writer.writer.WriteString("]")
+ case walk.StringStructure:
+ writer.writer.WriteString("\n")
+ writer.indent(i)
+ writer.writer.WriteString("}")
+ default:
+ panic("Invalid path segment type")
+ }
+ }
+ writer.writer.Flush()
+}
diff --git a/json_array/read.go b/json_array/read.go
index 6334197..786bc2c 100644
--- a/json_array/read.go
+++ b/json_array/read.go
@@ -15,30 +15,30 @@ const (
stateDead
)
-func atomiseValue(value interface{}) []walk.Atom {
+func atomiseValue(value interface{}) []walk.AtomOLD {
switch v := value.(type) {
case nil:
- return []walk.Atom{walk.NewAtomNull()}
+ return []walk.AtomOLD{walk.NewAtomNull()}
case bool:
- return []walk.Atom{walk.NewAtomBool(v)}
+ return []walk.AtomOLD{walk.NewAtomBool(v)}
case float64:
- return []walk.Atom{walk.NewAtomNumber(v)}
+ return []walk.AtomOLD{walk.NewAtomNumber(v)}
case string:
- atoms := []walk.Atom{walk.NewAtomStringTerminal()}
+ atoms := []walk.AtomOLD{walk.NewAtomStringTerminal()}
for _, r := range v {
atoms = append(atoms, walk.NewAtomStringRune(r))
}
atoms = append(atoms, walk.NewAtomStringTerminal())
return atoms
case []interface{}:
- atoms := []walk.Atom{walk.NewAtomTerminal(walk.ArrayBegin)}
+ atoms := []walk.AtomOLD{walk.NewAtomTerminal(walk.ArrayBegin)}
for _, element := range v {
atoms = append(atoms, atomiseValue(element)...)
}
atoms = append(atoms, walk.NewAtomTerminal(walk.ArrayEnd))
return atoms
case map[string]interface{}:
- atoms := []walk.Atom{walk.NewAtomTerminal(walk.MapBegin)}
+ atoms := []walk.AtomOLD{walk.NewAtomTerminal(walk.MapBegin)}
for key, element := range v {
atoms = append(atoms, atomiseValue(key)...)
atoms = append(atoms, atomiseValue(element)...)
@@ -90,8 +90,8 @@ func (in *JSONArrayReader) Read() (walk.WalkItem, error) {
}
in.index += 1
return walk.WalkItem {
- Path: []walk.Atom{walk.NewAtomNumber(float64(in.index - 1))},
- Value: atomiseValue(m),
+ Path: []interface{}{float64(in.index - 1)},
+ Value: []interface{}{m},
}, nil
case stateEnd:
arrayEnd, err := in.decoder.Token()
diff --git a/json_array/write.go b/json_array/write.go
index 4d202c4..aaa2851 100644
--- a/json_array/write.go
+++ b/json_array/write.go
@@ -7,7 +7,7 @@ import (
"encoding/json"
)
-func assembleValue(atoms []walk.Atom) (interface{}, []walk.Atom) {
+func assembleValue(atoms []walk.AtomOLD) (interface{}, []walk.AtomOLD) {
if len(atoms) == 0 {
panic("Missing JSON value in output")
}
@@ -89,21 +89,16 @@ func assembleValue(atoms []walk.Atom) (interface{}, []walk.Atom) {
}
}
-func outputValue(atoms []walk.Atom, writer *bufio.Writer) {
- if len(atoms) == 0 {
- return
- }
- value, atoms := assembleValue(atoms)
- if len(atoms) != 0 {
- panic("Tried to output more than one JSON value")
- }
- bytes, err := json.MarshalIndent(value, "\t", "\t")
- if err != nil {
- panic("Error marshalling json into bytes")
- }
- _, err = writer.Write(bytes)
- if err != nil {
- panic("Error writing value")
+func outputValue(values []interface{}, writer *bufio.Writer) {
+ for _, value := range values {
+ bytes, err := json.MarshalIndent(value, "\t", "\t")
+ if err != nil {
+ panic("Error marshalling json into bytes")
+ }
+ _, err = writer.Write(bytes)
+ if err != nil {
+ panic("Error writing value")
+ }
}
}
diff --git a/json_tokens/read.go b/json_tokens/read.go
index 95bbb9d..b0acf71 100644
--- a/json_tokens/read.go
+++ b/json_tokens/read.go
@@ -33,11 +33,11 @@ const (
)
type JSONIn struct {
- path []walk.Atom
+ path []walk.AtomOLD
reader *bufio.Reader
structure []JSONInStructure
state JSONInState
- readBuffer []walk.Atom
+ readBuffer []walk.AtomOLD
readIndex int
readBufferCapacity int
actionBuffer []ReadAction
@@ -46,11 +46,11 @@ type JSONIn struct {
func NewJSONIn(reader *bufio.Reader) *JSONIn {
return &JSONIn {
- path: make([]walk.Atom, 0, 256),
+ path: make([]walk.AtomOLD, 0, 256),
reader: reader,
structure: []JSONInStructure{},
state: JSONInValueStart,
- readBuffer: make([]walk.Atom, 0, 256),
+ readBuffer: make([]walk.AtomOLD, 0, 256),
readIndex: 0,
readBufferCapacity: 256,
actionBuffer: make([]ReadAction, 0, 256),
@@ -122,7 +122,7 @@ func (in *JSONIn) require(criterion rune) {
}
// Returns the first full value of a list of atoms and also a boolean to indicate if there isn't a value at the beginning
-func firstValue(atoms []walk.Atom) ([]walk.Atom, bool) {
+func firstValue(atoms []walk.AtomOLD) ([]walk.AtomOLD, bool) {
if len(atoms) == 0 {
return nil, true
}
@@ -141,12 +141,12 @@ func firstValue(atoms []walk.Atom) ([]walk.Atom, bool) {
}
}
-func (in *JSONIn) readValue() []walk.Atom {
+func (in *JSONIn) readValue() []walk.AtomOLD {
try:
value, incomplete := firstValue(in.readBuffer[in.readIndex:])
if incomplete {
if in.readIndex == 0 {
- newReadBuffer := make([]walk.Atom, len(in.readBuffer), in.readBufferCapacity * 2)
+ newReadBuffer := make([]walk.AtomOLD, len(in.readBuffer), in.readBufferCapacity * 2)
in.readBufferCapacity *= 2
copy(newReadBuffer, in.readBuffer)
in.readBuffer = newReadBuffer
@@ -216,7 +216,7 @@ func (in *JSONIn) AssertDone() {
}
}
-func (in *JSONIn) pushReadBuffer(atom walk.Atom) bool {
+func (in *JSONIn) pushReadBuffer(atom walk.AtomOLD) bool {
in.readBuffer = append(in.readBuffer, atom)
return len(in.readBuffer) == in.readBufferCapacity
}
diff --git a/json_tokens/write.go b/json_tokens/write.go
index 813f2f3..78ed186 100644
--- a/json_tokens/write.go
+++ b/json_tokens/write.go
@@ -29,7 +29,7 @@ func (out *JSONOut) indent(adjust int) {
fmt.Fprint(out.writer, strings.Repeat("\t", len(out.structure) - 1 + adjust))
}
-func (out *JSONOut) atomOut(key string, atom walk.Atom) {
+func (out *JSONOut) atomOut(key string, atom walk.AtomOLD) {
state := out.structure[len(out.structure) - 1]
switch state {
case JSONOutRoot, JSONOutMap, JSONOutArray:
@@ -115,7 +115,7 @@ func (out *JSONOut) atomOut(key string, atom walk.Atom) {
}
}
-func (out *JSONOut) Print(path walk.Path, values []walk.Atom) {
+func (out *JSONOut) Print(path walk.Path, values []walk.AtomOLD) {
var segment walk.PathSegment
if len(path) > 0 {
segment = path[len(path) - 1]
diff --git a/main/command.go b/main/command.go
index ef48596..5a898e2 100644
--- a/main/command.go
+++ b/main/command.go
@@ -1,8 +1,8 @@
package main
import (
- "main/walk"
"main/subex"
+ "main/walk"
"fmt"
)
@@ -46,7 +46,7 @@ func (cmd AppendNextCommand) exec(state *ProgramState) {
if err != nil {
panic("Missing next value")
}
- state.value = walk.ConcatData(state.value, nextItem.Value)
+ state.value = append(state.value, nextItem.Value...)
state.path = nextItem.Path
state.pc++
}
@@ -72,12 +72,12 @@ func (cmd DeletePathCommand) String() string {
return "D"
}
-func runSubex(state subex.Transducer, in []walk.Atom) (out []walk.Atom, error bool) {
- atomsOut, error := subex.RunTransducer(state, in)
+func runSubex(state subex.Transducer, in walk.ValueList) (walk.ValueList, bool) {
+ out, error := subex.RunTransducer(state, in)
if error {
return nil, true
}
- return atomsOut, false
+ return out, false
}
type SubstituteValueCommand struct {
@@ -193,7 +193,7 @@ func (cmd SwapPathCommand) String() string {
type AppendPathCommand struct {}
func (cmd AppendPathCommand) exec(state *ProgramState) {
- state.path = walk.ConcatData(state.path, state.value)
+ state.path = append(state.path, state.value...)
state.pc++
}
func (cmd AppendPathCommand) String() string {
diff --git a/main/lex.go b/main/lex.go
index 198c346..496abd0 100644
--- a/main/lex.go
+++ b/main/lex.go
@@ -180,7 +180,7 @@ func lexCommand(l *lexer) stateFunc {
case '}':
l.emit(TokenRBrace)
return lexCommand
- case 's', 'S', 'f', 'F', 'l', 'L', 'a', 'A':
+ case 's', 'S':
l.emit(TokenCommand)
return lexSubstitution
case ':', 'b':
diff --git a/main/main.go b/main/main.go
index a506954..8e8c369 100644
--- a/main/main.go
+++ b/main/main.go
@@ -4,13 +4,13 @@ import (
"os"
"bufio"
"main/walk"
- "main/json_array"
+ "main/json"
)
type Program []Command
type ProgramState struct {
- path, value, xreg, yreg, zreg []walk.Atom
+ path, value, xreg, yreg, zreg walk.ValueList
in walk.StredReader
out walk.StredWriter
program []Command
@@ -45,8 +45,8 @@ func main() {
stdout := bufio.NewWriter(os.Stdout)
state := ProgramState {
- in: json_array.NewJSONArrayReader(stdin),
- out: json_array.NewJSONArrayWriter(stdout),
+ in: json.NewJSONReader(stdin),
+ out: json.NewJSONWriter(stdout),
program: program,
}
diff --git a/main/parse.go b/main/parse.go
index cbbfb9a..141ae7e 100644
--- a/main/parse.go
+++ b/main/parse.go
@@ -71,61 +71,13 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma
return append(commands, NextCommand{})
case 'N':
return append(commands, AppendNextCommand{})
- case 's', 'S', 'f', 'F', 'l', 'L', 'a', 'A':
+ case 's', 'S':
ast := p.parseSubex()
- switch commandChar {
- case 'f':
- ast = subex.SubexASTConcat {
- First: ast,
- Second: subex.SubexASTRepeat {
- Content: subex.SubexASTCopyAny{},
- Acceptable: []subex.ConvexRange{{Start: -1, End: 0}},
- },
- }
- case 'F':
- ast = subex.SubexASTConcat {
- First: subex.SubexASTStore {
- Slot: '_',
- Match: ast,
- },
- Second: subex.SubexASTRepeat {
- Content: subex.SubexASTCopyAny{},
- Acceptable: []subex.ConvexRange{{Start: -1, End: 0}},
- },
- }
- case 'l':
- ast = subex.SubexASTConcat {
- First: subex.SubexASTRepeat {
- Content: subex.SubexASTCopyAny{},
- Acceptable: []subex.ConvexRange{{Start: 0, End: -1}},
- },
- Second: ast,
- }
- case 'L':
- ast = subex.SubexASTConcat {
- First: subex.SubexASTRepeat {
- Content: subex.SubexASTCopyAny{},
- Acceptable: []subex.ConvexRange{{Start: 0, End: -1}},
- },
- Second: subex.SubexASTStore {
- Slot: '_',
- Match: ast,
- },
- }
- case 'a', 'A':
- ast = subex.SubexASTRepeat {
- Acceptable: []subex.ConvexRange{{Start: -1, End: 0}},
- Content: subex.SubexASTOr {
- First: ast,
- Second: subex.SubexASTCopyAny{},
- },
- }
- }
subex := subex.CompileTransducer(ast)
switch commandChar {
- case 's', 'a':
+ case 's':
return append(commands, SubstituteValueCommand {subex}, JumpCommand {len(commands) + 3})
- case 'S', 'f', 'F', 'l', 'L', 'A':
+ case 'S':
return append(commands, SubstitutePathCommand {subex}, JumpCommand {len(commands) + 3})
default:
panic("Unreachable!?!?")
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 (
+)
diff --git a/walk/atom.go b/walk/atom.go
index 299c39d..471f030 100644
--- a/walk/atom.go
+++ b/walk/atom.go
@@ -14,78 +14,78 @@ const (
AtomStringTerminal
AtomStringRune
)
-type Atom struct {
+type AtomOLD struct {
Typ AtomType
data uint64
}
-func NewAtomNull() Atom {
- return Atom {
+func NewAtomNull() AtomOLD {
+ return AtomOLD {
Typ: AtomNull,
data: 0,
}
}
-func NewAtomBool(v bool) Atom {
+func NewAtomBool(v bool) AtomOLD {
if v {
- return Atom {
+ return AtomOLD {
Typ: AtomBool,
data: 1,
}
} else {
- return Atom {
+ return AtomOLD {
Typ: AtomBool,
data: 0,
}
}
}
-func (v Atom) Bool() bool {
+func (v AtomOLD) Bool() bool {
if v.Typ != AtomBool {
panic("Tried to use non-bool as bool")
}
return v.data == 1
}
-func NewAtomNumber(v float64) Atom {
- return Atom {
+func NewAtomNumber(v float64) AtomOLD {
+ return AtomOLD {
Typ: AtomNumber,
data: math.Float64bits(v),
}
}
-func (v Atom) Number() float64 {
+func (v AtomOLD) Number() float64 {
if v.Typ != AtomNumber {
panic("Tried to use non-number as number")
}
return math.Float64frombits(v.data)
}
-func NewAtomTerminal(v ValueTerminal) Atom {
- return Atom {
+func NewAtomTerminal(v ValueTerminal) AtomOLD {
+ return AtomOLD {
Typ: AtomTerminal,
data: uint64(v),
}
}
-func (v Atom) Terminal() ValueTerminal {
+func (v AtomOLD) Terminal() ValueTerminal {
if v.Typ != AtomTerminal {
panic("Tried to use non-terminal as terminal")
}
return ValueTerminal(v.data)
}
-func NewAtomStringTerminal() Atom {
- return Atom {
+func NewAtomStringTerminal() AtomOLD {
+ return AtomOLD {
Typ: AtomStringTerminal,
data: 0,
}
}
-func NewAtomStringRune(v rune) Atom {
- return Atom {
+func NewAtomStringRune(v rune) AtomOLD {
+ return AtomOLD {
Typ: AtomStringRune,
data: uint64(v),
}
}
-func (v Atom) StringRune() rune {
+func (v AtomOLD) StringRune() rune {
if v.Typ != AtomStringRune {
panic("Tried to use non-stringrune as stringrune")
}
return rune(v.data)
}
-func (v Atom) String() string {
+func (v AtomOLD) String() string {
switch v.Typ {
case AtomNull:
return "null"
diff --git a/walk/value.go b/walk/value.go
index 2e2c3c9..4459d89 100644
--- a/walk/value.go
+++ b/walk/value.go
@@ -11,7 +11,7 @@ const (
MapBegin
MapEnd
)
-func (value ValueTerminal) Atomise(in []Atom) []Atom {
+func (value ValueTerminal) Atomise(in []AtomOLD) []AtomOLD {
return append(in, NewAtomTerminal(value))
}
func (value ValueTerminal) String() string {
@@ -30,7 +30,7 @@ func (value ValueTerminal) String() string {
}
type ValueNull struct {}
-func (value ValueNull) Atomise(in []Atom) []Atom {
+func (value ValueNull) Atomise(in []AtomOLD) []AtomOLD {
return append(in, NewAtomNull())
}
func (value ValueNull) String() string {
@@ -38,7 +38,7 @@ func (value ValueNull) String() string {
}
type ValueBool bool
-func (value ValueBool) Atomise(in []Atom) []Atom {
+func (value ValueBool) Atomise(in []AtomOLD) []AtomOLD {
return append(in, NewAtomBool(bool(value)))
}
func (value ValueBool) String() string {
@@ -50,7 +50,7 @@ func (value ValueBool) String() string {
}
type ValueNumber float64
-func (value ValueNumber) Atomise(in []Atom) []Atom {
+func (value ValueNumber) Atomise(in []AtomOLD) []AtomOLD {
return append(in, NewAtomNumber(float64(value)))
}
func (value ValueNumber) String() string {
@@ -59,7 +59,7 @@ func (value ValueNumber) String() string {
}
type ValueString string
-func (value ValueString) Atomise(in []Atom) []Atom {
+func (value ValueString) Atomise(in []AtomOLD) []AtomOLD {
in = append(in, NewAtomStringTerminal())
for _, char := range value {
in = append(in, NewAtomStringRune(char))
@@ -71,8 +71,8 @@ func (value ValueString) String() string {
return fmt.Sprintf("\"%s\"", string(value))
}
-type Value interface {
+type ValueOLD interface {
// Append this values atoms to the input
- Atomise(in []Atom) []Atom
+ Atomise(in []AtomOLD) []AtomOLD
String() string
}
diff --git a/walk/walk.go b/walk/walk.go
index 1073c67..65fac6e 100644
--- a/walk/walk.go
+++ b/walk/walk.go
@@ -1,17 +1,232 @@
package walk
import (
- "strings"
+ "fmt"
"math"
+ "strings"
"unicode/utf8"
)
+type valueIter struct {
+ values []Value
+ index int
+}
+func (iter *valueIter) Next() Edible {
+ if iter.index >= len(iter.values) {
+ return nil
+ }
+ iter.index += 1
+ return iter.values[iter.index - 1]
+}
+
+func NewValueIter(values []Value) StructureIter {
+ return &valueIter {
+ values: values,
+ index: 0,
+ }
+}
+
+type OutputList interface {
+ outputList()
+}
+
+type StructureIter interface {
+ Next() Edible
+}
+
+type Edible interface {
+ edible()
+}
+
+type Atom interface {
+ Edible
+ atom()
+}
+
+type Scalar interface {
+ Atom
+ Value
+}
+
+type Structure interface {
+ Value
+ structure()
+ Iter() StructureIter
+}
+
+type Value interface {
+ Edible
+ value()
+ Debug() string
+}
+
+type Terminal interface {
+ Atom
+ terminal()
+}
+
+type ValueList []Value
+func (_ ValueList) outputList() {}
+
+type RuneList []StringRuneAtom
+func (_ RuneList) outputList() {}
+
+type NullScalar struct{}
+func (_ NullScalar) edible() {}
+func (_ NullScalar) atom() {}
+func (_ NullScalar) value() {}
+func (_ NullScalar) Debug() string {
+ return "null"
+}
+
+type BoolScalar bool
+func (_ BoolScalar) edible() {}
+func (_ BoolScalar) atom() {}
+func (_ BoolScalar) value() {}
+func (b BoolScalar) Debug() string {
+ if b {
+ return "true"
+ }
+ return "false"
+}
+
+type NumberScalar float64
+func (_ NumberScalar) edible() {}
+func (_ NumberScalar) atom() {}
+func (_ NumberScalar) value() {}
+func (n NumberScalar) Debug() string {
+ return fmt.Sprintf("%v", float64(n))
+}
+
+type StringStructure string
+func (_ StringStructure) edible() {}
+func (_ StringStructure) value() {}
+func (_ StringStructure) structure() {}
+func (s StringStructure) Iter() StructureIter {
+ return &stringStructureIter {
+ string: string(s),
+ position: 0,
+ }
+}
+func (s StringStructure) Debug() string {
+ return fmt.Sprintf("%q", string(s))
+}
+
+type stringStructureIter struct {
+ string string
+ position int
+}
+func (iter *stringStructureIter) Next() Edible {
+ if iter.position == -1 {
+ return nil
+ }
+ r, width := utf8.DecodeRuneInString(iter.string[iter.position:])
+ if width == 0 {
+ iter.position = -1
+ return StringEndTerminal{}
+ }
+ iter.position += width
+ return StringRuneAtom(r)
+}
+
+type StringBeginTerminal struct{}
+func (_ StringBeginTerminal) edible() {}
+func (_ StringBeginTerminal) atom() {}
+func (_ StringBeginTerminal) terminal() {}
+
+type StringEndTerminal struct{}
+func (_ StringEndTerminal) edible() {}
+func (_ StringEndTerminal) atom() {}
+func (_ StringEndTerminal) terminal() {}
+
+type StringRuneAtom rune
+func (_ StringRuneAtom) edible() {}
+func (_ StringRuneAtom) atom() {}
+
+type ArrayStructure []Value
+func (_ ArrayStructure) edible() {}
+func (_ ArrayStructure) value() {}
+func (_ ArrayStructure) structure() {}
+func (array ArrayStructure) Iter() StructureIter {
+ return &arrayStructureIter {
+ array: []Value(array),
+ index: 0,
+ }
+}
+func (array ArrayStructure) Debug() string {
+ builder := strings.Builder{}
+ builder.WriteRune('[')
+ var sep string
+ for _, element := range array {
+ builder.WriteString(sep)
+ builder.WriteString(fmt.Sprintf("%v", element))
+ sep = ", "
+ }
+ builder.WriteRune(']')
+ return builder.String()
+}
+
+type arrayStructureIter struct {
+ array []Value
+ index int
+}
+func (iter *arrayStructureIter) Next() Edible {
+ if iter.index > len(iter.array) {
+ return nil
+ }
+ if iter.index == len(iter.array) {
+ iter.index += 1
+ return ArrayEndTerminal{}
+ }
+ iter.index += 1
+ return iter.array[iter.index - 1]
+}
+
+type ArrayBeginTerminal struct{}
+func (_ ArrayBeginTerminal) edible() {}
+func (_ ArrayBeginTerminal) atom() {}
+func (_ ArrayBeginTerminal) terminal() {}
+
+type ArrayEndTerminal struct{}
+func (_ ArrayEndTerminal) edible() {}
+func (_ ArrayEndTerminal) atom() {}
+func (_ ArrayEndTerminal) terminal() {}
+
+type MapStructure map[string]Value
+func (_ MapStructure) edible() {}
+func (_ MapStructure) value() {}
+func (_ MapStructure) structure() {}
+func (m MapStructure) Debug() string {
+ builder := strings.Builder{}
+ builder.WriteRune('{')
+ var sep string
+ for key, value := range m {
+ builder.WriteString(sep)
+ builder.WriteString(fmt.Sprintf("%q", key))
+ builder.WriteString(": ")
+ builder.WriteString(fmt.Sprintf("%q", value))
+ sep = ", "
+ }
+ builder.WriteRune('}')
+ return builder.String()
+}
+
+type MapBeginTerminal struct{}
+func (_ MapBeginTerminal) edible() {}
+func (_ MapBeginTerminal) atom() {}
+func (_ MapBeginTerminal) terminal() {}
+
+type MapEndTerminal struct{}
+func (_ MapEndTerminal) edible() {}
+func (_ MapEndTerminal) atom() {}
+func (_ MapEndTerminal) terminal() {}
+
// int or string
type PathSegment interface {}
type Path []PathSegment
-func (path Path) ToWalkValues() []Value {
- var values []Value
+func (path Path) ToWalkValues() []ValueOLD {
+ var values []ValueOLD
for _, segment := range path {
switch s := segment.(type) {
case int:
@@ -25,7 +240,7 @@ func (path Path) ToWalkValues() []Value {
return values
}
-func PathFromWalkValues(values []Value) Path {
+func PathFromWalkValues(values []ValueOLD) Path {
var segments []PathSegment
for _, value := range values {
switch v := value.(type) {
@@ -41,18 +256,36 @@ func PathFromWalkValues(values []Value) Path {
}
type WalkItem struct {
- Value []Atom
- Path []Atom
+ Value ValueList
+ Path ValueList
+}
+
+func (item WalkItem) Debug() string {
+ builder := strings.Builder{}
+ var sep string
+ for _, pathSegment := range item.Path {
+ builder.WriteString(sep)
+ builder.WriteString(fmt.Sprintf("%s", pathSegment.Debug()))
+ sep = "."
+ }
+ builder.WriteString(": ")
+ sep = ""
+ for _, value := range item.Value {
+ builder.WriteString(sep)
+ builder.WriteString(fmt.Sprintf("%s", value.Debug()))
+ sep = ", "
+ }
+ return builder.String()
}
-func ConcatData(first []Atom, second []Atom) []Atom {
- res := make([]Atom, 0, len(first) + len(second))
+func ConcatData(first []AtomOLD, second []AtomOLD) []AtomOLD {
+ res := make([]AtomOLD, 0, len(first) + len(second))
res = append(res, first...)
res = append(res, second...)
return res
}
-func Atomise(in []Value) (out []Atom) {
+func Atomise(in []ValueOLD) (out []AtomOLD) {
numAtoms := 0
for _, value := range in {
switch v := value.(type) {
@@ -64,7 +297,7 @@ func Atomise(in []Value) (out []Atom) {
panic("Invalid WalkValue")
}
}
- out = make([]Atom, 0, numAtoms)
+ out = make([]AtomOLD, 0, numAtoms)
for _, value := range in {
out = value.Atomise(out)
}
@@ -96,11 +329,11 @@ func (err CompoundError) Error() string {
}
type CompoundResult struct {
- value Value
+ value ValueOLD
error error
}
-func Compound(in []Atom) (out []Value, error error) {
+func Compound(in []AtomOLD) (out []ValueOLD, error error) {
numValues := 0
i := 0
inString := false
@@ -118,7 +351,7 @@ func Compound(in []Atom) (out []Value, error error) {
}
}
i = 0
- out = make([]Value, 0, numValues)
+ out = make([]ValueOLD, 0, numValues)
for {
if i >= len(in) {
break
diff --git a/walk/walk_test.go b/walk/walk_test.go
new file mode 100644
index 0000000..c05da02
--- /dev/null
+++ b/walk/walk_test.go
@@ -0,0 +1,45 @@
+package walk
+
+import (
+ "testing"
+ "log"
+)
+
+func TestValueIter(t *testing.T) {
+ values := ValueList{
+ NumberScalar(1),
+ NumberScalar(2),
+ NumberScalar(3),
+ }
+
+ valuesCopy := ValueList{}
+
+ iter := NewValueIter(values)
+
+ for {
+ edible := iter.Next()
+ if edible == nil {
+ break
+ }
+
+ log.Println(edible)
+
+ value, isValue := edible.(Value)
+
+ if !isValue {
+ t.Fatalf("Iterator produced a non-value")
+ }
+
+ valuesCopy = append(valuesCopy, value)
+ }
+
+ if len(values) != len(valuesCopy) {
+ t.Fatalf("iter gave the wrong number of values")
+ }
+
+ for i, value := range values {
+ if value != valuesCopy[i] {
+ t.Fatalf("iter produced an incorrect value")
+ }
+ }
+}