Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--json/read.go2
-rw-r--r--main/command.go276
-rw-r--r--main/lex.go35
-rw-r--r--main/main.go91
-rw-r--r--main/main_test.go171
-rw-r--r--main/parse.go86
-rw-r--r--subex/arithmetic.go203
-rw-r--r--subex/filter.go197
-rw-r--r--subex/lex.go3
-rw-r--r--subex/main.go70
-rw-r--r--subex/main_test.go344
-rw-r--r--subex/numberexpr.go217
-rw-r--r--subex/parse.go978
-rw-r--r--subex/subexast.go511
-rw-r--r--subex/subexstate.go228
-rw-r--r--walk/walk.go85
-rw-r--r--walk/walk_test.go45
17 files changed, 2840 insertions, 702 deletions
diff --git a/json/read.go b/json/read.go
index f3a0a65..91589cf 100644
--- a/json/read.go
+++ b/json/read.go
@@ -29,7 +29,9 @@ const (
type JSONReaderState int
const (
+ // Immediately before a value
JSONReaderStateValue JSONReaderState = iota
+ // Immediate after a value
JSONReaderStateValueEnd
)
diff --git a/main/command.go b/main/command.go
index 1d089ee..bbbb036 100644
--- a/main/command.go
+++ b/main/command.go
@@ -27,11 +27,18 @@ func (cmd PrintValueCommand) String() string {
type NextCommand struct {}
func (cmd NextCommand) exec(state *ProgramState) {
- nextItem, err := state.in.Read()
+ nextItem, err := state.Read()
if err != nil {
panic("Missing next value")
}
+
+ state.prevStart = nextItem.PrevStart
+ state.start = nextItem.Start
+ state.end = nextItem.End
+ state.nextEnd = nextItem.NextEnd
+
state.value = []walk.Value{nextItem.Value}
+
state.pc++
}
func (cmd NextCommand) String() string {
@@ -40,17 +47,138 @@ func (cmd NextCommand) String() string {
type AppendNextCommand struct {}
func (cmd AppendNextCommand) exec(state *ProgramState) {
- nextItem, err := state.in.Read()
+ nextItem, err := state.Read()
if err != nil {
panic("Missing next value")
}
+
+ state.prevStart = nextItem.PrevStart
+ state.start = nextItem.Start
+ state.end = nextItem.End
+ state.nextEnd = nextItem.NextEnd
+
state.value = append(state.value, nextItem.Value)
+
state.pc++
}
func (cmd AppendNextCommand) String() string {
return "N"
}
+type SubstituteNextCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteNextCommand) exec(state *ProgramState) {
+ item, err := state.Peek()
+ if err != nil {
+ panic("Missing next value")
+ }
+
+ newValue, notOk := runSubex(cmd.subex, []walk.Value{item.Value})
+
+ if notOk {
+ state.pc++
+ } else {
+ state.Read()
+ state.prevStart = item.PrevStart
+ state.start = item.Start
+ state.end = item.End
+ state.nextEnd = item.NextEnd
+ state.pc += 2
+ state.value = newValue
+ }
+}
+func (cmd SubstituteNextCommand) String() string {
+ return "n/.../"
+}
+
+type SubstituteAppendNextCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteAppendNextCommand) exec(state *ProgramState) {
+ item, err := state.Peek()
+ if err != nil {
+ panic("Missing next value")
+ }
+
+ newValue, notOk := runSubex(cmd.subex, []walk.Value{item.Value})
+
+ if notOk {
+ state.pc++
+ } else {
+ state.Read()
+ state.prevStart = item.PrevStart
+ state.start = item.Start
+ state.end = item.End
+ state.nextEnd = item.NextEnd
+ state.pc += 2
+ state.value = append(state.value, newValue...)
+ }
+}
+func (cmd SubstituteAppendNextCommand) String() string {
+ return "N/.../"
+}
+
+type MergeCommand struct {}
+func (cmd MergeCommand) exec(state *ProgramState) {
+ if len(state.value) <= 1 {
+ state.pc++
+ return
+ }
+
+ newVals := walk.Merge(state.value[len(state.value) - 2], state.value[len(state.value) - 1])
+ state.value = append(
+ state.value[:len(state.value) - 2],
+ newVals...
+ )
+
+ state.pc++
+}
+func (cmd MergeCommand) String() string {
+ return "m"
+}
+
+type FullMergeCommand struct {
+ subex subex.Transducer
+}
+func (cmd FullMergeCommand) exec(state *ProgramState) {
+ _, notOk := runSubex(cmd.subex, state.value)
+ if notOk {
+ state.pc++
+ return
+ }
+ if !state.start {
+ state.pc += 2
+ return
+ }
+
+ for {
+ item, err := state.Read()
+ if err != nil {
+ panic("Missing next value")
+ }
+
+ _, nonTerminal := runSubex(cmd.subex, []walk.Value{item.Value})
+
+ state.value = append(
+ state.value[:len(state.value) - 1],
+ walk.Merge(state.value[len(state.value) - 1], item.Value)...
+ )
+
+ if !nonTerminal && item.End {
+ state.prevStart = item.PrevStart
+ state.start = item.Start
+ state.end = item.End
+ state.nextEnd = item.NextEnd
+ state.pc += 2
+ return
+ }
+ }
+}
+func (cmd FullMergeCommand) String() string {
+ return "M"
+}
+
type DeleteValueCommand struct {}
func (cmd DeleteValueCommand) exec(state *ProgramState) {
state.value = nil
@@ -84,6 +212,54 @@ func (cmd SubstituteValueCommand) String() string {
return "s/.../"
}
+type IsStartCommand struct {}
+func (cmd IsStartCommand) exec(state *ProgramState) {
+ if state.start {
+ state.pc += 2
+ } else {
+ state.pc += 1
+ }
+}
+func (cmd IsStartCommand) String() string {
+ return "a"
+}
+
+type IsPrevStartCommand struct {}
+func (cmd IsPrevStartCommand) exec(state *ProgramState) {
+ if state.prevStart {
+ state.pc += 2
+ } else {
+ state.pc += 1
+ }
+}
+func (cmd IsPrevStartCommand) String() string {
+ return "A"
+}
+
+type IsEndCommand struct {}
+func (cmd IsEndCommand) exec(state *ProgramState) {
+ if state.end {
+ state.pc += 2
+ } else {
+ state.pc += 1
+ }
+}
+func (cmd IsEndCommand) String() string {
+ return "e"
+}
+
+type IsNextEndCommand struct {}
+func (cmd IsNextEndCommand) exec(state *ProgramState) {
+ if state.nextEnd {
+ state.pc += 2
+ } else {
+ state.pc += 1
+ }
+}
+func (cmd IsNextEndCommand) String() string {
+ return "E"
+}
+
type NoopCommand struct {}
func (cmd NoopCommand) exec(state *ProgramState) {
state.pc++
@@ -112,6 +288,38 @@ func (cmd AppendXRegCommand) String() string {
return "X"
}
+type SubstituteToXRegCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteToXRegCommand) exec(state *ProgramState) {
+ newValue, err := runSubex(cmd.subex, state.value)
+ if err {
+ state.pc++
+ } else {
+ state.pc += 2
+ state.xreg = newValue
+ }
+}
+func (cmd SubstituteToXRegCommand) String() string {
+ return "x/.../"
+}
+
+type SubstituteAppendXRegCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteAppendXRegCommand) exec(state *ProgramState) {
+ newValue, err := runSubex(cmd.subex, state.value)
+ if err {
+ state.pc++
+ } else {
+ state.pc += 2
+ state.xreg = append(state.xreg, newValue...)
+ }
+}
+func (cmd SubstituteAppendXRegCommand) String() string {
+ return "X/.../"
+}
+
type SwapYRegCommand struct {}
func (cmd SwapYRegCommand) exec(state *ProgramState) {
v := state.value
@@ -132,6 +340,38 @@ func (cmd AppendYRegCommand) String() string {
return "Y"
}
+type SubstituteToYRegCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteToYRegCommand) exec(state *ProgramState) {
+ newValue, err := runSubex(cmd.subex, state.value)
+ if err {
+ state.pc++
+ } else {
+ state.pc += 2
+ state.yreg = newValue
+ }
+}
+func (cmd SubstituteToYRegCommand) String() string {
+ return "y/.../"
+}
+
+type SubstituteAppendYRegCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteAppendYRegCommand) exec(state *ProgramState) {
+ newValue, err := runSubex(cmd.subex, state.value)
+ if err {
+ state.pc++
+ } else {
+ state.pc += 2
+ state.yreg = append(state.xreg, newValue...)
+ }
+}
+func (cmd SubstituteAppendYRegCommand) String() string {
+ return "Y/.../"
+}
+
type SwapZRegCommand struct {}
func (cmd SwapZRegCommand) exec(state *ProgramState) {
v := state.value
@@ -152,6 +392,38 @@ func (cmd AppendZRegCommand) String() string {
return "Z"
}
+type SubstituteToZRegCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteToZRegCommand) exec(state *ProgramState) {
+ newValue, err := runSubex(cmd.subex, state.value)
+ if err {
+ state.pc++
+ } else {
+ state.pc += 2
+ state.zreg = newValue
+ }
+}
+func (cmd SubstituteToZRegCommand) String() string {
+ return "z/.../"
+}
+
+type SubstituteAppendZRegCommand struct {
+ subex subex.Transducer
+}
+func (cmd SubstituteAppendZRegCommand) exec(state *ProgramState) {
+ newValue, err := runSubex(cmd.subex, state.value)
+ if err {
+ state.pc++
+ } else {
+ state.pc += 2
+ state.zreg = append(state.xreg, newValue...)
+ }
+}
+func (cmd SubstituteAppendZRegCommand) String() string {
+ return "Z/.../"
+}
+
type JumpCommand struct {
destination int
}
diff --git a/main/lex.go b/main/lex.go
index 496abd0..da517cc 100644
--- a/main/lex.go
+++ b/main/lex.go
@@ -171,21 +171,28 @@ func lexCommand(l *lexer) stateFunc {
l.ignore()
r := l.next()
switch r {
- case eof:
- l.emit(TokenEOF)
- return nil
- case '{':
- l.emit(TokenLBrace)
- return lexCommand
- case '}':
- l.emit(TokenRBrace)
- return lexCommand
- case 's', 'S':
- l.emit(TokenCommand)
+ case eof:
+ l.emit(TokenEOF)
+ return nil
+ case '{':
+ l.emit(TokenLBrace)
+ return lexCommand
+ case '}':
+ l.emit(TokenRBrace)
+ return lexCommand
+ case 's', 'S', 'M':
+ l.emit(TokenCommand)
+ return lexSubstitution
+ case 'x', 'X', 'y', 'Y', 'z', 'Z', 'n', 'N':
+ l.emit(TokenCommand)
+ if l.peek() == '/' {
return lexSubstitution
- case ':', 'b':
- l.emit(TokenCommand)
- return lexLabel
+ } else {
+ return lexCommand
+ }
+ case ':', 'b':
+ l.emit(TokenCommand)
+ return lexLabel
}
if isAlpha(r) {
l.emit(TokenCommand)
diff --git a/main/main.go b/main/main.go
index b7ef568..bfa1afe 100644
--- a/main/main.go
+++ b/main/main.go
@@ -1,46 +1,53 @@
package main
import (
- "os"
"bufio"
- "main/walk"
+ "io"
"main/json"
+ "main/walk"
+ "os"
)
type ProgramState struct {
value, xreg, yreg, zreg []walk.Value
+ start, prevStart, end, nextEnd bool
+ // TODO: This will only ever have 0 or 1 values, it is a slice out of laziness
+ peekStack []walk.WalkItem
in walk.StredReader
out walk.StredWriter
program []Command
pc int
}
-
-func main() {
- quiet := false
- var input string
- hasInput := false
-
- for i := 1; i < len(os.Args); i += 1 {
- switch os.Args[i] {
- case "-n":
- quiet = true
- continue
- }
- if i < len(os.Args) - 1 {
- panic("Unexpected arguments after program")
- }
- input = os.Args[i]
- hasInput = true
+func (state *ProgramState) Read() (walk.WalkItem, error) {
+ if len(state.peekStack) > 0 {
+ item := state.peekStack[len(state.peekStack) - 1]
+ state.peekStack = state.peekStack[:len(state.peekStack) - 1]
+ return item, nil
}
- if !hasInput {
- panic("Missing program")
+ return state.in.Read()
+}
+func (state *ProgramState) Peek() (walk.WalkItem, error) {
+ item, err := state.Read()
+ if err != nil {
+ return walk.WalkItem{}, err
}
+ state.peekStack = append(state.peekStack, item)
+ return item, nil
+}
+
+type config struct {
+ quiet bool
+ program string
+ in io.Reader
+ out io.Writer
+}
- tokens := Lex(input)
+func run(config config) {
+ tokens := Lex(config.program)
program := Parse(tokens)
- stdin := bufio.NewReader(os.Stdin)
- stdout := bufio.NewWriter(os.Stdout)
+ stdin := bufio.NewReader(config.in)
+ stdout := bufio.NewWriter(config.out)
state := ProgramState {
in: json.NewJSONReader(stdin),
@@ -49,16 +56,20 @@ func main() {
}
for {
- walkItem, err := state.in.Read()
+ walkItem, err := state.Read()
if err != nil {
break
}
state.value = []walk.Value{walkItem.Value}
+ state.start = walkItem.Start
+ state.prevStart = walkItem.PrevStart
+ state.end = walkItem.End
+ state.nextEnd = walkItem.NextEnd
state.pc = 0
for state.pc < len(state.program) {
state.program[state.pc].exec(&state)
}
- if !quiet {
+ if !config.quiet {
for _, value := range state.value {
err := state.out.Write(value)
if err != nil {
@@ -71,3 +82,31 @@ func main() {
state.in.AssertDone()
state.out.AssertDone()
}
+
+func main() {
+ config := config {
+ quiet: false,
+ in: os.Stdin,
+ out: os.Stdout,
+ }
+ hasInput := false
+
+ for i := 1; i < len(os.Args); i += 1 {
+ switch os.Args[i] {
+ case "-n":
+ config.quiet = true
+ continue
+ }
+ if i < len(os.Args) - 1 {
+ panic("Unexpected arguments after program")
+ }
+ config.program = os.Args[i]
+ hasInput = true
+ }
+ if !hasInput {
+ panic("Missing program")
+ }
+
+ run(config)
+
+}
diff --git a/main/main_test.go b/main/main_test.go
new file mode 100644
index 0000000..802d248
--- /dev/null
+++ b/main/main_test.go
@@ -0,0 +1,171 @@
+package main
+
+import (
+ "strings"
+ "testing"
+)
+
+const miscInput string = `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`
+const mixedArray string = `{"array":["first",null,3,{"name":"second"},"third"]}`
+const mixedArray2 string = `{"array":["first",null,3,"second",{"name":"third"}]}`
+
+func TestSpecificCases(t *testing.T) {
+ type test struct {
+ name string
+ program string
+ quiet bool
+ input string
+ expected string
+ }
+
+ tests := []test {
+ {
+ name: "Verbose Extract",
+ program: `s/#(~(people)~>_@(1>_#(~(first_name)~>_.|(..)>_*)-|(..)>_*)-|(..)>_*)-/p`,
+ quiet: true,
+ input: miscInput,
+ expected: `"Tom"`,
+ },
+ {
+ name: "Extract",
+ program: `s/#("people">_ @(1>_#("first_name">_ .)-)-)-/p`,
+ quiet: true,
+ input: miscInput,
+ expected: `"Tom"`,
+ },
+ {
+ name: "Simple Extract",
+ program: "s/#(\"people\" @(1 #(\"first_name\" (.>a))-)-)->_ <a/p",
+ quiet: true,
+ input: miscInput,
+ expected: `"Tom"`,
+ },
+ {
+ name: "Larger Extract",
+ program: "s/#(\"people\" @(2 (.>a))-)->_ `<a`/p",
+ quiet: true,
+ input: miscInput,
+ expected: `{"first_name":"Charlie","last_name":"Chaplin","age":122}`,
+ },
+ {
+ name: "Extract ages",
+ program: "s/#(\"people\">_ :(#(\"age\">_ .)-):)-/p",
+ quiet: true,
+ input: miscInput,
+ expected: `[22,18,122,48]`,
+ },
+ {
+ name: "Low memory count people",
+ program: "aX/#(\"people\" :(#()#):)#>_ `1`/o es/#()#/{ xs/.*%+/p }",
+ quiet: true,
+ input: miscInput,
+ expected: "4",
+ },
+ {
+ name: "Get full names",
+ program: "s/#(\"people\">_ .)-/{ s/:():/p as/:(#()#):/{ xdx } s/:(#((\"first_name\"|\"last_name\") .)#)-/X es/@(.#()-)-/{ xs/(#(\"first_name\"\".*>a\")#|#(\"last_name\"\".*>b\")#|.)*>_`\"<a <b\"`/Xxs/-(..)@/p } }",
+ quiet: true,
+ input: miscInput,
+ expected: `["Charlie Johnson","Tom Johnson","Charlie Chaplin","John Johnson"]`,
+ },
+ {
+ name: "Get full names 2",
+ program: "s/#(\"people\">_.)-/{ s/:():/p as/:(#()#):/{ xdx } X/:(#((\"first_name\"|\"last_name\") .)#)-/o es/@(.#()-)-/{ xX/(#(\"first_name\"\".*>a\")#|#(\"last_name\"\".*>b\")#|.)*_`\"<a <b\"`/xs/-(..)@/p } }",
+ quiet: true,
+ input: miscInput,
+ expected: `["Charlie Johnson","Tom Johnson","Charlie Chaplin","John Johnson"]`,
+ },
+ {
+ name: "Change full names in place",
+ program: "s/#(\"people\"@(.#(\"first_name\".)#)@)#/{ Nms/#(\"people\"@(.(#(\"first_name\"\".*>a\"\"last_name\"\".*>b\")#_)`#(\"name\"\"<a <b\")#`)@)#/ }",
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values"],"people":[{"name":"Charlie Johnson","age":22},{"name":"Tom Johnson","age":18},{"name":"Charlie Chaplin","age":122},{"name":"John Johnson","age":48}]}`,
+ },
+ {
+ name: "Get full names with substitute next command",
+ program: "s/#(\"people\"_:(#(\"first_name\"_.)-)-)-/{ N/#(\"people\"_:(#(\"last_name\"_.)-)-)-/{ s/-(-(~(.*` `)-~(.*)-)~):/p }}",
+ quiet: true,
+ input: miscInput,
+ expected: `["Charlie Johnson","Tom Johnson","Charlie Chaplin","John Johnson"]`,
+ },
+ {
+ name: "Get full names with merge full command",
+ program: "s/#(\"people\"_:():)-/p M/#(\"people\"@(.#()#)@)#/{ s/#(\"people\"_@(.#[(\"first_name\"\".*>a\"|\"last_name\"\".*>b\"|..)_]-`\"<a <b\"`)@)-/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `["Charlie Johnson","Tom Johnson","Charlie Chaplin","John Johnson"]`,
+ },
+ {
+ name: "Verbose concat array values",
+ program: "as/#(\"array\"_:():)-/{ :s N/#(._.)-/{ es/.*:():/be mbs } :em s/:(-(~(.*` `)-*~(.*)-)~)-/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `"Hello world these are values"`,
+ },
+ {
+ name: "Short concat array values",
+ program: "M/#(\"array\":():)#/{ s/#(\"array\"_:(.*)-)-/ s/-(~(.*` `)-*~(.*)-)~/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `"Hello world these are values"`,
+ },
+ {
+ name: "Drop first element of array",
+ program: `s/#("people"@(0 .)@)#/d`,
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values"],"people":[{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ {
+ name: "Drop last element of array",
+ program: `M/#("people"@(.,)@)#/{ Ed }`,
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122}]}`,
+ },
+ {
+ name: "Drop last element of simple array",
+ program: `s/#("array"@(..)@)#/{ Ed }`,
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ {
+ name: "Drop last element of mixed array",
+ program: `M/#("array"@(.,)@)#/{ Ed }`,
+ input: mixedArray,
+ expected: `{"array":["first",null,3,{"name":"second"}]}`,
+ },
+ {
+ name: "Drop last element of mixed array 2",
+ program: `M/#("array"@(.,)@)#/{ Ed }`,
+ input: mixedArray2,
+ expected: `{"array":["first",null,3,"second"]}`,
+ },
+ {
+ name: "Prepend to array",
+ program: "as/#(\"array\":(`\"First\"`):)#/",
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["First","Hello","world","these","are","values"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ {
+ name: "Append to array",
+ program: "es/#(\"array\":(`\"Last\"`):)#/",
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values","Last"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ }
+
+ for _, test := range tests {
+ t.Logf("Running test: %s", test.name)
+
+ var output strings.Builder
+ run(config {
+ quiet: test.quiet,
+ program: test.program,
+ in: strings.NewReader(test.input),
+ out: &output,
+ })
+
+ if output.String() != test.expected {
+ t.Errorf("Ran '%s' and expected %s but got %s", test.program, test.expected, output.String())
+ }
+ }
+}
diff --git a/main/parse.go b/main/parse.go
index 9c7a437..36bd3ee 100644
--- a/main/parse.go
+++ b/main/parse.go
@@ -44,13 +44,7 @@ func (p *parser) parseSubex() subex.SubexAST {
if subexProgramToken.typ != TokenSubex {
panic("Missing subex from substitution")
}
- var subexProgram string
- if delim.val == "=" || delim.val == "~" || delim.val == "\"" || delim.val == "`" || delim.val == "^" {
- subexProgram = delim.val + subexProgramToken.val + delim.val
- } else {
- subexProgram = subexProgramToken.val
- }
- reader := subex.NewStringRuneReader(subexProgram)
+ reader := subex.NewStringRuneReader(subexProgramToken.val)
subexAST := subex.Parse(reader)
delim = p.next()
if delim.typ != TokenSubstituteDelimiter {
@@ -66,9 +60,27 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma
case 'd':
return append(commands, DeleteValueCommand{})
case 'n':
- return append(commands, NextCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, NextCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteNextCommand {subex}, JumpCommand {len(commands) + 3})
case 'N':
- return append(commands, AppendNextCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, AppendNextCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteAppendNextCommand {subex}, JumpCommand {len(commands) + 3})
+ case 'm':
+ return append(commands, MergeCommand{})
+ case 'M':
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, FullMergeCommand {subex}, JumpCommand {len(commands) + 3})
case 's':
ast := p.parseSubex()
subex := subex.CompileTransducer(ast)
@@ -76,17 +88,61 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma
case 'o':
return append(commands, NoopCommand{})
case 'x':
- return append(commands, SwapXRegCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, SwapXRegCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteToXRegCommand {subex}, JumpCommand {len(commands) + 3})
case 'X':
- return append(commands, AppendXRegCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, AppendXRegCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteAppendXRegCommand {subex}, JumpCommand {len(commands) + 3})
case 'y':
- return append(commands, SwapYRegCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, SwapYRegCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteToYRegCommand {subex}, JumpCommand {len(commands) + 3})
case 'Y':
- return append(commands, AppendYRegCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, AppendYRegCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteAppendYRegCommand {subex}, JumpCommand {len(commands) + 3})
case 'z':
- return append(commands, SwapZRegCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, SwapZRegCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteToZRegCommand {subex}, JumpCommand {len(commands) + 3})
case 'Z':
- return append(commands, AppendZRegCommand{})
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, AppendZRegCommand{})
+ }
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, SubstituteAppendZRegCommand {subex}, JumpCommand {len(commands) + 3})
+ case 'a':
+ return append(commands, IsStartCommand{}, JumpCommand {len(commands) + 3})
+ case 'A':
+ return append(commands, IsPrevStartCommand{}, JumpCommand {len(commands) + 3})
+ case 'e':
+ return append(commands, IsEndCommand{}, JumpCommand {len(commands) + 3})
+ case 'E':
+ return append(commands, IsNextEndCommand{}, JumpCommand {len(commands) + 3})
case ':':
labelToken := p.next()
if labelToken.typ != TokenLabel {
diff --git a/subex/arithmetic.go b/subex/arithmetic.go
index 4c87d5f..5e0eb44 100644
--- a/subex/arithmetic.go
+++ b/subex/arithmetic.go
@@ -3,171 +3,84 @@ package subex
import (
"main/walk"
"errors"
- "strconv"
)
-func sumValues(values []walk.Value) ([]walk.Value, error) {
- allBools := true
- var sum float64 = 0
- var any bool = false
- for _, value := range values {
- switch v := value.(type) {
- case walk.NullValue:
- allBools = false
- case walk.BoolValue:
- if v {
- sum += 1
- any = true
- }
- case walk.NumberValue:
- allBools = false
- sum += float64(v)
- case walk.StringValue:
- allBools = false
- num, err := strconv.ParseFloat(string(v), 64)
- if err == nil {
- sum += num
- } else {
- return nil, errors.New("Tried to sum non-castable string")
- }
- default:
- return nil, errors.New("Tried to sum non-number")
- }
+func binopAdd(values []walk.Value) ([]walk.Value, error) {
+ if len(values) != 2 {
+ return nil, errors.New("Tried to sum a weird number of values")
+ }
+
+ lhs, lhsIsNumber := values[0].(walk.NumberValue)
+ if !lhsIsNumber {
+ return nil, errors.New("Tried to sum a lhs that is not a number")
}
- if allBools {
- return []walk.Value{walk.BoolValue(any)}, nil
- } else {
- return []walk.Value{walk.NumberValue(sum)}, nil
+
+ rhs, rhsIsNumber := values[1].(walk.NumberValue)
+ if !rhsIsNumber {
+ return nil, errors.New("Tried to sum a rhs that is not a number")
}
+
+ return []walk.Value{walk.NumberValue(float64(lhs) + float64(rhs))}, nil
}
-// Compounds atoms into values, if all values are booleans, does AND, if not, tries to cast to numbers and multiply
-func multiplyValues(values []walk.Value) ([]walk.Value, error) {
- allBools := true
- var product float64 = 1
- var all bool = false
- for _, value := range values {
- switch v := value.(type) {
- case walk.NullValue:
- allBools = false
- product *= 0
- case walk.BoolValue:
- if !v {
- product *= 0
- all = false
- }
- case walk.NumberValue:
- allBools = false
- product *= float64(v)
- case walk.StringValue:
- allBools = false
- num, err := strconv.ParseFloat(string(v), 64)
- if err == nil {
- product *= num
- } else {
- return nil, errors.New("Tried to multiply non-castable string")
- }
- default:
- return nil, errors.New("Tried to multiply non-number")
- }
+func binopMultiply(values []walk.Value) ([]walk.Value, error) {
+ if len(values) != 2 {
+ return nil, errors.New("Tried to multiply a weird number of values")
}
- if allBools {
- return []walk.Value{walk.BoolValue(all)}, nil
- } else {
- return []walk.Value{walk.NumberValue(product)}, nil
+
+ lhs, lhsIsNumber := values[0].(walk.NumberValue)
+ if !lhsIsNumber {
+ return nil, errors.New("Tried to multiply a lhs that is not a number")
}
-}
-// Does tries to cast all to numbers and negates them
-func negateValues(values []walk.Value) ([]walk.Value, error) {
- var negatedNumbers []walk.Value
- for _, value := range values {
- switch v := value.(type) {
- case walk.NullValue:
- negatedNumbers = append(negatedNumbers, walk.NumberValue(0))
- case walk.BoolValue:
- if v {
- negatedNumbers = append(negatedNumbers, walk.NumberValue(-1))
- } else {
- negatedNumbers = append(negatedNumbers, walk.NumberValue(0))
- }
- case walk.NumberValue:
- negatedNumbers = append(negatedNumbers, walk.NumberValue(-float64(v)))
- case walk.StringValue:
- num, err := strconv.ParseFloat(string(v), 64)
- if err == nil {
- negatedNumbers = append(negatedNumbers, walk.NumberValue(-num))
- } else {
- return nil, errors.New("Tried to negate non-castable string")
- }
- default:
- return nil, errors.New("Tried to negate non-number")
- }
+ rhs, rhsIsNumber := values[1].(walk.NumberValue)
+ if !rhsIsNumber {
+ return nil, errors.New("Tried to multiply a rhs that is not a number")
}
- return negatedNumbers, nil
+
+ return []walk.Value{walk.NumberValue(float64(lhs) * float64(rhs))}, nil
}
-// If all are castable to numbers, takes reciprocals of all and returns them
-// Else errors
-func reciprocalValues(values []walk.Value) ([]walk.Value, error) {
- var reciprocals []walk.Value
- for _, value := range values {
- switch v := value.(type) {
- case walk.NullValue:
- return nil, errors.New("Tried to take reciprocal of null")
- case walk.BoolValue:
- if v {
- reciprocals = append(reciprocals, walk.NumberValue(1))
- } else {
- return nil, errors.New("Tried to take reciprocal of false")
- }
- case walk.NumberValue:
- reciprocals = append(reciprocals, walk.NumberValue(1 / float64(v)))
- case walk.StringValue:
- num, err := strconv.ParseFloat(string(v), 64)
- if err == nil {
- reciprocals = append(reciprocals, walk.NumberValue(1 / num))
- } else {
- return nil, errors.New("Tried to take reciprocal of non-castable string")
- }
- default:
- return nil, errors.New("Tried to take reciprocal of non-number")
- }
+func binopDivide(values []walk.Value) ([]walk.Value, error) {
+ if len(values) != 2 {
+ return nil, errors.New("Tried to divide a weird number of values")
+ }
+
+ lhs, lhsIsNumber := values[0].(walk.NumberValue)
+ if !lhsIsNumber {
+ return nil, errors.New("Tried to divide a lhs that is not a number")
+ }
+
+ rhs, rhsIsNumber := values[1].(walk.NumberValue)
+ if !rhsIsNumber {
+ return nil, errors.New("Tried to divide a rhs that is not a number")
}
- return reciprocals, nil
+
+ return []walk.Value{walk.NumberValue(float64(lhs) / float64(rhs))}, nil
}
-// If all are castable to booleans, NOTs all and returns them
-// Else errors
-func notValues(values []walk.Value) (notted []walk.Value, err error) {
+func arithmeticSum(values []walk.Value) ([]walk.Value, error) {
+ var total float64 = 0
for _, value := range values {
- switch v := value.(type) {
- case walk.NullValue:
- notted = append(notted, walk.BoolValue(true))
- case walk.BoolValue:
- notted = append(notted, walk.BoolValue(!bool(v)))
- case walk.NumberValue:
- notted = append(notted, walk.BoolValue(v == 0))
- case walk.StringValue:
- notted = append(notted, walk.BoolValue(len(v) == 0))
- default:
- return nil, errors.New("Tried to NOT non-boolean")
+ n, isNumber := value.(walk.NumberValue)
+ if !isNumber {
+ return nil, errors.New("Tried to sum non-number value")
}
+ total += float64(n)
}
- return notted, nil
+
+ return []walk.Value{walk.NumberValue(total)}, nil
}
-// Returns true if all values are equal, false if not
-func equalValues(values []walk.Value) ([]walk.Value, error) {
- if len(values) == 0 {
- return []walk.Value{walk.BoolValue(true)}, nil
- }
- first := values[0]
- for _, value := range values[1:] {
- // TODO: Refine the equality check
- if value != first {
- return []walk.Value{walk.BoolValue(false)}, nil
+func arithmeticProduct(values []walk.Value) ([]walk.Value, error) {
+ var product float64 = 0
+ for _, value := range values {
+ n, isNumber := value.(walk.NumberValue)
+ if !isNumber {
+ return nil, errors.New("Tried to sum non-number value")
}
+ product *= float64(n)
}
- return []walk.Value{walk.BoolValue(true)}, nil
+
+ return []walk.Value{walk.NumberValue(product)}, nil
}
diff --git a/subex/filter.go b/subex/filter.go
index dce0f0e..87c83a4 100644
--- a/subex/filter.go
+++ b/subex/filter.go
@@ -2,6 +2,8 @@ package subex
import (
"main/walk"
+ "math"
+ "fmt"
)
type valueFilter interface {
@@ -27,6 +29,26 @@ func (_ anyBoolFilter) valueFilter(value walk.Value) bool {
return isBool
}
+type simpleValueFilter struct {}
+func (_ simpleValueFilter) valueFilter(value walk.Value) bool {
+ switch value := value.(type) {
+ case walk.NullValue:
+ return true
+ case walk.BoolValue:
+ return true
+ case walk.NumberValue:
+ return true
+ case walk.StringValue:
+ return true
+ case walk.ArrayValue:
+ return len(value) == 0
+ case walk.MapValue:
+ return len(value) == 0
+ default:
+ panic("Invalid value type")
+ }
+}
+
type anyValueFilter struct {}
func (_ anyValueFilter) valueFilter(value walk.Value) bool {
return true
@@ -38,6 +60,12 @@ func (_ anyArrayFilter) valueFilter(value walk.Value) bool {
return isArray
}
+type anyMapFilter struct {}
+func (_ anyMapFilter) valueFilter(value walk.Value) bool {
+ _, isMap := value.(walk.MapValue)
+ return isMap
+}
+
type anyStringFilter struct {}
func (_ anyStringFilter) valueFilter(value walk.Value) bool {
_, isString := value.(walk.StringValue)
@@ -60,3 +88,172 @@ type selectRuneFilter struct {
func (f selectRuneFilter) runeFilter(r rune) bool {
return f.r == r
}
+
+type numberFilter interface {
+ add(m float64) numberFilter
+ multiply(m float64) numberFilter
+ numberFilter(n float64) bool
+}
+
+func (_ anyNumberFilter) numberFilter(n float64) bool {
+ return true
+}
+func (_ anyNumberFilter) add(m float64) numberFilter {
+ return anyNumberFilter{}
+}
+func (_ anyNumberFilter) multiply(m float64) numberFilter {
+ if m == 0.0 {
+ return equalNumberFilter {0.0}
+ } else {
+ return anyNumberFilter{}
+ }
+}
+func (_ anyNumberFilter) String() string {
+ return "r"
+}
+
+type divisibleNumberFilter struct {
+ divisor float64
+ target float64
+}
+func (d divisibleNumberFilter) numberFilter(n float64) bool {
+ mod := math.Mod(n, d.divisor)
+ if mod < 0 {
+ mod += d.divisor
+ }
+ return mod == d.target
+}
+func (d divisibleNumberFilter) add(m float64) numberFilter {
+ mod := math.Mod(m + d.target, d.divisor)
+ if mod < 0 {
+ mod += d.divisor
+ }
+ return divisibleNumberFilter {
+ divisor: d.divisor,
+ target: mod,
+ }
+}
+func (d divisibleNumberFilter) multiply(m float64) numberFilter {
+ if m == 0.0 {
+ return equalNumberFilter {0.0}
+ }
+
+ target := d.target
+ if m < 0 {
+ target = d.divisor - target
+ m = -m
+ }
+
+ return divisibleNumberFilter {
+ divisor: d.divisor * m,
+ target: target * m,
+ }
+}
+func (d divisibleNumberFilter) String() string {
+ return fmt.Sprintf("(x %% %v == %v)", d.divisor, d.target)
+}
+
+type andNumberFilter struct {
+ lhs, rhs numberFilter
+}
+func (a andNumberFilter) numberFilter(n float64) bool {
+ return a.lhs.numberFilter(n) && a.rhs.numberFilter(n)
+}
+func (a andNumberFilter) add(m float64) numberFilter {
+ return andNumberFilter {
+ lhs: a.lhs.add(m),
+ rhs: a.rhs.add(m),
+ }
+}
+func (a andNumberFilter) multiply(m float64) numberFilter {
+ return andNumberFilter {
+ lhs: a.lhs.multiply(m),
+ rhs: a.rhs.multiply(m),
+ }
+}
+func (a andNumberFilter) String() string {
+ return fmt.Sprintf("(%v && %v)", a.lhs, a.rhs)
+}
+
+type orNumberFilter struct {
+ lhs, rhs numberFilter
+}
+func (o orNumberFilter) numberFilter(n float64) bool {
+ return o.lhs.numberFilter(n) || o.rhs.numberFilter(n)
+}
+func (o orNumberFilter) String() string {
+ return fmt.Sprintf("(%v || %v)", o.lhs, o.rhs)
+}
+
+type notNumberFilter struct {
+ operand numberFilter
+}
+func (no notNumberFilter) numberFilter(n float64) bool {
+ return !no.operand.numberFilter(n)
+}
+func (no notNumberFilter) add(m float64) numberFilter {
+ return notNumberFilter {no.operand.add(m)}
+}
+func (no notNumberFilter) multiply(m float64) numberFilter {
+ return notNumberFilter {no.operand.multiply(m)}
+}
+func (no notNumberFilter) String() string {
+ return fmt.Sprintf("(!%v)", no.operand)
+}
+
+type lessThanNumberFilter struct {
+ rhs float64
+}
+func (l lessThanNumberFilter) numberFilter(n float64) bool {
+ return n < l.rhs
+}
+func (l lessThanNumberFilter) add(m float64) numberFilter {
+ return lessThanNumberFilter {l.rhs + m}
+}
+func (l lessThanNumberFilter) multiply(m float64) numberFilter {
+ if m > 0 {
+ return lessThanNumberFilter {l.rhs * m}
+ } else if m < 0 {
+ return greaterThanNumberFilter {l.rhs * m}
+ } else {
+ return equalNumberFilter {0}
+ }
+}
+func (l lessThanNumberFilter) String() string {
+ return fmt.Sprintf("(x < %v)", l.rhs)
+}
+
+type greaterThanNumberFilter struct {
+ rhs float64
+}
+func (g greaterThanNumberFilter) numberFilter(n float64) bool {
+ return n > g.rhs
+}
+func (g greaterThanNumberFilter) add(m float64) numberFilter {
+ return greaterThanNumberFilter {g.rhs + m}
+}
+func (g greaterThanNumberFilter) multiply(m float64) numberFilter {
+ if m > 0 {
+ return greaterThanNumberFilter {g.rhs * m}
+ } else if m < 0 {
+ return lessThanNumberFilter {g.rhs * m}
+ } else {
+ return equalNumberFilter {0}
+ }
+}
+func (g greaterThanNumberFilter) String() string {
+ return fmt.Sprintf("(x > %v)", g.rhs)
+}
+
+type equalNumberFilter struct {
+ rhs float64
+}
+func (e equalNumberFilter) numberFilter(n float64) bool {
+ return n == e.rhs
+}
+func (e equalNumberFilter) add(m float64) numberFilter {
+ return equalNumberFilter {e.rhs + m}
+}
+func (e equalNumberFilter) multiply(m float64) numberFilter {
+ return equalNumberFilter {e.rhs * m}
+}
diff --git a/subex/lex.go b/subex/lex.go
index 0f00a99..dfe89b7 100644
--- a/subex/lex.go
+++ b/subex/lex.go
@@ -22,6 +22,9 @@ func (l *StringRuneReader) Next() rune {
func (l *StringRuneReader) Rewind() {
l.pos -= l.width
}
+func (l *StringRuneReader) RewindRune(r rune) {
+ l.pos -= utf8.RuneLen(r)
+}
func NewStringRuneReader(input string) RuneReader {
return &StringRuneReader {
diff --git a/subex/main.go b/subex/main.go
index 86a8d41..d4cacb9 100644
--- a/subex/main.go
+++ b/subex/main.go
@@ -88,7 +88,7 @@ func CompileTransducer(transducerAst SubexAST) Transducer {
slotMap := SlotMap{
next: NextSlotIds{
values: 0,
- runes: 0,
+ runes: 0,
},
ids: make(map[rune]SlotId),
}
@@ -150,7 +150,7 @@ type auxiliaryState struct {
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
+ nesting []bool
}
func (aux auxiliaryState) cloneStore() auxiliaryState {
@@ -204,16 +204,6 @@ func (aux auxiliaryState) topAppendRune(runes []rune) auxiliaryState {
return aux
}
-func (aux auxiliaryState) incNest() auxiliaryState {
- aux.nesting++
- return aux
-}
-
-func (aux auxiliaryState) decNest() auxiliaryState {
- aux.nesting--
- return aux
-}
-
type SubexBranch struct {
state SubexState
aux auxiliaryState
@@ -236,8 +226,15 @@ func (pair SubexEatBranch) accepting() []OutputStack {
}
func equalStates(left SubexEatBranch, right SubexEatBranch) bool {
- // Only care about if they are the same pointer
- return left.state == right.state && left.aux.nesting == right.aux.nesting
+ if left.state != right.state || len(left.aux.nesting) != len(right.aux.nesting) {
+ return false
+ }
+ for i, l := range left.aux.nesting {
+ if l != right.aux.nesting[i] {
+ return false
+ }
+ }
+ return true
}
// If two branches have the same state, only the first has a chance of being successful
@@ -257,29 +254,36 @@ outer:
return states[:uniqueStates]
}
-func addStates(curStates []SubexEatBranch, newStates []SubexBranch) []SubexEatBranch {
+func addStates(curStates []SubexEatBranch, newStates []SubexBranch, nesting []bool) []SubexEatBranch {
for _, state := range newStates {
switch s := state.state.(type) {
case SubexEpsilonState:
- curStates = addStates(curStates, s.epsilon(state.aux))
+ curStates = addStates(curStates, s.epsilon(state.aux), nesting)
case SubexEatState:
curStates = append(curStates, SubexEatBranch{
state: s,
aux: state.aux,
})
+ default:
+ panic("Invalid type of state")
}
}
return curStates
}
-func processInput(states []SubexEatBranch, input walk.Edible, nesting int) []SubexEatBranch {
+func processInput(states []SubexEatBranch, input walk.Edible, nesting []bool) []SubexEatBranch {
newStates := make([]SubexEatBranch, 0, 2)
for _, state := range states {
- // TODO: What if nesting is changed by an epsilon state?
- if state.aux.nesting == nesting {
- newStates = addStates(newStates, state.eat(input))
- } else if state.aux.nesting < nesting {
+ if len(state.aux.nesting) > len(nesting) {
+ continue
+ }
+
+ if (len(state.aux.nesting) == len(nesting) &&
+ (len(state.aux.nesting) == 0 || len(nesting) == 0 ||
+ state.aux.nesting[len(nesting) - 1] || nesting[len(nesting) - 1])) {
+ newStates = addStates(newStates, state.eat(input), nesting)
+ } else {
newStates = append(newStates, state)
}
}
@@ -287,21 +291,21 @@ func processInput(states []SubexEatBranch, input walk.Edible, nesting int) []Sub
switch input := input.(type) {
case walk.StringValue:
for _, r := range input {
- newStates = processInput(newStates, walk.RuneEdible(r), nesting+1)
+ newStates = processInput(newStates, walk.RuneEdible(r), append(nesting, true))
}
- newStates = processInput(newStates, walk.StringEnd, nesting+1)
+ newStates = processInput(newStates, walk.StringEnd, append(nesting, true))
case walk.ArrayValue:
for _, el := range input {
- newStates = processInput(newStates, walk.NumberValue(el.Index), nesting+1)
- newStates = processInput(newStates, el.Value, nesting+1)
+ newStates = processInput(newStates, walk.NumberValue(el.Index), append(nesting, false))
+ newStates = processInput(newStates, el.Value, append(nesting, true))
}
- newStates = processInput(newStates, walk.ArrayEnd, nesting+1)
+ newStates = processInput(newStates, walk.ArrayEnd, append(nesting, true))
case walk.MapValue:
for _, el := range input {
- newStates = processInput(newStates, walk.StringValue(el.Key), nesting+1)
- newStates = processInput(newStates, el.Value, nesting+1)
+ newStates = processInput(newStates, walk.StringValue(el.Key), append(nesting, false))
+ newStates = processInput(newStates, el.Value, append(nesting, true))
}
- newStates = processInput(newStates, walk.MapEnd, nesting+1)
+ newStates = processInput(newStates, walk.MapEnd, append(nesting, true))
}
newStates = pruneStates(newStates)
@@ -322,20 +326,20 @@ func RunTransducer(transducer Transducer, input []walk.Value) (output []walk.Val
values: make([][]walk.Value, transducer.storeSize.values),
runes: make([][]rune, transducer.storeSize.runes),
},
- nesting: 0,
+ nesting: nil,
},
- }})
+ }}, nil)
for _, value := range input {
if len(states) == 0 {
break
}
- states = processInput(states, value, 0)
+ states = processInput(states, value, nil)
}
for _, state := range states {
- if state.aux.nesting > 0 {
+ if len(state.aux.nesting) > 0 {
continue
}
acceptingStacks := state.accepting()
diff --git a/subex/main_test.go b/subex/main_test.go
index 78a62c4..938e5cb 100644
--- a/subex/main_test.go
+++ b/subex/main_test.go
@@ -36,7 +36,64 @@ func TestSubexMain(t *testing.T) {
tests := []test {
{
- subex: `..+`,
+ // Keep only 5
+ subex: `(5|(.>_))*`,
+ input: []walk.Value {
+ walk.NumberValue(0),
+ walk.NumberValue(1),
+ walk.NumberValue(2),
+ walk.NumberValue(3),
+ walk.NumberValue(4),
+ walk.NumberValue(5),
+ walk.NumberValue(9),
+ walk.NumberValue(10),
+ walk.NumberValue(11),
+ walk.NumberValue(2.5),
+ walk.NumberValue(7.0),
+ walk.NumberValue(-3),
+ },
+ expected: []walk.Value {
+ walk.NumberValue(5),
+ },
+ },
+ {
+ // Keep only odd numbers between 0 and 10
+ subex: `([0<=n&n<=10&n%2=1]|(.>_))*`,
+ input: []walk.Value {
+ walk.NumberValue(0),
+ walk.NumberValue(1),
+ walk.NumberValue(2),
+ walk.NumberValue(3),
+ walk.NumberValue(4),
+ walk.NumberValue(5),
+ walk.NumberValue(9),
+ walk.NumberValue(10),
+ walk.NumberValue(11),
+ walk.NumberValue(2.5),
+ walk.NumberValue(7.0),
+ walk.NumberValue(-3),
+ },
+ expected: []walk.Value {
+ walk.NumberValue(1),
+ walk.NumberValue(3),
+ walk.NumberValue(5),
+ walk.NumberValue(9),
+ walk.NumberValue(7),
+ },
+ },
+ {
+ // Collatz
+ subex: "[1]*[n%2=0:n,n/2]|[n%2=1:n,n*3+1]",
+ input: []walk.Value {
+ walk.NumberValue(32),
+ },
+ expected: []walk.Value {
+ walk.NumberValue(32),
+ walk.NumberValue(16),
+ },
+ },
+ {
+ subex: `(..)%+`,
input: []walk.Value {
walk.NumberValue(12),
walk.NumberValue(34),
@@ -61,7 +118,16 @@ func TestSubexMain(t *testing.T) {
},
},
{
- subex: `~(.$_(.{-0}))~`,
+ subex: `~(.)~`,
+ input: []walk.Value {
+ walk.StringValue("a"),
+ },
+ expected: []walk.Value {
+ walk.StringValue("a"),
+ },
+ },
+ {
+ subex: `~(.>_(.*))~`,
input: []walk.Value {
walk.StringValue("hello"),
},
@@ -70,7 +136,22 @@ func TestSubexMain(t *testing.T) {
},
},
{
- subex: "@(..$a`$a$a`{-0})@",
+ subex: `#(".".*)-`,
+ input: []walk.Value {
+ walk.MapValue {
+ {
+ Key: "a",
+ Value: walk.NullValue{},
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.StringValue("a"),
+ walk.NullValue{},
+ },
+ },
+ {
+ subex: "@(((..)%a<a)*)@",
input: []walk.Value {
walk.ArrayValue {
walk.ArrayElement {
@@ -182,9 +263,221 @@ func TestSubexMain(t *testing.T) {
},
},
},
+ {
+ subex: "-(`0`.)@",
+ input: []walk.Value {
+ walk.NumberValue(4),
+ },
+ expected: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.NumberValue(4),
+ },
+ },
+ },
+ },
+ {
+ subex: `@((.>_~(.{-0})-){-0})~`,
+ input: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.StringValue("ab"),
+ },
+ {
+ Index: 1,
+ Value: walk.StringValue("cd"),
+ },
+ {
+ Index: 2,
+ Value: walk.StringValue("efg"),
+ },
+ {
+ Index: 3,
+ Value: walk.StringValue(""),
+ },
+ {
+ Index: 4,
+ Value: walk.StringValue("hijklm"),
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.StringValue("abcdefghijklm"),
+ },
+ },
+ {
+ subex: ":(.)-",
+ input: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.NullValue{},
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.NullValue{},
+ },
+ },
+ {
+ subex: ":(.{-0}%+)-",
+ input: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.NumberValue(4),
+ },
+ {
+ Index: 1,
+ Value: walk.NumberValue(-123),
+ },
+ {
+ Index: 2,
+ Value: walk.NumberValue(124),
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.NumberValue(5),
+ },
+ },
+ {
+ subex: "~(-(.)~*):",
+ input: []walk.Value {
+ walk.StringValue("abc"),
+ },
+ expected: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.StringValue("a"),
+ },
+ {
+ Index: 0,
+ Value: walk.StringValue("b"),
+ },
+ {
+ Index: 0,
+ Value: walk.StringValue("c"),
+ },
+ },
+ },
+ },
+ {
+ subex: "#((..>_)*):",
+ input: []walk.Value {
+ walk.MapValue {
+ {
+ Key: "a",
+ Value: walk.NullValue{},
+ },
+ {
+ Key: "b",
+ Value: walk.NumberValue(4),
+ },
+ {
+ Key: "c",
+ Value: walk.StringValue("hello"),
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.StringValue("a"),
+ },
+ {
+ Index: 0,
+ Value: walk.StringValue("b"),
+ },
+ {
+ Index: 0,
+ Value: walk.StringValue("c"),
+ },
+ },
+ },
+ },
+ {
+ subex: ":((.`null`)*)#",
+ input: []walk.Value {
+ walk.ArrayValue {
+ {
+ Index: 0,
+ Value: walk.StringValue("a"),
+ },
+ {
+ Index: 1,
+ Value: walk.StringValue("b"),
+ },
+ {
+ Index: 2,
+ Value: walk.StringValue("c"),
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.MapValue {
+ {
+ Key: "a",
+ Value: walk.NullValue{},
+ },
+ {
+ Key: "b",
+ Value: walk.NullValue{},
+ },
+ {
+ Key: "c",
+ Value: walk.NullValue{},
+ },
+ },
+ },
+ },
+ {
+ subex: `#((".>_.*".)*)#`,
+ input: []walk.Value {
+ walk.MapValue {
+ {
+ Key: "hello",
+ Value: walk.NullValue{},
+ },
+ {
+ Key: "world",
+ Value: walk.NullValue{},
+ },
+ },
+ },
+ expected: []walk.Value {
+ walk.MapValue {
+ {
+ Key: "ello",
+ Value: walk.NullValue{},
+ },
+ {
+ Key: "orld",
+ Value: walk.NullValue{},
+ },
+ },
+ },
+ },
+ {
+ subex: ".*`\"hello\"`",
+ input: []walk.Value {
+ walk.NumberValue(1),
+ walk.NumberValue(2),
+ },
+ expected: []walk.Value {
+ walk.NumberValue(1),
+ walk.NumberValue(2),
+ walk.StringValue("hello"),
+ },
+ },
}
- for _, test := range tests {
+ for i, test := range tests {
+ t.Logf("Running test: %d", i)
lexer := NewStringRuneReader(test.subex)
ast := Parse(lexer)
transducer := CompileTransducer(ast)
@@ -201,3 +494,46 @@ func TestSubexMain(t *testing.T) {
}
}
}
+
+func doCollatzTest(t *testing.T, init int) {
+ input := []walk.Value {
+ walk.NumberValue(init),
+ }
+ last := init
+
+ lexer := NewStringRuneReader("[1]*([n%2=0:n,n/2]|[n%2=1&n>1:n,n*3+1])")
+ ast := Parse(lexer)
+ transducer := CompileTransducer(ast)
+
+ for last != 1 {
+ output, err := RunTransducer(transducer, input)
+
+ if err {
+ t.Errorf("Collatz rejected %v", input)
+ return
+ }
+
+ if last % 2 == 0 {
+ last = last / 2
+ } else {
+ last = last * 3 + 1
+ }
+
+ if !reflect.DeepEqual(append(input, walk.NumberValue(last)), output) {
+ t.Errorf("Collatz took input: %v and produced output %v", input, output)
+ return
+ }
+
+ input = output
+ }
+
+ output, err := RunTransducer(transducer, input)
+ if !err {
+ t.Errorf("Collatz accepted input %v. Produced output: %v", input, output)
+ }
+}
+
+func TestSubexCollatz(t *testing.T) {
+ doCollatzTest(t, 32)
+ doCollatzTest(t, 7)
+}
diff --git a/subex/numberexpr.go b/subex/numberexpr.go
new file mode 100644
index 0000000..3649305
--- /dev/null
+++ b/subex/numberexpr.go
@@ -0,0 +1,217 @@
+package subex
+
+import (
+ "fmt"
+ "math"
+)
+
+type NumberExpr interface {
+ String() string
+ Eval(float64) float64
+}
+
+type NumberExprVariable struct {}
+func (ev NumberExprVariable) String() string {
+ return "n"
+}
+func (ev NumberExprVariable) Eval(v float64) float64 {
+ return v
+}
+
+type NumberExprLiteral struct {
+ Value float64
+}
+func (el NumberExprLiteral) String() string {
+ return fmt.Sprintf("%v", el.Value)
+}
+func (el NumberExprLiteral) Eval(v float64) float64 {
+ return el.Value
+}
+
+type NumberExprAnd struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ea NumberExprAnd) String() string {
+ return fmt.Sprintf("(%v & %v)", ea.Left, ea.Right)
+}
+func (ea NumberExprAnd) Eval(v float64) float64 {
+ left := ea.Left.Eval(v)
+ if left != 0.0 {
+ return ea.Right.Eval(v)
+ } else {
+ return left
+ }
+}
+
+type NumberExprOr struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (eo NumberExprOr) String() string {
+ return fmt.Sprintf("(%v | %v)", eo.Left, eo.Right)
+}
+func (eo NumberExprOr) Eval(v float64) float64 {
+ left := eo.Left.Eval(v)
+ if left != 0.0 {
+ return left
+ } else {
+ return eo.Right.Eval(v)
+ }
+}
+
+type NumberExprNot struct {
+ Right NumberExpr
+}
+func (en NumberExprNot) String() string {
+ return fmt.Sprintf("(!%v)", en.Right)
+}
+func (en NumberExprNot) Eval(v float64) float64 {
+ inner := en.Right.Eval(v)
+ if inner == 0.0 {
+ return 1.0
+ }
+
+ return 0.0
+}
+
+type NumberExprEqual struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ee NumberExprEqual) String() string {
+ return fmt.Sprintf("(%v = %v)", ee.Left, ee.Right)
+}
+func (ee NumberExprEqual) Eval(v float64) float64 {
+ if ee.Left.Eval(v) == ee.Right.Eval(v) {
+ return 1.0
+ } else {
+ return 0.0
+ }
+}
+
+type NumberExprAtMost struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ea NumberExprAtMost) String() string {
+ return fmt.Sprintf("(%v <= %v)", ea.Left, ea.Right)
+}
+func (ea NumberExprAtMost) Eval(v float64) float64 {
+ if ea.Left.Eval(v) <= ea.Right.Eval(v) {
+ return 1.0
+ } else {
+ return 0.0
+ }
+}
+
+type NumberExprLessThan struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (el NumberExprLessThan) String() string {
+ return fmt.Sprintf("(%v < %v)", el.Left, el.Right)
+}
+func (el NumberExprLessThan) Eval(v float64) float64 {
+ if el.Left.Eval(v) < el.Right.Eval(v) {
+ return 1.0
+ } else {
+ return 0.0
+ }
+}
+
+type NumberExprGreaterThan struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (eg NumberExprGreaterThan) String() string {
+ return fmt.Sprintf("(%v > %v)", eg.Left, eg.Right)
+}
+func (eg NumberExprGreaterThan) Eval(v float64) float64 {
+ if eg.Left.Eval(v) > eg.Right.Eval(v) {
+ return 1.0
+ } else {
+ return 0.0
+ }
+}
+
+type NumberExprAtLeast struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ea NumberExprAtLeast) String() string {
+ return fmt.Sprintf("(%v >= %v)", ea.Left, ea.Right)
+}
+func (ea NumberExprAtLeast) Eval(v float64) float64 {
+ if ea.Left.Eval(v) >= ea.Right.Eval(v) {
+ return 1.0
+ } else {
+ return 0.0
+ }
+}
+
+type NumberExprAdd struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ea NumberExprAdd) String() string {
+ return fmt.Sprintf("(%v + %v)", ea.Left, ea.Right)
+}
+func (ea NumberExprAdd) Eval(v float64) float64 {
+ return ea.Left.Eval(v) + ea.Right.Eval(v)
+}
+
+type NumberExprSubtract struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (es NumberExprSubtract) String() string {
+ return fmt.Sprintf("(%v - %v)", es.Left, es.Right)
+}
+func (es NumberExprSubtract) Eval(v float64) float64 {
+ return es.Left.Eval(v) - es.Right.Eval(v)
+}
+
+type NumberExprMultiply struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (em NumberExprMultiply) String() string {
+ return fmt.Sprintf("(%v * %v)", em.Left, em.Right)
+}
+func (em NumberExprMultiply) Eval(v float64) float64 {
+ return em.Left.Eval(v) * em.Right.Eval(v)
+}
+
+type NumberExprDivide struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ed NumberExprDivide) String() string {
+ return fmt.Sprintf("(%v / %v)", ed.Left, ed.Right)
+}
+func (ed NumberExprDivide) Eval(v float64) float64 {
+ return ed.Left.Eval(v) / ed.Right.Eval(v)
+}
+
+type NumberExprMod struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (em NumberExprMod) String() string {
+ return fmt.Sprintf("(%v %% %v)", em.Left, em.Right)
+}
+func (em NumberExprMod) Eval(v float64) float64 {
+ return math.Mod(em.Left.Eval(v), em.Right.Eval(v))
+}
+
+type NumberExprExponent struct {
+ Left NumberExpr
+ Right NumberExpr
+}
+func (ee NumberExprExponent) String() string {
+ return fmt.Sprintf("(%v * %v)", ee.Left, ee.Right)
+}
+func (ee NumberExprExponent) Eval(v float64) float64 {
+ return ee.Left.Eval(v) * ee.Right.Eval(v)
+}
diff --git a/subex/parse.go b/subex/parse.go
index 9602a4b..01a747b 100644
--- a/subex/parse.go
+++ b/subex/parse.go
@@ -8,13 +8,62 @@ import (
type Type int
const (
- ValueType Type = iota
+ AnyType Type = iota
+ ValueType
RuneType
)
+func resolveTypes(t1 Type, t2 Type) Type {
+ if t1 == AnyType {
+ return t2
+ }
+
+ if t2 == AnyType {
+ return t1
+ }
+
+ if t1 == t2 {
+ return t1
+ }
+
+ panic("Types don't match in parser")
+}
+
+type Structure int
+const (
+ NoneStructure Structure = iota
+ StringStructure
+ ArrayStructure
+ ArrayValuesStructure
+ MapStructure
+)
+func (s Structure) String() string {
+ switch s {
+ case NoneStructure:
+ return "-"
+ case StringStructure:
+ return "~"
+ case ArrayStructure:
+ return "@"
+ case ArrayValuesStructure:
+ return ":"
+ case MapStructure:
+ return "#"
+ default:
+ panic("Invalid structure")
+ }
+}
+
+type DestructureMethod int
+const (
+ Normal DestructureMethod = iota
+ Iterate
+)
+
type RuneReader interface {
Next() rune
Rewind()
+ RewindRune(r rune)
}
func accept(l RuneReader, chars string) bool {
@@ -95,6 +144,166 @@ func parseInt(l RuneReader) (output int) {
return output
}
+// Parse a number literal in a number expression
+func parseNumberLiteral(l RuneReader) NumberExprLiteral {
+ var builder strings.Builder
+ for {
+ r := l.Next()
+ if !isNumericRune(r) {
+ l.Rewind()
+ break
+ }
+ builder.WriteRune(r)
+ }
+ numberString := builder.String()
+ number, err := strconv.ParseFloat(numberString, 64)
+ if err != nil {
+ panic("Invalid number literal")
+ }
+ return NumberExprLiteral {
+ Value: number,
+ }
+}
+
+// Parse a numeric expression
+func parseNumberExpression(l RuneReader, minPower int) NumberExpr {
+ var lhs NumberExpr
+ switch l.Next() {
+ case '(':
+ lhs = parseNumberExpression(l, 0)
+ if !accept(l, ")") {
+ panic("Missing closing )")
+ }
+ case 'n':
+ lhs = NumberExprVariable{}
+ case '-':
+ lhs = NumberExprLiteral{0}
+ l.Rewind()
+ case '!':
+ lhs = NumberExprNot {
+ Right: parseNumberExpression(l, 13),
+ }
+ default:
+ l.Rewind()
+ lhs = parseNumberLiteral(l)
+ }
+
+ loop: for {
+ r := l.Next()
+ switch {
+ case r == '|' && minPower <= 8:
+ lhs = NumberExprOr {
+ Left: lhs,
+ Right: parseNumberExpression(l, 9),
+ }
+ case r == '&' && minPower <= 10:
+ lhs = NumberExprAnd {
+ Left: lhs,
+ Right: parseNumberExpression(l, 11),
+ }
+ case r == '<' && minPower <= 20:
+ if accept(l, "=") {
+ lhs = NumberExprAtMost {
+ Left: lhs,
+ Right: parseNumberExpression(l, 21),
+ }
+ } else {
+ lhs = NumberExprLessThan {
+ Left: lhs,
+ Right: parseNumberExpression(l, 21),
+ }
+ }
+ case r == '>' && minPower <= 20:
+ if accept(l, "=") {
+ lhs = NumberExprAtLeast {
+ Left: lhs,
+ Right: parseNumberExpression(l, 21),
+ }
+ } else {
+ lhs = NumberExprGreaterThan {
+ Left: lhs,
+ Right: parseNumberExpression(l, 21),
+ }
+ }
+ case r == '=' && minPower <= 20:
+ lhs = NumberExprEqual {
+ Left: lhs,
+ Right: parseNumberExpression(l, 21),
+ }
+ case r == '~' && minPower <= 20:
+ lhs = NumberExprNot {
+ Right: NumberExprEqual {
+ Left: lhs,
+ Right: parseNumberExpression(l, 21),
+ },
+ }
+ case r == '+' && minPower <= 30:
+ lhs = NumberExprAdd {
+ Left: lhs,
+ Right: parseNumberExpression(l, 31),
+ }
+ case r == '-' && minPower <= 30:
+ lhs = NumberExprSubtract {
+ Left: lhs,
+ Right: parseNumberExpression(l, 31),
+ }
+ case r == '*' && minPower <= 36:
+ lhs = NumberExprMultiply {
+ Left: lhs,
+ Right: parseNumberExpression(l, 37),
+ }
+ case r == '/' && minPower <= 36:
+ lhs = NumberExprDivide {
+ Left: lhs,
+ Right: parseNumberExpression(l, 37),
+ }
+ case r == '%' && minPower <= 36:
+ lhs = NumberExprMod {
+ Left: lhs,
+ Right: parseNumberExpression(l, 37),
+ }
+ case r == '^' && minPower <= 40:
+ lhs = NumberExprExponent {
+ Left: lhs,
+ Right: parseNumberExpression(l, 41),
+ }
+ default:
+ l.Rewind()
+ break loop
+ }
+ }
+
+ return lhs
+}
+
+// Having just read a [ in a value subex, parse the number mapping contents up
+// to but not including the closing ]
+func parseNumberMapping(l RuneReader) SubexAST {
+ numRange := parseNumberExpression(l, 0)
+ var numReplace []NumberExpr
+ if accept(l, ":") {
+ if !accept(l, "]") {
+ for {
+ numReplace = append(
+ numReplace,
+ parseNumberExpression(l, 0),
+ )
+ if !accept(l, ",") {
+ break
+ }
+ }
+ } else {
+ l.Rewind()
+ }
+ } else {
+ numReplace = []NumberExpr{NumberExprVariable{}}
+ }
+ return SubexASTNumberMapping {
+ Range: numRange,
+ Replace: numReplace,
+ }
+}
+
// Having just read {, read in and parse the range contents
func parseRepeatRange(l RuneReader) (output []ConvexRange) {
loop: for {
@@ -139,7 +348,8 @@ func parseRepeatRange(l RuneReader) (output []ConvexRange) {
return output
}
-func parseValueReplacement(l RuneReader) (output []OutputValueAST) {
+func parseValueReplacementOLD(l RuneReader, end rune) (output SubexAST) {
+ output = SubexASTEmpty{}
// TODO escaping
// TODO add arrays, maps and strings
loop: for {
@@ -148,49 +358,214 @@ func parseValueReplacement(l RuneReader) (output []OutputValueAST) {
case eof:
panic("Missing closing `")
case ' ':
- case '`':
+ case end:
break loop
case '$':
slot := l.Next()
if slot == eof {
panic("Missing slot character")
}
- output = append(output, OutputValueLoadAST {slot: slot})
+ output = SubexASTConcat {
+ First: output,
+ Second: SubexASTOutputValueLoad {
+ slot: slot,
+ },
+ }
+ // TODO: destructures
+ case '#':
+ if !accept(l, "(") {
+ panic("Missing ( after #")
+ }
+ output = SubexASTConcat {
+ First: output,
+ Second: SubexASTDestructure {
+ Destructure: NoneStructure,
+ Structure: MapStructure,
+ Content: parseValueReplacementOLD(l, ')'),
+ },
+ }
+ if !accept(l, "#") {
+ panic("Missing # after )")
+ }
+ case '"':
+ output = SubexASTConcat {
+ First: output,
+ Second: SubexASTDestructure {
+ Destructure: NoneStructure,
+ Structure: StringStructure,
+ Content: parseRuneReplacement(l, '"'),
+ },
+ }
default:
l.Rewind()
scalar, ok := parseScalarLiteral(l)
if !ok {
panic("Invalid scalar literal")
}
- output = append(output, OutputValueLiteralAST {scalar})
+ output = SubexASTConcat {
+ First: output,
+ Second: SubexASTOutputValueLiteral {
+ literal: scalar,
+ },
+ }
}
}
return output
}
-func parseRuneReplacement(l RuneReader) (output []OutputRuneAST) {
+func parseRuneReplacement(l RuneReader, end rune) (output SubexAST) {
+ output = SubexASTEmpty{}
// TODO escaping
- // TODO add arrays, maps and strings
loop: for {
r := l.Next()
switch r {
case eof:
panic("Missing closing `")
- case '`':
+ case end:
break loop
- case '$':
+ case '<':
slot := l.Next()
if slot == eof {
panic("Missing slot character")
}
- output = append(output, OutputRuneLoadAST {slot: slot})
+ output = SubexASTConcat {
+ First: output,
+ Second: SubexASTOutputRuneLoad {
+ slot: slot,
+ },
+ }
default:
- output = append(output, OutputRuneLiteralAST {r})
+ output = SubexASTConcat {
+ First: output,
+ Second: SubexASTOutputRuneLiteral {
+ literal: r,
+ },
+ }
}
}
return output
}
+func parseValueReplacement(l RuneReader, end rune, minPower int) SubexAST {
+ // TODO: escaping probably
+ var lhs SubexAST
+ r := l.Next()
+ switch r {
+ case eof:
+ panic("Missing closing `")
+ case end:
+ l.Rewind()
+ return SubexASTEmpty{}
+ case 'n':
+ if !accept(l, "u") {
+ panic("Expected null")
+ }
+ if !accept(l, "l") {
+ panic("Expected null")
+ }
+ if !accept(l, "l") {
+ panic("Expected null")
+ }
+ lhs = SubexASTOutputValueLiteral {
+ literal: walk.NullValue{},
+ }
+ // TODO: everything except numbers, strings, maps, and null
+ case '"':
+ lhs = SubexASTDestructure {
+ Destructure: NoneStructure,
+ Structure: StringStructure,
+ Content: parseRuneReplacement(l, '"'),
+ }
+ case '#':
+ if !accept(l, "(") {
+ panic("Missing ( after #")
+ }
+ lhs = SubexASTDestructure {
+ Destructure: NoneStructure,
+ Structure: MapStructure,
+ Content: parseValueReplacement(l, ')', 0),
+ }
+ if !accept(l, ")") {
+ panic("Missing closing )")
+ }
+ if !accept(l, "#") {
+ panic("Missing # after )")
+ }
+ case '<':
+ slot := l.Next()
+ if slot == eof {
+ panic("Missing slot character")
+ }
+ lhs = SubexASTOutputValueLoad {
+ slot: slot,
+ }
+ default:
+ if !isNumericRune(r) {
+ panic("Invalid character in numeric")
+ }
+
+ var builder strings.Builder
+ builder.WriteRune(r)
+ for {
+ r := l.Next()
+ if !isNumericRune(r) {
+ l.Rewind()
+ break
+ }
+ builder.WriteRune(r)
+ }
+ numberString := builder.String()
+ number, err := strconv.ParseFloat(numberString, 64)
+ if err != nil {
+ panic("Invalid number literal")
+ }
+
+ lhs = SubexASTOutputValueLiteral {
+ literal: walk.NumberValue(number),
+ }
+ }
+
+ loop: for {
+ r := l.Next()
+ switch {
+ case r == eof:
+ panic("Missing closing `")
+ case r == '+' && minPower <= 10:
+ lhs = SubexASTBinop {
+ op: binopAdd,
+ lhs: lhs,
+ rhs: parseValueReplacement(l, end, 11),
+ }
+ case r == '*' && minPower <= 20:
+ lhs = SubexASTBinop {
+ op: binopMultiply,
+ lhs: lhs,
+ rhs: parseValueReplacement(l, end, 21),
+ }
+ case r == '/' && minPower <= 20:
+ lhs = SubexASTBinop {
+ op: binopDivide,
+ lhs: lhs,
+ rhs: parseValueReplacement(l, end, 21),
+ }
+ case r == end:
+ l.Rewind()
+ break loop
+ case minPower <= 2:
+ l.Rewind()
+ lhs = SubexASTConcat {
+ First: lhs,
+ Second: parseValueReplacement(l, end, 3),
+ }
+ default:
+ l.Rewind()
+ break loop
+ }
+ }
+
+ return lhs
+}
+
// Parse the contents of a range subex [] into a map
// func parseRangeSubex(l RuneReader) map[walk.AtomOLD]walk.AtomOLD {
// // TODO escaping
@@ -270,166 +645,491 @@ func parseRuneReplacement(l RuneReader) (output []OutputRuneAST) {
// return parts
// }
-func parseSubex(l RuneReader, minPower int, inType Type, outType Type) SubexAST {
- var lhs SubexAST
+func parseDestructure(l RuneReader, destructure Structure, inType Type) (lhs SubexAST, outType Type) {
+ var method rune
+ switch l.Next() {
+ case '(':
+ method = ')'
+ case '[':
+ method = ']'
+ default:
+ panic("Missing ( or [ after destructure start")
+ }
+
+ var innerInType Type
+ var expectedInType Type
+ switch destructure {
+ case NoneStructure:
+ innerInType = inType
+ expectedInType = inType
+ case StringStructure:
+ innerInType = RuneType
+ expectedInType = ValueType
+ case ArrayStructure:
+ innerInType = ValueType
+ expectedInType = ValueType
+ case ArrayValuesStructure:
+ innerInType = ValueType
+ expectedInType = ValueType
+ case MapStructure:
+ innerInType = ValueType
+ expectedInType = ValueType
+ default:
+ panic("Invalid structure")
+ }
+
+ resolveTypes(inType, expectedInType)
+
+ lhs, innerOutType := parseSubex(l, 0, innerInType)
+ if !accept(l, string(method)) {
+ panic("Missing matching ) or ]")
+ }
+
+ switch method {
+ case ')':
+ case ']':
+ lhs = SubexASTRepeat {
+ Content: lhs,
+ Acceptable: []ConvexRange{{
+ Start: -1,
+ End: 0,
+ }},
+ }
+ default:
+ panic("Invalid method")
+ }
+
+ var structure Structure
+ var expectedInnerOutType Type
r := l.Next()
switch r {
- case eof:
- return nil
- case '(':
- lhs = parseSubex(l, 0, inType, outType)
- if !accept(l, ")") {
- panic("Missing matching )")
- }
- case '~':
- if !accept(l, "(") {
- panic("Missing ( after ~")
- }
- lhs = parseSubex(l, 0, RuneType, RuneType)
- if !accept(l, ")") {
- panic("Missing matching )")
- }
- if !accept(l, "~") {
- panic("Missing matching ~")
+ case '-':
+ structure = NoneStructure
+ expectedInnerOutType = innerOutType
+ case '~':
+ structure = StringStructure
+ expectedInnerOutType = RuneType
+ case '@':
+ structure = ArrayStructure
+ expectedInnerOutType = ValueType
+ case ':':
+ structure = ArrayValuesStructure
+ expectedInnerOutType = ValueType
+ case '#':
+ structure = MapStructure
+ expectedInnerOutType = ValueType
+ default:
+ panic("Missing matching destructure")
+ }
+
+ innerOutType = resolveTypes(innerOutType, expectedInnerOutType)
+
+ switch structure {
+ case NoneStructure:
+ outType = innerOutType
+ case StringStructure:
+ outType = ValueType
+ case ArrayStructure:
+ outType = ValueType
+ case ArrayValuesStructure:
+ outType = ValueType
+ case MapStructure:
+ outType = ValueType
+ }
+
+ lhs = SubexASTDestructure {
+ Destructure: destructure,
+ Structure: structure,
+ Content: lhs,
+ }
+
+ return lhs, outType
+}
+
+func parseSubex(l RuneReader, minPower int, inType Type) (lhs SubexAST, outType Type) {
+ start:
+ r := l.Next()
+ switch r {
+ case eof:
+ return nil, inType
+ case '(':
+ lhs, outType = parseSubex(l, 0, inType)
+ if !accept(l, ")") {
+ panic("Missing matching )")
+ }
+ case '-':
+ lhs, outType = parseDestructure(l, NoneStructure, inType)
+ case '~':
+ lhs, outType = parseDestructure(l, StringStructure, inType)
+ case '@':
+ lhs, outType = parseDestructure(l, ArrayStructure, inType)
+ case ':':
+ lhs, outType = parseDestructure(l, ArrayValuesStructure, inType)
+ case '#':
+ lhs, outType = parseDestructure(l, MapStructure, inType)
+ case '"':
+ switch inType {
+ case ValueType:
+ var innerOutType Type
+ lhs, innerOutType = parseSubex(l, 0, RuneType)
+ if !accept(l, "\"") {
+ panic("Missing matching \"")
}
- lhs = SubexASTEnterString {lhs}
- case '@':
- if !accept(l, "(") {
- panic("Missing ( after @")
+ resolveTypes(innerOutType, RuneType)
+ lhs = SubexASTDestructure {
+ Destructure: StringStructure,
+ Structure: StringStructure,
+ Content: lhs,
}
- lhs = parseSubex(l, 0, ValueType, ValueType)
- if !accept(l, ")") {
- panic("Missing matching )")
+ outType = ValueType
+ // RuneType
+ default:
+ l.Rewind()
+ return SubexASTEmpty{}, inType
+ }
+ case '<':
+ slot := l.Next()
+ switch slot {
+ case eof:
+ panic("Missing slot")
+ case '>':
+ panic("Parsing error. Tried to parse <> as a subex with nothing before it")
+ default:
+ switch inType {
+ case ValueType:
+ lhs = SubexASTOutputValueLoad {
+ slot: slot,
+ }
+ case RuneType:
+ lhs = SubexASTOutputRuneLoad {
+ slot: slot,
+ }
+ default:
+ panic("Invalid inType")
}
- if !accept(l, "@") {
- panic("Missing matching ~")
+ }
+ case '[':
+ switch inType {
+ case ValueType:
+ lhs = parseNumberMapping(l)
+ if !accept(l, "]") {
+ panic("Missing matching ]")
}
- lhs = SubexASTEnterArray {lhs}
- // TODO
- // case '[':
- // rangeParts := parseRangeSubex(l)
- // lhs = SubexASTRange {rangeParts}
- case ')', ']', '"', '|', ';', '{', '+', '-', '*', '/', '!', '=', '$':
- l.Rewind()
- return SubexASTEmpty{}
- // case '=':
- // replacement := parseReplacement(l)
- // lhs = SubexASTOutput{replacement}
- // case '^':
- // replacement := parseReplacement(l)
- // replacement = append(
- // []OutputContentAST{OutputValueLiteralAST {walk.NewAtomStringTerminal()}},
- // replacement...
- // )
- // replacement = append(
- // replacement,
- // OutputValueLiteralAST {walk.NewAtomStringTerminal()},
- // )
- // lhs = SubexASTOutput {replacement}
- case '.':
- if inType != outType {
- panic("Copying value changes type!")
- }
- if inType == RuneType {
- lhs = SubexASTCopyAnyRune{}
- } else {
- lhs = SubexASTCopyAnyValue{}
- }
- case '?':
- lhs = SubexASTCopyBool{}
- case '%':
- lhs = SubexASTCopyNumber{}
- case '`':
- lhs = SubexASTOutputValues {parseValueReplacement(l)}
- // TODO
- // case '_':
- // lhs = SubexASTCopyStringAtom{}
- // case '#':
- // lhs = SubexASTCopyString{}
- // case ',':
- // lhs = SubexASTCopyValue{}
- // case '"':
- // lhs = SubexASTCopyScalar {walk.NewAtomStringTerminal()}
default:
- if inType != outType {
- panic("inType and outType don't match in copy")
+ // TODO: other types
+ panic("[] is only valid for values currently")
+ }
+ case ')', ']', '|', '{', '+', '*':
+ l.Rewind()
+ return SubexASTEmpty{}, inType
+ case '.':
+ outType = inType
+ if inType == RuneType {
+ lhs = SubexASTCopyAnyRune{}
+ } else {
+ lhs = SubexASTCopyAnyValue{}
+ }
+ case ',':
+ switch inType {
+ case ValueType:
+ outType = inType
+ lhs = SubexASTCopyAnySimpleValue{}
+ case RuneType:
+ outType = inType
+ lhs = SubexASTCopyRune{','}
+ default:
+ panic("Invalid inType")
+ }
+ case '?':
+ outType = inType
+ lhs = SubexASTCopyBool{}
+ case '`':
+ outType = inType
+ switch inType {
+ case ValueType:
+ lhs = parseValueReplacement(l, '`', 0)
+ if !accept(l, "`") {
+ panic("Missing closing `")
}
- if inType == RuneType {
- lhs = SubexASTCopyRune {r}
- } else {
- l.Rewind()
- scalar, ok := parseScalarLiteral(l)
- if !ok {
- panic("Invalid subex")
- }
- lhs = SubexASTCopyScalar {scalar}
+ case RuneType:
+ lhs = parseRuneReplacement(l, '`')
+ default:
+ panic("Invalid inType")
+ }
+ case ' ':
+ switch inType {
+ case RuneType:
+ outType = RuneType
+ lhs = SubexASTCopyRune {' '}
+ case ValueType:
+ goto start
+ }
+ default:
+ outType = inType
+ switch inType {
+ case RuneType:
+ lhs = SubexASTCopyRune {r}
+ // ValueType, NumberType
+ case ValueType:
+ l.Rewind()
+ scalar, ok := parseScalarLiteral(l)
+ if !ok {
+ panic("Invalid subex")
}
+ lhs = SubexASTCopyScalar {scalar}
+ }
}
loop: for {
- if minPower <= 20 {
- next := parseSubex(l, 21, inType, outType)
- if next != nil && (next != SubexASTEmpty{}) {
- lhs = SubexASTConcat{lhs, next}
- continue loop
- }
- }
r := l.Next()
switch {
- case r == '{' && minPower <= 4:
- lhs = SubexASTRepeat {
+ case r == eof:
+ break loop
+ case r == '{' && minPower <= 10:
+ lhs = SubexASTRepeat {
+ Content: lhs,
+ Acceptable: parseRepeatRange(l),
+ }
+ case r == '+' && minPower <= 10:
+ lhs = SubexASTRepeat {
+ Content: lhs,
+ Acceptable: []ConvexRange {{
+ Start: -1,
+ End: 1,
+ }},
+ }
+ case r == '*' && minPower <= 10:
+ lhs = SubexASTRepeat {
+ Content: lhs,
+ Acceptable: []ConvexRange {{
+ Start: -1,
+ End: 0,
+ }},
+ }
+ case r == '_' && minPower <= 10:
+ switch inType {
+ case ValueType:
+ lhs = SubexASTDiscard {
Content: lhs,
- Acceptable: parseRepeatRange(l),
+ InnerOutType: outType,
+ }
+ outType = AnyType
+ case RuneType:
+ // Just a concat
+ lhs = SubexASTConcat {
+ lhs,
+ SubexASTCopyRune {
+ rune: '_',
+ },
+ }
+ outType = AnyType
+ default:
+ panic("Invalid inType")
+ }
+ case r == '%' && minPower <= 10:
+ slot := l.Next()
+ switch slot {
+ case eof:
+ panic("Missing slot character")
+ case '<', '>':
+ panic("Invalid character after %")
+ case '_':
+ panic("Cannot load from _")
+ default:
+ switch inType {
+ case ValueType:
+ lhs = SubexASTConcat {
+ First: SubexASTStoreValues {
+ Match: lhs,
+ Slot: slot,
+ },
+ Second: SubexASTOutputValueLoad {
+ slot: slot,
+ },
+ }
+ case RuneType:
+ lhs = SubexASTConcat {
+ First: SubexASTStoreRunes {
+ Match: lhs,
+ Slot: slot,
+ },
+ Second: SubexASTOutputRuneLoad {
+ slot: slot,
+ },
+ }
+ default:
+ panic("Invalid inType")
+ }
+ }
+ case r == '>' && minPower <= 10:
+ slot := l.Next()
+ switch slot {
+ case eof:
+ panic("Missing slot character")
+ case '>':
+ slot := l.Next()
+ switch slot {
+ case eof:
+ panic("Missing slot character")
+ case '_':
+ lhs = SubexASTDiscard {
+ Content: lhs,
+ InnerOutType: outType,
+ }
+ outType = AnyType
+ default:
+ switch inType {
+ case ValueType:
+ lhs = SubexASTAppendStoreValues {
+ Match: lhs,
+ Slot: slot,
+ }
+ case RuneType:
+ lhs = SubexASTAppendStoreRunes {
+ Match: lhs,
+ Slot: slot,
+ }
+ default:
+ panic("Invalid inType")
+ }
+ outType = AnyType
}
- case r == '+' && minPower <= 4:
- lhs = SubexASTSum {lhs}
- case r == '*' && minPower <= 4:
- lhs = SubexASTProduct {lhs}
- case r == '-' && minPower <= 4:
- lhs = SubexASTNegate {lhs}
- // case r == '/' && minPower <= 4:
- // lhs = SubexASTReciprocal {lhs}
- case r == '!' && minPower <= 4:
- lhs = SubexASTNot {lhs}
- // case r == '=' && minPower <= 4:
- // lhs = SubexASTEqual {lhs}
- case r == '$' && minPower <= 4:
+ case '<':
slot := l.Next()
- if slot == eof {
+ switch slot {
+ case eof:
panic("Missing slot character")
+ case '_':
+ panic("Cannot load from _ slot")
+ default:
+ switch inType {
+ case ValueType:
+ lhs = SubexASTConcat {
+ First: SubexASTStoreValues {
+ Match: lhs,
+ Slot: slot,
+ },
+ Second: SubexASTOutputValueLoad {
+ slot: slot,
+ },
+ }
+ case RuneType:
+ lhs = SubexASTConcat {
+ First: SubexASTStoreRunes {
+ Match: lhs,
+ Slot: slot,
+ },
+ Second: SubexASTOutputRuneLoad {
+ slot: slot,
+ },
+ }
+ default:
+ panic("Invalid inType")
+ }
+ outType = inType
}
- if slot == '_' {
- lhs = SubexASTDiscard {lhs}
- } else {
+ case '_':
+ lhs = SubexASTDiscard {
+ Content: lhs,
+ InnerOutType: outType,
+ }
+ outType = AnyType
+ default:
+ switch inType {
+ case ValueType:
lhs = SubexASTStoreValues {
Match: lhs,
Slot: slot,
}
+ case RuneType:
+ lhs = SubexASTStoreRunes {
+ Match: lhs,
+ Slot: slot,
+ }
+ default:
+ panic("Invalid type")
}
- case r == '|' && minPower <= 8:
- rhs := parseSubex(l, 9, inType, outType)
- if rhs == nil {
- panic("Missing subex after |")
- }
- lhs = SubexASTOr{lhs, rhs}
- /*case r == ';' && minPower <= 10:
- rhs := parseSubex(l, 11, inType, outType)
- if rhs == nil {
- panic("Missing subex after ;")
+ outType = AnyType
+ }
+ case r == '<' && minPower <= 6:
+ slot := l.Next()
+ switch slot {
+ case eof:
+ panic("Missing slot character")
+ case '_':
+ panic("Cannot load from _ slot")
+ case '>':
+ slot := l.Next()
+ switch slot {
+ case eof:
+ panic("Missing slot character")
+ case '_':
+ panic("Cannot load from _ slot")
+ default:
+ switch inType {
+ case ValueType:
+ lhs = SubexASTConcat {
+ SubexASTOutputValueLoad {
+ slot: slot,
+ },
+ SubexASTStoreValues {
+ Match: lhs,
+ Slot: slot,
+ },
+ }
+ case RuneType:
+ lhs = SubexASTConcat {
+ SubexASTOutputRuneLoad {
+ slot: slot,
+ },
+ SubexASTStoreRunes {
+ Match: lhs,
+ Slot: slot,
+ },
+ }
+ default:
+ panic("Invalid inType")
+ }
}
- lhs = SubexASTJoin {
- Content: lhs,
- Delimiter: rhs,
- }*/
default:
+ // This is just a concat
l.Rewind()
+ l.RewindRune('<')
+ next, outType2 := parseSubex(l, 7, inType)
+ // TODO: next might legitimately be SubexASTEmpty, e.g. ``
+ if next != nil && (next != SubexASTEmpty{}) {
+ outType = resolveTypes(outType, outType2)
+ lhs = SubexASTConcat{lhs, next}
+ continue loop
+ }
+ }
+ case r == '|' && minPower <= 2:
+ rhs, outType2 := parseSubex(l, 3, inType)
+ outType = resolveTypes(outType, outType2)
+ if rhs == nil {
+ panic("Missing subex after |")
+ }
+ lhs = SubexASTOr{lhs, rhs}
+ case minPower <= 6:
+ l.Rewind()
+ next, outType2 := parseSubex(l, 7, inType)
+ // TODO: next might legitimately be SubexASTEmpty, e.g. ``
+ if next != nil && (next != SubexASTEmpty{}) {
+ outType = resolveTypes(outType, outType2)
+ lhs = SubexASTConcat{lhs, next}
+ } else {
break loop
+ }
+ default:
+ l.Rewind()
+ break loop
}
}
- return lhs
+ return lhs, outType
}
func Parse(l RuneReader) SubexAST {
- ast := parseSubex(l, 0, ValueType, ValueType)
+ ast, outType := parseSubex(l, 0, ValueType)
+ outType = resolveTypes(outType, ValueType)
if ast == nil {
return SubexASTEmpty{}
}
diff --git a/subex/subexast.go b/subex/subexast.go
index cef853b..01a5e0d 100644
--- a/subex/subexast.go
+++ b/subex/subexast.go
@@ -32,21 +32,99 @@ type SubexASTStoreValues struct {
Slot rune
}
func (ast SubexASTStoreValues) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if inType != ValueType {
+ panic("Invalid inType storing to value slot")
+ }
id := slotMap.getId(ast.Slot)
- newNext := ast.Match.compileWith(&SubexStoreEndState {
+ var endState SubexState = &SubexStoreEndState {
slot: id,
next: next,
- }, slotMap, inType, ValueType)
+ }
+ switch ast.Slot {
+ case '+':
+ endState = &SubexCaptureBeginState {
+ next: ast.Match.compileWith(&SubexArithmeticEndState {
+ calculate: arithmeticSum,
+ next: endState,
+ }, slotMap, inType, outType),
+ }
+ case '*':
+ endState = &SubexCaptureBeginState {
+ next: ast.Match.compileWith(&SubexArithmeticEndState {
+ calculate: arithmeticProduct,
+ next: endState,
+ }, slotMap, inType, outType),
+ }
+ default:
+ endState = ast.Match.compileWith(endState, slotMap, inType, outType)
+ }
return &SubexCaptureBeginState {
- next: newNext,
+ next: endState,
}
}
func (ast SubexASTStoreValues) String() string {
return fmt.Sprintf("$%c(%v)", ast.Slot, ast.Match)
}
+type SubexASTAppendStoreValues struct {
+ Match SubexAST
+ Slot rune
+}
+func (ast SubexASTAppendStoreValues) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ id := slotMap.getId(ast.Slot)
+ newNext := ast.Match.compileWith(&SubexStoreEndState {
+ slot: id,
+ next: next,
+ }, slotMap, inType, ValueType)
+
+ return &SubexOutputValueLoadState {
+ slot: id,
+ next: &SubexCaptureBeginState {
+ next: newNext,
+ },
+ }
+}
+
+type SubexASTStoreRunes struct {
+ Match SubexAST
+ Slot rune
+}
+func (ast SubexASTStoreRunes) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ id := slotMap.getRuneId(ast.Slot)
+ newNext := ast.Match.compileWith(&SubexStoreRunesEndState {
+ slot: id,
+ next: next,
+ }, slotMap, inType, RuneType)
+
+ return &SubexCaptureRunesBeginState {
+ next: newNext,
+ }
+}
+func (ast SubexASTStoreRunes) String() string {
+ return fmt.Sprintf("(%v)$%c", ast.Match, ast.Slot)
+}
+
// Try to run the first subex, if it fails then backtrack and use the second
+type SubexASTAppendStoreRunes struct {
+ Match SubexAST
+ Slot rune
+}
+func (ast SubexASTAppendStoreRunes) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ id := slotMap.getId(ast.Slot)
+ newNext := ast.Match.compileWith(&SubexStoreEndState {
+ slot: id,
+ next: next,
+ }, slotMap, inType, RuneType)
+
+ return &SubexOutputRuneLoadState {
+ slot: id,
+ next: &SubexCaptureBeginState {
+ next: newNext,
+ },
+ }
+}
+
type SubexASTOr struct {
First, Second SubexAST
}
@@ -132,9 +210,6 @@ type SubexASTRepeat struct {
Acceptable []ConvexRange
}
func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- if inType != outType {
- panic("Invalid types")
- }
var state SubexState = &SubexDeadState{}
for _, convex := range ast.Acceptable {
state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap, inType, outType)}
@@ -151,7 +226,7 @@ type SubexASTCopyScalar struct {
}
func (ast SubexASTCopyScalar) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
+ panic("Invalid types for SubexASTCopyScalar")
}
return &SubexCopyState{
filter: selectScalarFilter {ast.Scalar},
@@ -165,7 +240,7 @@ func (ast SubexASTCopyScalar) String() string {
type SubexASTCopyAnyRune struct {}
func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
if inType != RuneType || outType != RuneType {
- panic("Invalid types for SubexASTNot")
+ panic("Invalid types for SubexASTCopyAnyRune")
}
return &SubexCopyRuneState {
next: next,
@@ -173,7 +248,7 @@ func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, in
}
}
func (ast SubexASTCopyAnyRune) String() string {
- return "."
+ return ".RUNE"
}
type SubexASTCopyRune struct {
@@ -181,19 +256,22 @@ type SubexASTCopyRune struct {
}
func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
if inType != RuneType || outType != RuneType {
- panic("Invalid types for SubexASTNot")
+ panic("Invalid types for SubexASTCopyRune")
}
return &SubexCopyRuneState {
next: next,
filter: selectRuneFilter {ast.rune},
}
}
+func (ast SubexASTCopyRune) String() string {
+ return string(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, inType Type, outType Type) SubexState {
if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
+ panic("Invalid types for SubexASTCopyBool")
}
return &SubexCopyState {
next: next,
@@ -208,7 +286,7 @@ func (ast SubexASTCopyBool) String() string {
type SubexASTCopyNumber struct {}
func (ast SubexASTCopyNumber) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
+ panic("Invalid types for SubexASTCopyNumber")
}
return &SubexCopyState {
next: next,
@@ -219,11 +297,29 @@ func (ast SubexASTCopyNumber) String() string {
return "%"
}
+type SubexASTNumberFilter interface {
+ compile() numberFilter
+ computable() bool
+ compute() float64
+}
+
+// Read in a null, bool, number, string or empty array or map and output it unchanged
+type SubexASTCopyAnySimpleValue struct {}
+func (ast SubexASTCopyAnySimpleValue) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if inType != ValueType || outType != ValueType {
+ panic("Invalid types for SubexASTCopyAnySimpleValue")
+ }
+ return &SubexCopyState {
+ next: next,
+ filter: simpleValueFilter{},
+ }
+}
+
// Read in any single Atom and output it unchanged
type SubexASTCopyAnyValue struct {}
func (ast SubexASTCopyAnyValue) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
+ panic("Invalid types for SubexASTCopyAnyValue")
}
return &SubexCopyState {
next: next,
@@ -279,64 +375,71 @@ func (ast SubexASTOutput) String() string {
}
*/
-type OutputValueAST interface {
- compile(slotMap *SlotMap) OutputValue
-}
-
-type OutputValueLoadAST struct {
- slot rune
+type SubexASTNumberMapping struct {
+ Range NumberExpr
+ Replace []NumberExpr
}
-func (ast OutputValueLoadAST) compile(slotMap *SlotMap) OutputValue {
- return OutputValueLoad {
- slotMap.getId(ast.slot),
+func (ast SubexASTNumberMapping) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if inType != ValueType || outType != ValueType {
+ panic("Invalid in/out type for SubexASTNumberMapping")
}
-}
-
-type OutputValueLiteralAST struct {
- scalar walk.Scalar
-}
-func (ast OutputValueLiteralAST) compile(slotMap *SlotMap) OutputValue {
- return OutputValueLiteral {
- ast.scalar,
+ return &SubexNumberMappingState {
+ Range: ast.Range,
+ Replace: ast.Replace,
+ next: next,
}
}
-type SubexASTOutputValues struct {
- Replacement []OutputValueAST
+type SubexASTOutputValueLiteral struct {
+ literal walk.Scalar
}
-func (ast SubexASTOutputValues) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+func (ast SubexASTOutputValueLiteral) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
if outType != ValueType {
- panic("Invalid outType")
- }
- var content []OutputValue
- for _, el := range ast.Replacement {
- content = append(content, el.compile(slotMap))
+ panic("Invalid outType for SubexASTOutputValueLiteral")
}
- return &SubexOutputValuesState {
- content: content,
+ return &SubexOutputValueLiteralState {
+ literal: ast.literal,
next: next,
}
}
-type OutputRuneAST interface {
- compile(slotMap *SlotMap) OutputRune
-}
-
-type OutputRuneLoadAST struct {
+type SubexASTOutputValueLoad struct {
slot rune
}
-func (ast OutputRuneLoadAST) compile(slotMap *SlotMap) OutputRune {
- return OutputRuneLoad {slotMap.getRuneId(ast.slot)}
+func (ast SubexASTOutputValueLoad) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if outType != ValueType {
+ panic("Invalid outType for SubexASTOutputValueLoad")
+ }
+ return &SubexOutputValueLoadState {
+ slot: slotMap.getId(ast.slot),
+ next: next,
+ }
}
-type OutputRuneLiteralAST struct {
- r rune
+type SubexASTOutputRuneLiteral struct {
+ literal rune
}
-func (ast OutputRuneLiteralAST) compile (slotMap *SlotMap) OutputRune {
- return OutputRuneLiteral {ast.r}
+func (ast SubexASTOutputRuneLiteral) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if outType != RuneType {
+ panic("Invalid outType for SubexASTOutputRuneLiteral")
+ }
+ return &SubexOutputRuneLiteralState {
+ literal: ast.literal,
+ next: next,
+ }
}
-type SubexASTOutputRunes struct {
+type SubexASTOutputRuneLoad struct {
+ slot rune
+}
+func (ast SubexASTOutputRuneLoad) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if outType != RuneType {
+ panic("Invalid outType for SubexASTOutputRuneLoad")
+ }
+ return &SubexOutputRuneLoadState {
+ slot: slotMap.getRuneId(ast.slot),
+ next: next,
+ }
}
// Run each input Atom through a map to produce an output Atom
@@ -354,85 +457,31 @@ type SubexASTOutputRunes struct {
// 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, inType Type, outType Type) SubexState {
- if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
- }
- return &SubexCaptureBeginState {
- next: ast.Content.compileWith(&SubexArithmeticEndState {
- next: next,
- calculate: sumValues,
- }, slotMap, inType, outType),
- }
-}
-func (ast SubexASTSum) String() string {
- return fmt.Sprintf("(%v)+", ast.Content)
-}
-
-// Like sum but for AND and product
-type SubexASTProduct struct {
- Content SubexAST
+type SubexASTBinop struct {
+ op func ([]walk.Value) ([]walk.Value, error)
+ lhs, rhs SubexAST
}
-func (ast SubexASTProduct) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
- }
- return &SubexCaptureBeginState {
- next: ast.Content.compileWith(&SubexArithmeticEndState {
- next: next,
- calculate: multiplyValues,
- }, slotMap, inType, outType),
- }
-}
-func (ast SubexASTProduct) String() string {
- return fmt.Sprintf("(%v)*", ast.Content)
-}
-
-// Runs the content Subex, if all outputted atoms can be cast to numbers, outputs them all negated
-// Rejects if this fails
-type SubexASTNegate struct {
- Content SubexAST
-}
-func (ast SubexASTNegate) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
- }
- return &SubexCaptureBeginState {
- next: ast.Content.compileWith(&SubexArithmeticEndState {
- next: next,
- calculate: negateValues,
- }, slotMap, inType, outType),
- }
-}
-func (ast SubexASTNegate) String() string {
- return fmt.Sprintf("(%v)-", ast.Content)
-}
-
-// Runs the content Subex and collects the output
-// Maps over the values in the output, casting each to a boolean, notting each and then outputs them
-// Rejects if it cannot cast to boolean
-type SubexASTNot struct {
- Content SubexAST
-}
-func (ast SubexASTNot) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTNot")
+func (ast SubexASTBinop) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if outType != ValueType {
+ panic("Invalid types for SubexASTBinop")
}
return &SubexCaptureBeginState {
- next: ast.Content.compileWith(&SubexArithmeticEndState {
- next: next,
- calculate: notValues,
- }, slotMap, ValueType, ValueType),
+ next: ast.lhs.compileWith(
+ ast.rhs.compileWith(
+ &SubexArithmeticEndState {
+ next: next,
+ calculate: ast.op,
+ },
+ slotMap,
+ inType,
+ outType,
+ ),
+ slotMap,
+ inType,
+ outType,
+ ),
}
}
-func (ast SubexASTNot) String() string {
- return fmt.Sprintf("(%v)!", ast.Content)
-}
// Does nothing
type SubexASTEmpty struct {}
@@ -446,9 +495,10 @@ func (ast SubexASTEmpty) String() string {
// Discards the output from the content subex
type SubexASTDiscard struct {
Content SubexAST
+ InnerOutType Type
}
func (ast SubexASTDiscard) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, inType, outType)
+ newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, inType, ast.InnerOutType)
if inType == ValueType {
return &SubexCaptureBeginState {
next: newNext,
@@ -463,65 +513,170 @@ 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 {
+type SubexASTDestructure struct {
+ Destructure Structure
+ Structure Structure
Content SubexAST
}
-func (ast SubexASTEnterArray) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTEnterArray")
- }
- return &SubexCaptureBeginState {
- next: &SubexCopyState {
- filter: anyArrayFilter{},
- next: &SubexDiscardState {
- next: &SubexIncrementNestState {
- next: &SubexCaptureBeginState {
- next: ast.Content.compileWith(
- &SubexDiscardTerminalState {
- terminal: walk.ArrayEnd,
- next: &SubexDecrementNestState {
- next: &SubexConstructArrayState {next: next},
- },
- },
- slotMap,
- ValueType,
- ValueType,
- ),
- },
- },
+func (ast SubexASTDestructure) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ var innerOutType Type
+ var construct SubexState
+ switch ast.Structure {
+ case NoneStructure:
+ innerOutType = outType
+ construct = next
+ case StringStructure:
+ innerOutType = RuneType
+ construct = &SubexConstructStringState {
+ next: next,
+ }
+ case ArrayStructure:
+ innerOutType = ValueType
+ construct = &SubexConstructArrayState {
+ next: next,
+ }
+ case ArrayValuesStructure:
+ innerOutType = ValueType
+ construct = &SubexConstructArrayValuesState {
+ next: next,
+ }
+ case MapStructure:
+ innerOutType = ValueType
+ construct = &SubexConstructMapState {
+ next: next,
+ }
+ default:
+ panic("Invalid ast structure")
+ }
+
+ var innerInType Type
+ var destructFooter SubexState
+ switch ast.Destructure {
+ case NoneStructure:
+ innerInType = inType
+ destructFooter = construct
+ case StringStructure:
+ innerInType = RuneType
+ destructFooter = &SubexDiscardTerminalState {
+ terminal: walk.StringEnd,
+ next: &SubexDecrementNestState {
+ next: construct,
},
- },
+ }
+ case ArrayStructure:
+ innerInType = ValueType
+ destructFooter = &SubexDiscardTerminalState {
+ terminal: walk.ArrayEnd,
+ next: &SubexDecrementNestState {
+ next: construct,
+ },
+ }
+ case ArrayValuesStructure:
+ innerInType = ValueType
+ destructFooter = &SubexDiscardTerminalState {
+ terminal: walk.ArrayEnd,
+ next: &SubexDecrementNestState {
+ next: construct,
+ },
+ }
+ case MapStructure:
+ innerInType = ValueType
+ destructFooter = &SubexDiscardTerminalState {
+ terminal: walk.MapEnd,
+ next: &SubexDecrementNestState {
+ next: construct,
+ },
+ }
+ default:
+ panic("Invalid ast destructure")
}
-}
-type SubexASTEnterString struct {
- Content SubexAST
-}
-func (ast SubexASTEnterString) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
- if inType != ValueType || outType != ValueType {
- panic("Invalid types for SubexASTEnterString")
+ inner := ast.Content.compileWith(
+ destructFooter,
+ slotMap,
+ innerInType,
+ innerOutType,
+ )
+
+ var beginConstruct SubexState
+ switch ast.Structure {
+ case NoneStructure:
+ beginConstruct = inner
+ case StringStructure:
+ beginConstruct = &SubexCaptureRunesBeginState {
+ next: inner,
+ }
+ case ArrayStructure:
+ beginConstruct = &SubexCaptureBeginState {
+ next: inner,
+ }
+ case ArrayValuesStructure:
+ beginConstruct = &SubexCaptureBeginState {
+ next: inner,
+ }
+ case MapStructure:
+ beginConstruct = &SubexCaptureBeginState {
+ next: inner,
+ }
+ default:
+ panic("Invalid ast structure")
}
- return &SubexCaptureBeginState {
- next: &SubexCopyState {
- filter: anyStringFilter{},
- next: &SubexDiscardState {
- next: &SubexIncrementNestState {
- next: &SubexCaptureRunesBeginState {
- next: ast.Content.compileWith(
- &SubexDiscardTerminalState {
- terminal: walk.StringEnd,
- next: &SubexDecrementNestState {
- next: &SubexConstructStringState {next: next},
- },
- },
- slotMap,
- RuneType,
- RuneType,
- ),
+
+ switch ast.Destructure {
+ case NoneStructure:
+ return beginConstruct
+ case StringStructure:
+ return &SubexCaptureBeginState {
+ next: &SubexCopyState {
+ filter: anyStringFilter{},
+ next: &SubexDiscardState {
+ next: &SubexIncrementNestState {
+ keys: true,
+ next: beginConstruct,
},
},
},
- },
+ }
+ case ArrayStructure:
+ return &SubexCaptureBeginState {
+ next: &SubexCopyState {
+ filter: anyArrayFilter{},
+ next: &SubexDiscardState {
+ next: &SubexIncrementNestState {
+ keys: true,
+ next: beginConstruct,
+ },
+ },
+ },
+ }
+ case ArrayValuesStructure:
+ return &SubexCaptureBeginState {
+ next: &SubexCopyState {
+ filter: anyArrayFilter{},
+ next: &SubexDiscardState {
+ next: &SubexIncrementNestState {
+ keys: false,
+ next: beginConstruct,
+ },
+ },
+ },
+ }
+ case MapStructure:
+ return &SubexCaptureBeginState {
+ next: &SubexCopyState {
+ filter: anyMapFilter{},
+ next: &SubexDiscardState {
+ next: &SubexIncrementNestState {
+ keys: true,
+ next: beginConstruct,
+ },
+ },
+ },
+ }
+ default:
+ panic("Invalid destructure in ast")
}
}
+func (ast SubexASTDestructure) String() string {
+ return fmt.Sprintf("%v(%v)%v", ast.Destructure, ast.Content, ast.Structure)
+}
diff --git a/subex/subexstate.go b/subex/subexstate.go
index 4de8ae2..bf5bab9 100644
--- a/subex/subexstate.go
+++ b/subex/subexstate.go
@@ -43,6 +43,9 @@ func (state SubexGroupState) epsilon(aux auxiliaryState) []SubexBranch {
},
}
}
+func (state SubexGroupState) String() string {
+ return fmt.Sprintf("{%T %p, %T %p}", state.first, state.first, state.second, state.second)
+}
type SubexCopyState struct {
next SubexState
@@ -83,6 +86,47 @@ func (state SubexCopyRuneState) String() string {
return fmt.Sprintf("SubexCopyRuneState[%v]", state.filter)
}
+type SubexCopyNumberState struct {
+ next SubexState
+ filter numberFilter
+}
+func (state SubexCopyNumberState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch {
+ number, isNumber := edible.(walk.NumberValue)
+ if !isNumber || !state.filter.numberFilter(float64(number)) {
+ return nil
+ }
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppend([]walk.Value {number}),
+ }}
+}
+func (state SubexCopyNumberState) accepting(aux auxiliaryState) []OutputStack {
+ return nil
+}
+
+type SubexNumberMappingState struct {
+ Range NumberExpr
+ Replace []NumberExpr
+ next SubexState
+}
+func (state SubexNumberMappingState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch {
+ number, isNumber := edible.(walk.NumberValue)
+ if !isNumber || state.Range.Eval(float64(number)) == 0.0 {
+ return nil
+ }
+ var res []walk.Value
+ for _, expr := range state.Replace {
+ res = append(res, walk.NumberValue(expr.Eval(float64(number))))
+ }
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppend(res),
+ }}
+}
+func (state SubexNumberMappingState) accepting(aux auxiliaryState) []OutputStack {
+ return nil
+}
+
// Just pushes to the OutputStack and hands over to the next state
// Used to capture the output of the state being handed over to
type SubexCaptureBeginState struct {
@@ -133,126 +177,61 @@ func (state SubexStoreEndState) epsilon(aux auxiliaryState) []SubexBranch {
}}
}
-/*
-// A part of an output literal, either an Atom or a slot from which to load
-type OutputContent interface {
- // Given the current store, return the ValueList produced by the TransducerOutput
- buildValues(Store) walk.ValueList
- // Given the current store, return the RuneList produced by the TransducerOutput
- buildRunes(Store) walk.RuneList
-}
-
-// An OutputContent which is just a Value literal
-type OutputValueLiteral struct {
- value walk.Value
-}
-func (replacement OutputValueLiteral) buildValues(store Store) walk.ValueList {
- return walk.ValueList{replacement.value}
-}
-func (replacement OutputValueLiteral) buildRunes(store Store) walk.RuneList {
- // TODO: serialise to JSON
- panic("Unimplemented!")
-}
-
-// An OutputContent which is just a rune literal
-type OutputRuneLiteral struct {
- rune walk.StringRuneAtom
-}
-func (replacement OutputRuneLiteral) buildValues(store Store) walk.ValueList {
- // TODO: Try to deserialise
- panic("Unimplemented!")
-}
-func (replacement OutputRuneLiteral) buildRunes(store Store) walk.RuneList {
- return walk.RuneList {replacement.rune}
-}
-
-// An OutputContent which is a slot that is loaded from
-type OutputLoad struct {
+type SubexStoreRunesEndState struct {
slot int
+ next SubexState
}
-func (replacement OutputLoad) buildValues(store Store) walk.ValueList {
- values, isValues := store[replacement.slot].(walk.ValueList)
- if !isValues {
- panic("Tried to output non-values list")
- }
- return values
-}
-func (replacement OutputLoad) buildRunes(store Store) walk.RuneList {
- runes, isRunes := store[replacement.slot].(walk.RuneList)
- if !isRunes {
- panic("Tried to output non-runes as runes")
- }
- return runes
+func (state SubexStoreRunesEndState) epsilon(aux auxiliaryState) []SubexBranch {
+ toStore, aux := aux.popOutputRunes()
+ aux.store = aux.store.withRunes(state.slot, toStore)
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux,
+ }}
}
-// Don't read in anything, just output the series of data and slots specified
-type SubexOutputState struct {
- content []OutputContent
+type SubexOutputValueLiteralState struct {
+ literal walk.Scalar
next SubexState
}
-// Given a store, return what is outputted by an epsilon transition from this state
-// 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.buildValues(store)...)
- }
- return result
-}
-func (state SubexOutputState) eat(aux auxiliaryState, char walk.Value) []SubexBranch {
- content := state.build(aux.store)
- nextStates := state.next.eat(aux.topAppend(content), char)
- return nextStates
-}
-func (state SubexOutputState) accepting(aux auxiliaryState) []OutputStack {
- content := state.build(aux.store)
- outputStacks := state.next.accepting(aux.topAppend(content))
- return outputStacks
-}
-*/
-
-type OutputValue interface {
- build(store Store) []walk.Value
+func (state SubexOutputValueLiteralState) epsilon(aux auxiliaryState) []SubexBranch {
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppend([]walk.Value {state.literal}),
+ }}
}
-type OutputValueLoad struct {
+type SubexOutputValueLoadState struct {
slot int
+ next SubexState
}
-func (ov OutputValueLoad) build(store Store) []walk.Value {
- return store.values[ov.slot]
-}
-
-type OutputValueLiteral struct {
- scalar walk.Scalar
-}
-func (ov OutputValueLiteral) build(store Store) []walk.Value {
- return []walk.Value{ov.scalar}
+func (state SubexOutputValueLoadState) epsilon(aux auxiliaryState) []SubexBranch {
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppend(aux.store.values[state.slot]),
+ }}
}
-type SubexOutputValuesState struct {
- content []OutputValue
+type SubexOutputRuneLiteralState struct {
+ literal rune
next SubexState
}
-func (state SubexOutputValuesState) epsilon(aux auxiliaryState) []SubexBranch {
- var content []walk.Value
- for _, el := range state.content {
- content = append(content, el.build(aux.store)...)
- }
+func (state SubexOutputRuneLiteralState) epsilon(aux auxiliaryState) []SubexBranch {
return []SubexBranch {{
state: state.next,
- aux: aux.topAppend(content),
+ aux: aux.topAppendRune([]rune {state.literal}),
}}
}
-type OutputRune interface {
-}
-
-type OutputRuneLoad struct {
+type SubexOutputRuneLoadState struct {
slot int
+ next SubexState
}
-
-type OutputRuneLiteral struct {
- r rune
+func (state SubexOutputRuneLoadState) epsilon(aux auxiliaryState) []SubexBranch {
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppendRune(aux.store.runes[state.slot]),
+ }}
}
// A final state, transitions to nothing but is accepting
@@ -355,6 +334,49 @@ func (state SubexConstructArrayState) epsilon(aux auxiliaryState) []SubexBranch
}}
}
+type SubexConstructArrayValuesState struct {
+ next SubexState
+}
+func (state SubexConstructArrayValuesState) epsilon(aux auxiliaryState) []SubexBranch {
+ values, aux := aux.popOutput()
+ var array walk.ArrayValue
+ for _, v := range values {
+ array = append(array, walk.ArrayElement {
+ Index: 0,
+ Value: v,
+ })
+ }
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppend([]walk.Value {array}),
+ }}
+}
+
+type SubexConstructMapState struct {
+ next SubexState
+}
+func (state SubexConstructMapState) epsilon(aux auxiliaryState) []SubexBranch {
+ values, aux := aux.popOutput()
+ var m walk.MapValue
+ if len(values) % 2 != 0 {
+ panic("Tried to construct array with odd length input")
+ }
+ for i := 0; i < len(values); i += 2 {
+ key, isNum := values[i].(walk.StringValue)
+ if !isNum {
+ panic("Tried to construct array with non-numeric index")
+ }
+ m = append(m, walk.MapElement {
+ Key: string(key),
+ Value: values[i + 1],
+ })
+ }
+ return []SubexBranch {{
+ state: state.next,
+ aux: aux.topAppend([]walk.Value {m}),
+ }}
+}
+
type SubexConstructStringState struct {
next SubexState
}
@@ -377,12 +399,14 @@ func (state SubexConstructStringState) String() string {
}
type SubexIncrementNestState struct {
+ keys bool
next SubexState
}
func (state SubexIncrementNestState) epsilon(aux auxiliaryState) []SubexBranch {
+ aux.nesting = append(aux.nesting, state.keys)
return []SubexBranch {{
state: state.next,
- aux: aux.incNest(),
+ aux: aux,
}}
}
func (state SubexIncrementNestState) String() string {
@@ -393,8 +417,10 @@ type SubexDecrementNestState struct {
next SubexState
}
func (state SubexDecrementNestState) epsilon(aux auxiliaryState) []SubexBranch {
+ aux.nesting = aux.nesting[:len(aux.nesting) - 1]
+ // aux.nestingValue will be set in addStates
return []SubexBranch {{
state: state.next,
- aux: aux.decNest(),
+ aux: aux,
}}
}
diff --git a/walk/walk.go b/walk/walk.go
index 3fba62f..e66feff 100644
--- a/walk/walk.go
+++ b/walk/walk.go
@@ -148,3 +148,88 @@ func (item WalkItem) Debug() string {
builder.WriteString(item.Value.Debug())
return builder.String()
}
+
+func Merge(first Value, second Value) []Value {
+ switch first := first.(type) {
+ case NullValue:
+ return []Value {first, second}
+ case BoolValue:
+ return []Value {first, second}
+ case NumberValue:
+ return []Value {first, second}
+ case StringValue:
+ return []Value {first, second}
+ case ArrayValue:
+ secondArr, isArr := second.(ArrayValue)
+ if !isArr {
+ return []Value {first, second}
+ }
+
+ if len(first) == 0 {
+ return []Value {second}
+ }
+
+ if len(secondArr) == 0 {
+ return []Value {first}
+ }
+
+ var res ArrayValue
+ for _, el := range first[:len(first) - 1] {
+ res = append(res, el)
+ }
+ midFirst := first[len(first) - 1]
+ midSecond := secondArr[0]
+ if midFirst.Index == midSecond.Index {
+ for _, el := range Merge(midFirst.Value, midSecond.Value) {
+ res = append(res, ArrayElement {
+ Index: midFirst.Index,
+ Value: el,
+ })
+ }
+ } else {
+ res = append(res, midFirst, midSecond)
+ }
+ for _, el := range secondArr[1:] {
+ res = append(res, el)
+ }
+
+ return []Value {res}
+ case MapValue:
+ secondMap, isMap := second.(MapValue)
+ if !isMap {
+ return []Value {first, second}
+ }
+
+ if len(first) == 0 {
+ return []Value {second}
+ }
+
+ if len(secondMap) == 0 {
+ return []Value {first}
+ }
+
+ var res MapValue
+ for _, el := range first[:len(first) - 1] {
+ res = append(res, el)
+ }
+ midFirst := first[len(first) - 1]
+ midSecond := secondMap[0]
+ if midFirst.Key == midSecond.Key {
+ for _, el := range Merge(midFirst.Value, midSecond.Value) {
+ res = append(res, MapElement {
+ Key: midFirst.Key,
+ Value: el,
+ })
+ }
+ } else {
+ res = append(res, midFirst, midSecond)
+ }
+ for _, el := range secondMap[1:] {
+ res = append(res, el)
+ }
+
+ return []Value {res}
+ default:
+ panic("first is invalid value type")
+ }
+}
diff --git a/walk/walk_test.go b/walk/walk_test.go
deleted file mode 100644
index 759c501..0000000
--- a/walk/walk_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package walk
-
-import (
- "testing"
- "log"
-)
-
-func TestValueIter(t *testing.T) {
- values := ValueList{
- NumberValue(1),
- NumberValue(2),
- NumberValue(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")
- }
- }
-}