diff options
| -rw-r--r-- | json/read.go | 2 | ||||
| -rw-r--r-- | json/write.go | 4 | ||||
| -rw-r--r-- | json/write_test.go | 57 | ||||
| -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 | ||||
| -rw-r--r-- | subex/arithmetic.go | 203 | ||||
| -rw-r--r-- | subex/filter.go | 213 | ||||
| -rw-r--r-- | subex/lex.go | 3 | ||||
| -rw-r--r-- | subex/main.go | 317 | ||||
| -rw-r--r-- | subex/main_test.go | 867 | ||||
| -rw-r--r-- | subex/numberexpr.go | 217 | ||||
| -rw-r--r-- | subex/parse.go | 1038 | ||||
| -rw-r--r-- | subex/subexast.go | 602 | ||||
| -rw-r--r-- | subex/subexast_test.go | 19 | ||||
| -rw-r--r-- | subex/subexstate.go | 393 | ||||
| -rw-r--r-- | subex/subexstate_test.go | 4 | ||||
| -rw-r--r-- | walk/read.go | 4 | ||||
| -rw-r--r-- | walk/walk.go | 134 | ||||
| -rw-r--r-- | walk/walk_test.go | 45 |
22 files changed, 3692 insertions, 1403 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/json/write.go b/json/write.go index c2a220e..3a5a621 100644 --- a/json/write.go +++ b/json/write.go @@ -209,7 +209,7 @@ func (writer *JSONWriter) navigateTo(keepLen int, path []walk.PathSegment, state } func (writer *JSONWriter) inMapAt(keepLen int, path []walk.PathSegment) bool { - if keepLen < len(path) { + if len(path) != 0 { return false } @@ -222,7 +222,7 @@ func (writer *JSONWriter) inMapAt(keepLen int, path []walk.PathSegment) bool { } func (writer *JSONWriter) inArrayAt(keepLen int, path []walk.PathSegment) bool { - if keepLen < len(path) { + if len(path) != 0 { return false } diff --git a/json/write_test.go b/json/write_test.go index 05b228e..b495d94 100644 --- a/json/write_test.go +++ b/json/write_test.go @@ -222,6 +222,63 @@ func TestWrite(t *testing.T) { }, expected: `[[100],[],null,[200]]`, }, + { + values: []walk.Value { + walk.MapValue {{ + Key: "a", + Value: walk.MapValue {{ + Key: "b", + Value: walk.StringValue("map"), + }}, + }}, + walk.MapValue {{ + Key: "a", + Value: walk.ArrayValue {{ + Index: 0, + Value: walk.StringValue("array"), + }}, + }}, + }, + expected: `{"a":{"b":"map"},"a":["array"]}`, + }, + { + values: []walk.Value { + walk.ArrayValue {{ + Index: 0, + Value: walk.ArrayValue {{ + Index: 0, + Value: walk.ArrayValue {{ + Index: 1, + Value: walk.StringValue("a"), + }}, + }}, + }}, + walk.ArrayValue {{ + Index: 0, + Value: walk.ArrayValue {{ + Index: 1, + Value: walk.ArrayValue{}, + }}, + }}, + }, + expected: `[[["a"],[]]]`, + }, + { + values: []walk.Value { + walk.ArrayValue {{ + Index: 0, + Value: walk.MapValue {{ + Key: "a", + Value: walk.StringValue("a"), + }}, + }}, + walk.ArrayValue {{ + Index: 0, + Value: walk.MapValue{}, + }}, + }, + expected: `[{"a":"a"}]`, + }, } for i, test := range tests { 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 diff --git a/subex/arithmetic.go b/subex/arithmetic.go index 4cbc9db..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.ValueList) (walk.ValueList, error) { - allBools := true - var sum float64 = 0 - var any bool = false - for _, value := range values { - switch v := value.(type) { - case walk.NullScalar: - allBools = false - case walk.BoolScalar: - if v { - sum += 1 - any = true - } - case walk.NumberScalar: - allBools = false - sum += float64(v) - case walk.StringStructure: - 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.ValueList{walk.BoolScalar(any)}, nil - } else { - return walk.ValueList{walk.NumberScalar(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.ValueList) (walk.ValueList, error) { - allBools := true - var product float64 = 1 - var all bool = false - for _, value := range values { - switch v := value.(type) { - case walk.NullScalar: - allBools = false - product *= 0 - case walk.BoolScalar: - if !v { - product *= 0 - all = false - } - case walk.NumberScalar: - allBools = false - product *= float64(v) - case walk.StringStructure: - 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.ValueList{walk.BoolScalar(all)}, nil - } else { - return walk.ValueList{walk.NumberScalar(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.ValueList) (walk.ValueList, error) { - var negatedNumbers walk.ValueList - for _, value := range values { - switch v := value.(type) { - case walk.NullScalar: - negatedNumbers = append(negatedNumbers, walk.NumberScalar(0)) - case walk.BoolScalar: - if v { - negatedNumbers = append(negatedNumbers, walk.NumberScalar(-1)) - } else { - negatedNumbers = append(negatedNumbers, walk.NumberScalar(0)) - } - case walk.NumberScalar: - negatedNumbers = append(negatedNumbers, walk.NumberScalar(-float64(v))) - case walk.StringStructure: - num, err := strconv.ParseFloat(string(v), 64) - if err == nil { - negatedNumbers = append(negatedNumbers, walk.NumberScalar(-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.ValueList) (walk.ValueList, error) { - var reciprocals walk.ValueList - for _, value := range values { - switch v := value.(type) { - case walk.NullScalar: - return nil, errors.New("Tried to take reciprocal of null") - case walk.BoolScalar: - if v { - reciprocals = append(reciprocals, walk.NumberScalar(1)) - } else { - return nil, errors.New("Tried to take reciprocal of false") - } - case walk.NumberScalar: - reciprocals = append(reciprocals, walk.NumberScalar(1 / float64(v))) - case walk.StringStructure: - num, err := strconv.ParseFloat(string(v), 64) - if err == nil { - reciprocals = append(reciprocals, walk.NumberScalar(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.ValueList) (notted walk.ValueList, err error) { +func arithmeticSum(values []walk.Value) ([]walk.Value, error) { + var total float64 = 0 for _, value := range values { - switch v := value.(type) { - case walk.NullScalar: - notted = append(notted, walk.BoolScalar(true)) - case walk.BoolScalar: - notted = append(notted, walk.BoolScalar(!bool(v))) - case walk.NumberScalar: - notted = append(notted, walk.BoolScalar(v == 0)) - case walk.StringStructure: - notted = append(notted, walk.BoolScalar(len(v) == 0)) - default: - return nil, errors.New("Tried to NOT non-boolean") + 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.ValueList) (walk.ValueList, error) { - if len(values) == 0 { - return walk.ValueList{walk.BoolScalar(true)}, nil - } - first := values[0] - for _, value := range values[1:] { - // TODO: Refine the equality check - if value != first { - return walk.ValueList{walk.BoolScalar(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.ValueList{walk.BoolScalar(true)}, nil + + return []walk.Value{walk.NumberValue(product)}, nil } diff --git a/subex/filter.go b/subex/filter.go index 1a1b6db..87c83a4 100644 --- a/subex/filter.go +++ b/subex/filter.go @@ -2,6 +2,8 @@ package subex import ( "main/walk" + "math" + "fmt" ) type valueFilter interface { @@ -17,16 +19,36 @@ func (scalar selectScalarFilter) valueFilter(value walk.Value) bool { type anyNumberFilter struct {} func (_ anyNumberFilter) valueFilter(value walk.Value) bool { - _, isNumber := value.(walk.NumberScalar) + _, isNumber := value.(walk.NumberValue) return isNumber } type anyBoolFilter struct {} func (_ anyBoolFilter) valueFilter(value walk.Value) bool { - _, isBool := value.(walk.BoolScalar) + _, isBool := value.(walk.BoolValue) 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 @@ -34,29 +56,204 @@ func (_ anyValueFilter) valueFilter(value walk.Value) bool { type anyArrayFilter struct {} func (_ anyArrayFilter) valueFilter(value walk.Value) bool { - _, isArray := value.(walk.ArrayStructure) + _, isArray := value.(walk.ArrayValue) 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.StringStructure) + _, isString := value.(walk.StringValue) return isString } type runeFilter interface { - runeFilter(r walk.StringRuneAtom) bool + runeFilter(r rune) bool } type anyRuneFilter struct {} -func (_ anyRuneFilter) runeFilter(r walk.StringRuneAtom) bool { +func (_ anyRuneFilter) runeFilter(r rune) bool { return true } type selectRuneFilter struct { r rune } -func (f selectRuneFilter) runeFilter(r walk.StringRuneAtom) bool { - return f.r == rune(r) +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 e2cec0b..d4cacb9 100644 --- a/subex/main.go +++ b/subex/main.go @@ -5,50 +5,96 @@ import ( ) type Transducer struct { - storeSize int + storeSize NextSlotIds initialState SubexState } -type StoreItem struct {} // Where slots are stored -type Store []walk.OutputList +type Store struct { + values [][]walk.Value + runes [][]rune +} + // Return a new store with all the data from this one func (store Store) clone() Store { - newStore := make([]walk.OutputList, len(store)) - copy(newStore, store) + newStore := Store{ + values: make([][]walk.Value, len(store.values)), + runes: make([][]rune, len(store.runes)), + } + copy(newStore.values, store.values) + copy(newStore.runes, store.runes) return newStore } + // Return a copy of this store but with an additional slot set -func (store Store) withValue(key int, value walk.OutputList) Store { +func (store Store) withValue(key int, value []walk.Value) Store { newStore := store.clone() - newStore[key] = value + newStore.values[key] = value return newStore } +func (store Store) withRunes(key int, runes []rune) Store { + newStore := store.clone() + newStore.runes[key] = runes + return newStore +} + +type SlotId struct { + id int + typ Type +} + +type NextSlotIds struct { + values int + runes int +} + type SlotMap struct { - nextId int - ids map[rune]int + next NextSlotIds + ids map[rune]SlotId } + func (m *SlotMap) getId(slot rune) int { id, exists := m.ids[slot] if exists { - return id + if id.typ != ValueType { + panic("Slot with wrong type used") + } + return id.id + } + id.id = m.next.values + id.typ = ValueType + m.next.values++ + m.ids[slot] = id + return id.id +} +func (m *SlotMap) getRuneId(slot rune) int { + id, exists := m.ids[slot] + if exists { + if id.typ != RuneType { + panic("Slot with wrong type used") + } + return id.id } - id = m.nextId - m.nextId++ + id.id = m.next.runes + id.typ = RuneType + m.next.runes++ m.ids[slot] = id - return id + return id.id } // Compile the SubexAST into a transducer SubexState that can be run func CompileTransducer(transducerAst SubexAST) Transducer { - slotMap := SlotMap { - nextId: 0, - ids: make(map[rune]int), + slotMap := SlotMap{ + next: NextSlotIds{ + values: 0, + runes: 0, + }, + ids: make(map[rune]SlotId), } - initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, false) - return Transducer { - storeSize: slotMap.nextId, + initial := transducerAst.compileWith(&SubexNoneState{}, &slotMap, ValueType, ValueType) + return Transducer{ + storeSize: slotMap.next, initialState: initial, } } @@ -58,46 +104,44 @@ type OutputStack struct { head walk.OutputList tail *OutputStack } -func (stack OutputStack) pop() (walk.OutputList, OutputStack) { - return stack.head, *stack.tail + +func (stack OutputStack) pop() ([]walk.Value, OutputStack) { + return stack.head.(walk.OutputValueList), *stack.tail } -func (stack OutputStack) push(atoms walk.OutputList) OutputStack { - return OutputStack { - head: atoms, +func (stack OutputStack) push(atoms []walk.Value) OutputStack { + return OutputStack{ + head: walk.OutputValueList(atoms), tail: &stack, } } -func (stack OutputStack) replace(atoms walk.OutputList) OutputStack { - return OutputStack { - head: atoms, +func (stack OutputStack) replace(atoms []walk.Value) OutputStack { + return OutputStack{ + head: walk.OutputValueList(atoms), tail: stack.tail, } } -func (stack OutputStack) peek() walk.OutputList { - return stack.head +func (stack OutputStack) peek() []walk.Value { + return stack.head.(walk.OutputValueList) } func topAppend(outputStack OutputStack, values []walk.Value) OutputStack { - head, isValues := outputStack.peek().(walk.ValueList) - if !isValues { - panic("Tried to append a value to an output of non-values") - } - head = append(walk.ValueList{}, head...) + head := outputStack.peek() + head = append([]walk.Value{}, head...) head = append(head, values...) return outputStack.replace(head) } -func topAppendRunes(outputStack OutputStack, runes walk.RuneList) OutputStack { - head, isRunes := outputStack.peek().(walk.RuneList) - if !isRunes { - panic("Tried to append runes to a non-rune list") - } - head = append(walk.RuneList{}, head...) +func topAppendRune(outputStack OutputStack, runes []rune) OutputStack { + head := outputStack.head.(walk.OutputRuneList) + head = append([]rune{}, head...) head = append(head, runes...) - return outputStack.replace(head) + return OutputStack{ + head: head, + tail: outputStack.tail, + } } -// Addition state that goes along with a subex state in an execution branch +// Additional state that goes along with a subex state in an execution branch type auxiliaryState struct { // Content of slots in this branch store Store @@ -106,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 { @@ -114,67 +158,91 @@ func (aux auxiliaryState) cloneStore() auxiliaryState { return aux } -func (aux auxiliaryState) withValue(slot int, value walk.OutputList) auxiliaryState { +func (aux auxiliaryState) withValue(slot int, value []walk.Value) auxiliaryState { aux.store = aux.store.withValue(slot, value) return aux } -func (aux auxiliaryState) pushOutput(data walk.OutputList) auxiliaryState { +func (aux auxiliaryState) pushOutput(data []walk.Value) auxiliaryState { aux.outputStack = aux.outputStack.push(data) return aux } -func (aux auxiliaryState) popOutput() (walk.OutputList, auxiliaryState) { +func (aux auxiliaryState) pushOutputRunes(runes []rune) auxiliaryState { + tail := aux.outputStack + aux.outputStack = OutputStack{ + head: walk.OutputRuneList(runes), + tail: &tail, + } + return aux +} + +func (aux auxiliaryState) popDiscardOutput() auxiliaryState { + aux.outputStack = *aux.outputStack.tail + return aux +} + +func (aux auxiliaryState) popOutput() ([]walk.Value, auxiliaryState) { data, output := aux.outputStack.pop() aux.outputStack = output return data, aux } -func (aux auxiliaryState) topAppend(values walk.OutputList) auxiliaryState { - switch output := values.(type) { - case walk.ValueList: - aux.outputStack = topAppend(aux.outputStack, output) - case walk.RuneList: - aux.outputStack = topAppendRunes(aux.outputStack, output) - } - return aux +func (aux auxiliaryState) popOutputRunes() ([]rune, auxiliaryState) { + runes := aux.outputStack.head.(walk.OutputRuneList) + aux.outputStack = *aux.outputStack.tail + return runes, aux } -func (aux auxiliaryState) incNest() auxiliaryState { - aux.nesting++ +func (aux auxiliaryState) topAppend(values []walk.Value) auxiliaryState { + aux.outputStack = topAppend(aux.outputStack, values) return aux } -func (aux auxiliaryState) decNest() auxiliaryState { - aux.nesting-- +func (aux auxiliaryState) topAppendRune(runes []rune) auxiliaryState { + aux.outputStack = topAppendRune(aux.outputStack, runes) return aux } -// One branch of subex execution type SubexBranch struct { - // State in this branch state SubexState + aux auxiliaryState +} + +// One branch of subex execution +type SubexEatBranch struct { + // State in this branch + state SubexEatState // Axiliary state aux auxiliaryState } + // Read a single character and return all the branches resulting from this branch consuming it -func (pair SubexBranch) eat(char walk.Edible) []SubexBranch { - return pair.state.eat(pair.aux, char) +func (pair SubexEatBranch) eat(edible walk.Edible) []SubexBranch { + return pair.state.eat(pair.aux, edible) } -func (pair SubexBranch) accepting() []OutputStack { +func (pair SubexEatBranch) accepting() []OutputStack { return pair.state.accepting(pair.aux) } -func equalStates(left SubexBranch, right SubexBranch) bool { - // Only care about if they are the same pointer - return left.state == right.state && left.aux.nesting == right.aux.nesting +func equalStates(left SubexEatBranch, right SubexEatBranch) bool { + 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 // This function removes all of the pointless execution branches to save execution time -func pruneStates(states []SubexBranch) []SubexBranch { +func pruneStates(states []SubexEatBranch) []SubexEatBranch { uniqueStates := 0 - outer: for _, state := range states { +outer: + for _, state := range states { for i := 0; i < uniqueStates; i++ { if equalStates(state, states[i]) { continue outer @@ -186,68 +254,97 @@ func pruneStates(states []SubexBranch) []SubexBranch { return states[:uniqueStates] } -func processInput(states []SubexBranch, input walk.StructureIter, nesting int) []SubexBranch { - if len(states) == 0 { - return states - } - var tmp []SubexBranch - newStates := make([]SubexBranch, 0, 2) - for { - piece := input.Next() - if piece == nil { - break +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), nesting) + case SubexEatState: + curStates = append(curStates, SubexEatBranch{ + state: s, + aux: state.aux, + }) + default: + panic("Invalid type of state") } + } + return curStates +} - // TODO: break if there are no states at this nesting level left - // TODO: What to do if there are remaining nested states after all pieces have been used? - for _, state := range states { - if state.aux.nesting == nesting { - newStates = append(newStates, state.eat(piece)...) - } else { - newStates = append(newStates, state) - } +func processInput(states []SubexEatBranch, input walk.Edible, nesting []bool) []SubexEatBranch { + newStates := make([]SubexEatBranch, 0, 2) + + for _, state := range states { + if len(state.aux.nesting) > len(nesting) { + continue } - structure, isStructure := piece.(walk.Structure) - if isStructure { - iter := structure.Iter() - newStates = processInput(newStates, iter, nesting + 1) + 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) } + } - tmp = states - states = pruneStates(newStates) - newStates = tmp[:0] - if len(states) == 0 { - return states + switch input := input.(type) { + case walk.StringValue: + for _, r := range input { + newStates = processInput(newStates, walk.RuneEdible(r), append(nesting, true)) } + newStates = processInput(newStates, walk.StringEnd, append(nesting, true)) + case walk.ArrayValue: + for _, el := range input { + newStates = processInput(newStates, walk.NumberValue(el.Index), append(nesting, false)) + newStates = processInput(newStates, el.Value, append(nesting, true)) + } + newStates = processInput(newStates, walk.ArrayEnd, append(nesting, true)) + case walk.MapValue: + for _, el := range input { + newStates = processInput(newStates, walk.StringValue(el.Key), append(nesting, false)) + newStates = processInput(newStates, el.Value, append(nesting, true)) + } + newStates = processInput(newStates, walk.MapEnd, append(nesting, true)) } - return states + + newStates = pruneStates(newStates) + + return newStates } // Run the subex transducer -func RunTransducer(transducer Transducer, input walk.ValueList) (output []walk.Value, err bool) { - states := []SubexBranch{{ +func RunTransducer(transducer Transducer, input []walk.Value) (output []walk.Value, err bool) { + states := addStates(nil, []SubexBranch{{ state: transducer.initialState, - aux: auxiliaryState { - outputStack: OutputStack { - head: walk.ValueList{}, + aux: auxiliaryState{ + outputStack: OutputStack{ + head: walk.OutputValueList(nil), tail: nil, }, - store: make([]walk.OutputList, transducer.storeSize), - nesting: 0, + store: Store{ + values: make([][]walk.Value, transducer.storeSize.values), + runes: make([][]rune, transducer.storeSize.runes), + }, + nesting: nil, }, - }} + }}, nil) + + for _, value := range input { + if len(states) == 0 { + break + } - states = processInput(states, walk.NewValueIter(input), 0) + states = processInput(states, value, nil) + } for _, state := range states { + if len(state.aux.nesting) > 0 { + continue + } acceptingStacks := state.accepting() for _, stack := range acceptingStacks { - output, isValues := stack.head.(walk.ValueList) - if !isValues { - panic("Tried to output a non-values list") - } - return output, false + return stack.head.(walk.OutputValueList), false } } return nil, true diff --git a/subex/main_test.go b/subex/main_test.go index f0350d2..938e5cb 100644 --- a/subex/main_test.go +++ b/subex/main_test.go @@ -1,10 +1,10 @@ package subex import ( - "testing" "main/walk" - "fmt" + "reflect" "strings" + "testing" ) func buildTransducer(subex string) Transducer { @@ -14,429 +14,526 @@ func buildTransducer(subex string) Transducer { return transducer } -func fatalMismatch(t *testing.T, path walk.ValueList, message string) { - var sep string +func fmtValueList(values []walk.Value) string { var builder strings.Builder - for _, segment := range path { - builder.WriteString(sep) - builder.WriteString(segment.Debug()) - sep = "." - } - builder.WriteString(": ") - builder.WriteString(message) - t.Fatal(builder.String()) -} - -func expectEqual(t *testing.T, path walk.ValueList, output walk.Value, expected walk.Value) { - switch expected := expected.(type) { - case walk.NullScalar: - _, isNull := output.(walk.NullScalar) - if !isNull { - fatalMismatch(t, path, fmt.Sprintf("expected null, found %s", output.Debug())) - } - case walk.BoolScalar: - b, isBool := output.(walk.BoolScalar) - if !isBool { - fatalMismatch(t, path, fmt.Sprintf("expected boolean, found %s", output.Debug())) - } - if expected != b { - fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), b.Debug())) - } - case walk.NumberScalar: - n, isNumber := output.(walk.NumberScalar) - if !isNumber { - fatalMismatch(t, path, fmt.Sprintf("expected number, found %s", output.Debug())) - } - if expected != n { - fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), n.Debug())) + builder.WriteRune('[') + for i, value := range values { + if i != 0 { + builder.WriteString(", ") } - case walk.StringStructure: - s, isString := output.(walk.StringStructure) - if !isString { - fatalMismatch(t, path, fmt.Sprintf("expected string, found %s", output.Debug())) - } - if s != expected { - fatalMismatch(t, path, fmt.Sprintf("expected %s, found %s", expected.Debug(), s.Debug())) - } - case walk.ArrayStructure: - array, isArray := output.(walk.ArrayStructure) - if !isArray { - fatalMismatch(t, path, fmt.Sprintf("expected array, found %s", output.Debug())) - } - if len(array) != len(expected) { - fatalMismatch(t, path, fmt.Sprintf("Expected array length %d, found %d", len(expected), len(array))) - } - for i, value := range expected { - expectEqual(t, append(path, walk.NumberScalar(i)), array[i], value) - } - case walk.MapStructure: - m, isMap := output.(walk.MapStructure) - if !isMap { - fatalMismatch(t, path, fmt.Sprintf("expected map, found %s", output.Debug())) - } - for key, expected := range expected { - value, hasValue := m[key] - if !hasValue { - fatalMismatch(t, path, fmt.Sprintf("expected map to have key %s, but it doesn't", key)) - } - expectEqual(t, append(path, walk.StringStructure(key)), value, expected) - } - for key := range m { - _, hasValue := expected[key] - if !hasValue { - fatalMismatch(t, path, fmt.Sprintf("Didn't expect map to have key %s, but it does", key)) - } - } - default: - panic("Expected contains an invalid value") + builder.WriteString(value.Debug()) } + builder.WriteRune(']') + return builder.String() } -func expectOutput(t *testing.T, transducer Transducer, input walk.ValueList, expected walk.ValueList) { - output, err := RunTransducer(transducer, input) - - if err { - t.Fatalf("Error") - } - - if len(output) != len(expected) { - t.Fatalf("Output has incorrect length. Expected %d, got %d", len(expected), len(output)) +func TestSubexMain(t *testing.T) { + type test struct { + subex string + input []walk.Value + expected []walk.Value } - for i, value := range output { - expectEqual(t, walk.ValueList{walk.NumberScalar(i)}, value, expected[i]) - } -} - -func expectReject(t *testing.T, transducer Transducer, input walk.ValueList) { - _, err := RunTransducer(transducer, input) - - if !err { - t.Fatalf("Expected transducer to error, but it accepted input: %v", input) - } -} - -func TestSimpleProcessInput(t *testing.T) { - states := []SubexBranch{{ - state: SubexCopyState { - next: SubexNoneState{}, - filter: anyValueFilter{}, - }, - aux: auxiliaryState { - outputStack: OutputStack { - head: walk.ValueList{}, - tail: nil, + tests := []test { + { + // 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), }, - store: nil, - nesting: 0, - }, - }} - - input := walk.ValueList{ - walk.NumberScalar(2), - } - - states = processInput(states, walk.NewValueIter(input), 0) - - if len(states) != 1 { - t.Fatalf("States has wrong length") - } - - accepting := states[0].accepting() - - if len(accepting) != 1 { - t.Fatalf("Wrong number of accepting branches") - } - - values, isValues := accepting[0].head.(walk.ValueList) - - if !isValues { - t.Fatalf("Output is not a value list") - } - - if len(values) != 1 { - t.Fatalf("Output has wrong length") - } - - if values[0] != walk.NumberScalar(2) { - t.Fatalf("Outputted the wrong value") - } -} - -func TestTopAppendFromEmpty(t *testing.T) { - output := OutputStack { - head: walk.ValueList{}, - tail: nil, - } - - output = topAppend(output, []walk.Value{walk.NumberScalar(1), walk.NumberScalar(2)}) - - values, isValues := output.head.(walk.ValueList) - - if !isValues { - t.Fatalf("head is not values") - } - - if len(values) != 2 { - t.Fatalf("values has the wrong length") - } - - if values[0] != walk.NumberScalar(1) || values[1] != walk.NumberScalar(2) { - t.Fatalf("output has the wrong values") - } -} - -func TestArrayPriority1(t *testing.T) { - expectOutput( - t, - buildTransducer(":[.$_]|."), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(5), + expected: []walk.Value { + walk.NumberValue(5), }, }, - walk.ValueList{ - walk.ArrayStructure{}, - }, - ) -} - -func TestArrayPriority2(t *testing.T) { - expectOutput( - t, - buildTransducer(".|:[.$_]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(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), }, - }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(5), + expected: []walk.Value { + walk.NumberValue(1), + walk.NumberValue(3), + walk.NumberValue(5), + walk.NumberValue(9), + walk.NumberValue(7), }, }, - ) -} - -func TestDropSecondArrayElement(t *testing.T) { - expectOutput( - t, - buildTransducer(":[.(.$_)(.{-0})]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), + { + // Collatz + subex: "[1]*[n%2=0:n,n/2]|[n%2=1:n,n*3+1]", + input: []walk.Value { + walk.NumberValue(32), }, - }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(3), - walk.NumberScalar(4), + expected: []walk.Value { + walk.NumberValue(32), + walk.NumberValue(16), }, }, - ) -} - -func TestDropSecondElement(t *testing.T) { - expectOutput( - t, - buildTransducer(".(.$_)(.{-0})"), - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), - }, - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(3), - walk.NumberScalar(4), + { + subex: `(..)%+`, + input: []walk.Value { + walk.NumberValue(12), + walk.NumberValue(34), + }, + expected: []walk.Value { + walk.NumberValue(46), + }, }, - ) -} - -func TestCopyManyValues(t *testing.T) { - expectOutput( - t, - buildTransducer(".{-0}"), - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), + { + subex: `.`, + input: []walk.Value { + walk.MapValue {{ + Key: "a", + Value: walk.StringValue("b"), + }}, + }, + expected: []walk.Value { + walk.MapValue {{ + Key: "a", + Value: walk.StringValue("b"), + }}, + }, }, - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), - walk.NumberScalar(3), - walk.NumberScalar(4), + { + subex: `~(.)~`, + input: []walk.Value { + walk.StringValue("a"), + }, + expected: []walk.Value { + walk.StringValue("a"), + }, }, - ) -} - -func TestCopyTwoValues(t *testing.T) { - expectOutput( - t, - buildTransducer(".."), - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), + { + subex: `~(.>_(.*))~`, + input: []walk.Value { + walk.StringValue("hello"), + }, + expected: []walk.Value { + walk.StringValue("ello"), + }, }, - walk.ValueList{ - walk.NumberScalar(1), - walk.NumberScalar(2), + { + subex: `#(".".*)-`, + input: []walk.Value { + walk.MapValue { + { + Key: "a", + Value: walk.NullValue{}, + }, + }, + }, + expected: []walk.Value { + walk.StringValue("a"), + walk.NullValue{}, + }, }, - ) -} - -func TestCopyValue(t *testing.T) { - expectOutput( - t, - buildTransducer("."), - walk.ValueList{ - walk.NumberScalar(1), + { + subex: "@(((..)%a<a)*)@", + input: []walk.Value { + walk.ArrayValue { + walk.ArrayElement { + Index: 0, + Value: walk.NullValue{}, + }, + walk.ArrayElement { + Index: 0, + Value: walk.BoolValue(true), + }, + walk.ArrayElement { + Index: 0, + Value: walk.NumberValue(5.4), + }, + walk.ArrayElement { + Index: 5, + Value: walk.StringValue("hello"), + }, + walk.ArrayElement { + Index: 3, + Value: walk.ArrayValue { + walk.ArrayElement { + Index: 0, + Value: walk.NullValue{}, + }, + }, + }, + walk.ArrayElement { + Index: 1, + Value: walk.MapValue { + walk.MapElement { + Key: "key", + Value: walk.StringValue("value"), + }, + }, + }, + }, + }, + expected: []walk.Value { + walk.ArrayValue { + walk.ArrayElement { + Index: 0, + Value: walk.NullValue{}, + }, + walk.ArrayElement { + Index: 0, + Value: walk.NullValue{}, + }, + walk.ArrayElement { + Index: 0, + Value: walk.BoolValue(true), + }, + walk.ArrayElement { + Index: 0, + Value: walk.BoolValue(true), + }, + walk.ArrayElement { + Index: 0, + Value: walk.NumberValue(5.4), + }, + walk.ArrayElement { + Index: 0, + Value: walk.NumberValue(5.4), + }, + walk.ArrayElement { + Index: 5, + Value: walk.StringValue("hello"), + }, + walk.ArrayElement { + Index: 5, + Value: walk.StringValue("hello"), + }, + walk.ArrayElement { + Index: 3, + Value: walk.ArrayValue { + walk.ArrayElement { + Index: 0, + Value: walk.NullValue{}, + }, + }, + }, + walk.ArrayElement { + Index: 3, + Value: walk.ArrayValue { + walk.ArrayElement { + Index: 0, + Value: walk.NullValue{}, + }, + }, + }, + walk.ArrayElement { + Index: 1, + Value: walk.MapValue { + walk.MapElement { + Key: "key", + Value: walk.StringValue("value"), + }, + }, + }, + walk.ArrayElement { + Index: 1, + Value: walk.MapValue { + walk.MapElement { + Key: "key", + Value: walk.StringValue("value"), + }, + }, + }, + }, + }, }, - walk.ValueList{ - walk.NumberScalar(1), + { + subex: "-(`0`.)@", + input: []walk.Value { + walk.NumberValue(4), + }, + expected: []walk.Value { + walk.ArrayValue { + { + Index: 0, + Value: walk.NumberValue(4), + }, + }, + }, }, - ) -} - -func TestSimpleArrayEntry(t *testing.T) { - expectOutput( - t, - buildTransducer(":[..]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(2), + { + 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"), }, }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(2), + { + subex: ":(.)-", + input: []walk.Value { + walk.ArrayValue { + { + Index: 0, + Value: walk.NullValue{}, + }, + }, + }, + expected: []walk.Value { + walk.NullValue{}, }, }, - ) -} - -func TestArrayEntrySum(t *testing.T) { - expectOutput( - t, - buildTransducer(":[%{-0}+]"), - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(1), - walk.NumberScalar(7), - walk.NumberScalar(8), - walk.NumberScalar(3), + { + 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), }, }, - walk.ValueList{ - walk.ArrayStructure{ - walk.NumberScalar(19), + { + 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"), + }, + }, }, }, - ) -} - -func TestStringEmptyMatch(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"\""), - walk.ValueList{ - walk.StringStructure(""), + { + 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"), + }, + }, + }, }, - walk.ValueList{ - walk.StringStructure(""), + { + 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{}, + }, + }, + }, }, - ) -} - -func TestStringSimpleMatch(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"hello\""), - walk.ValueList{ - walk.StringStructure("hello"), + { + 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{}, + }, + }, + }, }, - walk.ValueList{ - walk.StringStructure("hello"), + { + subex: ".*`\"hello\"`", + input: []walk.Value { + walk.NumberValue(1), + walk.NumberValue(2), + }, + expected: []walk.Value { + walk.NumberValue(1), + walk.NumberValue(2), + walk.StringValue("hello"), + }, }, - ) -} + } -func TestDiscardString(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"test\"$_."), - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - }, - walk.ValueList{ - walk.NumberScalar(2), - }, - ) -} + for i, test := range tests { + t.Logf("Running test: %d", i) + lexer := NewStringRuneReader(test.subex) + ast := Parse(lexer) + transducer := CompileTransducer(ast) + output, err := RunTransducer(transducer, test.input) + + if err { + t.Errorf("Subex %q rejected input %v", test.subex, fmtValueList(test.input)) + t.Logf("AST: %v", ast) + continue + } -func TestStringThenValue(t *testing.T) { - expectOutput( - t, - buildTransducer("~\"test\"."), - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - }, - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - }, - ) + if !reflect.DeepEqual(output, test.expected) { + t.Errorf("Subex %q transformed input %s to output %s", test.subex, fmtValueList(test.input), fmtValueList(output)) + } + } } -func TestCutStringFromStart(t *testing.T) { - //transducer := buildTransducer("~\"test\"$_(.{-0})") - lexer := NewStringRuneReader("~\"test\"$_(.{-0})") +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) - t.Log(ast) transducer := CompileTransducer(ast) - expectOutput( - t, - transducer, - walk.ValueList{ - walk.StringStructure("test"), - walk.NumberScalar(2), - walk.StringStructure("test"), - }, - walk.ValueList{ - walk.NumberScalar(2), - walk.StringStructure("test"), - }, - ) - expectOutput( - t, - transducer, - walk.ValueList{ - walk.StringStructure("test"), - }, - walk.ValueList{}, - ) - expectReject( - t, - transducer, - walk.ValueList{ - walk.StringStructure("yeet"), - }, - ) - expectReject( - t, - transducer, - walk.ValueList{}, - ) + 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 35baaa2..01a747b 100644 --- a/subex/parse.go +++ b/subex/parse.go @@ -6,9 +6,64 @@ import ( "strings" ) +type Type int +const ( + 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 { @@ -45,24 +100,24 @@ func parseScalarLiteral(l RuneReader) (walk.Scalar, bool) { if err != nil { panic("Invalid number literal") } - return walk.NumberScalar(number), true + return walk.NumberValue(number), true } switch r { case 'n': if accept(l, "u") && accept(l, "l") && accept(l, "l") { - return walk.NullScalar{}, true + return walk.NullValue{}, true } else { panic("Invalid literal") } case 't': if accept(l, "r") && accept(l, "u") && accept(l, "e") { - return walk.BoolScalar(true), true + return walk.BoolValue(true), true } else { panic("Invalid literal") } case 'f': if accept(l, "a") && accept(l, "l") && accept(l, "s") && accept(l, "e") { - return walk.BoolScalar(false), true + return walk.BoolValue(false), true } else { panic("Invalid literal") } @@ -89,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 { @@ -133,39 +348,224 @@ func parseRepeatRange(l RuneReader) (output []ConvexRange) { return output } -// TODO: Consider if it's worth making better use of the go type system to enforce output being all runes or all values -func parseReplacement(l RuneReader, runic bool) (output []OutputContentAST) { +func parseValueReplacementOLD(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 '`': - break loop - case '$': - slot := l.Next() - if slot == eof { - panic("Missing slot character") - } - output = append(output, OutputLoadAST{slot: slot}) - default: - if runic { - output = append(output, OutputRuneLiteralAST {walk.StringRuneAtom(r)}) - } else { - l.Rewind() - scalar, ok := parseScalarLiteral(l) - if !ok { - panic("Invalid scalar literal") - } - output = append(output, OutputValueLiteralAST {scalar}) - } + case eof: + panic("Missing closing `") + case ' ': + case end: + break loop + case '$': + slot := l.Next() + if slot == eof { + panic("Missing slot character") + } + 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 = SubexASTConcat { + First: output, + Second: SubexASTOutputValueLiteral { + literal: scalar, + }, + } + } + } + return output +} + +func parseRuneReplacement(l RuneReader, end rune) (output SubexAST) { + output = SubexASTEmpty{} + // TODO escaping + loop: for { + r := l.Next() + switch r { + case eof: + panic("Missing closing `") + case end: + break loop + case '<': + slot := l.Next() + if slot == eof { + panic("Missing slot character") + } + output = SubexASTConcat { + First: output, + Second: SubexASTOutputRuneLoad { + slot: slot, + }, + } + default: + 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 @@ -245,167 +645,491 @@ func parseReplacement(l RuneReader, runic bool) (output []OutputContentAST) { // return parts // } -func parseSubex(l RuneReader, minPower int, runic bool) 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, runic) - if !accept(l, ")") { - panic("Missing matching )") - } - // TODO - // case '[': - // rangeParts := parseRangeSubex(l) - // lhs = SubexASTRange {rangeParts} - case ')', ']', '"', '|', ';', '{', '+', '-', '*', '/', '!', '=', '$': + 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 \"") + } + resolveTypes(innerOutType, RuneType) + lhs = SubexASTDestructure { + Destructure: StringStructure, + Structure: StringStructure, + Content: lhs, + } + outType = ValueType + // RuneType + default: 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 runic { - lhs = SubexASTCopyAnyRune{} - } else { - lhs = SubexASTCopyAnyValue{} - } - case '?': - lhs = SubexASTCopyBool{} - case '%': - lhs = SubexASTCopyNumber{} - case ':': - if runic { - lhs = SubexASTCopyRune {':'} - } else { - if !accept(l, "[") { - panic("Missing [ after :") + 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, } - lhs = SubexASTEnterArray {parseSubex(l, 0, runic)} - if !accept(l, "]") { - panic("Missing matching ]") + case RuneType: + lhs = SubexASTOutputRuneLoad { + slot: slot, } + default: + panic("Invalid inType") } - case '`': - lhs = SubexASTOutput {parseReplacement(l, runic)} - case '~': - if runic { - lhs = SubexASTCopyRune {'~'} - } else { - if !accept(l, "\"") { - panic("Missing \" after ~") - } - lhs = SubexASTEnterString {parseSubex(l, 0, true)} - if !accept(l, "\"") { - panic("Missing matching \"") - } + } + case '[': + switch inType { + case ValueType: + lhs = parseNumberMapping(l) + if !accept(l, "]") { + panic("Missing matching ]") } - // TODO - // case '_': - // lhs = SubexASTCopyStringAtom{} - // case '#': - // lhs = SubexASTCopyString{} - // case ',': - // lhs = SubexASTCopyValue{} - // case '"': - // lhs = SubexASTCopyScalar {walk.NewAtomStringTerminal()} - // case '~': - // literals := parseNonStringLiteral(l) - // var replacement []OutputContentAST - // for _, literal := range literals { - // replacement = append(replacement, OutputValueLiteralAST {literal}) - // } - // lhs = SubexASTOutput {replacement} default: - if runic { - lhs = SubexASTCopyRune {r} - } else { - l.Rewind() - scalar, ok := parseScalarLiteral(l) - if !ok { - panic("Invalid subex") - } - lhs = SubexASTCopyScalar {scalar} + // 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 `") } - } - loop: for { - if minPower <= 20 { - next := parseSubex(l, 21, runic) - if next != nil && (next != SubexASTEmpty{}) { - lhs = SubexASTConcat{lhs, next} - continue loop + 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 { 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 { - lhs = SubexASTStore{ + 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, runic) - if rhs == nil { - panic("Missing subex after |") - } - lhs = SubexASTOr{lhs, rhs} - case r == ';' && minPower <= 10: - rhs := parseSubex(l, 11, runic) - if rhs == nil { - panic("Missing subex after ;") - } - lhs = SubexASTJoin{ - Content: lhs, - Delimiter: rhs, + 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") + } } 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, false) + 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 e02091d..01a5e0d 100644 --- a/subex/subexast.go +++ b/subex/subexast.go @@ -7,54 +7,131 @@ import ( // A node in the AST of a subex type SubexAST interface { - compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState + compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState } // Process the first subex, then the second, splitting the input text in two type SubexASTConcat struct { First, Second SubexAST } -func (ast SubexASTConcat) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return ast.First.compileWith(ast.Second.compileWith(next, slotMap, runic), slotMap, runic) +func (ast SubexASTConcat) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + return ast.First.compileWith( + ast.Second.compileWith(next, slotMap, inType, outType), + slotMap, + inType, + outType, + ) } func (ast SubexASTConcat) String() string { return fmt.Sprintf("(%v)(%v)", ast.First, ast.Second) } // Processing a subex and storing the output in a slot instead of outputting it -type SubexASTStore struct { +type SubexASTStoreValues struct { Match SubexAST Slot rune } -func (ast SubexASTStore) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +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, runic) - - if !runic { - return &SubexCaptureBeginState { - next: newNext, + } + switch ast.Slot { + case '+': + endState = &SubexCaptureBeginState { + next: ast.Match.compileWith(&SubexArithmeticEndState { + calculate: arithmeticSum, + next: endState, + }, slotMap, inType, outType), } - } else { - return &SubexCaptureRunesBeginState { - next: newNext, + 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: endState, } } -func (ast SubexASTStore) String() string { +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 } -func (ast SubexASTOr) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTOr) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { return &SubexGroupState { - ast.First.compileWith(next, slotMap, runic), - ast.Second.compileWith(next, slotMap, runic), + ast.First.compileWith(next, slotMap, inType, outType), + ast.Second.compileWith(next, slotMap, inType, outType), } } func (ast SubexASTOr) String() string { @@ -84,19 +161,24 @@ func (cr ConvexRange) decrement() ConvexRange { return ConvexRange{cr.Start - 1, cr.End - 1} } } -func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { min, _ := cr.minmax() if min != 0 { - return content.compileWith(cr.decrement().compile(content, next, slotMap, runic), slotMap, runic) + return content.compileWith( + cr.decrement().compile(content, next, slotMap, inType, outType), + slotMap, + inType, + outType, + ) } if cr.Start == -1 { state := &SubexGroupState {nil, next} - state.first = content.compileWith(state, slotMap, runic) + state.first = content.compileWith(state, slotMap, inType, outType) return state } if cr.End == -1 { state := &SubexGroupState {next, nil} - state.second = content.compileWith(state, slotMap, runic) + state.second = content.compileWith(state, slotMap, inType, outType) return state } @@ -104,7 +186,7 @@ func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMa state := next; for i := 0; i < cr.Start; i += 1 { state = &SubexGroupState { - content.compileWith(state, slotMap, runic), + content.compileWith(state, slotMap, inType, outType), next, } } @@ -114,7 +196,7 @@ func (cr ConvexRange) compile(content SubexAST, next SubexState, slotMap *SlotMa for i := 0; i < cr.End; i += 1 { state = &SubexGroupState { next, - content.compileWith(state, slotMap, runic), + content.compileWith(state, slotMap, inType, outType), } } return state @@ -127,10 +209,10 @@ type SubexASTRepeat struct { Content SubexAST Acceptable []ConvexRange } -func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTRepeat) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { var state SubexState = &SubexDeadState{} for _, convex := range ast.Acceptable { - state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap, runic)} + state = &SubexGroupState {state, convex.compile(ast.Content, next, slotMap, inType, outType)} } return state } @@ -142,7 +224,10 @@ func (ast SubexASTRepeat) String() string { type SubexASTCopyScalar struct { Scalar walk.Scalar } -func (ast SubexASTCopyScalar) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyScalar) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTCopyScalar") + } return &SubexCopyState{ filter: selectScalarFilter {ast.Scalar}, next: next, @@ -153,26 +238,41 @@ func (ast SubexASTCopyScalar) String() string { } type SubexASTCopyAnyRune struct {} -func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyAnyRune) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != RuneType || outType != RuneType { + panic("Invalid types for SubexASTCopyAnyRune") + } return &SubexCopyRuneState { next: next, filter: anyRuneFilter{}, } } +func (ast SubexASTCopyAnyRune) String() string { + return ".RUNE" +} type SubexASTCopyRune struct { rune rune } -func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyRune) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != RuneType || outType != RuneType { + 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, runic bool) SubexState { +func (ast SubexASTCopyBool) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTCopyBool") + } return &SubexCopyState { next: next, filter: anyBoolFilter{}, @@ -184,7 +284,10 @@ func (ast SubexASTCopyBool) String() string { // Read in a single atom that must be a number and output it unchanged type SubexASTCopyNumber struct {} -func (ast SubexASTCopyNumber) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTCopyNumber) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTCopyNumber") + } return &SubexCopyState { next: next, filter: anyNumberFilter{}, @@ -194,34 +297,30 @@ func (ast SubexASTCopyNumber) String() string { return "%" } -// Read in a full string value and copy it out unchanged -// # is equivalent to "_{-0}" -// TODO -// type SubexASTCopyString struct {} -// func (ast SubexASTCopyString) compileWith(next SubexState, slotMap *SlotMap) SubexState { -// stringAtomState := &SubexCopyStringAtomState { -// next: nil, -// } -// stringContentState := &SubexGroupState { -// &SubexCopyScalarState { -// scalar: walk.NewAtomStringTerminal(), -// next: next, -// }, -// stringAtomState, -// } -// stringAtomState.next = stringContentState -// return &SubexCopyScalarState { -// scalar: walk.NewAtomStringTerminal(), -// next: stringContentState, -// } -// } -// func (ast SubexASTCopyString) String() string { -// return "#" -// } +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, runic bool) SubexState { +func (ast SubexASTCopyAnyValue) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if inType != ValueType || outType != ValueType { + panic("Invalid types for SubexASTCopyAnyValue") + } return &SubexCopyState { next: next, filter: anyValueFilter{}, @@ -231,6 +330,7 @@ func (ast SubexASTCopyAnyValue) String() string { return "." } +/* type OutputContentAST interface { compile(slotMap *SlotMap) OutputContent } @@ -273,145 +373,119 @@ func (ast SubexASTOutput) compileWith(next SubexState, slotMap *SlotMap, runic b func (ast SubexASTOutput) String() string { return "=...=" } +*/ -// Read in a repeated subex separated by a delimiter. Greedy -type SubexASTJoin struct { - Content, Delimiter SubexAST +type SubexASTNumberMapping struct { + Range NumberExpr + Replace []NumberExpr } -func (ast SubexASTJoin) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - afterContentState := &SubexGroupState { - nil, - next, +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") } - manyContentsState := ast.Content.compileWith(afterContentState, slotMap, runic) - afterContentState.first = ast.Delimiter.compileWith(manyContentsState, slotMap, runic) - return &SubexGroupState { - manyContentsState, - next, + return &SubexNumberMappingState { + Range: ast.Range, + Replace: ast.Replace, + next: next, } } -func (ast SubexASTJoin) String() string { - return fmt.Sprintf("(%v);(%v)", ast.Content, ast.Delimiter) -} -// Run each input Atom through a map to produce an output Atom -// Atoms not in the map cause this to not match -// type SubexASTRange struct { -// Parts map[walk.Atom]walk.Atom -// } -// func (ast SubexASTRange) compileWith(next SubexState, slotMap *SlotMap) SubexState { -// return &SubexRangeState { -// parts: ast.Parts, -// next: next, -// } -// } -// func (ast SubexASTRange) String() string { -// return fmt.Sprintf("[abc=xyz]") -// } - -// 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 +type SubexASTOutputValueLiteral struct { + literal walk.Scalar } -func (ast SubexASTSum) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: sumValues, - }, slotMap, runic), +func (ast SubexASTOutputValueLiteral) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + if outType != ValueType { + panic("Invalid outType for SubexASTOutputValueLiteral") } -} -func (ast SubexASTSum) String() string { - return fmt.Sprintf("(%v)+", ast.Content) -} - -// Like sum but for AND and product -type SubexASTProduct struct { - Content SubexAST -} -func (ast SubexASTProduct) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: multiplyValues, - }, slotMap, runic), + return &SubexOutputValueLiteralState { + literal: ast.literal, + next: next, } } -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 +type SubexASTOutputValueLoad struct { + slot rune } -func (ast SubexASTNegate) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: negateValues, - }, slotMap, runic), +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, } -} -func (ast SubexASTNegate) String() string { - return fmt.Sprintf("(%v)-", ast.Content) } -// Runs the content Subex and collects the output -// If it is a list of atoms castable to numbers, it takes the reciprocal of them all and outputs them -// Else it rejects -type SubexASTReciprocal struct { - Content SubexAST +type SubexASTOutputRuneLiteral struct { + literal rune } -func (ast SubexASTReciprocal) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: reciprocalValues, - }, slotMap, runic), +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, } -} -func (ast SubexASTReciprocal) 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 +type SubexASTOutputRuneLoad struct { + slot rune } -func (ast SubexASTNot) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: ast.Content.compileWith(&SubexArithmeticEndState { - next: next, - calculate: notValues, - }, slotMap, runic), +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, } -} -func (ast SubexASTNot) String() string { - return fmt.Sprintf("(%v)!", ast.Content) } -// Runs the content Subex and collects the output -// Replaces it with true if all output values are equal and false otherwise -type SubexASTEqual struct { - Content SubexAST +// Run each input Atom through a map to produce an output Atom +// Atoms not in the map cause this to not match +// type SubexASTRange struct { +// Parts map[walk.Atom]walk.Atom +// } +// func (ast SubexASTRange) compileWith(next SubexState, slotMap *SlotMap) SubexState { +// return &SubexRangeState { +// parts: ast.Parts, +// next: next, +// } +// } +// func (ast SubexASTRange) String() string { +// return fmt.Sprintf("[abc=xyz]") +// } + +type SubexASTBinop struct { + op func ([]walk.Value) ([]walk.Value, error) + lhs, rhs SubexAST } -func (ast SubexASTEqual) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +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: equalValues, - }, slotMap, runic), + next: ast.lhs.compileWith( + ast.rhs.compileWith( + &SubexArithmeticEndState { + next: next, + calculate: ast.op, + }, + slotMap, + inType, + outType, + ), + slotMap, + inType, + outType, + ), } } // Does nothing type SubexASTEmpty struct {} -func (ast SubexASTEmpty) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { +func (ast SubexASTEmpty) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { return next } func (ast SubexASTEmpty) String() string { @@ -421,10 +495,11 @@ 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, runic bool) SubexState { - newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, runic) - if !runic { +func (ast SubexASTDiscard) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState { + newNext := ast.Content.compileWith(&SubexDiscardState {next}, slotMap, inType, ast.InnerOutType) + if inType == ValueType { return &SubexCaptureBeginState { next: newNext, } @@ -438,57 +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, runic bool) SubexState { - return &SubexCaptureBeginState { - next: &SubexIncrementNestState { +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") + } + + 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") + } + + 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: &SubexCaptureBeginState { - next: ast.Content.compileWith( - &SubexDiscardTerminalState { - terminal: walk.ArrayEndTerminal{}, - next: &SubexDecrementNestState { - next: &SubexConstructArrayState {next: next}, - }, - }, - slotMap, - runic, - ), + next: &SubexIncrementNestState { + keys: true, + next: beginConstruct, }, }, }, - }, - } -} - -type SubexASTEnterString struct { - Content SubexAST -} -func (ast SubexASTEnterString) compileWith(next SubexState, slotMap *SlotMap, runic bool) SubexState { - return &SubexCaptureBeginState { - next: &SubexIncrementNestState { + } + case ArrayValuesStructure: + return &SubexCaptureBeginState { next: &SubexCopyState { - filter: anyStringFilter{}, + filter: anyArrayFilter{}, next: &SubexDiscardState { - next: &SubexCaptureRunesBeginState { - next: ast.Content.compileWith( - &SubexDecrementNestState { - next: &SubexDiscardTerminalState { - terminal: walk.StringEndTerminal{}, - next: &SubexConstructStringState {next: next}, - }, - }, - slotMap, - true, - ), + 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/subexast_test.go b/subex/subexast_test.go deleted file mode 100644 index 156162e..0000000 --- a/subex/subexast_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package subex - -import ( - "testing" -) - -func expectASTEqual(t *testing.T, ast SubexAST, expected SubexAST) { - if ast == expected { - return - } - - t.Fatalf("Expected %v, found %v", expected, ast) -} - -func expectAST(t *testing.T, subex string, expected SubexAST) { - lexer := NewStringRuneReader(subex) - ast := Parse(lexer) - expectASTEqual(t, ast, expected) -} diff --git a/subex/subexstate.go b/subex/subexstate.go index 0b21c93..bf5bab9 100644 --- a/subex/subexstate.go +++ b/subex/subexstate.go @@ -4,13 +4,24 @@ package subex // e.g. Combine all of the copy states into a single type that has a filter function import ( + "fmt" "main/walk" + "strings" ) -// A state of execution for the transducer type SubexState interface { +} + +type SubexEpsilonState interface { + SubexState + epsilon(aux auxiliaryState) []SubexBranch +} + +// A state of execution for the transducer +type SubexEatState interface { + SubexState // Eat a Atom and transition to any number of new states - eat(aux auxiliaryState, char walk.Edible) []SubexBranch + eat(aux auxiliaryState, edible walk.Edible) []SubexBranch // Find accepting states reachable through epsilon transitions and return their outputs accepting(aux auxiliaryState) []OutputStack } @@ -19,13 +30,21 @@ type SubexState interface { type SubexGroupState struct { first, second SubexState } -func (state SubexGroupState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexGroupState) epsilon(aux auxiliaryState) []SubexBranch { otherAux := aux.cloneStore() - return append(state.first.eat(aux, char), state.second.eat(otherAux, char)...) + return []SubexBranch { + { + state: state.first, + aux: aux, + }, + { + state: state.second, + aux: otherAux, + }, + } } -func (state SubexGroupState) accepting(aux auxiliaryState) []OutputStack { - otherAux := aux.cloneStore() - return append(state.first.accepting(aux), state.second.accepting(otherAux)...) +func (state SubexGroupState) String() string { + return fmt.Sprintf("{%T %p, %T %p}", state.first, state.first, state.second, state.second) } type SubexCopyState struct { @@ -39,7 +58,7 @@ func (state SubexCopyState) eat(aux auxiliaryState, edible walk.Edible) []SubexB } return []SubexBranch{{ state: state.next, - aux: aux.topAppend(walk.ValueList{value}), + aux: aux.topAppend([]walk.Value{value}), }} } func (state SubexCopyState) accepting(aux auxiliaryState) []OutputStack { @@ -51,29 +70,73 @@ type SubexCopyRuneState struct { filter runeFilter } func (state SubexCopyRuneState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - r, isRune := edible.(walk.StringRuneAtom) - if !isRune || !state.filter.runeFilter(r) { + r, isRune := edible.(walk.RuneEdible) + if !isRune || !state.filter.runeFilter(rune(r)) { return nil } return []SubexBranch{{ state: state.next, - aux: aux.topAppend(walk.RuneList{r}), + aux: aux.topAppendRune([]rune{rune(r)}), }} } func (state SubexCopyRuneState) accepting(aux auxiliaryState) []OutputStack { return nil } +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 { next SubexState } -func (state SubexCaptureBeginState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - return state.next.eat(aux.pushOutput(walk.ValueList{}), char) -} -func (state SubexCaptureBeginState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.pushOutput(walk.ValueList{})) +func (state SubexCaptureBeginState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.pushOutput(nil), + }} } func (state SubexCaptureBeginState) String() string { return "CaptureBeginState" @@ -82,24 +145,22 @@ func (state SubexCaptureBeginState) String() string { type SubexCaptureRunesBeginState struct { next SubexState } -func (state SubexCaptureRunesBeginState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - return state.next.eat(aux.pushOutput(walk.RuneList{}), char) -} -func (state SubexCaptureRunesBeginState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.pushOutput(walk.RuneList{})) +func (state SubexCaptureRunesBeginState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.pushOutputRunes(nil), + }} } // Discard the top of the OutputStack type SubexDiscardState struct { next SubexState } -func (state SubexDiscardState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - _, newAux := aux.popOutput() - return state.next.eat(newAux, char) -} -func (state SubexDiscardState) accepting(aux auxiliaryState) []OutputStack { - _, newAux := aux.popOutput() - return state.next.accepting(newAux) +func (state SubexDiscardState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.popDiscardOutput(), + }} } // Pop the top of the OutputStack which contains the stuff outputted since the start of the store @@ -108,96 +169,74 @@ type SubexStoreEndState struct { slot int next SubexState } -func (state SubexStoreEndState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - toStore, aux := aux.popOutput() - aux = aux.withValue(state.slot, toStore) - return state.next.eat(aux, char) -} -func (state SubexStoreEndState) accepting(aux auxiliaryState) []OutputStack { +func (state SubexStoreEndState) epsilon(aux auxiliaryState) []SubexBranch { toStore, aux := aux.popOutput() - aux = aux.withValue(state.slot, toStore) - return state.next.accepting(aux) -} - -// A part of an output literal, either an Atom or a slot from which to load -type OutputContent interface { - // Given the current store, return the ValueList produced by the TransducerOutput - buildValues(Store) walk.ValueList - // Given the current store, return the RuneList produced by the TransducerOutput - buildRunes(Store) walk.RuneList + return []SubexBranch {{ + state: state.next, + aux: aux.withValue(state.slot, toStore), + }} } -// 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} +type SubexStoreRunesEndState struct { + slot int + next SubexState } -func (replacement OutputValueLiteral) buildRunes(store Store) walk.RuneList { - // TODO: serialise to JSON - panic("Unimplemented!") +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, + }} } -// 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!") +type SubexOutputValueLiteralState struct { + literal walk.Scalar + next SubexState } -func (replacement OutputRuneLiteral) buildRunes(store Store) walk.RuneList { - return walk.RuneList {replacement.rune} +func (state SubexOutputValueLiteralState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend([]walk.Value {state.literal}), + }} } -// An OutputContent which is a slot that is loaded from -type OutputLoad struct { +type SubexOutputValueLoadState 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 SubexOutputValueLoadState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend(aux.store.values[state.slot]), + }} } -// Don't read in anything, just output the series of data and slots specified -type SubexOutputState struct { - content []OutputContent +type SubexOutputRuneLiteralState struct { + literal rune 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 SubexOutputRuneLiteralState) epsilon(aux auxiliaryState) []SubexBranch { + return []SubexBranch {{ + state: state.next, + aux: aux.topAppendRune([]rune {state.literal}), + }} } -func (state SubexOutputState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - content := state.build(aux.store) - nextStates := state.next.eat(aux.topAppend(content), char) - return nextStates + +type SubexOutputRuneLoadState struct { + slot int + next SubexState } -func (state SubexOutputState) accepting(aux auxiliaryState) []OutputStack { - content := state.build(aux.store) - outputStacks := state.next.accepting(aux.topAppend(content)) - return outputStacks +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 type SubexNoneState struct {} -func (state SubexNoneState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexNoneState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { return nil } func (state SubexNoneState) accepting(aux auxiliaryState) []OutputStack { @@ -206,7 +245,7 @@ func (state SubexNoneState) accepting(aux auxiliaryState) []OutputStack { // A dead end state, handy for making internals work nicer but technically redundant type SubexDeadState struct {} -func (state SubexDeadState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { +func (state SubexDeadState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { return nil } func (state SubexDeadState) accepting (aux auxiliaryState) []OutputStack { @@ -239,31 +278,18 @@ func (state SubexDeadState) accepting (aux auxiliaryState) []OutputStack { type SubexArithmeticEndState struct { next SubexState - calculate func(walk.ValueList) (walk.ValueList, error) + calculate func([]walk.Value) ([]walk.Value, error) } -func (state SubexArithmeticEndState) eat(aux auxiliaryState, char walk.Edible) []SubexBranch { - toCompute, aux := aux.popOutput() - values, isValues := toCompute.(walk.ValueList) - if !isValues { - panic("Tried to do arithmetic on non-values") - } +func (state SubexArithmeticEndState) epsilon(aux auxiliaryState) []SubexBranch { + values, aux := aux.popOutput() result, err := state.calculate(values) if err != nil { return nil } - return state.next.eat(aux.topAppend(result), char) -} -func (state SubexArithmeticEndState) accepting(aux auxiliaryState) []OutputStack { - toCompute, aux := aux.popOutput() - values, isValues := toCompute.(walk.ValueList) - if !isValues { - panic("Tried to do arithmetic on non-values") - } - result, err := state.calculate(values) - if err != nil { - return nil - } - return state.next.accepting(aux.topAppend(result)) + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend(result), + }} } type SubexDiscardTerminalState struct { @@ -286,55 +312,102 @@ func (state SubexDiscardTerminalState) accepting(aux auxiliaryState) []OutputSta type SubexConstructArrayState struct { next SubexState } -func (state SubexConstructArrayState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - outputs, aux := aux.popOutput() - values, isValues := outputs.(walk.ValueList) - if !isValues { - panic("Tried to create an array from non-values") +func (state SubexConstructArrayState) epsilon(aux auxiliaryState) []SubexBranch { + values, aux := aux.popOutput() + var array walk.ArrayValue + if len(values) % 2 != 0 { + panic("Tried to construct array with odd length input") } - array := walk.ArrayStructure(values) - return state.next.eat(aux.topAppend(walk.ValueList{array}), edible) -} -func (state SubexConstructArrayState) accepting(aux auxiliaryState) []OutputStack { - outputs, aux := aux.popOutput() - values, isValues := outputs.(walk.ValueList) - if !isValues { - panic("Tried to create an array from non-values") + for i := 0; i < len(values); i += 2 { + index, isNum := values[i].(walk.NumberValue) + if !isNum { + panic("Tried to construct array with non-numeric index") + } + array = append(array, walk.ArrayElement { + Index: int(index), + Value: values[i + 1], + }) } - array := walk.ArrayStructure(values) - return state.next.accepting(aux.topAppend(walk.ValueList{array})) + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend([]walk.Value {array}), + }} } -type SubexConstructStringState struct { +type SubexConstructArrayValuesState struct { next SubexState } -func (state SubexConstructStringState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - outputs, aux := aux.popOutput() - runes, isRunes := outputs.(walk.RuneList) - if !isRunes { - panic("Tried to create a string from non-runes") +func (state 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, + }) } - s := walk.StringStructure(runes) - return state.next.eat(aux.topAppend(walk.ValueList{s}), edible) -} -func (state SubexConstructStringState) accepting(aux auxiliaryState) []OutputStack { - outputs, aux := aux.popOutput() - runes, isRunes := outputs.(walk.RuneList) - if !isRunes { - panic("Tried to create a string from non-runes") + 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], + }) } - s := walk.StringStructure(runes) - return state.next.accepting(aux.topAppend(walk.ValueList{s})) + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend([]walk.Value {m}), + }} } -type SubexIncrementNestState struct { +type SubexConstructStringState struct { next SubexState } -func (state SubexIncrementNestState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - return state.next.eat(aux.incNest(), edible) +func (state SubexConstructStringState) construct(runes []rune) []walk.Value { + var builder strings.Builder + for _, r := range runes { + builder.WriteRune(r) + } + return []walk.Value{walk.StringValue(builder.String())} } -func (state SubexIncrementNestState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.incNest()) +func (state SubexConstructStringState) epsilon(aux auxiliaryState) []SubexBranch { + runes, aux := aux.popOutputRunes() + return []SubexBranch {{ + state: state.next, + aux: aux.topAppend(state.construct(runes)), + }} +} +func (state SubexConstructStringState) String() string { + return "SubexConstructStringState" +} + +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, + }} } func (state SubexIncrementNestState) String() string { return "IncrementNestState" @@ -343,9 +416,11 @@ func (state SubexIncrementNestState) String() string { type SubexDecrementNestState struct { next SubexState } -func (state SubexDecrementNestState) eat(aux auxiliaryState, edible walk.Edible) []SubexBranch { - return state.next.eat(aux.decNest(), edible) -} -func (state SubexDecrementNestState) accepting(aux auxiliaryState) []OutputStack { - return state.next.accepting(aux.decNest()) +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, + }} } diff --git a/subex/subexstate_test.go b/subex/subexstate_test.go deleted file mode 100644 index 4eb8969..0000000 --- a/subex/subexstate_test.go +++ /dev/null @@ -1,4 +0,0 @@ -package subex - -import ( -) diff --git a/walk/read.go b/walk/read.go index f25109c..5aedafb 100644 --- a/walk/read.go +++ b/walk/read.go @@ -6,6 +6,6 @@ type StredReader interface { } type StredWriter interface { - Write(WalkItem) error + Write(Value) error AssertDone() -}
\ No newline at end of file +} diff --git a/walk/walk.go b/walk/walk.go index fc9e9de..e66feff 100644 --- a/walk/walk.go +++ b/walk/walk.go @@ -7,7 +7,41 @@ import ( type PathSegment interface {} +type OutputList interface { + outputList() +} + +type OutputValueList []Value +func (_ OutputValueList) outputList() {} + +type OutputRuneList []rune +func (_ OutputRuneList) outputList() {} + +// Can be passed to .eat on a state +type Edible interface { + edible() +} + +type RuneEdible rune +func (_ RuneEdible) edible() {} + +type Terminal int +const ( + ArrayEnd Terminal = iota + MapEnd + StringEnd +) +func (_ Terminal) edible() {} + +// Simple value +// Has a literal +type Scalar interface { + Value + scalar() +} + type Value interface { + Edible value() Debug() string } @@ -17,6 +51,8 @@ func (_ NullValue) value() {} func (_ NullValue) Debug() string { return "null" } +func (_ NullValue) edible() {} +func (_ NullValue) scalar() {} type BoolValue bool func (_ BoolValue) value() {} @@ -26,24 +62,23 @@ func (b BoolValue) Debug() string { } return "false" } +func (_ BoolValue) edible() {} +func (_ BoolValue) scalar() {} type NumberValue float64 func (_ NumberValue) value() {} func (n NumberValue) Debug() string { return fmt.Sprintf("%v", float64(n)) } - -type RuneValue rune -func (_ RuneValue) value() {} -func (r RuneValue) Debug() string { - return string(r) -} +func (_ NumberValue) edible() {} +func (_ NumberValue) scalar() {} type StringValue string func (_ StringValue) value() {} func (s StringValue) Debug() string { return fmt.Sprintf("%q", string(s)) } +func (_ StringValue) edible() {} type ArrayElement struct { Index int @@ -66,6 +101,7 @@ func (array ArrayValue) Debug() string { builder.WriteRune(']') return builder.String() } +func (_ ArrayValue) edible() {} type MapElement struct { Key string @@ -88,6 +124,7 @@ func (m MapValue) Debug() string { builder.WriteRune('}') return builder.String() } +func (_ MapValue) edible() {} type WalkItem struct { Value Value @@ -111,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") - } - } -} |
