1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-02-21 19:50:09 +02:00

Bugfix/#295 arithmetic operators (#298)

* Some work

* Updated Add operator

* Updated Subtract operator

* Updated Subtract operator tests

* Added tests for multiplication

* Added division

* Updated the rest of operators
This commit is contained in:
Tim Voronov 2019-05-19 12:12:11 -04:00 committed by GitHub
parent ab0a617031
commit 6933798419
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1296 additions and 285 deletions

View File

@ -2,6 +2,7 @@ package operators
import (
"context"
"strings"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
@ -88,9 +89,62 @@ func Not(left, _ core.Value) core.Value {
return values.True
}
func ToNumberOrString(input core.Value) core.Value {
switch input.Type() {
case types.Int, types.Float, types.String:
return input
default:
return values.ToInt(input)
}
}
func ToNumberOnly(input core.Value) core.Value {
switch input.Type() {
case types.Int, types.Float:
return input
case types.String:
if strings.Contains(input.String(), ".") {
return values.ToFloat(input)
}
return values.ToInt(input)
case types.Array:
arr := input.(*values.Array)
length := arr.Length()
if length == 0 {
return values.ZeroInt
}
i := values.ZeroInt
f := values.ZeroFloat
for y := values.Int(0); y < length; y++ {
out := ToNumberOnly(arr.Get(y))
if out.Type() == types.Int {
i += out.(values.Int)
} else {
f += out.(values.Float)
}
}
if f == 0 {
return i
}
return values.Float(i) + f
default:
return values.ToInt(input)
}
}
// Adds numbers
// Concats strings
func Add(left, right core.Value) core.Value {
// Concatenates strings
func Add(inputL, inputR core.Value) core.Value {
left := ToNumberOrString(inputL)
right := ToNumberOrString(inputR)
if left.Type() == types.Int {
if right.Type() == types.Int {
l := left.(values.Int)
@ -126,7 +180,10 @@ func Add(left, right core.Value) core.Value {
return values.NewString(left.String() + right.String())
}
func Subtract(left, right core.Value) core.Value {
func Subtract(inputL, inputR core.Value) core.Value {
left := ToNumberOnly(inputL)
right := ToNumberOnly(inputR)
if left.Type() == types.Int {
if right.Type() == types.Int {
l := left.(values.Int)
@ -162,7 +219,10 @@ func Subtract(left, right core.Value) core.Value {
return values.ZeroInt
}
func Multiply(left, right core.Value) core.Value {
func Multiply(inputL, inputR core.Value) core.Value {
left := ToNumberOnly(inputL)
right := ToNumberOnly(inputR)
if left.Type() == types.Int {
if right.Type() == types.Int {
l := left.(values.Int)
@ -198,20 +258,31 @@ func Multiply(left, right core.Value) core.Value {
return values.ZeroInt
}
func Divide(left, right core.Value) core.Value {
func Divide(inputL, inputR core.Value) core.Value {
left := ToNumberOnly(inputL)
right := ToNumberOnly(inputR)
if left.Type() == types.Int {
if right.Type() == types.Int {
l := left.(values.Int)
r := right.(values.Int)
l := values.Float(left.(values.Int))
r := values.Float(right.(values.Int))
if r == 0.0 {
panic("divide by zero")
}
return l / r
}
if right.Type() == types.Float {
l := left.(values.Int)
l := values.Float(left.(values.Int))
r := right.(values.Float)
return values.Float(l) / r
if r == 0.0 {
panic("divide by zero")
}
return l / r
}
}
@ -220,21 +291,32 @@ func Divide(left, right core.Value) core.Value {
l := left.(values.Float)
r := right.(values.Float)
if r == 0.0 {
panic("divide by zero")
}
return l / r
}
if right.Type() == types.Int {
l := left.(values.Float)
r := right.(values.Int)
r := values.Float(right.(values.Int))
return l / values.Float(r)
if r == 0.0 {
panic("divide by zero")
}
return l / r
}
}
return values.ZeroInt
}
func Modulus(left, right core.Value) core.Value {
func Modulus(inputL, inputR core.Value) core.Value {
left := ToNumberOnly(inputL)
right := ToNumberOnly(inputR)
if left.Type() == types.Int {
if right.Type() == types.Int {
l := left.(values.Int)
@ -270,7 +352,9 @@ func Modulus(left, right core.Value) core.Value {
return values.ZeroInt
}
func Increment(left, _ core.Value) core.Value {
func Increment(inputL, _ core.Value) core.Value {
left := ToNumberOnly(inputL)
if left.Type() == types.Int {
l := left.(values.Int)
@ -286,7 +370,9 @@ func Increment(left, _ core.Value) core.Value {
return values.None
}
func Decrement(left, _ core.Value) core.Value {
func Decrement(inputL, _ core.Value) core.Value {
left := ToNumberOnly(inputL)
if left.Type() == types.Int {
l := left.(values.Int)
@ -303,31 +389,27 @@ func Decrement(left, _ core.Value) core.Value {
}
func Negative(value, _ core.Value) core.Value {
err := core.ValidateType(value, types.Int, types.Float)
if err != nil {
return values.ZeroInt
}
if value.Type() == types.Int {
return -value.(values.Int)
}
return -value.(values.Float)
if value.Type() == types.Float {
return -value.(values.Float)
}
return value
}
func Positive(value, _ core.Value) core.Value {
err := core.ValidateType(value, types.Int, types.Float)
if err != nil {
return values.ZeroInt
}
if value.Type() == types.Int {
return +value.(values.Int)
}
return +value.(values.Float)
if value.Type() == types.Float {
return +value.(values.Float)
}
return value
}
func ToBoolean(value, _ core.Value) core.Value {

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,8 @@ import (
type Float float64
var NaN = Float(math.NaN())
const ZeroFloat = Float(0.0)
func NewFloat(input float64) Float {
@ -80,7 +82,7 @@ func (t Float) Type() core.Type {
}
func (t Float) String() string {
return fmt.Sprintf("%f", t)
return fmt.Sprintf("%v", float64(t))
}
func (t Float) Compare(other core.Value) int64 {

View File

@ -235,58 +235,116 @@ func ToBoolean(input core.Value) core.Value {
switch input.Type() {
case types.Boolean:
return input
case types.None:
return False
case types.String:
return NewBoolean(input.String() != "")
return NewBoolean(input.(String) != "")
case types.Int:
return NewBoolean(input.(Int) != 0)
case types.Float:
return NewBoolean(input.(Float) != 0)
case types.DateTime:
return NewBoolean(!input.(DateTime).IsZero())
case types.None:
return False
default:
return True
}
}
func ToFloat(input core.Value) (Float, error) {
func ToFloat(input core.Value) Float {
switch val := input.(type) {
case Float:
return val, nil
return val
case Int:
return Float(val), nil
return Float(val)
case String:
i, err := strconv.ParseFloat(string(val), 64)
if err != nil {
return ZeroFloat, err
return ZeroFloat
}
return Float(i), nil
return Float(i)
case Boolean:
if val {
return Float(1)
}
return Float(0)
case DateTime:
dt := input.(DateTime)
if dt.IsZero() {
return ZeroFloat
}
return NewFloat(float64(dt.Unix()))
case *Array:
length := val.Length()
if length == 0 {
return ZeroFloat
}
res := ZeroFloat
for i := Int(0); i < length; i++ {
res += ToFloat(val.Get(i))
}
return res
default:
return ZeroFloat, core.TypeError(input.Type(), types.Int, types.Float, types.String)
return ZeroFloat
}
}
func ToInt(input core.Value) (Int, error) {
func ToInt(input core.Value) Int {
switch val := input.(type) {
case Int:
return val, nil
return val
case Float:
return Int(val), nil
return Int(val)
case String:
i, err := strconv.ParseInt(string(val), 10, 64)
if err != nil {
return ZeroInt, err
return ZeroInt
}
return Int(i), nil
return Int(i)
case Boolean:
if val {
return Int(1)
}
return Int(0)
case DateTime:
dt := input.(DateTime)
if dt.IsZero() {
return ZeroInt
}
return NewInt(int(dt.Unix()))
case *Array:
length := val.Length()
if length == 0 {
return ZeroInt
}
res := ZeroInt
for i := Int(0); i < length; i++ {
res += ToInt(val.Get(i))
}
return res
default:
return ZeroInt, core.TypeError(input.Type(), types.Int, types.Float, types.String)
return ZeroInt
}
}
func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
func ToArray(ctx context.Context, input core.Value) core.Value {
switch value := input.(type) {
case Boolean,
Int,
@ -294,9 +352,9 @@ func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
String,
DateTime:
return NewArrayWith(value), nil
return NewArrayWith(value)
case *Array:
return value.Copy(), nil
return value.Copy()
case *Object:
arr := NewArray(int(value.Length()))
@ -306,12 +364,12 @@ func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
return true
})
return arr, nil
return arr
case core.Iterable:
iterator, err := value.Iterate(ctx)
if err != nil {
return None, err
return None
}
arr := NewArray(10)
@ -320,7 +378,7 @@ func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
val, _, err := iterator.Next(ctx)
if err != nil {
return None, err
return None
}
if val == None {
@ -330,9 +388,9 @@ func ToArray(ctx context.Context, input core.Value) (core.Value, error) {
arr.Push(val)
}
return arr, nil
return arr
default:
return NewArray(0), nil
return NewArray(0)
}
}
@ -372,3 +430,9 @@ func MapHash(input map[string]core.Value) uint64 {
return h.Sum64()
}
func IsNumber(input core.Value) Boolean {
t := input.Type()
return t == types.Int || t == types.Float
}

View File

@ -225,41 +225,58 @@ func TestHelpers(t *testing.T) {
Convey("ToFloat", func() {
Convey("Should convert Int", func() {
input := values.NewInt(100)
output, err := values.ToFloat(input)
output := values.ToFloat(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewFloat(100))
})
Convey("Should convert Float", func() {
input := values.NewFloat(100)
output, err := values.ToFloat(input)
output := values.ToFloat(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewFloat(100))
})
Convey("Should convert String", func() {
input := values.NewString("100.1")
output, err := values.ToFloat(input)
output := values.ToFloat(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewFloat(100.1))
output2 := values.ToFloat(values.NewString("foobar"))
So(output2, ShouldEqual, values.ZeroFloat)
})
Convey("Should convert Boolean", func() {
So(values.ToFloat(values.True), ShouldEqual, values.NewFloat(1))
So(values.ToFloat(values.False), ShouldEqual, values.NewFloat(0))
})
Convey("Should convert Array with single item", func() {
So(values.ToFloat(values.NewArrayWith(values.NewFloat(1))), ShouldEqual, values.NewFloat(1))
})
Convey("Should convert Array with multiple items", func() {
arg := values.NewArrayWith(values.NewFloat(1), values.NewFloat(1))
So(values.ToFloat(arg), ShouldEqual, values.NewFloat(2))
})
Convey("Should convert DateTime", func() {
dt := values.NewCurrentDateTime()
ts := dt.Time.Unix()
So(values.ToFloat(dt), ShouldEqual, values.NewFloat(float64(ts)))
})
Convey("Should NOT convert other types", func() {
inputs := []core.Value{
values.NewBoolean(true),
values.NewCurrentDateTime(),
values.NewArray(1),
values.NewObject(),
values.NewBinary([]byte("")),
}
for _, input := range inputs {
_, err := values.ToFloat(input)
So(err, ShouldNotBeNil)
So(values.ToFloat(input), ShouldEqual, values.ZeroFloat)
}
})
})
@ -267,41 +284,58 @@ func TestHelpers(t *testing.T) {
Convey("ToInt", func() {
Convey("Should convert Int", func() {
input := values.NewInt(100)
output, err := values.ToInt(input)
output := values.ToInt(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewInt(100))
})
Convey("Should convert Float", func() {
input := values.NewFloat(100.1)
output, err := values.ToInt(input)
output := values.ToInt(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewInt(100))
})
Convey("Should convert String", func() {
input := values.NewString("100")
output, err := values.ToInt(input)
output := values.ToInt(input)
So(err, ShouldBeNil)
So(output, ShouldEqual, values.NewInt(100))
output2 := values.ToInt(values.NewString("foobar"))
So(output2, ShouldEqual, values.ZeroInt)
})
Convey("Should convert Boolean", func() {
So(values.ToInt(values.True), ShouldEqual, values.NewInt(1))
So(values.ToInt(values.False), ShouldEqual, values.NewInt(0))
})
Convey("Should convert Array with single item", func() {
So(values.ToInt(values.NewArrayWith(values.NewFloat(1))), ShouldEqual, values.NewInt(1))
})
Convey("Should convert Array with multiple items", func() {
arg := values.NewArrayWith(values.NewFloat(1), values.NewFloat(1))
So(values.ToInt(arg), ShouldEqual, values.NewFloat(2))
})
Convey("Should convert DateTime", func() {
dt := values.NewCurrentDateTime()
ts := dt.Time.Unix()
So(values.ToInt(dt), ShouldEqual, values.NewInt(int(ts)))
})
Convey("Should NOT convert other types", func() {
inputs := []core.Value{
values.NewBoolean(true),
values.NewCurrentDateTime(),
values.NewArray(1),
values.NewObject(),
values.NewBinary([]byte("")),
}
for _, input := range inputs {
_, err := values.ToInt(input)
So(err, ShouldNotBeNil)
So(values.ToInt(input), ShouldEqual, values.ZeroInt)
}
})
})
@ -338,10 +372,9 @@ func TestHelpers(t *testing.T) {
}
for _, pairs := range inputs {
actual, err := values.ToArray(context.Background(), pairs[0])
actual := values.ToArray(context.Background(), pairs[0])
expected := pairs[1]
So(err, ShouldBeNil)
So(actual.Compare(expected), ShouldEqual, 0)
}
})
@ -357,9 +390,7 @@ func TestHelpers(t *testing.T) {
}
input := values.NewArrayWith(vals...)
output, err := values.ToArray(context.Background(), input)
So(err, ShouldBeNil)
output := values.ToArray(context.Background(), input)
arr := output.(*values.Array)
@ -383,9 +414,7 @@ func TestHelpers(t *testing.T) {
values.NewObjectProperty("qaz", values.NewObject()),
)
output, err := values.ToArray(context.Background(), input)
So(err, ShouldBeNil)
output := values.ToArray(context.Background(), input)
arr := output.(*values.Array).Sort()

View File

@ -38,17 +38,8 @@ func MouseMoveXY(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
x, err := values.ToFloat(args[0])
if err != nil {
return values.None, err
}
y, err := values.ToFloat(args[1])
if err != nil {
return values.None, err
}
x := values.ToFloat(args[0])
y := values.ToFloat(args[1])
doc := args[0].(drivers.HTMLDocument)

View File

@ -38,17 +38,8 @@ func ScrollXY(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
x, err := values.ToFloat(args[1])
if err != nil {
return values.None, err
}
y, err := values.ToFloat(args[2])
if err != nil {
return values.None, err
}
x := values.ToFloat(args[1])
y := values.ToFloat(args[2])
doc := args[0].(drivers.HTMLDocument)

View File

@ -26,22 +26,10 @@ func Rand(_ context.Context, args ...core.Value) (core.Value, error) {
var max float64
var min float64
arg1, err := values.ToFloat(args[0])
if err != nil {
return values.None, err
}
max = float64(arg1)
max = float64(values.ToFloat(args[0]))
if len(args) > 1 {
arg2, err := values.ToFloat(args[1])
if err != nil {
return values.None, err
}
min = float64(arg2)
min = float64(values.ToFloat(args[1]))
} else {
max, min = core.NumberBoundaries(max)
}

View File

@ -20,5 +20,5 @@ func ToArray(ctx context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
return values.ToArray(ctx, args[0])
return values.ToArray(ctx, args[0]), nil
}

View File

@ -5,7 +5,6 @@ import (
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ToBool takes an input value of any type and converts it into the appropriate boolean value.
@ -24,44 +23,5 @@ func ToBool(_ context.Context, args ...core.Value) (core.Value, error) {
return values.None, err
}
arg := args[0]
switch arg.Type() {
case types.Boolean:
return arg, nil
case types.Int:
val := arg.(values.Int)
if val != 0 {
return values.True, nil
}
return values.False, nil
case types.Float:
val := arg.(values.Float)
if val != 0 {
return values.True, nil
}
return values.False, nil
case types.String:
if arg.String() != "" {
return values.True, nil
}
return values.False, nil
case types.DateTime:
val := arg.(values.DateTime)
if !val.IsZero() {
return values.True, nil
}
return values.False, nil
case types.None:
return values.False, nil
default:
return values.True, nil
}
return values.ToBoolean(args[0]), nil
}

View File

@ -2,11 +2,9 @@ package types
import (
"context"
"strconv"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ToFloat takes an input value of any type and convert it into a float value.
@ -20,67 +18,12 @@ import (
// An empty array is converted to 0, an array with one member is converted into the result of TO_NUMBER() for its sole member.
// An array with two or more members is converted to the number 0.
// An object / HTML node is converted to the number 0.
func ToFloat(ctx context.Context, args ...core.Value) (core.Value, error) {
func ToFloat(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
arg := args[0]
switch arg.Type() {
case types.Boolean:
val := arg.(values.Boolean)
if val {
return values.NewFloat(1), nil
}
return values.ZeroFloat, nil
case types.Int:
val := arg.(values.Int)
return values.Float(val), nil
case types.Float:
return arg, nil
case types.String:
str := arg.String()
if str == "" {
return values.ZeroFloat, nil
}
num, err := strconv.ParseFloat(str, 64)
if err != nil {
return values.ZeroFloat, nil
}
return values.NewFloat(num), nil
case types.DateTime:
val := arg.(values.DateTime)
if val.IsZero() {
return values.ZeroFloat, nil
}
return values.NewFloat(float64(val.Unix())), nil
case types.None:
return values.ZeroFloat, nil
case types.Array:
val := arg.(*values.Array)
if val.Length() == 0 {
return values.ZeroFloat, nil
}
if val.Length() == 1 {
return ToFloat(ctx, val.Get(0))
}
return values.ZeroFloat, nil
default:
return values.ZeroFloat, nil
}
return values.ToFloat(args[0]), nil
}

View File

@ -2,11 +2,9 @@ package types
import (
"context"
"strconv"
"github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/MontFerret/ferret/pkg/runtime/values/types"
)
// ToInt takes an input value of any type and convert it into an integer value.
@ -20,67 +18,12 @@ import (
// An empty array is converted to 0, an array with one member is converted into the result of TO_NUMBER() for its sole member.
// An array with two or more members is converted to the number 0.
// An object / HTML node is converted to the number 0.
func ToInt(ctx context.Context, args ...core.Value) (core.Value, error) {
func ToInt(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 1, 1)
if err != nil {
return values.None, err
}
arg := args[0]
switch arg.Type() {
case types.Boolean:
val := arg.(values.Boolean)
if val {
return values.NewInt(1), nil
}
return values.ZeroInt, nil
case types.Int:
return arg, nil
case types.Float:
val := arg.(values.Float)
return values.Int(val), nil
case types.String:
str := arg.String()
if str == "" {
return values.ZeroInt, nil
}
num, err := strconv.Atoi(str)
if err != nil {
return values.ZeroInt, nil
}
return values.NewInt(num), nil
case types.DateTime:
val := arg.(values.DateTime)
if val.IsZero() {
return values.ZeroInt, nil
}
return values.NewInt(int(val.Unix())), nil
case types.None:
return values.ZeroInt, nil
case types.Array:
val := arg.(*values.Array)
if val.Length() == 0 {
return values.ZeroInt, nil
}
if val.Length() == 1 {
return ToInt(ctx, val.Get(0))
}
return values.ZeroInt, nil
default:
return values.ZeroInt, nil
}
return values.ToInt(args[0]), nil
}

View File

@ -17,11 +17,7 @@ func Wait(_ context.Context, args ...core.Value) (core.Value, error) {
return values.None, nil
}
arg, err := values.ToInt(args[0])
if err != nil {
return values.None, err
}
arg := values.ToInt(args[0])
time.Sleep(time.Millisecond * time.Duration(arg))