diff --git a/dict_test.go b/dict_test.go index 6a8778e..1f293d1 100644 --- a/dict_test.go +++ b/dict_test.go @@ -24,7 +24,7 @@ func TestDictParser(t *testing.T) { /* 1 */ {`{}`, map[any]any{}, nil}, /* 2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)}, /* 3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil}, - /* 4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil}, + /* 4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil}, /* 5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil}, /* 6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil}, /* 7 */ {`2 in {1:"one", 2:"two"}`, true, nil}, diff --git a/list_test.go b/list_test.go index 9552384..5bd9137 100644 --- a/list_test.go +++ b/list_test.go @@ -33,7 +33,7 @@ func TestListParser(t *testing.T) { /* 11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil}, /* 12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil}, /* 13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil}, - /* 14 */ {`[1,2,3].1`, int64(2), nil}, + /* 14 */ {`[1,2,3][1]`, int64(2), nil}, /* 15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil}, /* 16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil}, /* 17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)}, @@ -43,6 +43,8 @@ func TestListParser(t *testing.T) { /* 21 */ {`"b" in ["a", "b", "c"]`, true, nil}, /* 22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil}, /* 23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil}, + /* 24 */ {`["a","b","c","d"][1]`, "b", nil}, + /* 25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)}, // /* 8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil}, // /* 9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil}, @@ -61,8 +63,6 @@ func TestListParser(t *testing.T) { var gotErr error ctx := NewSimpleStore() - // ctx.SetVar("var1", int64(123)) - // ctx.SetVar("var2", "abc") ImportMathFuncs(ctx) parser := NewParser(ctx) diff --git a/operand-list.go b/operand-list.go index d6a8784..e3d39e1 100644 --- a/operand-list.go +++ b/operand-list.go @@ -83,12 +83,12 @@ func (list *ListType) indexDeepCmp(target any) (index int) { // -------- list term func newListTermA(args ...*term) *term { - return newListTerm(args) + return newListTerm(0, 0, args) } -func newListTerm(args []*term) *term { +func newListTerm(row, col int, args []*term) *term { return &term{ - tk: *NewValueToken(0, 0, SymList, "[]", args), + tk: *NewValueToken(row, col, SymList, "[]", args), parent: nil, children: nil, position: posLeaf, diff --git a/operator-dot.go b/operator-dot.go index cd3a226..a82bf6b 100644 --- a/operator-dot.go +++ b/operator-dot.go @@ -17,7 +17,7 @@ func newDotTerm(tk *Token) (inst *term) { } } -func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) { +func verifyDotIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) { var v int var indexValue any if indexValue, err = indexTerm.compute(ctx); err == nil { @@ -51,12 +51,12 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) { case *ListType: var index int array := ([]any)(*unboxedValue) - if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil { + if index, err = verifyDotIndex(ctx, indexTerm, len(array)); err == nil { v = array[index] } case string: var index int - if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil { + if index, err = verifyDotIndex(ctx, indexTerm, len(unboxedValue)); err == nil { v = string(unboxedValue[index]) } case *DictType: diff --git a/operator-index.go b/operator-index.go new file mode 100644 index 0000000..f8bd4b5 --- /dev/null +++ b/operator-index.go @@ -0,0 +1,91 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// operator-index.go +package expr + +// -------- index term +func newIndexTerm(tk *Token) (inst *term) { + return &term{ + tk: *tk, + children: make([]*term, 0, 2), + position: posInfix, + priority: priDot, + evalFunc: evalIndex, + } +} + +func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) { + if len(*indexList) != 1 { + err = indexTerm.Errorf("one index only is allowed") + } else { + index = (*indexList)[0] + } + return +} + +func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) { + var v int + + if len(*indexList) != 1 { + err = indexTerm.Errorf("one index only is allowed") + } else if v, err = toInt((*indexList)[0], "index expression"); err == nil { + if v < 0 && v >= -maxValue { + v = maxValue + v + } + if v >= 0 && v < maxValue { + index = v + } else { + err = indexTerm.Errorf("index %d out of bounds", v) + } + } + return +} + +func evalIndex(ctx ExprContext, self *term) (v any, err error) { + var leftValue, rightValue any + var indexList *ListType + var ok bool + + if err = self.checkOperands(); err != nil { + return + } + if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { + return + } + + if indexList, ok = rightValue.(*ListType); !ok { + err = self.Errorf("invalid index expression") + return + } + indexTerm := self.children[1] + + switch unboxedValue := leftValue.(type) { + case *ListType: + var index int + if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil { + v = (*unboxedValue)[index] + } + case string: + var index int + if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil { + v = string(unboxedValue[index]) + } + case *DictType: + var ok bool + var indexValue any + if indexValue, err = verifyKey(indexTerm, indexList); err == nil { + if v, ok = (*unboxedValue)[indexValue]; !ok { + err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue) + } + } + default: + err = self.errIncompatibleTypes(leftValue, rightValue) + } + return +} + +// init +func init() { + registerTermConstructor(SymIndex, newIndexTerm) +} diff --git a/operator-sum.go b/operator-sum.go index e0bc003..af824eb 100644 --- a/operator-sum.go +++ b/operator-sum.go @@ -38,23 +38,6 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) { rightInt, _ := rightValue.(int64) v = leftInt + rightInt } - // } else if IsList(leftValue) || IsList(rightValue) { - // var leftList, rightList *ListType - // var ok bool - // if leftList, ok = leftValue.(*ListType); !ok { - // leftList = &ListType{leftValue} - // } - // if rightList, ok = rightValue.(*ListType); !ok { - // rightList = &ListType{rightValue} - // } - // sumList := make(ListType, 0, len(*leftList)+len(*rightList)) - // for _, item := range *leftList { - // sumList = append(sumList, item) - // } - // for _, item := range *rightList { - // sumList = append(sumList, item) - // } - // v = &sumList } else if IsList(leftValue) && IsList(rightValue) { var leftList, rightList *ListType leftList, _ = leftValue.(*ListType) diff --git a/parser.go b/parser.go index 3428060..cb7315b 100644 --- a/parser.go +++ b/parser.go @@ -155,6 +155,7 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) { } func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) { + r, c := scanner.lastPos() args := make([]*term, 0) lastSym := SymUnknown itemExpected := false @@ -179,7 +180,7 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term if lastSym != SymClosedSquare { err = scanner.Previous().ErrorExpectedGot("]") } else { - subtree = newListTerm(args) + subtree = newListTerm(r, c, args) } } return @@ -301,7 +302,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul startRow = tk.row startCol = tk.col } else if !defaultCase { - filterList = newListTerm(make([]*term, 0)) + filterList = newListTerm(startRow, startCol, make([]*term, 0)) } if tk.Sym == SymOpenBrace { @@ -402,7 +403,19 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef case SymOpenSquare: var listTerm *term if listTerm, err = self.parseList(scanner, allowVarRef); err == nil { - err = tree.addTerm(listTerm) + var sym = SymUnknown + if currentTerm != nil { + sym = currentTerm.symbol() + } + if sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression { + indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source()) + indexTerm := newTerm(indexTk) + if err = tree.addTerm(indexTerm); err == nil { + err = tree.addTerm(listTerm) + } + } else { + err = tree.addTerm(listTerm) + } currentTerm = listTerm } case SymOpenBrace: diff --git a/scanner.go b/scanner.go index cb977f2..b88ee98 100644 --- a/scanner.go +++ b/scanner.go @@ -74,6 +74,14 @@ func (self *scanner) unreadChar() (err error) { return } +func (self *scanner) lastPos() (r, c int) { + if self.prev != nil { + r = self.prev.row + c = self.prev.col + } + return +} + func (self *scanner) Previous() *Token { return self.prev } diff --git a/symbol.go b/symbol.go index ede606a..3004c24 100644 --- a/symbol.go +++ b/symbol.go @@ -85,6 +85,7 @@ const ( SymFuncDef SymList SymDict + SymIndex SymExpression SymSelector // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>] SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"