diff options
Diffstat (limited to 'main')
| -rw-r--r-- | main/command.go | 358 | ||||
| -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 | 298 |
5 files changed, 819 insertions, 154 deletions
diff --git a/main/command.go b/main/command.go index 5a898e2..832a236 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,141 @@ 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 + elseJump int +} +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 += cmd.elseJump + } else { + state.pc++ + state.Read() + state.prevStart = item.PrevStart + state.start = item.Start + state.end = item.End + state.nextEnd = item.NextEnd + state.value = newValue + } +} +func (cmd SubstituteNextCommand) String() string { + return "n/.../" +} + +type SubstituteAppendNextCommand struct { + subex subex.Transducer + elseJump int +} +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 += cmd.elseJump + } else { + state.Read() + state.prevStart = item.PrevStart + state.start = item.Start + state.end = item.End + state.nextEnd = item.NextEnd + state.pc++ + 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 + elseJump int +} +func (cmd FullMergeCommand) exec(state *ProgramState) { + _, notOk := runSubex(cmd.subex, state.value) + if notOk { + state.pc += cmd.elseJump + return + } + if !state.start { + state.pc++ + 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++ + return + } + } +} +func (cmd FullMergeCommand) String() string { + return "M" +} + type DeleteValueCommand struct {} func (cmd DeleteValueCommand) exec(state *ProgramState) { state.value = nil @@ -63,16 +191,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 @@ -82,13 +201,14 @@ func runSubex(state subex.Transducer, in walk.ValueList) (walk.ValueList, bool) type SubstituteValueCommand struct { subex subex.Transducer + elseJump int } func (cmd SubstituteValueCommand) exec(state *ProgramState) { newValue, err := runSubex(cmd.subex, state.value) if err { - state.pc++ + state.pc += cmd.elseJump } else { - state.pc += 2 + state.pc++ state.value = newValue } } @@ -96,20 +216,70 @@ func (cmd SubstituteValueCommand) String() string { return "s/.../" } -type SubstitutePathCommand struct { - subex subex.Transducer +type IsStartCommand struct { + elseJump int } -func (cmd SubstitutePathCommand) exec(state *ProgramState) { - newPath, err := runSubex(cmd.subex, state.path) - if err { +func (cmd IsStartCommand) exec(state *ProgramState) { + if state.start { + state.pc++ + } else { + state.pc += cmd.elseJump + } +} +func (cmd IsStartCommand) String() string { + return "a" +} + +type IsPrevStartCommand struct { + elseJump int +} +func (cmd IsPrevStartCommand) exec(state *ProgramState) { + if state.prevStart { + state.pc++ + } else { + state.pc += cmd.elseJump + } +} +func (cmd IsPrevStartCommand) String() string { + return "A" +} + +type IsEndCommand struct { + elseJump int +} +func (cmd IsEndCommand) exec(state *ProgramState) { + if state.end { state.pc++ } else { - state.pc += 2 - state.path = newPath + state.pc += cmd.elseJump } } -func (cmd SubstitutePathCommand) String() string { - return "S/.../" +func (cmd IsEndCommand) String() string { + return "e" +} + +type IsNextEndCommand struct { + elseJump int +} +func (cmd IsNextEndCommand) exec(state *ProgramState) { + if state.nextEnd { + state.pc++ + } else { + state.pc += cmd.elseJump + } +} +func (cmd IsNextEndCommand) String() string { + return "E" +} + +type LabelCommand struct { + label rune +} +func (cmd LabelCommand) exec(state *ProgramState) { + state.pc++ +} +func (cmd LabelCommand) String() string { + return fmt.Sprintf(":%c", cmd.label) } type NoopCommand struct {} @@ -140,6 +310,40 @@ func (cmd AppendXRegCommand) String() string { return "X" } +type SubstituteToXRegCommand struct { + subex subex.Transducer + elseJump int +} +func (cmd SubstituteToXRegCommand) exec(state *ProgramState) { + newValue, err := runSubex(cmd.subex, state.value) + if err { + state.pc += cmd.elseJump + } else { + state.pc++ + state.xreg = newValue + } +} +func (cmd SubstituteToXRegCommand) String() string { + return "x/.../" +} + +type SubstituteAppendXRegCommand struct { + subex subex.Transducer + elseJump int +} +func (cmd SubstituteAppendXRegCommand) exec(state *ProgramState) { + newValue, err := runSubex(cmd.subex, state.value) + if err { + state.pc += cmd.elseJump + } else { + state.pc++ + 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 +364,40 @@ func (cmd AppendYRegCommand) String() string { return "Y" } +type SubstituteToYRegCommand struct { + subex subex.Transducer + elseJump int +} +func (cmd SubstituteToYRegCommand) exec(state *ProgramState) { + newValue, err := runSubex(cmd.subex, state.value) + if err { + state.pc += cmd.elseJump + } else { + state.pc++ + state.yreg = newValue + } +} +func (cmd SubstituteToYRegCommand) String() string { + return "y/.../" +} + +type SubstituteAppendYRegCommand struct { + subex subex.Transducer + elseJump int +} +func (cmd SubstituteAppendYRegCommand) exec(state *ProgramState) { + newValue, err := runSubex(cmd.subex, state.value) + if err { + state.pc += cmd.elseJump + } else { + state.pc++ + 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 +418,48 @@ 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 + elseJump int +} +func (cmd SubstituteToZRegCommand) exec(state *ProgramState) { + newValue, err := runSubex(cmd.subex, state.value) + if err { + state.pc += cmd.elseJump + } else { + state.pc++ + state.zreg = newValue + } } -func (cmd SwapPathCommand) String() string { - return "k" +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 + elseJump int } -func (cmd AppendPathCommand) String() string { - return "K" +func (cmd SubstituteAppendZRegCommand) exec(state *ProgramState) { + newValue, err := runSubex(cmd.subex, state.value) + if err { + state.pc += cmd.elseJump + } else { + state.pc++ + state.zreg = append(state.xreg, newValue...) + } +} +func (cmd SubstituteAppendZRegCommand) String() string { + return "Z/.../" +} + +type RelativeJumpCommand struct { + destination int +} +func (cmd RelativeJumpCommand) exec(state *ProgramState) { + state.pc += cmd.destination +} +func (cmd RelativeJumpCommand) String() string { + return fmt.Sprintf("b+%v", cmd.destination) } type JumpCommand struct { @@ -220,4 +482,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..0bcdaec 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', 'r': + 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..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 141ae7e..d0a0255 100644 --- a/main/parse.go +++ b/main/parse.go @@ -10,7 +10,6 @@ import ( type parser struct { tokenStream chan Token rewinds []Token - labels map[rune]int } func (p *parser) next() Token { var token Token @@ -44,13 +43,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 { @@ -59,97 +52,288 @@ func (p *parser) parseSubex() subex.SubexAST { return subexAST } -func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Command { +func (p *parser) parseBasicCommand(commandChar rune) []Command { switch commandChar { case 'p': - return append(commands, PrintValueCommand{}) + return []Command {PrintValueCommand{}} case 'd': - return append(commands, DeleteValueCommand{}) - case 'D': - return append(commands, DeletePathCommand{}) + return []Command {DeleteValueCommand{}} case 'n': - return append(commands, NextCommand{}) + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {NextCommand{}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteNextCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) case 'N': - return append(commands, AppendNextCommand{}) - case 's', 'S': + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {AppendNextCommand{}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteAppendNextCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 'm': + return []Command {MergeCommand {}} + case 'M': + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + FullMergeCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 's': 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!?!?") + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteValueCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 'r': + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + return []Command { + SubstituteValueCommand { + subex: subex, + elseJump: 2, + }, + RelativeJumpCommand { + destination: -1, + }, } case 'o': - return append(commands, NoopCommand{}) + return []Command {NoopCommand {}} case 'x': - return append(commands, SwapXRegCommand{}) + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {SwapXRegCommand {}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteToXRegCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) case 'X': - return append(commands, AppendXRegCommand{}) + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {AppendXRegCommand {}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteAppendXRegCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) case 'y': - return append(commands, SwapYRegCommand{}) + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {SwapYRegCommand {}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteToYRegCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) case 'Y': - return append(commands, AppendYRegCommand{}) + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {AppendYRegCommand {}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteAppendYRegCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) case 'z': - return append(commands, SwapZRegCommand{}) + delim := p.peek() + if delim.typ != TokenSubstituteDelimiter { + return []Command {SwapZRegCommand {}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteToZRegCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) 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 []Command {AppendZRegCommand {}} + } + ast := p.parseSubex() + subex := subex.CompileTransducer(ast) + elseBranch := p.parseCommand() + return append( + []Command { + SubstituteAppendZRegCommand { + subex: subex, + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 'a': + elseBranch := p.parseCommand() + return append( + []Command { + IsStartCommand { + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 'A': + elseBranch := p.parseCommand() + return append( + []Command { + IsPrevStartCommand { + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 'e': + elseBranch := p.parseCommand() + return append( + []Command { + IsEndCommand { + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) + case 'E': + elseBranch := p.parseCommand() + return append( + []Command { + IsNextEndCommand { + elseJump: len(elseBranch) + 1, + }, + }, + elseBranch..., + ) case ':': labelToken := p.next() if labelToken.typ != TokenLabel { panic("Missing branch label") } label, _ := utf8.DecodeRuneInString(labelToken.val) - p.labels[label] = len(commands) - return commands + return []Command { + LabelCommand { + label: label, + }, + } case 'b': labelToken := p.next() if labelToken.typ != TokenLabel { panic("Missing branch label") } label, _ := utf8.DecodeRuneInString(labelToken.val) - return append(commands, BranchPlaceholderCommand {label}) + return []Command { + BranchPlaceholderCommand { + label: label, + }, + } default: panic("Invalid command") } } -func (p *parser) parseCommand(commands []Command) []Command { +func (p *parser) parseCommand() []Command { token := p.next() switch token.typ { case TokenLBrace: - jumpToBlockCommand := &JumpCommand{0} - commands = append(commands, JumpCommand {len(commands) + 2}, jumpToBlockCommand) - commands = p.parseCommands(commands) + children := p.parseCommandSequence() if p.next().typ != TokenRBrace { panic("Missing matching }") } - jumpToBlockCommand.destination = len(commands) - return commands + return children + case TokenRBrace, TokenEOF: + p.rewind(token) + return nil case TokenCommand: commandChar, _, err := strings.NewReader(token.val).ReadRune() if err != nil { panic("Error reading a command character!?") } - return p.parseBasicCommand(commands, commandChar) + return p.parseBasicCommand(commandChar) default: panic("Invalid token, expected command") } } -func (p *parser) parseCommands(commands []Command) []Command { +func (p *parser) parseCommandSequence() []Command { + var commands []Command for { nextToken := p.peek() if nextToken.typ == TokenEOF || nextToken.typ == TokenRBrace { return commands } - commands = p.parseCommand(commands) + commands = append(commands, p.parseCommand()...) } } @@ -157,17 +341,23 @@ func Parse(tokens chan Token) []Command { p := parser { tokenStream: tokens, rewinds: nil, - labels: make(map[rune]int), } - program := p.parseCommands(nil) + program := p.parseCommandSequence() + labels := make(map[rune]int) + for i, command := range program { + switch label := command.(type) { + case LabelCommand: + labels[label.label] = i + } + } for i, command := range program { switch branch := command.(type) { - case BranchPlaceholderCommand: - destination, exists := p.labels[branch.label] - if !exists { - panic("Tried to branch to a label that doesn't exist") - } - program[i] = JumpCommand {destination} + case BranchPlaceholderCommand: + destination, exists := labels[branch.label] + if !exists { + panic("Tried to branch to a label that doesn't exist") + } + program[i] = JumpCommand {destination} } } return program |
