<- Back to shtanton's homepage
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main/command.go37
-rw-r--r--main/lex.go2
-rw-r--r--main/main_test.go69
-rw-r--r--main/parse.go4
-rw-r--r--subex/filter.go20
-rw-r--r--subex/parse.go72
-rw-r--r--subex/subexast.go12
-rw-r--r--subex/subexstate.go78
8 files changed, 180 insertions, 114 deletions
diff --git a/main/command.go b/main/command.go
index 38e1c95..bbbb036 100644
--- a/main/command.go
+++ b/main/command.go
@@ -138,9 +138,42 @@ func (cmd MergeCommand) String() string {
return "m"
}
-type FullMergeCommand struct {}
+type FullMergeCommand struct {
+ subex subex.Transducer
+}
func (cmd FullMergeCommand) exec(state *ProgramState) {
- panic("Unimplemented")
+ _, notOk := runSubex(cmd.subex, state.value)
+ if notOk {
+ state.pc++
+ return
+ }
+ if !state.start {
+ state.pc += 2
+ return
+ }
+
+ for {
+ item, err := state.Read()
+ if err != nil {
+ panic("Missing next value")
+ }
+
+ _, nonTerminal := runSubex(cmd.subex, []walk.Value{item.Value})
+
+ state.value = append(
+ state.value[:len(state.value) - 1],
+ walk.Merge(state.value[len(state.value) - 1], item.Value)...
+ )
+
+ if !nonTerminal && item.End {
+ state.prevStart = item.PrevStart
+ state.start = item.Start
+ state.end = item.End
+ state.nextEnd = item.NextEnd
+ state.pc += 2
+ return
+ }
+ }
}
func (cmd FullMergeCommand) String() string {
return "M"
diff --git a/main/lex.go b/main/lex.go
index 8e66890..da517cc 100644
--- a/main/lex.go
+++ b/main/lex.go
@@ -180,7 +180,7 @@ func lexCommand(l *lexer) stateFunc {
case '}':
l.emit(TokenRBrace)
return lexCommand
- case 's', 'S':
+ case 's', 'S', 'M':
l.emit(TokenCommand)
return lexSubstitution
case 'x', 'X', 'y', 'Y', 'z', 'Z', 'n', 'N':
diff --git a/main/main_test.go b/main/main_test.go
index 1510497..076693d 100644
--- a/main/main_test.go
+++ b/main/main_test.go
@@ -5,9 +5,11 @@ import (
"testing"
)
-var 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 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 TestMain(t *testing.T) {
+func TestSpecificCases(t *testing.T) {
type test struct {
name string
program string
@@ -86,6 +88,69 @@ func TestMain(t *testing.T) {
input: miscInput,
expected: `["Charlie Johnson","Tom Johnson","Charlie Chaplin","John Johnson"]`,
},
+ {
+ name: "Get full names with merge full command",
+ program: "s/#(\"people\"$_ :(): )-/p M/#( \"people\" @( . #()# )@ )#/{ s/#( \"people\"$_ @( . #[ \"first_name\" \".{-0}$a\" | \"last_name\" \".{-0}$b\" | .. $_]- `\"$a $b\"` )@ )-/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `["Charlie Johnson","Tom Johnson","Charlie Chaplin","John Johnson"]`,
+ },
+ {
+ name: "Verbose concat array values",
+ program: "as/#( \"array\"$_ :(): )-/{ :s N/#( .$_ . )-/{ es/.{-0}:():/be mbs } :em s/:( -( ~(.{-0}` `)-{-0} ~(.{-0})- )~ )-/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `"Hello world these are values"`,
+ },
+ {
+ name: "Short concat array values",
+ program: "M/#( \"array\" :(): )#/{ s/#( \"array\"$_ :( .{-0} )- )-/ s/-( ~(.{-0}` `)-{-0} ~(.{-0})- )~/p }",
+ quiet: true,
+ input: miscInput,
+ expected: `"Hello world these are values"`,
+ },
+ {
+ name: "Drop first element of array",
+ program: `s/#( "people" @( 0 . )@ )#/d`,
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values"],"people":[{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ {
+ name: "Drop last element of array",
+ program: `M/#( "people" @( . , )@ )#/{ Ed }`,
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122}]}`,
+ },
+ {
+ name: "Drop last element of simple array",
+ program: `s/#( "array" @( . . )@ )#/{ Ed }`,
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ {
+ name: "Drop last element of mixed array",
+ program: `M/#( "array" @( . , )@ )#/{ Ed }`,
+ input: mixedArray,
+ expected: `{"array":["first",null,3,{"name":"second"}]}`,
+ },
+ {
+ name: "Drop last element of mixed array 2",
+ program: `M/#( "array" @( . , )@ )#/{ Ed }`,
+ input: mixedArray2,
+ expected: `{"array":["first",null,3,"second"]}`,
+ },
+ {
+ name: "Prepend to array",
+ program: "as/#( \"array\" :( `\"First\"` ): )#/",
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["First","Hello","world","these","are","values"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
+ {
+ name: "Append to array",
+ program: "es/#( \"array\" :( `\"Last\"` ): )#/",
+ input: miscInput,
+ expected: `{"something":{"nested":"Here is my test value"},"array":["Hello","world","these","are","values","Last"],"people":[{"first_name":"Charlie","last_name":"Johnson","age":22},{"first_name":"Tom","last_name":"Johnson","age":18},{"first_name":"Charlie","last_name":"Chaplin","age":122},{"first_name":"John","last_name":"Johnson","age":48}]}`,
+ },
}
for _, test := range tests {
diff --git a/main/parse.go b/main/parse.go
index 36917ac..36bd3ee 100644
--- a/main/parse.go
+++ b/main/parse.go
@@ -77,6 +77,10 @@ func (p *parser) parseBasicCommand(commands []Command, commandChar rune) []Comma
return append(commands, SubstituteAppendNextCommand {subex}, JumpCommand {len(commands) + 3})
case 'm':
return append(commands, MergeCommand{})
+ case 'M':
+ ast := p.parseSubex()
+ subex := subex.CompileTransducer(ast)
+ return append(commands, FullMergeCommand {subex}, JumpCommand {len(commands) + 3})
case 's':
ast := p.parseSubex()
subex := subex.CompileTransducer(ast)
diff --git a/subex/filter.go b/subex/filter.go
index ae4b8ab..309d6c7 100644
--- a/subex/filter.go
+++ b/subex/filter.go
@@ -27,6 +27,26 @@ func (_ anyBoolFilter) valueFilter(value walk.Value) bool {
return isBool
}
+type simpleValueFilter struct {}
+func (_ simpleValueFilter) valueFilter(value walk.Value) bool {
+ switch value := value.(type) {
+ case walk.NullValue:
+ return true
+ case walk.BoolValue:
+ return true
+ case walk.NumberValue:
+ return true
+ case walk.StringValue:
+ return true
+ case walk.ArrayValue:
+ return len(value) == 0
+ case walk.MapValue:
+ return len(value) == 0
+ default:
+ panic("Invalid value type")
+ }
+}
+
type anyValueFilter struct {}
func (_ anyValueFilter) valueFilter(value walk.Value) bool {
return true
diff --git a/subex/parse.go b/subex/parse.go
index b6bf2f6..e91008a 100644
--- a/subex/parse.go
+++ b/subex/parse.go
@@ -55,6 +55,12 @@ func (s Structure) String() string {
}
}
+type DestructureMethod int
+const (
+ Normal DestructureMethod = iota
+ Iterate
+)
+
type RuneReader interface {
Next() rune
Rewind()
@@ -361,8 +367,14 @@ func parseRuneReplacement(l RuneReader, end rune) (output SubexAST) {
// }
func parseDestructure(l RuneReader, destructure Structure, inType Type) (lhs SubexAST, outType Type) {
- if !accept(l, "(") {
- panic("Missing ( after destructure start")
+ var method rune
+ switch l.Next() {
+ case '(':
+ method = ')'
+ case '[':
+ method = ']'
+ default:
+ panic("Missing ( or [ after destructure start")
}
var innerInType Type
@@ -390,8 +402,22 @@ func parseDestructure(l RuneReader, destructure Structure, inType Type) (lhs Sub
resolveTypes(inType, expectedInType)
lhs, innerOutType := parseSubex(l, 0, innerInType)
- if !accept(l, ")") {
- panic("Missing matching )")
+ 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
@@ -487,20 +513,6 @@ func parseSubex(l RuneReader, minPower int, inType Type) (lhs SubexAST, outType
case ')', ']', '|', ';', '{', '+', '*', '/', '!', '=', '$':
l.Rewind()
return SubexASTEmpty{}, inType
- // 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 '.':
outType = inType
if inType == RuneType {
@@ -508,6 +520,17 @@ func parseSubex(l RuneReader, minPower int, inType Type) (lhs SubexAST, outType
} 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{}
@@ -569,14 +592,10 @@ func parseSubex(l RuneReader, minPower int, inType Type) (lhs SubexAST, outType
lhs = SubexASTProduct {lhs}
resolveTypes(inType, ValueType)
outType = resolveTypes(outType, ValueType)
- // case r == '/' && minPower <= 4:
- // lhs = SubexASTReciprocal {lhs}
case r == '!' && minPower <= 4:
lhs = SubexASTNot {lhs}
resolveTypes(inType, ValueType)
outType = resolveTypes(outType, ValueType)
- // case r == '=' && minPower <= 4:
- // lhs = SubexASTEqual {lhs}
case r == '$' && minPower <= 4:
slot := l.Next()
if slot == eof {
@@ -608,15 +627,6 @@ func parseSubex(l RuneReader, minPower int, inType Type) (lhs SubexAST, outType
panic("Missing subex after |")
}
lhs = SubexASTOr{lhs, rhs}
- /*case r == ';' && minPower <= 10:
- rhs := parseSubex(l, 11, inType, outType)
- if rhs == nil {
- panic("Missing subex after ;")
- }
- lhs = SubexASTJoin {
- Content: lhs,
- Delimiter: rhs,
- }*/
default:
l.Rewind()
break loop
diff --git a/subex/subexast.go b/subex/subexast.go
index d08ddac..655a783 100644
--- a/subex/subexast.go
+++ b/subex/subexast.go
@@ -238,6 +238,18 @@ func (ast SubexASTCopyNumber) String() string {
return "%"
}
+// Read in a null, bool, number, string or empty array or map and output it unchanged
+type SubexASTCopyAnySimpleValue struct {}
+func (ast SubexASTCopyAnySimpleValue) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
+ if inType != ValueType || outType != ValueType {
+ panic("Invalid types for SubexASTCopyAnySimpleValue")
+ }
+ return &SubexCopyState {
+ next: next,
+ filter: simpleValueFilter{},
+ }
+}
+
// Read in any single Atom and output it unchanged
type SubexASTCopyAnyValue struct {}
func (ast SubexASTCopyAnyValue) compileWith(next SubexState, slotMap *SlotMap, inType Type, outType Type) SubexState {
diff --git a/subex/subexstate.go b/subex/subexstate.go
index 1e1e94e..8f27a10 100644
--- a/subex/subexstate.go
+++ b/subex/subexstate.go
@@ -146,84 +146,6 @@ func (state SubexStoreRunesEndState) epsilon(aux auxiliaryState) []SubexBranch {
}}
}
-/*
-// A part of an output literal, either an Atom or a slot from which to load
-type OutputContent interface {
- // Given the current store, return the ValueList produced by the TransducerOutput
- buildValues(Store) walk.ValueList
- // Given the current store, return the RuneList produced by the TransducerOutput
- buildRunes(Store) walk.RuneList
-}
-
-// An OutputContent which is just a Value literal
-type OutputValueLiteral struct {
- value walk.Value
-}
-func (replacement OutputValueLiteral) buildValues(store Store) walk.ValueList {
- return walk.ValueList{replacement.value}
-}
-func (replacement OutputValueLiteral) buildRunes(store Store) walk.RuneList {
- // TODO: serialise to JSON
- panic("Unimplemented!")
-}
-
-// An OutputContent which is just a rune literal
-type OutputRuneLiteral struct {
- rune walk.StringRuneAtom
-}
-func (replacement OutputRuneLiteral) buildValues(store Store) walk.ValueList {
- // TODO: Try to deserialise
- panic("Unimplemented!")
-}
-func (replacement OutputRuneLiteral) buildRunes(store Store) walk.RuneList {
- return walk.RuneList {replacement.rune}
-}
-
-// An OutputContent which is a slot that is loaded from
-type OutputLoad struct {
- slot int
-}
-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
-}
-
-// Don't read in anything, just output the series of data and slots specified
-type SubexOutputState struct {
- content []OutputContent
- next SubexState
-}
-// Given a store, return what is outputted by an epsilon transition from this state
-// TODO: separate into buildValues and buildRunes
-func (state SubexOutputState) build(store Store) walk.ValueList {
- var result walk.ValueList
- for _, part := range state.content {
- result = append(result, part.buildValues(store)...)
- }
- return result
-}
-func (state SubexOutputState) eat(aux auxiliaryState, char walk.Value) []SubexBranch {
- content := state.build(aux.store)
- nextStates := state.next.eat(aux.topAppend(content), char)
- return nextStates
-}
-func (state SubexOutputState) accepting(aux auxiliaryState) []OutputStack {
- content := state.build(aux.store)
- outputStacks := state.next.accepting(aux.topAppend(content))
- return outputStacks
-}
-*/
-
type SubexOutputValueLiteralState struct {
literal walk.Scalar
next SubexState