Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add boundary calc for slices #31

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 33 additions & 40 deletions expr/builtins/list_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ var _ = u.EMPTY

// len length of array types
//
// len([1,2,3]) => 3, true
// len(not_a_field) => -- NilInt, false
//
// len([1,2,3]) => 3, true
// len(not_a_field) => -- NilInt, false
type Length struct{}

// Type is IntType
Expand Down Expand Up @@ -63,13 +62,12 @@ func lenEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) {

// ArrayIndex array.index choose the nth element of an array
//
// // given context input of
// "items" = [1,2,3]
//
// array.index(items, 1) => 1, true
// array.index(items, 5) => nil, false
// array.index(items, -1) => 3, true
// // given context input of
// "items" = [1,2,3]
//
// array.index(items, 1) => 1, true
// array.index(items, 5) => nil, false
// array.index(items, -1) => 3, true
type ArrayIndex struct{}

// Type unknown - returns single value from SliceValue array
Expand Down Expand Up @@ -108,12 +106,12 @@ func arrayIndexEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool

// array.slice slice element m -> n of a slice. First arg must be a slice.
//
// // given context of
// "items" = [1,2,3,4,5]
// // given context of
// "items" = [1,2,3,4,5]
//
// array.slice(items, 1, 3) => [2,3], true
// array.slice(items, 2) => [3,4,5], true
// array.slice(items, -2) => [4,5], true
// array.slice(items, 1, 3) => [2,3], true
// array.slice(items, 2) => [3,4,5], true
// array.slice(items, -2) => [4,5], true
type ArraySlice struct{}

// Type Unknown for Array Slice
Expand Down Expand Up @@ -208,8 +206,7 @@ func arraySliceEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool
// Map Create a map from two values. If the right side value is nil
// then does not evaluate.
//
// map(left, right) => map[string]value{left:right}
//
// map(left, right) => map[string]value{left:right}
type MapFunc struct{}

// Type is MapValueType
Expand All @@ -236,9 +233,8 @@ func mapEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) {

// MapTime() Create a map[string]time of each key
//
// maptime(field) => map[string]time{field_value:message_timestamp}
// maptime(field, timestamp) => map[string]time{field_value:timestamp}
//
// maptime(field) => map[string]time{field_value:message_timestamp}
// maptime(field, timestamp) => map[string]time{field_value:timestamp}
type MapTime struct{}

// Type MapTime
Expand Down Expand Up @@ -268,9 +264,9 @@ func mapTimeEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) {
}
k = strings.ToLower(kitem.ToString())

