diff options
Diffstat (limited to 'main')
-rw-r--r-- | main/command.go | 316 | ||||
-rw-r--r-- | main/lex.go | 35 | ||||
-rw-r--r-- | main/main.go | 111 | ||||
-rw-r--r-- | main/main_test.go | 171 | ||||
-rw-r--r-- | main/parse.go | 103 |
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 { |