diff --git a/rel/expr_slice.go b/rel/expr_slice.go new file mode 100644 index 00000000..a6c37e57 --- /dev/null +++ b/rel/expr_slice.go @@ -0,0 +1,76 @@ +package rel + +import ( + "fmt" + "strings" + + "github.com/go-errors/errors" +) + +// SliceExpr is an expression that evaluates to a slice of the setToSlice. +type SliceExpr struct { + setToSlice, start, end, step Expr + include bool +} + +// NewSliceExpr returns a SliceExpr +func NewSliceExpr(setToSlice, start, end, step Expr, include bool) SliceExpr { + return SliceExpr{setToSlice, start, end, step, include} +} + +// Eval evaluates SliceExpr to the slice of the set. +func (s SliceExpr) Eval(local Scope) (Value, error) { + var start, end, step Value + var err error + + if s.start != nil { + start, err = s.start.Eval(local) + if err != nil { + return nil, err + } + } + + if s.end != nil { + end, err = s.end.Eval(local) + if err != nil { + return nil, err + } + } + + if s.step != nil { + step, err = s.step.Eval(local) + if err != nil { + return nil, err + } + if _, isNumber := step.(Number); !isNumber { + return nil, errors.Errorf("step %s must be a number", step) + } + } else { + step = Number(1) + } + + set, err := s.setToSlice.Eval(local) + if err != nil { + return nil, err + } + + return set.(Set).CallSlice(start, end, int(step.(Number)), s.include), nil +} + +func (s SliceExpr) String() string { + str := strings.Builder{} + switch { + case s.start == nil && s.end == nil: + str.WriteString(";") + case s.start != nil && s.end == nil: + str.WriteString(fmt.Sprintf("%s;", s.start)) + case s.start == nil && s.end != nil: + str.WriteString(fmt.Sprintf(";%s", s.end)) + default: + str.WriteString(fmt.Sprintf("%s;%s", s.start, s.end)) + } + if s.step != nil { + str.WriteString(fmt.Sprintf(";%s", s.step)) + } + return str.String() +} diff --git a/rel/slice_util.go b/rel/slice_util.go new file mode 100644 index 00000000..a10e5343 --- /dev/null +++ b/rel/slice_util.go @@ -0,0 +1,92 @@ +package rel + +import "math" + +// resolveArrayIndexes returns an array of indexes to get for array. +func resolveArrayIndexes(start, end Value, step, offset, maxLen int, inclusive bool) []int { + if maxLen == 0 { + return []int{} + } + startIndex, endIndex := initDefaultArrayIndex(start, end, offset, maxLen+offset, step) + + if startIndex == endIndex { + if inclusive { + return []int{startIndex} + } + return []int{} + } + + return getIndexes(startIndex, endIndex, step, inclusive) +} + +// initDefaultArrayIndex initialize the start and end values for arrays. +func initDefaultArrayIndex(start, end Value, minLen, maxLen, step int) (startIndex int, endIndex int) { + if start != nil { + startIndex = resolveIndex(int(start.(Number)), minLen, maxLen) + if startIndex == maxLen { + startIndex-- + } + } else { + if step > 0 { + startIndex = minLen + } else { + startIndex = maxLen - 1 + } + } + + if end != nil { + endIndex = resolveIndex(int(end.(Number)), minLen, maxLen) + } else { + // TODO: apply inclusivity to the undefined end index + if step > 0 { + endIndex = maxLen + } else { + endIndex = minLen - 1 + } + } + return +} + +// resolveIndex solves the edge cases of index values. +func resolveIndex(i, minVal, maxVal int) int { + if i > maxVal { + return maxVal + } else if i < 0 { + if -i > maxVal { + return minVal + } + return maxVal + i + } + return i +} + +// getIndexes returns a range of numbers between start and end with the provided step. +// inclusive decides whether end can be included or not. +func getIndexes(start, end, step int, inclusive bool) []int { + if !isValidRange(start, end, step) { + return []int{} + } + if inclusive { + if step > 0 { + end++ + } else { + end-- + } + } + + length := int(math.Abs(float64(start - end))) + if step != 1 && step != -1 { + length = int(math.Ceil(float64(length) / math.Abs(float64(step)))) + } + indexes := make([]int, 0, length) + for i := 0; i < length; i++ { + indexes = append(indexes, start+step*i) + } + + return indexes +} + +// isValidRange checks whether start, end, and step are valid values. +func isValidRange(start, end, step int) bool { + return step != 0 && ((start > end && step < 0) || (start < end && step > 0)) +} diff --git a/rel/slice_util_test.go b/rel/slice_util_test.go new file mode 100644 index 00000000..f968589c --- /dev/null +++ b/rel/slice_util_test.go @@ -0,0 +1,84 @@ +package rel + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetIndexes(t *testing.T) { + t.Parallel() + + assert.Equal(t, []int{1, 2, 3, 4, 5}, getIndexes(1, 6, 1, false)) + assert.Equal(t, []int{1, 2, 3, 4, 5}, getIndexes(1, 5, 1, true)) + assert.Equal(t, []int{2, 4}, getIndexes(2, 5, 2, false)) + assert.Equal(t, []int{1, 3, 5}, getIndexes(1, 5, 2, true)) + assert.Equal(t, []int{5, 3}, getIndexes(5, 1, -2, false)) + assert.Equal(t, []int{5, 3, 1}, getIndexes(5, 1, -2, true)) + assert.Equal(t, []int{}, getIndexes(5, 1, 1, true)) + assert.Equal(t, []int{}, getIndexes(5, 1, 0, false)) +} + +func TestInitDefaultArrayIndex(t *testing.T) { + t.Parallel() + + start, end := initDefaultArrayIndex(Number(10), Number(20), 5, 25, 1) + assert.Equal(t, 10, start) + assert.Equal(t, 20, end) + + start, end = initDefaultArrayIndex(nil, nil, 5, 25, 1) + assert.Equal(t, 5, start) + assert.Equal(t, 25, end) + + start, end = initDefaultArrayIndex(nil, nil, 5, 25, -2) + assert.Equal(t, 24, start) + assert.Equal(t, 4, end) + + start, end = initDefaultArrayIndex(nil, Number(12), 5, 25, -2) + assert.Equal(t, 24, start) + assert.Equal(t, 12, end) + + start, end = initDefaultArrayIndex(nil, Number(12), 5, 25, 2) + assert.Equal(t, 5, start) + assert.Equal(t, 12, end) + + start, end = initDefaultArrayIndex(Number(7), nil, 5, 25, -2) + assert.Equal(t, 7, start) + assert.Equal(t, 4, end) + + start, end = initDefaultArrayIndex(Number(7), nil, 5, 25, 2) + assert.Equal(t, 7, start) + assert.Equal(t, 25, end) + + start, end = initDefaultArrayIndex(nil, Number(42), 5, 25, -2) + assert.Equal(t, 24, start) + assert.Equal(t, 25, end) + + start, end = initDefaultArrayIndex(Number(42), nil, 5, 25, 2) + assert.Equal(t, 24, start) + assert.Equal(t, 25, end) + + start, end = initDefaultArrayIndex(Number(-5), nil, 5, 25, 2) + assert.Equal(t, 20, start) + assert.Equal(t, 25, end) + + start, end = initDefaultArrayIndex(nil, Number(-5), 5, 25, 2) + assert.Equal(t, 5, start) + assert.Equal(t, 20, end) + + start, end = initDefaultArrayIndex(Number(-30), nil, 5, 25, 2) + assert.Equal(t, 5, start) + assert.Equal(t, 25, end) + + start, end = initDefaultArrayIndex(nil, Number(-30), 5, 25, 2) + assert.Equal(t, 5, start) + assert.Equal(t, 5, end) + + start, end = initDefaultArrayIndex(Number(42), Number(-30), 5, 25, 2) + assert.Equal(t, 24, start) + assert.Equal(t, 5, end) + + start, end = initDefaultArrayIndex(Number(-30), Number(42), 5, 25, 2) + assert.Equal(t, 5, start) + assert.Equal(t, 25, end) +} diff --git a/rel/value.go b/rel/value.go index b0755e60..47f7948c 100644 --- a/rel/value.go +++ b/rel/value.go @@ -106,6 +106,7 @@ type Set interface { Map(func(Value) Value) Set Where(func(Value) bool) Set Call(arg Value) Value + CallSlice(start, end Value, step int, inclusive bool) Set ArrayEnumerator() (OffsetValueEnumerator, bool) } diff --git a/rel/value_set_array.go b/rel/value_set_array.go index 0bfcf32d..c81566a7 100644 --- a/rel/value_set_array.go +++ b/rel/value_set_array.go @@ -263,6 +263,15 @@ func (a Array) Call(arg Value) Value { return a.values[i-a.offset] } +func (a Array) CallSlice(start, end Value, step int, inclusive bool) Set { + indexes := resolveArrayIndexes(start, end, step, a.offset, len(a.values), inclusive) + slice := make([]Value, 0, len(indexes)) + for _, i := range indexes { + slice = append(slice, a.values[i-a.offset]) + } + return NewOffsetArray(a.offset, slice...) +} + func (a Array) index(pos int) int { pos -= a.offset if 0 <= pos && pos <= len(a.values) { diff --git a/rel/value_set_bytes.go b/rel/value_set_bytes.go index 37b4cda2..8db953eb 100644 --- a/rel/value_set_bytes.go +++ b/rel/value_set_bytes.go @@ -223,6 +223,15 @@ func (b Bytes) Call(arg Value) Value { return NewNumber(float64(string(b.b)[i-b.offset])) } +func (b Bytes) CallSlice(start, end Value, step int, inclusive bool) Set { + indexes := resolveArrayIndexes(start, end, step, b.offset, len(b.b), inclusive) + slice := make([]byte, 0, len(indexes)) + for _, i := range indexes { + slice = append(slice, b.b[i-b.offset]) + } + return NewOffsetBytes(slice, b.offset) +} + func (b Bytes) index(pos int) int { pos -= b.offset if 0 <= pos && pos <= len(b.b) { diff --git a/rel/value_set_dict.go b/rel/value_set_dict.go index 6c4ad9a7..46d262b2 100644 --- a/rel/value_set_dict.go +++ b/rel/value_set_dict.go @@ -269,6 +269,92 @@ func (d Dict) Call(arg Value) Value { } } +func (d Dict) CallSlice(start, end Value, step int, inclusive bool) Set { + allKeys := d.m.Keys().OrderedElements( + func(a, b interface{}) bool { + return a.(Value).Less(b.(Value)) + }, + ) + + keys := []int{} + for _, k := range allKeys { + if f, isFloat := k.(Number); isFloat { + keys = append(keys, int(f)) + } + } + + if len(keys) == 0 { + return None + } + + // TODO: apply inclusivity to the undefined end index + startIndex, endIndex := keys[0], keys[len(keys)-1]+1 + + if step < 0 { + startIndex, endIndex = endIndex, startIndex + } + if start != nil { + startIndex = int(start.(Number)) + } + if end != nil { + endIndex = int(end.(Number)) + } + + if startIndex == endIndex { + if inclusive { + val, exists := d.m.Get(Number(startIndex)) + if !exists { + return None + } + return NewArray(NewArrayItemTuple(0, val.(Value))) + } + return None + } + + indexes := getIndexes(startIndex, endIndex, step, inclusive) + if len(indexes) == 0 { + return None + } + + array := Array{[]Value{}, 0} + for _, i := range indexes { + val, exists := d.m.Get(Number(i)) + if !exists { + continue + } + + array = arrayFromDictEntry(val, array) + } + return array +} + +func arrayFromDictEntry(val interface{}, array Array) Array { + switch v := val.(type) { + case Value: + if len(array.values) == 0 { + return NewArray(v).(Array) + } + return array.With( + NewArrayItemTuple(array.offset+len(array.values), v), + ).(Array) + case multipleValues: + values := make([]Value, 0, frozen.Set(v).Count()) + for s := frozen.Set(v).Range(); s.Next(); { + values = append(values, s.Value().(Value)) + } + if len(array.values) == 0 { + return NewArray(values...).(Array) + } + return array.With( + NewArrayItemTuple( + array.offset+len(array.values), + NewArray(values...), + ), + ).(Array) + } + return array +} + func (d Dict) ArrayEnumerator() (OffsetValueEnumerator, bool) { return nil, false } diff --git a/rel/value_set_generic.go b/rel/value_set_generic.go index 5bd563f4..47d4119e 100644 --- a/rel/value_set_generic.go +++ b/rel/value_set_generic.go @@ -2,6 +2,7 @@ package rel import ( "bytes" + "fmt" "reflect" "sort" @@ -335,6 +336,49 @@ func (s GenericSet) Call(arg Value) Value { return nil } +func (s GenericSet) CallSlice(start, end Value, step int, inclusive bool) Set { + if s.set.Count() == 0 { + return None + } + i := s.Enumerator() + i.MoveNext() + typ := reflect.TypeOf(i.Current()) + for i.MoveNext() { + if reflect.TypeOf(i.Current()) != typ { + typ = nil + } + } + if typ != nil { + switch typ { + case stringCharTupleType: + g, is := AsString(s) + if !is { + panic("unsupported array expr") + } + return g.CallSlice(start, end, step, inclusive) + case bytesByteTupleType: + b, is := AsBytes(s) + if !is { + panic("unsupported array expr") + } + return b.CallSlice(start, end, step, inclusive) + case arrayItemTupleType: + array, is := asArray(s) + if !is { + panic("unsupported array expr") + } + return array.CallSlice(start, end, step, inclusive) + case dictEntryTupleType: + tuples := make([]DictEntryTuple, 0, s.set.Count()) + for i := s.Enumerator(); i.MoveNext(); { + tuples = append(tuples, i.Current().(DictEntryTuple)) + } + return NewDict(true, tuples...).CallSlice(start, end, step, inclusive) + } + } + panic(fmt.Sprintf("Set %s is not indexed", s)) +} + // Enumerator returns an enumerator over the Values in the genericSet. func (s GenericSet) Enumerator() ValueEnumerator { return &genericSetEnumerator{s.set.Range()} diff --git a/rel/value_set_str.go b/rel/value_set_str.go index bb803094..04de20eb 100644 --- a/rel/value_set_str.go +++ b/rel/value_set_str.go @@ -216,6 +216,15 @@ func (s String) Call(arg Value) Value { return NewNumber(float64(string(s.s)[i-s.offset])) } +func (s String) CallSlice(start, end Value, step int, inclusive bool) Set { + indexes := resolveArrayIndexes(start, end, step, s.offset, len(s.s), inclusive) + slice := make([]rune, 0, len(indexes)) + for _, i := range indexes { + slice = append(slice, s.s[i-s.offset]) + } + return NewOffsetString(slice, s.offset) +} + func (s String) index(pos int) int { pos -= s.offset if 0 <= pos && pos <= len(s.s) { diff --git a/syntax/compile.go b/syntax/compile.go index 7daaf50b..3fd79ee6 100644 --- a/syntax/compile.go +++ b/syntax/compile.go @@ -293,12 +293,12 @@ func (pc ParseContext) compileCallGet(b ast.Branch) rel.Expr { for _, part := range b.Many("tail") { if call := part.One("call"); call != nil { args := call.Many("arg") - exprs := make([]ast.Node, 0, len(args)) for _, arg := range args { - exprs = append(exprs, arg.One("expr")) - } - for _, arg := range pc.parseExprs(exprs...) { - result = rel.NewCallExpr(result, arg) + if expr := arg.One("expr"); expr != nil { + result = rel.NewCallExpr(result, pc.CompileExpr(expr.(ast.Branch))) + continue + } + result = pc.compileRange(result, arg.One("range").(ast.Branch)) } } get(part.One("get")) @@ -463,6 +463,32 @@ func (pc ParseContext) compileNumber(c ast.Children) rel.Expr { return rel.NewNumber(n) } +func (pc ParseContext) compileRange(set rel.Expr, c ast.Branch) rel.Expr { + var start, end, step rel.Expr + if startNode := c.One("start"); startNode != nil { + start = pc.CompileExpr(startNode.(ast.Branch)) + } + + if endNode := c.One("end"); endNode != nil { + end = pc.CompileExpr(endNode.(ast.Branch)) + } + + if stepNode := c.One("step"); stepNode != nil { + step = pc.CompileExpr(stepNode.(ast.Branch)) + } + + // TODO: decide on syntax to allow inclusivity + inclusive := false + // switch option := c.One("option").String(); option { + // case "=": + // inclusive = true + // case ">": + // step = rel.NewNumber(-1) + // } + + return rel.NewSliceExpr(set, start, end, step, inclusive) +} + func (pc ParseContext) compileExpr(c ast.Children) rel.Expr { switch c := c.(type) { case ast.One: diff --git a/syntax/expr_slice_test.go b/syntax/expr_slice_test.go new file mode 100644 index 00000000..495860c9 --- /dev/null +++ b/syntax/expr_slice_test.go @@ -0,0 +1,183 @@ +package syntax + +import "testing" + +//nolint:dupl +func TestArraySlice(t *testing.T) { + t.Parallel() + + AssertCodesEvalToSameValue(t, `[1, 2, 3] `, `[0, 1, 2, 3, 4](1;4) `) + AssertCodesEvalToSameValue(t, `[2, 3, 4] `, `[0, 1, 2, 3, 4](2;) `) + AssertCodesEvalToSameValue(t, `[0, 1, 2] `, `[0, 1, 2, 3, 4](;3) `) + AssertCodesEvalToSameValue(t, `[0, 1] `, `[0, 1, 2, 3, 4](;-3) `) + AssertCodesEvalToSameValue(t, `[0, 1, 2, 3] `, `[0, 1, 2, 3, 4](;-1) `) + AssertCodesEvalToSameValue(t, `[1, 2, 3] `, `[0, 1, 2, 3, 4](1;-1) `) + AssertCodesEvalToSameValue(t, `[1, 2, 3] `, `[0, 1, 2, 3, 4](-4;-1) `) + AssertCodesEvalToSameValue(t, `[1, 3] `, `[0, 1, 2, 3, 4](1;;2) `) + AssertCodesEvalToSameValue(t, `[0, 2] `, `[0, 1, 2, 3, 4](;4;2) `) + AssertCodesEvalToSameValue(t, `[4, 2] `, `[0, 1, 2, 3, 4](4;1;-2) `) + AssertCodesEvalToSameValue(t, `[4, 3, 2, 1, 0]`, `[0, 1, 2, 3, 4](;;-1) `) + AssertCodesEvalToSameValue(t, `[0] `, `[0, 1, 2, 3, 4](0;;-1) `) + AssertCodesEvalToSameValue(t, `[4, 3] `, `[0, 1, 2, 3, 4](;2;-1) `) + AssertCodesEvalToSameValue(t, `[4, 3, 2] `, `[0, 1, 2, 3, 4](10;1;-1)`) + AssertCodesEvalToSameValue(t, `[0, 1, 2, 3, 4]`, `[0, 1, 2, 3, 4](;) `) + AssertCodesEvalToSameValue(t, `{} `, `[0, 1, 2, 3, 4](1;3;-1) `) + AssertCodesEvalToSameValue(t, `{} `, `[0, 1, 2, 3, 4](1;1) `) +} + +//nolint:dupl +func TestArrayString(t *testing.T) { + t.Parallel() + + AssertCodesEvalToSameValue(t, `"bcd" `, `"abcde"(1;4) `) + AssertCodesEvalToSameValue(t, `"cde" `, `"abcde"(2;) `) + AssertCodesEvalToSameValue(t, `"abc" `, `"abcde"(;3) `) + AssertCodesEvalToSameValue(t, `"ab" `, `"abcde"(;-3) `) + AssertCodesEvalToSameValue(t, `"abcd" `, `"abcde"(;-1) `) + AssertCodesEvalToSameValue(t, `"bcd" `, `"abcde"(1;-1) `) + AssertCodesEvalToSameValue(t, `"bcd" `, `"abcde"(-4;-1) `) + AssertCodesEvalToSameValue(t, `"bd" `, `"abcde"(1;;2) `) + AssertCodesEvalToSameValue(t, `"ac" `, `"abcde"(;4;2) `) + AssertCodesEvalToSameValue(t, `"ec" `, `"abcde"(4;1;-2) `) + AssertCodesEvalToSameValue(t, `"edcba"`, `"abcde"(;;-1) `) + AssertCodesEvalToSameValue(t, `"a" `, `"abcde"(0;;-1) `) + AssertCodesEvalToSameValue(t, `"ed" `, `"abcde"(;2;-1) `) + AssertCodesEvalToSameValue(t, `"edc" `, `"abcde"(10;1;-1)`) + AssertCodesEvalToSameValue(t, `"abcde"`, `"abcde"(;) `) + AssertCodesEvalToSameValue(t, `{} `, `"abcde"(1;3;-1) `) + AssertCodesEvalToSameValue(t, `{} `, `"abcde"(1;1) `) +} + +//nolint:dupl +func TestArrayBytes(t *testing.T) { + t.Parallel() + + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 98), (1, 99), (2, 100) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(1;4)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 99), (1, 100), (2, 101) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(2;)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 97), (1, 98), (2, 99) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;3)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 97), (1, 98) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;-3)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 98), (1, 99), (2, 100) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(1;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 98), (1, 99), (2, 100) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(-4;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 98), (1, 100) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(1;;2)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 97), (1, 99) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;4;2)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 101), (1, 99) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(4;1;-2)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 101), (1, 100), (2, 99), (3, 98), (4, 97) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 97) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(0;;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 101), (1, 100) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;2;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 101), (1, 100), (2, 99)}`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(10;1;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(;)`, + ) + AssertCodesEvalToSameValue(t, + `{}`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(1;3;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{}`, + `{ |@, @byte| (0, 97), (1, 98), (2, 99), (3, 100), (4, 101) }(1;1)`, + ) +} + +//nolint:dupl +func TestDictSlice(t *testing.T) { + t.Parallel() + + AssertCodesEvalToSameValue(t, + `[10, "abc", 30]`, + `{1: 10, 2: "abc", 3: 30, 4: 40, 5: 50}(1;4)`, + ) + AssertCodesEvalToSameValue(t, + `["abc", 30, 40, 50]`, + `{1: 10, 2: "abc", 3: 30, 4: 40, 5: 50}(2;)`, + ) + AssertCodesEvalToSameValue(t, + `[10, "abc", 30, 40]`, + `{1: 10, 2: "abc", 3: 30, 4: 40, 5: 50}(;5)`, + ) + AssertCodesEvalToSameValue(t, + `["abc", 40, 50]`, + `{"a": 10, 2: "abc", "c": 30, 4: 40, 5: 50}(1;)`, + ) + AssertCodesEvalToSameValue(t, + `["abc", 40, 50]`, + `{"a": 10, 2: "abc", "c": 30, 4: 40, 5: 50}(;)`, + ) + AssertCodesEvalToSameValue(t, + `[50, 40, "abc"]`, + `{1: 10, 2: "abc", "c": 30, 4: 40, 5: 50}(5;1;-1)`, + ) + AssertCodesEvalToSameValue(t, + `[50, 40, "abc"]`, + `{1: 10, 2: "abc", "c": 30, 4: 40, 5: 50}(5;1;-1)`, + ) + AssertCodesEvalToSameValue(t, + `[10, 40]`, + `{1: 10, 2: "abc", 3: 30, 4: 40, 5: 50}(;10;3)`, + ) + AssertCodesEvalToSameValue(t, + `{}`, + `{1: 10, 2: "abc", "c": 30, 4: 40, 5: 50}(1;1)`, + ) + AssertCodesEvalToSameValue(t, + `{}`, + `{1: 10, 2: "abc", "c": 30, 4: 40, 5: 50}(1;10;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{}`, + `{1: 10, 2: "abc", 3: 30, 4: 40, 5: 50}(1;-1)`, + ) + AssertCodesEvalToSameValue(t, + `{}`, + `{"a": 10, "b": "abc", "c": 30, "d": 40, "e": 50}(1;10)`, + ) +} + +func TestGenericSetSlice(t *testing.T) { + t.Parallel() + + AssertCodePanics(t, `{1, 2, 3}(1;3)`) +} diff --git a/syntax/parse.go b/syntax/parse.go index aaac87c4..24a862e9 100644 --- a/syntax/parse.go +++ b/syntax/parse.go @@ -49,10 +49,7 @@ expr -> C* amp="&"* @ C* arrow=( > C* (get | @) tail=( get | call=("(" - arg=( - expr (":" end=expr? (":" step=expr)?)? - | ":" end=expr (":" step=expr)? - ):",", + arg=( range | expr ):",", ")") )* C* > C* "{" C* rel=(names tuple=("(" v=@:",", ")"):",",?) "}" C* @@ -75,6 +72,7 @@ expr -> C* amp="&"* @ C* arrow=( | C* STR C* | C* NUM C*; nest -> C* "nest" names IDENT C*; +range -> start=expr? ";" end=expr? (";" step=expr)?; unnest -> C* "unnest" IDENT C*; touch -> C* ("->*" ("&"? IDENT | STR))+ "(" expr:"," ","? ")" C*; get -> C* dot="." ("&"? IDENT | STR | "*") C*;