Eprx now supports range of indeces to extract parts of strings or lists
This commit is contained in:
parent
691c213d17
commit
2c87d6bf9e
@ -13,5 +13,6 @@ const (
|
|||||||
typeInt = "integer"
|
typeInt = "integer"
|
||||||
typeItem = "item"
|
typeItem = "item"
|
||||||
typeNumber = "number"
|
typeNumber = "number"
|
||||||
|
typePair = "pair"
|
||||||
typeString = "string"
|
typeString = "string"
|
||||||
)
|
)
|
||||||
|
28
index_test.go
Normal file
28
index_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// collection_test.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollections(t *testing.T) {
|
||||||
|
section := "Collection"
|
||||||
|
inputs := []inputType{
|
||||||
|
/* 1 */ {`"abcdef"[1:3]`, "bc", nil},
|
||||||
|
/* 2 */ {`"abcdef"[:3]`, "abc", nil},
|
||||||
|
/* 3 */ {`"abcdef"[1:]`, "bcdef", nil},
|
||||||
|
/* 4 */ {`"abcdef"[:]`, "abcdef", nil},
|
||||||
|
// /* 5 */ {`[0,1,2,3,4]`, []any{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||||
|
// /* 5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
|
||||||
|
/* 6 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Setenv("EXPR_PATH", ".")
|
||||||
|
|
||||||
|
// parserTestSpec(t, section, inputs, 5)
|
||||||
|
parserTest(t, section, inputs)
|
||||||
|
}
|
@ -16,20 +16,14 @@ func newIndexTerm(tk *Token) (inst *term) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
|
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]
|
index = (*indexList)[0]
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
|
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
|
||||||
var v int
|
var v int
|
||||||
|
|
||||||
if len(*indexList) != 1 {
|
if v, err = toInt((*indexList)[0], "index expression"); err == nil {
|
||||||
err = indexTerm.Errorf("one index only is allowed")
|
|
||||||
} else if v, err = toInt((*indexList)[0], "index expression"); err == nil {
|
|
||||||
if v < 0 && v >= -maxValue {
|
if v < 0 && v >= -maxValue {
|
||||||
v = maxValue + v
|
v = maxValue + v
|
||||||
}
|
}
|
||||||
@ -42,24 +36,48 @@ func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
|
||||||
|
v, _ := ((*indexList)[0]).(*intPair)
|
||||||
|
startIndex = v.a
|
||||||
|
endIndex = v.b
|
||||||
|
if startIndex < 0 && startIndex >= -maxValue {
|
||||||
|
startIndex = maxValue + startIndex
|
||||||
|
}
|
||||||
|
if endIndex < 0 && endIndex >= -maxValue {
|
||||||
|
endIndex = maxValue + endIndex + 1
|
||||||
|
}
|
||||||
|
if startIndex < 0 || startIndex > maxValue {
|
||||||
|
err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
|
||||||
|
} else if endIndex < 0 || endIndex > maxValue {
|
||||||
|
err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
|
||||||
|
} else if startIndex > endIndex {
|
||||||
|
err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
|
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
|
||||||
var leftValue, rightValue any
|
var leftValue, rightValue any
|
||||||
var indexList *ListType
|
var indexList *ListType
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if err = self.checkOperands(); err != nil {
|
// if err = self.checkOperands(); err != nil {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indexTerm := self.children[1]
|
||||||
if indexList, ok = rightValue.(*ListType); !ok {
|
if indexList, ok = rightValue.(*ListType); !ok {
|
||||||
err = self.Errorf("invalid index expression")
|
err = self.Errorf("invalid index expression")
|
||||||
return
|
return
|
||||||
|
} else if len(*indexList) != 1 {
|
||||||
|
err = indexTerm.Errorf("one index only is allowed")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
indexTerm := self.children[1]
|
|
||||||
|
|
||||||
|
if IsInteger((*indexList)[0]) {
|
||||||
switch unboxedValue := leftValue.(type) {
|
switch unboxedValue := leftValue.(type) {
|
||||||
case *ListType:
|
case *ListType:
|
||||||
var index int
|
var index int
|
||||||
@ -82,6 +100,23 @@ func evalIndex(ctx ExprContext, self *term) (v any, err error) {
|
|||||||
default:
|
default:
|
||||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
}
|
}
|
||||||
|
} else if isIntPair((*indexList)[0]) {
|
||||||
|
switch unboxedValue := leftValue.(type) {
|
||||||
|
case *ListType:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
|
||||||
|
sublist := ListType((*unboxedValue)[start:end])
|
||||||
|
v = &sublist
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
var start, end int
|
||||||
|
if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
|
||||||
|
v = unboxedValue[start:end]
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
69
operator-range.go
Normal file
69
operator-range.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
// operator-range.go
|
||||||
|
package expr
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// -------- range term
|
||||||
|
type intPair struct {
|
||||||
|
a, b int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *intPair) TypeName() string {
|
||||||
|
return typePair
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *intPair) ToString(opt FmtOpt) string {
|
||||||
|
return fmt.Sprintf("(%d, %d)", p.a, p.b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIntPair(v any) bool {
|
||||||
|
_, ok := v.(*intPair)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRangeTerm(tk *Token) (inst *term) {
|
||||||
|
return &term{
|
||||||
|
tk: *tk,
|
||||||
|
children: make([]*term, 0, 2),
|
||||||
|
position: posInfix,
|
||||||
|
priority: priDot,
|
||||||
|
evalFunc: evalRange,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evalRange(ctx ExprContext, self *term) (v any, err error) {
|
||||||
|
var leftValue, rightValue any
|
||||||
|
|
||||||
|
// if err = self.checkOperands(); err != nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
if len(self.children) == 0 {
|
||||||
|
leftValue = int64(0)
|
||||||
|
rightValue = int64(-1)
|
||||||
|
} else if len(self.children) == 1 {
|
||||||
|
if leftValue, err = self.children[0].compute(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rightValue = int64(-1)
|
||||||
|
} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !(IsInteger(leftValue) && IsInteger(rightValue)) {
|
||||||
|
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex, _ := leftValue.(int64)
|
||||||
|
endIndex, _ := rightValue.(int64)
|
||||||
|
|
||||||
|
v = &intPair{int(startIndex), int(endIndex)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
func init() {
|
||||||
|
registerTermConstructor(SymColon, newRangeTerm)
|
||||||
|
}
|
56
parser.go
56
parser.go
@ -154,16 +154,34 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
|
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
|
||||||
r, c := scanner.lastPos()
|
r, c := scanner.lastPos()
|
||||||
args := make([]*term, 0)
|
args := make([]*term, 0)
|
||||||
lastSym := SymUnknown
|
lastSym := SymUnknown
|
||||||
itemExpected := false
|
itemExpected := false
|
||||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||||
var subTree *ast
|
var subTree *ast
|
||||||
|
zeroRequired := scanner.current.Sym == SymColon
|
||||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
||||||
if subTree.root != nil {
|
root := subTree.root
|
||||||
args = append(args, subTree.root)
|
if root != nil {
|
||||||
|
if !parsingIndeces && root.symbol() == SymColon {
|
||||||
|
err = root.Errorf("unexpected range expression")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
args = append(args, root)
|
||||||
|
if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
|
||||||
|
if len(root.children) == 1 {
|
||||||
|
root.children = append(root.children, root.children[0])
|
||||||
|
} else if len(root.children) > 1 {
|
||||||
|
err = root.Errorf("invalid range specification")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
|
||||||
|
zeroTerm := newTerm(zeroTk)
|
||||||
|
zeroTerm.setParent(root)
|
||||||
|
root.children[0] = zeroTerm
|
||||||
|
}
|
||||||
} else if itemExpected {
|
} else if itemExpected {
|
||||||
prev := scanner.Previous()
|
prev := scanner.Previous()
|
||||||
err = prev.ErrorExpectedGot("list-item")
|
err = prev.ErrorExpectedGot("list-item")
|
||||||
@ -176,7 +194,6 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
|||||||
itemExpected = lastSym == SymComma
|
itemExpected = lastSym == SymComma
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// TODO Check arguments
|
|
||||||
if lastSym != SymClosedSquare {
|
if lastSym != SymClosedSquare {
|
||||||
err = scanner.Previous().ErrorExpectedGot("]")
|
err = scanner.Previous().ErrorExpectedGot("]")
|
||||||
} else {
|
} else {
|
||||||
@ -295,7 +312,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
|||||||
err = tk.Errorf("case list in default clause")
|
err = tk.Errorf("case list in default clause")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
|
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tk = scanner.Next()
|
tk = scanner.Next()
|
||||||
@ -353,6 +370,14 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
|
|||||||
return self.parseGeneral(scanner, true, false, termSymbols...)
|
return self.parseGeneral(scanner, true, false, termSymbols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func couldBeACollection(t *term) bool {
|
||||||
|
var sym = SymUnknown
|
||||||
|
if t != nil {
|
||||||
|
sym = t.symbol()
|
||||||
|
}
|
||||||
|
return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
|
||||||
|
}
|
||||||
|
|
||||||
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
|
||||||
var selectorTerm *term = nil
|
var selectorTerm *term = nil
|
||||||
var currentTerm *term = nil
|
var currentTerm *term = nil
|
||||||
@ -402,12 +427,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
}
|
}
|
||||||
case SymOpenSquare:
|
case SymOpenSquare:
|
||||||
var listTerm *term
|
var listTerm *term
|
||||||
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
|
parsingIndeces := couldBeACollection(currentTerm)
|
||||||
var sym = SymUnknown
|
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
|
||||||
if currentTerm != nil {
|
if parsingIndeces {
|
||||||
sym = currentTerm.symbol()
|
|
||||||
}
|
|
||||||
if sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable {
|
|
||||||
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
|
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
|
||||||
indexTerm := newTerm(indexTk)
|
indexTerm := newTerm(indexTk)
|
||||||
if err = tree.addTerm(indexTerm); err == nil {
|
if err = tree.addTerm(indexTerm); err == nil {
|
||||||
@ -419,11 +441,15 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
currentTerm = listTerm
|
currentTerm = listTerm
|
||||||
}
|
}
|
||||||
case SymOpenBrace:
|
case SymOpenBrace:
|
||||||
|
if currentTerm != nil && currentTerm.symbol() == SymColon {
|
||||||
|
err = currentTerm.Errorf(`selector-case outside of a selector context`)
|
||||||
|
} else {
|
||||||
var mapTerm *term
|
var mapTerm *term
|
||||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||||
err = tree.addTerm(mapTerm)
|
err = tree.addTerm(mapTerm)
|
||||||
currentTerm = mapTerm
|
currentTerm = mapTerm
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case SymEqual:
|
case SymEqual:
|
||||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
@ -452,15 +478,17 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
|||||||
}
|
}
|
||||||
case SymColon, SymDoubleColon:
|
case SymColon, SymDoubleColon:
|
||||||
var caseTerm *term
|
var caseTerm *term
|
||||||
if selectorTerm == nil {
|
if selectorTerm != nil {
|
||||||
err = tk.Errorf("selector-case outside of a selector context")
|
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||||
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
|
||||||
addSelectorCase(selectorTerm, caseTerm)
|
addSelectorCase(selectorTerm, caseTerm)
|
||||||
currentTerm = caseTerm
|
currentTerm = caseTerm
|
||||||
if tk.Sym == SymDoubleColon {
|
if tk.Sym == SymDoubleColon {
|
||||||
selectorTerm = nil
|
selectorTerm = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
currentTerm, err = tree.addToken2(tk)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
currentTerm, err = tree.addToken2(tk)
|
currentTerm, err = tree.addToken2(tk)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user