var ok bool
ts, ok = value.ValueToTime(args[1])
if !ok {
var err error
ts, err = value.ValueToTime(args[1])
if err != nil {
return value.EmptyMapTimeValue, false
}
}
Expand All @@ -282,13 +278,13 @@ func mapTimeEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) {
// - May pass as many match strings as you want.
// - Must match on Prefix of key.
//
// given input context of:
// {"score_value":24,"event_click":true, "tag_apple": "apple", "label_orange": "orange"}
// given input context of:
// {"score_value":24,"event_click":true, "tag_apple": "apple", "label_orange": "orange"}
//
// match("score_") => {"value":24}
// match("amount_") => false
// match("event_") => {"click":true}
// match("label_","tag_") => {"apple":"apple","orange":"orange"}
// match("score_") => {"value":24}
// match("amount_") => false
// match("event_") => {"click":true}
// match("label_","tag_") => {"apple":"apple","orange":"orange"}
type Match struct{}

// Type is MapValueType
Expand Down Expand Up @@ -328,11 +324,10 @@ func matchEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) {

// MapKeys: Take a map and extract array of keys
//
// //given input:
// {"tag.1":"news","tag.2":"sports"}
//
// mapkeys(match("tag.")) => []string{"news","sports"}
// //given input:
// {"tag.1":"news","tag.2":"sports"}
//
// mapkeys(match("tag.")) => []string{"news","sports"}
type MapKeys struct{}

// Type []string aka strings
Expand Down Expand Up @@ -368,11 +363,10 @@ func mapKeysEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool) {

// MapValues: Take a map and extract array of values
//
// // given input:
// {"tag.1":"news","tag.2":"sports"}
//
// mapvalue(match("tag.")) => []string{"1","2"}
// // given input:
// {"tag.1":"news","tag.2":"sports"}
//
// mapvalue(match("tag.")) => []string{"1","2"}
type MapValues struct{}

// Type strings aka []string
Expand Down Expand Up @@ -409,11 +403,10 @@ func mapValuesEval(ctx expr.EvalContext, args []value.Value) (value.Value, bool)

// MapInvert: Take a map and invert key/values
//
// // given input:
// tags = {"1":"news","2":"sports"}
//
// mapinvert(tags) => map[string]string{"news":"1","sports":"2"}
// // given input:
// tags = {"1":"news","2":"sports"}
//
// mapinvert(tags) => map[string]string{"news":"1","sports":"2"}
type MapInvert struct{}

// Type MapValue
Expand Down
42 changes: 24 additions & 18 deletions value/coerce.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ func Cast(valType ValueType, val Value) (Value, error) {
}

case TimeType:
t, ok := ValueToTime(val)
if ok {
t, err := ValueToTime(val)
if err != nil {
return NewTimeValue(t), nil
}
case StringType:
Expand Down Expand Up @@ -312,55 +312,61 @@ func ValueToInt64(val Value) (int64, bool) {

// StringToTimeAnchor Convert a string type to a time if possible.
// If "now-3d" then use date-anchoring ie if prefix = 'now'.
func StringToTimeAnchor(val string, anchor time.Time) (time.Time, bool) {
func StringToTimeAnchor(val string, anchor time.Time) (time.Time, error) {
if len(val) > 3 && strings.ToLower(val[:3]) == "now" {
// Is date math
t, err := datemath.EvalAnchor(anchor, val)
if err != nil {
return time.Time{}, false
return time.Time{}, fmt.Errorf("failed to eval datemath anchor: %w", err)
}
return t, true
return t, nil
}

t, err := dateparse.ParseAny(val)
if err == nil {
return t, true
if err != nil {
return t, fmt.Errorf("failed to parse time %v: %w", val, err)
}
return time.Time{}, false
return t, nil
}

// ValueToTime Convert a value type to a time if possible
func ValueToTime(val Value) (time.Time, bool) {
func ValueToTime(val Value) (time.Time, error) {
return ValueToTimeAnchor(val, time.Now())
}

// ValueToTimeAnchor given a value, and a time anchor, conver to time.
// use "now-3d" anchoring if has prefix "now".
func ValueToTimeAnchor(val Value, anchor time.Time) (time.Time, bool) {
func ValueToTimeAnchor(val Value, anchor time.Time) (time.Time, error) {
switch v := val.(type) {
case TimeValue:
return v.Val(), true
case StringValue:
return StringToTimeAnchor(v.Val(), anchor)
return v.Val(), nil
case StringsValue:
vals := v.Val()
if len(vals) < 1 {
return time.Time{}, false
return time.Time{}, fmt.Errorf("empty string slice")
}
return StringToTimeAnchor(vals[0], anchor)
case SliceValue:
vals := v.SliceValue()
if len(vals) < 1 {
return time.Time{}, fmt.Errorf("empty slice")
}
return time.Time{}, fmt.Errorf("slice values")
case StringValue:
return StringToTimeAnchor(v.Val(), anchor)
case IntValue, NumberValue:
t, err := dateparse.ParseIn(v.ToString(), time.Local)
if err != nil {
return time.Time{}, false
return t, fmt.Errorf("parsing number: %w", err)
}
if t.Year() < 1800 || t.Year() > 2120 {
return t, false
return t, fmt.Errorf("year outside of time bounds 1800, 2120: %w", err)
}
return t, true
return t, nil
default:
//u.Warnf("un-handled type to time? %#v", val)
}
return time.Time{}, false
return time.Time{}, fmt.Errorf("unhandled type to time: %#v", val)
}

// StringToFloat64 converts a string to a float
Expand Down
39 changes: 34 additions & 5 deletions vm/datemath.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,46 @@ func compareBoundaries(currBoundary, newBoundary time.Time) time.Time {
return currBoundary
}
func evalBoundary(anchorTime, currBoundary time.Time, lhv value.Value, op lex.TokenType, val string) (time.Time, error) {
ct, ok := value.ValueToTime(lhv)
if !ok {
return currBoundary, fmt.Errorf("Could not convert %T: %v to time.Time", lhv, lhv)
}

// Given Anchor Time At calculate Relative Time Rt
rt, err := datemath.EvalAnchor(anchorTime, val)
if err != nil {
return currBoundary, err
}

ct, err := value.ValueToTime(lhv)
if err != nil && strings.Contains(err.Error(), "slice values") {
switch op {
case lex.TokenGT, lex.TokenGE:
bt := currBoundary
for _, val := range lhv.(value.SliceValue).SliceValue() {
ct, err := value.ValueToTime(val)
if err != nil {
return time.Time{}, fmt.Errorf("converting slice value: %w", err)
}
if rt.Before(ct) {
bt = compareBoundaries(currBoundary, anchorTime.Add(ct.Sub(rt)))
}
}
return bt, nil
case lex.TokenLT, lex.TokenLE:
bt := currBoundary
for _, val := range lhv.(value.SliceValue).SliceValue() {
ct, err := value.ValueToTime(val)
if err != nil {
return time.Time{}, fmt.Errorf("converting slice value: %w", err)
}
if !ct.Before(rt) {
bt = compareBoundaries(bt, anchorTime.Add(ct.Sub(rt)))
}
}
return bt, nil
default:
return currBoundary, nil
}
} else if err != nil {
return currBoundary, fmt.Errorf("Could not convert %T: %v to time.Time %w", lhv, lhv, err)
}

// Ct = Comparison time, left hand side of expression
// At = Anchor Time
// Rt = Relative time result of Anchor Time offset by datemath "now-3d"
Expand Down
4 changes: 2 additions & 2 deletions vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,8 +619,8 @@ func evalBinary(ctx expr.EvalContext, node *expr.BinaryNode, depth int, visitedI
n := operateNumbers(node.Operator, at.NumberValue(), bt)
return n, true
case value.TimeValue:
lht, ok := value.ValueToTime(at)
if !ok {
lht, err := value.ValueToTime(at)
if err != nil {
return value.BoolValueFalse, false
}
return operateTime(node.Operator.T, lht, bt.Val())
Expand Down
Loading