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"
|
||||
typeItem = "item"
|
||||
typeNumber = "number"
|
||||
typePair = "pair"
|
||||
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) {
|
||||
if len(*indexList) != 1 {
|
||||
err = indexTerm.Errorf("one index only is allowed")
|
||||
} else {
|
||||
index = (*indexList)[0]
|
||||
}
|
||||
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, err = toInt((*indexList)[0], "index expression"); err == nil {
|
||||
if v < 0 && v >= -maxValue {
|
||||
v = maxValue + v
|
||||
}
|
||||
@ -42,45 +36,86 @@ func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int,
|
||||
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) {
|
||||
var leftValue, rightValue any
|
||||
var indexList *ListType
|
||||
var ok bool
|
||||
|
||||
if err = self.checkOperands(); err != nil {
|
||||
return
|
||||
}
|
||||
// if err = self.checkOperands(); err != nil {
|
||||
// return
|
||||
// }
|
||||
if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexTerm := self.children[1]
|
||||
if indexList, ok = rightValue.(*ListType); !ok {
|
||||
err = self.Errorf("invalid index expression")
|
||||
return
|
||||
} else if len(*indexList) != 1 {
|
||||
err = indexTerm.Errorf("one index only is allowed")
|
||||
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)
|
||||
if IsInteger((*indexList)[0]) {
|
||||
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)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
default:
|
||||
err = self.errIncompatibleTypes(leftValue, rightValue)
|
||||
}
|
||||
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)
|
||||
}
|
72
parser.go
72
parser.go
@ -154,16 +154,34 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
|
||||
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()
|
||||
args := make([]*term, 0)
|
||||
lastSym := SymUnknown
|
||||
itemExpected := false
|
||||
for lastSym != SymClosedSquare && lastSym != SymEos {
|
||||
var subTree *ast
|
||||
zeroRequired := scanner.current.Sym == SymColon
|
||||
if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
|
||||
if subTree.root != nil {
|
||||
args = append(args, subTree.root)
|
||||
root := 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 {
|
||||
prev := scanner.Previous()
|
||||
err = prev.ErrorExpectedGot("list-item")
|
||||
@ -176,7 +194,6 @@ func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term
|
||||
itemExpected = lastSym == SymComma
|
||||
}
|
||||
if err == nil {
|
||||
// TODO Check arguments
|
||||
if lastSym != SymClosedSquare {
|
||||
err = scanner.Previous().ErrorExpectedGot("]")
|
||||
} else {
|
||||
@ -295,7 +312,7 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
|
||||
err = tk.Errorf("case list in default clause")
|
||||
return
|
||||
}
|
||||
if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
|
||||
if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
|
||||
return
|
||||
}
|
||||
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...)
|
||||
}
|
||||
|
||||
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) {
|
||||
var selectorTerm *term = nil
|
||||
var currentTerm *term = nil
|
||||
@ -402,12 +427,9 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
}
|
||||
case SymOpenSquare:
|
||||
var listTerm *term
|
||||
if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
|
||||
var sym = SymUnknown
|
||||
if currentTerm != nil {
|
||||
sym = currentTerm.symbol()
|
||||
}
|
||||
if sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable {
|
||||
parsingIndeces := couldBeACollection(currentTerm)
|
||||
if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
|
||||
if parsingIndeces {
|
||||
indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
|
||||
indexTerm := newTerm(indexTk)
|
||||
if err = tree.addTerm(indexTerm); err == nil {
|
||||
@ -419,10 +441,14 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
currentTerm = listTerm
|
||||
}
|
||||
case SymOpenBrace:
|
||||
var mapTerm *term
|
||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||
err = tree.addTerm(mapTerm)
|
||||
currentTerm = mapTerm
|
||||
if currentTerm != nil && currentTerm.symbol() == SymColon {
|
||||
err = currentTerm.Errorf(`selector-case outside of a selector context`)
|
||||
} else {
|
||||
var mapTerm *term
|
||||
if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
|
||||
err = tree.addTerm(mapTerm)
|
||||
currentTerm = mapTerm
|
||||
}
|
||||
}
|
||||
case SymEqual:
|
||||
if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
|
||||
@ -452,14 +478,16 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
|
||||
}
|
||||
case SymColon, SymDoubleColon:
|
||||
var caseTerm *term
|
||||
if selectorTerm == nil {
|
||||
err = tk.Errorf("selector-case outside of a selector context")
|
||||
} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||
addSelectorCase(selectorTerm, caseTerm)
|
||||
currentTerm = caseTerm
|
||||
if tk.Sym == SymDoubleColon {
|
||||
selectorTerm = nil
|
||||
if selectorTerm != nil {
|
||||
if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
|
||||
addSelectorCase(selectorTerm, caseTerm)
|
||||
currentTerm = caseTerm
|
||||
if tk.Sym == SymDoubleColon {
|
||||
selectorTerm = nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
}
|
||||
default:
|
||||
currentTerm, err = tree.addToken2(tk)
|
||||
|
Loading…
Reference in New Issue
Block a user