Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
Diffstat (limited to 'main')
-rw-r--r--main/command.go316
-rw-r--r--main/lex.go35
-rw-r--r--main/main.go111
-rw-r--r--main/main_test.go171
-rw-r--r--main/parse.go103
5 files changed, 608 insertions, 128 deletions
diff --git a/main/command.go b/main/command.go
index 5a898e2..bbbb036 100644
--- a/main/command.go
+++ b/main/command.go
@@ -13,12 +13,11 @@ type Command interface {
type PrintValueCommand struct {}
func (cmd PrintValueCommand) exec(state *ProgramState) {
- err := state.out.Write(walk.WalkItem {
- Path: state.path,
- Value: state.value,
- })
- if err != nil {
- panic("Error while outputting")
+ for _, value := range state.value {
+ err := state.out.Write(value)
+ if err != nil {
+ panic("Error while outputting")
+ }
}
state.pc++
}
@@ -28,12 +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.value = nextItem.Value
- state.path = nextItem.Path
+
+ 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 {
@@ -42,18 +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.value = append(state.value, nextItem.Value...)
- state.path = nextItem.Path
+
+ 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
@@ -63,16 +188,7 @@ func (cmd DeleteValueCommand) String() string {
return "d"
}
-type DeletePathCommand struct {}
-func (cmd DeletePathCommand) exec(state *ProgramState) {
- state.path = nil
- state.pc++
-}
-func (cmd DeletePathCommand) String() string {
- return "D"
-}
-
-func runSubex(state subex.Transducer, in walk.ValueList) (walk.ValueList, bool) {
+func runSubex(state subex.Transducer, in []walk.Value) ([]walk.Value, bool) {
out, error := subex.RunTransducer(state, in)
if error {
return nil, true
@@ -96,20 +212,52 @@ func (cmd SubstituteValueCommand) String() string {
return "s/.../"
}
-type SubstitutePathCommand struct {
- subex subex.Transducer
+type IsStartCommand struct {}
+func (cmd IsStartCommand) exec(state *ProgramState) {
+ if state.start {
+ state.pc += 2
+ } else {
+ state.pc += 1
+ }
}
-func (cmd SubstitutePathCommand) exec(state *ProgramState) {
- newPath, err := runSubex(cmd.subex, state.path)
- if err {
- state.pc++
+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
- state.path = newPath
+ } else {
+ state.pc += 1
}
}
-func (cmd SubstitutePathCommand) String() string {
- return "S/.../"
+func (cmd IsNextEndCommand) String() string {
+ return "E"
}
type NoopCommand struct {}
@@ -140,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
@@ -160,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
@@ -180,24 +392,36 @@ func (cmd AppendZRegCommand) String() string {
return "Z"
}
-type SwapPathCommand struct {}
-func (cmd SwapPathCommand) exec(state *ProgramState) {
- v := state.value
- state.value = state.path
- state.path = v
- state.pc++
+type SubstituteToZRegCommand struct {
+ subex subex.Transducer
}
-func (cmd SwapPathCommand) String() string {
- return "k"
+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 AppendPathCommand struct {}
-func (cmd AppendPathCommand) exec(state *ProgramState) {
- state.path = append(state.path, state.value...)
- state.pc++
+type SubstituteAppendZRegCommand struct {
+ subex subex.Transducer
}
-func (cmd AppendPathCommand) String() string {
- return "K"
+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 {
@@ -220,4 +444,4 @@ func (cmd BranchPlaceholderCommand) exec(state *ProgramState) {
}
func (cmd BranchPlaceholderCommand) String() string {
return fmt.Sprintf("b%c", cmd.label)
-} \ No newline at end of file
+}
diff --git a/main/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 8e8c369..bfa1afe 100644
--- a/main/main.go
+++ b/main/main.go
@@ -1,48 +1,53 @@
package main
import (
- "os"
"bufio"
- "main/walk"
+ "io"
"main/json"
+ "main/walk"
+ "os"
)
-type Program []Command
-
type ProgramState struct {
- path, value, xreg, yreg, zreg walk.ValueList
+ 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),
@@ -51,27 +56,57 @@ func main() {
}
for {
- walkItem, err := state.in.Read()
+ walkItem, err := state.Read()
if err != nil {
break
}
- state.value = walkItem.Value
- state.path = walkItem.Path
+ 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 {
- err := state.out.Write(walk.WalkItem {
- Path: state.path,
- Value: state.value,
- })
- if err != nil {
- panic("Error while outputting")
+ if !config.quiet {
+ for _, value := range state.value {
+ err := state.out.Write(value)
+ if err != nil {
+ panic("Error while outputting")
+ }
}
}
}
state.in.AssertDone()
state.out.AssertDone()
-} \ No newline at end of file
+}
+
+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..076693d
--- /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)~$_.|(..$_){-0})-|(..$_){-0})-|(..$_){-0})-/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/.{-0}+/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\" \".{-0}$a\")# | #(\"last_name\" \".{-0}$b\")# | .){-0}$_ `\"$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\" \".{-0}$a\")# | #(\"last_name\" \".{-0}$b\")# | .){-0}$_ `\"$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\" \".{-0}$a\" \"last_name\" \".{-0}$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/-( -( ~(.{-0}` `)- ~(.{-0})- )~ ):/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\" \".{-0}$a\" | \"last_name\" \".{-0}$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/.{-0}:():/be mbs } :em s/:( -( ~(.{-0}` `)-{-0} ~(.{-0})- )~ )-/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `"Hello world these are values"`,
+ },
+ {
+ name: "Short concat array values",
+ program: "M/#( \"array\" :(): )#/{ s/#( \"array\"$_ :( .{-0} )- )-/ s/-( ~(.{-0}` `)-{-0} ~(.{-0})- )~/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 141ae7e..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 {
@@ -65,41 +59,90 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma
return append(commands, PrintValueCommand{})
case 'd':
return append(commands, DeleteValueCommand{})
- case 'D':
- return append(commands, DeletePathCommand{})
case 'n':
- return append(commands, NextCommand{})
- case 'N':
- return append(commands, AppendNextCommand{})
- case 's', 'S':
+ delim := p.peek()
+ if delim.typ != TokenSubstituteDelimiter {
+ return append(commands, NextCommand{})
+ }
ast := p.parseSubex()
subex := subex.CompileTransducer(ast)
- switch commandChar {
- case 's':
- return append(commands, SubstituteValueCommand {subex}, JumpCommand {len(commands) + 3})
- case 'S':
- return append(commands, SubstitutePathCommand {subex}, JumpCommand {len(commands) + 3})
- default:
- panic("Unreachable!?!?")
+ return append(commands, SubstituteNextCommand {subex}, JumpCommand {len(commands) + 3})
+ case 'N':
+ 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)
+ return append(commands, SubstituteValueCommand {subex}, JumpCommand {len(commands) + 3})
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{})
- case 'k':
- return append(commands, SwapPathCommand{})
- case 'K':
- return append(commands, AppendPathCommand{})
+ 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 {