diff --git a/ast/node.go b/ast/node.go
index 7aabf064c..cf24a2301 100644
--- a/ast/node.go
+++ b/ast/node.go
@@ -3,6 +3,7 @@ package ast
import (
"reflect"
"regexp"
+ "time"
"github.com/expr-lang/expr/file"
)
@@ -93,6 +94,12 @@ type BoolNode struct {
Value bool // Value of the boolean.
}
+// DurationNode represents a duration.
+type DurationNode struct {
+ base
+ Value time.Duration // Value of the duration.
+}
+
// StringNode represents a string.
type StringNode struct {
base
diff --git a/ast/print.go b/ast/print.go
index 9a7d12391..27b536604 100644
--- a/ast/print.go
+++ b/ast/print.go
@@ -29,6 +29,10 @@ func (n *BoolNode) String() string {
return fmt.Sprintf("%t", n.Value)
}
+func (n *DurationNode) String() string {
+ return fmt.Sprintf("%v", n.Value)
+}
+
func (n *StringNode) String() string {
return fmt.Sprintf("%q", n.Value)
}
diff --git a/ast/visitor.go b/ast/visitor.go
index 287a75589..9c43ea4dd 100644
--- a/ast/visitor.go
+++ b/ast/visitor.go
@@ -12,6 +12,7 @@ func Walk(node *Node, v Visitor) {
case *IdentifierNode:
case *IntegerNode:
case *FloatNode:
+ case *DurationNode:
case *BoolNode:
case *StringNode:
case *ConstantNode:
diff --git a/builtin/builtin.go b/builtin/builtin.go
index 8576160a6..f0527a83f 100644
--- a/builtin/builtin.go
+++ b/builtin/builtin.go
@@ -4,6 +4,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
+ "math"
"reflect"
"sort"
"strings"
@@ -154,20 +155,6 @@ var Builtins = []*ast.Function{
return anyType, fmt.Errorf("invalid argument for floor (type %s)", args[0])
},
},
- {
- Name: "round",
- Fast: Round,
- Validate: func(args []reflect.Type) (reflect.Type, error) {
- if len(args) != 1 {
- return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
- }
- switch kind(args[0]) {
- case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface:
- return floatType, nil
- }
- return anyType, fmt.Errorf("invalid argument for floor (type %s)", args[0])
- },
- },
{
Name: "int",
Fast: Int,
@@ -209,6 +196,40 @@ var Builtins = []*ast.Function{
Fast: String,
Types: types(new(func(any any) string)),
},
+ {
+ Name: "round",
+ Func: func(args ...any) (any, error) {
+ switch l := len(args); l {
+ case 1:
+ a := args[0]
+ switch a := a.(type) {
+ case float32:
+ return math.Round(float64(a)), nil
+ case float64:
+ return math.Round(a), nil
+ case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+ return Float(a), nil
+ }
+ return nil, fmt.Errorf("invalid argument for round (type %T)", a)
+ case 2:
+ a, ok := args[0].(time.Duration)
+ if !ok {
+ return nil, fmt.Errorf("invalid argument for round (type %T)", args[0])
+ }
+ b, ok := args[1].(time.Duration)
+ if !ok {
+ return nil, fmt.Errorf("invalid argument for round (type %T)", args[1])
+ }
+ return a.Round(b), nil
+ default:
+ return nil, fmt.Errorf("invalid number of arguments for round (expected 1 or 2, got %d)", l)
+ }
+ },
+ Types: types(
+ math.Round,
+ new(func(time.Duration, time.Duration) time.Duration),
+ ),
+ },
{
Name: "trim",
Func: func(args ...any) (any, error) {
diff --git a/builtin/builtin_test.go b/builtin/builtin_test.go
index 40f6e32ee..dd1359ce4 100644
--- a/builtin/builtin_test.go
+++ b/builtin/builtin_test.go
@@ -36,6 +36,7 @@ func TestBuiltin(t *testing.T) {
{`abs(-5)`, 5},
{`abs(.5)`, .5},
{`abs(-.5)`, .5},
+ {`abs(-24h)`, 24 * time.Hour},
{`ceil(5.5)`, 6.0},
{`ceil(5)`, 5.0},
{`floor(5.5)`, 5.0},
@@ -43,15 +44,19 @@ func TestBuiltin(t *testing.T) {
{`round(5.5)`, 6.0},
{`round(5)`, 5.0},
{`round(5.49)`, 5.0},
+ {`round(24h2m, 1h)`, 24 * time.Hour},
{`int(5.5)`, 5},
{`int(5)`, 5},
{`int("5")`, 5},
+ {`int(2ns)`, 2},
{`float(5)`, 5.0},
{`float(5.5)`, 5.5},
{`float("5.5")`, 5.5},
+ {`float(2ns)`, 2.0},
{`string(5)`, "5"},
{`string(5.5)`, "5.5"},
{`string("5.5")`, "5.5"},
+ {`string(1m2s)`, "1m2s"},
{`trim(" foo ")`, "foo"},
{`trim("__foo___", "_")`, "foo"},
{`trimPrefix("prefix_foo", "prefix_")`, "foo"},
@@ -75,8 +80,10 @@ func TestBuiltin(t *testing.T) {
{`hasSuffix("foo,bar,baz", "baz")`, true},
{`max(1, 2, 3)`, 3},
{`max(1.5, 2.5, 3.5)`, 3.5},
+ {`max(3h, 2m, 1s)`, 3 * time.Hour},
{`min(1, 2, 3)`, 1},
{`min(1.5, 2.5, 3.5)`, 1.5},
+ {`min(3h, 2m, 1s)`, time.Second},
{`sum(1..9)`, 45},
{`sum([.5, 1.5, 2.5])`, 4.5},
{`sum([])`, 0},
diff --git a/builtin/func.go b/builtin/func.go
index 9bcd7827b..71c90d7a0 100644
--- a/builtin/func.go
+++ b/builtin/func.go
@@ -5,6 +5,7 @@ import (
"math"
"reflect"
"strconv"
+ "time"
"github.com/expr-lang/expr/vm/runtime"
)
@@ -35,6 +36,8 @@ func Type(arg any) any {
}
if v.Type().Name() != "" && v.Type().PkgPath() != "" {
return fmt.Sprintf("%s.%s", v.Type().PkgPath(), v.Type().Name())
+ } else if v.Type().String() == "time.Duration" {
+ return "duration"
}
switch v.Type().Kind() {
case reflect.Invalid:
@@ -135,6 +138,11 @@ func Abs(x any) any {
} else {
return x
}
+ case time.Duration:
+ if x.(time.Duration) < 0 {
+ return -x.(time.Duration)
+ }
+ return x
}
panic(fmt.Sprintf("invalid argument for abs (type %T)", x))
}
@@ -163,18 +171,6 @@ func Floor(x any) any {
panic(fmt.Sprintf("invalid argument for floor (type %T)", x))
}
-func Round(x any) any {
- switch x := x.(type) {
- case float32:
- return math.Round(float64(x))
- case float64:
- return math.Round(x)
- case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
- return Float(x)
- }
- panic(fmt.Sprintf("invalid argument for round (type %T)", x))
-}
-
func Int(x any) any {
switch x := x.(type) {
case float32:
@@ -201,6 +197,8 @@ func Int(x any) any {
return int(x)
case uint64:
return int(x)
+ case time.Duration:
+ return int(x)
case string:
i, err := strconv.Atoi(x)
if err != nil {
@@ -244,6 +242,8 @@ func Float(x any) any {
panic(fmt.Sprintf("invalid operation: float(%s)", x))
}
return f
+ case time.Duration:
+ return float64(x)
default:
panic(fmt.Sprintf("invalid operation: float(%T)", x))
}
diff --git a/checker/checker.go b/checker/checker.go
index c214acf3b..74327209c 100644
--- a/checker/checker.go
+++ b/checker/checker.go
@@ -94,6 +94,8 @@ func (v *checker) visit(node ast.Node) (reflect.Type, info) {
t, i = v.IntegerNode(n)
case *ast.FloatNode:
t, i = v.FloatNode(n)
+ case *ast.DurationNode:
+ t, i = v.DurationNode(n)
case *ast.BoolNode:
t, i = v.BoolNode(n)
case *ast.StringNode:
@@ -202,6 +204,10 @@ func (v *checker) FloatNode(*ast.FloatNode) (reflect.Type, info) {
return floatType, info{}
}
+func (v *checker) DurationNode(*ast.DurationNode) (reflect.Type, info) {
+ return durationType, info{}
+}
+
func (v *checker) BoolNode(*ast.BoolNode) (reflect.Type, info) {
return boolType, info{}
}
@@ -233,6 +239,9 @@ func (v *checker) UnaryNode(node *ast.UnaryNode) (reflect.Type, info) {
if isNumber(t) {
return t, info{}
}
+ if isDuration(t) {
+ return t, info{}
+ }
if isAny(t) {
return anyType, info{}
}
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 4831a3103..28d067364 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -200,6 +200,8 @@ func (c *compiler) compile(node ast.Node) {
c.IntegerNode(n)
case *ast.FloatNode:
c.FloatNode(n)
+ case *ast.DurationNode:
+ c.DurationNode(n)
case *ast.BoolNode:
c.BoolNode(n)
case *ast.StringNode:
@@ -319,6 +321,10 @@ func (c *compiler) FloatNode(node *ast.FloatNode) {
}
}
+func (c *compiler) DurationNode(node *ast.DurationNode) {
+ c.emitPush(node.Value)
+}
+
func (c *compiler) BoolNode(node *ast.BoolNode) {
if node.Value {
c.emit(OpTrue)
diff --git a/docs/Language-Definition.md b/docs/Language-Definition.md
index 46bad9322..73b2d8451 100644
--- a/docs/Language-Definition.md
+++ b/docs/Language-Definition.md
@@ -33,6 +33,12 @@
"foo"
, 'bar'
+
+ Duration |
+
+ 1h16m7ms
+ |
+
Array |
@@ -358,6 +364,30 @@ date("2023-08-14T00:00:00Z")
date("2023-08-14 00:00:00", "2006-01-02 15:04:05", "Europe/Zurich")
```
+## Duration Functions
+
+The following operators can be used to manipulate durations:
+
+```expr
+-1h == -1 * 1h
++1h == 1h
+2 * 1h == 2h
+1h + 1m == 1h1m
+1h - 1m == 59m
+1h / 10m == 6
+1h / 2 == 30m
+```
+
+Some number functions (max, min and abs) are compatible with durations as well.
+
+### round(d1, d2)
+
+Returns the result of rounding d1 to the nearest multiple of d2.
+
+```expr
+round(24h2m, 1h) == 24h
+```
+
## Number Functions
### max(n1, n2)
diff --git a/expr_test.go b/expr_test.go
index eb77408b1..0288a9535 100644
--- a/expr_test.go
+++ b/expr_test.go
@@ -1031,6 +1031,90 @@ func TestExpr(t *testing.T) {
`duration("1s") * .5`,
5e8,
},
+ {
+ `1h == 1h`,
+ true,
+ },
+ {
+ `TimePlusDay - Time >= 24h`,
+ true,
+ },
+ {
+ `1h > 1m`,
+ true,
+ },
+ {
+ `1h < 1m`,
+ false,
+ },
+ {
+ `1h >= 1m`,
+ true,
+ },
+ {
+ `1h <= 1m`,
+ false,
+ },
+ {
+ `1h > 1m`,
+ true,
+ },
+ {
+ `1h + 1m`,
+ time.Hour + time.Minute,
+ },
+ {
+ `7 * 1h`,
+ 7 * time.Hour,
+ },
+ {
+ `1h * 7`,
+ 7 * time.Hour,
+ },
+ {
+ `1s * .5`,
+ 5e8,
+ },
+ {
+ "-1h",
+ -time.Hour,
+ },
+ {
+ "+1h",
+ time.Hour,
+ },
+ {
+ "1h - 1m",
+ 59 * time.Minute,
+ },
+ {
+ "1h - -1m",
+ time.Hour + time.Minute,
+ },
+ {
+ "1h / 2 * 2",
+ time.Hour,
+ },
+ {
+ "1h * 2 / 2",
+ time.Hour,
+ },
+ {
+ "1h5m / 10m",
+ 6.5,
+ },
+ {
+ `date("2023-08-14") - 24h == date("2023-08-13")`,
+ true,
+ },
+ {
+ `date("2023-08-13") + 24h == date("2023-08-14")`,
+ true,
+ },
+ {
+ `let res = 24h; date("2023-08-14") - date("2023-08-13") == res`,
+ true,
+ },
{
`1 /* one */ + 2 // two`,
3,
diff --git a/optimizer/fold.go b/optimizer/fold.go
index 910c92402..da140fc84 100644
--- a/optimizer/fold.go
+++ b/optimizer/fold.go
@@ -4,15 +4,17 @@ import (
"fmt"
"math"
"reflect"
+ "time"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/file"
)
var (
- integerType = reflect.TypeOf(0)
- floatType = reflect.TypeOf(float64(0))
- stringType = reflect.TypeOf("")
+ integerType = reflect.TypeOf(0)
+ floatType = reflect.TypeOf(float64(0))
+ durationType = reflect.TypeOf(time.Nanosecond)
+ stringType = reflect.TypeOf("")
)
type fold struct {
@@ -32,6 +34,8 @@ func (fold *fold) Visit(node *Node) {
newNode.SetType(integerType)
case *FloatNode:
newNode.SetType(floatType)
+ case *DurationNode:
+ newNode.SetType(durationType)
case *StringNode:
newNode.SetType(stringType)
default:
@@ -49,6 +53,9 @@ func (fold *fold) Visit(node *Node) {
if i, ok := n.Node.(*FloatNode); ok {
patchWithType(&FloatNode{Value: -i.Value})
}
+ if i, ok := n.Node.(*DurationNode); ok {
+ patchWithType(&DurationNode{Value: -i.Value})
+ }
case "+":
if i, ok := n.Node.(*IntegerNode); ok {
patchWithType(&IntegerNode{Value: i.Value})
@@ -56,6 +63,9 @@ func (fold *fold) Visit(node *Node) {
if i, ok := n.Node.(*FloatNode); ok {
patchWithType(&FloatNode{Value: i.Value})
}
+ if i, ok := n.Node.(*DurationNode); ok {
+ patchWithType(&DurationNode{Value: i.Value})
+ }
case "!", "not":
if a := toBool(n.Node); a != nil {
patch(&BoolNode{Value: !a.Value})
diff --git a/parser/lexer/state.go b/parser/lexer/state.go
index 5d82329eb..b57ae52cc 100644
--- a/parser/lexer/state.go
+++ b/parser/lexer/state.go
@@ -57,10 +57,18 @@ func root(l *lexer) stateFn {
}
func number(l *lexer) stateFn {
+ kind := Number
+ loc, prev, end := l.loc, l.prev, l.end
if !l.scanNumber() {
- return l.error("bad number syntax: %q", l.word())
+ loc2, prev2, end2 := l.loc, l.prev, l.end
+ l.loc, l.prev, l.end = loc, prev, end
+ if !l.scanDuration() {
+ l.loc, l.prev, l.end = loc2, prev2, end2
+ return l.error("bad number syntax: %q", l.word())
+ }
+ kind = Duration
}
- l.emit(Number)
+ l.emit(kind)
return root
}
@@ -102,6 +110,49 @@ func (l *lexer) scanNumber() bool {
return true
}
+func (l *lexer) scanDuration() bool {
+ digits := "0123456789"
+ ok := false
+ for {
+ l.acceptRun(digits)
+ if l.accept(".") {
+ l.acceptRun(digits)
+ }
+ if l.accept("h") {
+ ok = true
+ continue
+ } else if l.accept("m") {
+ l.accept("s")
+ ok = true
+ continue
+ } else if l.accept("s") {
+ ok = true
+ continue
+ } else if l.accept("uµ") {
+ if l.accept("s") {
+ ok = true
+ continue
+ } else {
+ break
+ }
+ } else if l.accept("n") {
+ if l.accept("s") {
+ ok = true
+ continue
+ } else {
+ break
+ }
+ } else {
+ break
+ }
+ }
+ if !ok || utils.IsAlphaNumeric(l.peek()) {
+ l.next()
+ return false
+ }
+ return true
+}
+
func dot(l *lexer) stateFn {
l.next()
if l.accept("0123456789") {
diff --git a/parser/lexer/token.go b/parser/lexer/token.go
index 459fa6905..96e082c54 100644
--- a/parser/lexer/token.go
+++ b/parser/lexer/token.go
@@ -11,6 +11,7 @@ type Kind string
const (
Identifier Kind = "Identifier"
Number Kind = "Number"
+ Duration Kind = "Duration"
String Kind = "String"
Operator Kind = "Operator"
Bracket Kind = "Bracket"
diff --git a/parser/parser.go b/parser/parser.go
index 5f29279bc..e2012a431 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -5,6 +5,7 @@ import (
"math"
"strconv"
"strings"
+ "time"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/builtin"
@@ -344,6 +345,17 @@ func (p *parser) parseSecondary() Node {
node.SetLocation(token.Location)
}
return node
+
+ case Duration:
+ p.next()
+ duration, err := time.ParseDuration(token.Value)
+ if err != nil {
+ p.error("invalid duration literal: %v", err)
+ }
+ var node Node = &DurationNode{Value: duration}
+ node.SetLocation(token.Location)
+ return node
+
case String:
p.next()
node := &StringNode{Value: token.Value}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 2af7635ed..efadca243 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"testing"
+ "time"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser"
@@ -68,6 +69,55 @@ func TestParse(t *testing.T) {
"1e9",
&FloatNode{Value: 1e9},
},
+ {
+ "1ns",
+ &DurationNode{Value: time.Nanosecond},
+ },
+ {
+ "1us",
+ &DurationNode{Value: time.Microsecond},
+ },
+ {
+ "1µs",
+ &DurationNode{Value: time.Microsecond},
+ },
+ {
+ "1ms",
+ &DurationNode{Value: time.Millisecond},
+ },
+ {
+ "1s",
+ &DurationNode{Value: time.Second},
+ },
+ {
+ "1m",
+ &DurationNode{Value: time.Minute},
+ },
+ {
+ "1h",
+ &DurationNode{Value: time.Hour},
+ },
+ {
+ "-1h",
+ &UnaryNode{Operator: "-",
+ Node: &DurationNode{Value: time.Hour}},
+ },
+ {
+ ".2m",
+ &DurationNode{Value: 12 * time.Second},
+ },
+ {
+ "5m3ms",
+ &DurationNode{Value: 5*time.Minute + 3*time.Millisecond},
+ },
+ {
+ "1h.2m",
+ &DurationNode{Value: time.Hour + 12*time.Second},
+ },
+ {
+ "2m2h2m2h",
+ &DurationNode{Value: 4*time.Hour + 4*time.Minute},
+ },
{
"true",
&BoolNode{Value: true},
@@ -609,6 +659,16 @@ invalid float literal: strconv.ParseFloat: parsing "0o1E+1": invalid syntax (1:6
invalid float literal: strconv.ParseFloat: parsing "1E": invalid syntax (1:2)
| 1E
| .^
+
+1mt
+bad number syntax: "1m" (1:3)
+ | 1mt
+ | ..^
+
+1h1x1s
+bad number syntax: "1h" (1:3)
+ | 1h1x1s
+ | ..^
`
func TestParse_error(t *testing.T) {
diff --git a/vm/runtime/generated.go b/vm/runtime/generated.go
index 720feb455..2872de8b7 100644
--- a/vm/runtime/generated.go
+++ b/vm/runtime/generated.go
@@ -2808,7 +2808,7 @@ func Multiply(a, b interface{}) interface{} {
panic(fmt.Sprintf("invalid operation: %T * %T", a, b))
}
-func Divide(a, b interface{}) float64 {
+func Divide(a, b interface{}) interface{} {
switch x := a.(type) {
case uint:
switch y := b.(type) {
@@ -2836,6 +2836,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case uint8:
switch y := b.(type) {
@@ -2863,6 +2865,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case uint16:
switch y := b.(type) {
@@ -2890,6 +2894,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case uint32:
switch y := b.(type) {
@@ -2917,6 +2923,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case uint64:
switch y := b.(type) {
@@ -2944,6 +2952,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case int:
switch y := b.(type) {
@@ -2971,6 +2981,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case int8:
switch y := b.(type) {
@@ -2998,6 +3010,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case int16:
switch y := b.(type) {
@@ -3025,6 +3039,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case int32:
switch y := b.(type) {
@@ -3052,6 +3068,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case int64:
switch y := b.(type) {
@@ -3079,6 +3097,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case float32:
switch y := b.(type) {
@@ -3106,6 +3126,8 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
case float64:
switch y := b.(type) {
@@ -3133,6 +3155,37 @@ func Divide(a, b interface{}) float64 {
return float64(x) / float64(y)
case float64:
return float64(x) / float64(y)
+ case time.Duration:
+ return float64(x) / float64(y)
+ }
+ case time.Duration:
+ switch y := b.(type) {
+ case uint:
+ return float64(x) / float64(y)
+ case uint8:
+ return float64(x) / float64(y)
+ case uint16:
+ return float64(x) / float64(y)
+ case uint32:
+ return float64(x) / float64(y)
+ case uint64:
+ return float64(x) / float64(y)
+ case int:
+ return time.Duration(x) / time.Duration(y)
+ case int8:
+ return time.Duration(x) / time.Duration(y)
+ case int16:
+ return time.Duration(x) / time.Duration(y)
+ case int32:
+ return time.Duration(x) / time.Duration(y)
+ case int64:
+ return time.Duration(x) / time.Duration(y)
+ case float32:
+ return time.Duration(x) / time.Duration(y)
+ case float64:
+ return time.Duration(x) / time.Duration(y)
+ case time.Duration:
+ return float64(x) / float64(y)
}
}
panic(fmt.Sprintf("invalid operation: %T / %T", a, b))
diff --git a/vm/runtime/helpers/main.go b/vm/runtime/helpers/main.go
index 66eb68dba..b6164c531 100644
--- a/vm/runtime/helpers/main.go
+++ b/vm/runtime/helpers/main.go
@@ -79,7 +79,11 @@ func cases(op string, xs ...[]string) string {
}
echo(`case %v:`, b)
if op == "/" {
- echo(`return float64(x) / float64(y)`)
+ if isDuration(a) && (isFloat(b) || isInt(b)) {
+ echo(`return time.Duration(x) / time.Duration(y)`)
+ } else {
+ echo(`return float64(x) / float64(y)`)
+ }
} else {
echo(`return %v(x) %v %v(y)`, t, op, t)
}
@@ -274,9 +278,9 @@ func Multiply(a, b interface{}) interface{} {
panic(fmt.Sprintf("invalid operation: %T * %T", a, b))
}
-func Divide(a, b interface{}) float64 {
+func Divide(a, b interface{}) interface{} {
switch x := a.(type) {
- {{ cases "/" }}
+ {{ cases_with_duration "/" }}
}
panic(fmt.Sprintf("invalid operation: %T / %T", a, b))
}
diff --git a/vm/runtime/runtime.go b/vm/runtime/runtime.go
index 406f85096..ccc26a0a8 100644
--- a/vm/runtime/runtime.go
+++ b/vm/runtime/runtime.go
@@ -6,6 +6,7 @@ import (
"fmt"
"math"
"reflect"
+ "time"
)
func deref(kind reflect.Kind, value reflect.Value) (reflect.Kind, reflect.Value) {
@@ -294,6 +295,8 @@ func Negate(i any) any {
return -v
case uint64:
return -v
+ case time.Duration:
+ return -v
default:
panic(fmt.Sprintf("invalid operation: - %T", v))
}
|