Skip to content

Commit

Permalink
use generic algorithm to base equality checks solely on the coalescer
Browse files Browse the repository at this point in the history
  • Loading branch information
xrstf committed Dec 1, 2023
1 parent ba5f243 commit 201842d
Show file tree
Hide file tree
Showing 18 changed files with 1,231 additions and 556 deletions.
12 changes: 0 additions & 12 deletions aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,3 @@ func NewVariables() Variables {
func NewDocument(data any) (Document, error) {
return types.NewDocument(data)
}

// Unwrap returns the native Go value for either native Go values or an
// Rudi AST node (like turning an ast.Number into an int64).
func Unwrap(val any) (any, error) {
return types.UnwrapType(val)
}

// WrapNative returns the Rudi node equivalent of a native Go value, like turning
// a string into ast.String.
func WrapNative(val any) (any, error) {
return types.WrapNative(val)
}
4 changes: 2 additions & 2 deletions pkg/coalescing/coalescer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import (
)

type Coalescer interface {
ToNull(val any) (bool, error)
ToBool(val any) (bool, error)
ToFloat64(val any) (float64, error)
ToInt64(val any) (int64, error)
ToFloat64(val any) (float64, error)
ToNumber(val any) (ast.Number, error)
ToString(val any) (string, error)
ToVector(val any) ([]any, error)
ToObject(val any) (map[string]any, error)
ToNull(val any) (bool, error)
}

func deliteral(val any) any {
Expand Down
132 changes: 107 additions & 25 deletions pkg/coalescing/humane.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package coalescing

import (
"errors"
"fmt"
"strconv"
"strings"
Expand All @@ -24,14 +25,59 @@ func (humane) ToNull(val any) (bool, error) {
case nil:
return true, nil
case bool:
return !v, nil
if v {
return false, fmt.Errorf("cannot coalesce true into null")
}
return true, nil
case int:
if v != 0 {
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
}
return true, nil
case int32:
if v != 0 {
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
}
return true, nil
case int64:
if v != 0 {
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
}
return true, nil
case float32:
if v != 0 {
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
}
return true, nil
case float64:
if v != 0 {
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
}
return true, nil
case string:
if len(v) != 0 {
return false, fmt.Errorf("cannot coalesce %q (%T) into null", v, v)
}
return true, nil
case []any:
if len(v) != 0 {
return false, errors.New("cannot coalesce non-empty vector into null")
}
return true, nil
case map[string]any:
if len(v) != 0 {
return false, errors.New("cannot coalesce non-empty object into null")
}
return true, nil
default:
return false, fmt.Errorf("cannot coalesce %T into null", v)
}
}

func (humane) ToBool(val any) (bool, error) {
switch v := deliteral(val).(type) {
case nil:
return false, nil
case bool:
return v, nil
case int:
Expand All @@ -54,15 +100,21 @@ func (humane) ToBool(val any) (bool, error) {
return len(v) > 0, nil
case map[string]any:
return len(v) > 0, nil
case nil:
return false, nil
default:
return false, fmt.Errorf("cannot coalesce %T into bool", v)
}
}

func (humane) ToFloat64(val any) (float64, error) {
switch v := deliteral(val).(type) {
case nil:
return 0, nil
case bool:
if v {
return 1, nil
} else {
return 0, nil
}
case int:
return float64(v), nil
case int32:
Expand All @@ -74,46 +126,64 @@ func (humane) ToFloat64(val any) (float64, error) {
case float64:
return v, nil
case string:
v = strings.TrimSpace(v)
if v == "" {
return 0, nil
}

parsed, err := strconv.ParseFloat(v, 64)
if err != nil {
return 0, fmt.Errorf("cannot convert %q losslessly to float64", v)
return 0, fmt.Errorf("cannot coalesce %T into float64", v)
}
return parsed, nil
case bool:
if v {
return 1, nil
} else {
return 0, nil
}
case nil:
return 0, nil
default:
return 0, fmt.Errorf("cannot coalesce %T into float64", v)
}
}

func (humane) ToInt64(val any) (int64, error) {
switch v := deliteral(val).(type) {
case nil:
return 0, nil
case bool:
if v {
return 1, nil
} else {
return 0, nil
}
case int:
return int64(v), nil
case int32:
return int64(v), nil
case int64:
return v, nil
case float32:
if v == float32(int32(v)) {
return int64(v), nil
}
return 0, fmt.Errorf("cannot convert %s losslessly to int64", formatFloat(float64(v)))
case float64:
if v == float64(int64(v)) {
return int64(v), nil
}
return 0, fmt.Errorf("cannot convert %s losslessly to int64", formatFloat(v))
case string:
v = strings.TrimSpace(v)
if v == "" {
return 0, nil
}

parsed, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot convert %q losslessly to int64", v)
// allows "2.0" to turn into int64(2)
parsed, err := strconv.ParseFloat(v, 64)
if err == nil && parsed == float64(int64(parsed)) {
return int64(parsed), nil
}

return 0, fmt.Errorf("cannot coalesce %T into int64", v)
}
return parsed, nil
case bool:
if v {
return 1, nil
} else {
return 0, nil
}
case nil:
return 0, nil
default:
return 0, fmt.Errorf("cannot coalesce %T into int64", v)
}
Expand All @@ -125,8 +195,8 @@ func (h humane) ToNumber(val any) (ast.Number, error) {

func (humane) ToString(val any) (string, error) {
switch v := deliteral(val).(type) {
case string:
return v, nil
case nil:
return "", nil
case bool:
return strconv.FormatBool(v), nil
case int:
Expand All @@ -137,8 +207,8 @@ func (humane) ToString(val any) (string, error) {
return strconv.FormatInt(v, 10), nil
case float64:
return formatFloat(v), nil
case nil:
return "", nil
case string:
return v, nil
default:
return "", fmt.Errorf("cannot coalesce %T into string", v)
}
Expand All @@ -159,6 +229,12 @@ func (humane) ToVector(val any) ([]any, error) {
return []any{}, nil
case []any:
return v, nil
case map[string]any:
if len(v) == 0 {
return []any{}, nil
} else {
return nil, fmt.Errorf("cannot coalesce %T into vector", v)
}
default:
return nil, fmt.Errorf("cannot coalesce %T into vector", v)
}
Expand All @@ -168,6 +244,12 @@ func (humane) ToObject(val any) (map[string]any, error) {
switch v := deliteral(val).(type) {
case nil:
return map[string]any{}, nil
case []any:
if len(v) == 0 {
return map[string]any{}, nil
} else {
return nil, fmt.Errorf("cannot coalesce %T into object", v)
}
case map[string]any:
return v, nil
default:
Expand Down
49 changes: 49 additions & 0 deletions pkg/coalescing/humane_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package coalescing

import (
"testing"
)

func TestHumaneCoalescer(t *testing.T) {
testCoalescer(t, NewHumane(), getHumaneTestcases())
}

func getHumaneTestcases() []testcase {
return []testcase{
// (source, canBeNull, toBool, toInt, toFloat, toNumber, toString, toVector, toObject)
// nil source value
newTestcase(nil, true, false, int64(0), 0.0, newNum(int64(0)), "", []any{}, map[string]any{}),
// boolean source values
newTestcase(true, invalid, true, int64(1), 1.0, newNum(int64(1)), "true", invalid, invalid),
newTestcase(false, true, false, int64(0), 0.0, newNum(int64(0)), "false", invalid, invalid),
// numeric source values
newTestcase(0, true, false, int64(0), 0.0, newNum(int64(0)), "0", invalid, invalid),
newTestcase(0.0, true, false, int64(0), 0.0, newNum(int64(0)), "0", invalid, invalid),
newTestcase(0.1, invalid, true, invalid, 0.1, newNum(0.1), "0.1", invalid, invalid),
newTestcase(1, invalid, true, int64(1), 1.0, newNum(int64(1)), "1", invalid, invalid),
newTestcase(1.0, invalid, true, int64(1), 1.0, newNum(int64(1)), "1", invalid, invalid),
newTestcase(-3.14, invalid, true, invalid, -3.14, newNum(-3.14), "-3.14", invalid, invalid),
// string source values
newTestcase("", true, false, int64(0), 0.0, newNum(int64(0)), "", invalid, invalid),
newTestcase(" ", invalid, true, int64(0), 0.0, newNum(int64(0)), " ", invalid, invalid),
newTestcase("\n", invalid, true, int64(0), 0.0, newNum(int64(0)), "\n", invalid, invalid),
newTestcase("0", invalid, false, int64(0), 0.0, newNum(int64(0)), "0", invalid, invalid),
newTestcase("000", invalid, true, int64(0), 0.0, newNum(int64(0)), "000", invalid, invalid),
newTestcase(" 0 ", invalid, true, int64(0), 0.0, newNum(int64(0)), " 0 ", invalid, invalid),
newTestcase(" 000 ", invalid, true, int64(0), 0.0, newNum(int64(0)), " 000 ", invalid, invalid),
newTestcase("1", invalid, true, int64(1), 1.0, newNum(int64(1)), "1", invalid, invalid),
newTestcase("001", invalid, true, int64(1), 1.0, newNum(int64(1)), "001", invalid, invalid),
newTestcase("1.0", invalid, true, int64(1), 1.0, newNum(int64(1)), "1.0", invalid, invalid),
newTestcase(" 1.1 ", invalid, true, invalid, 1.1, newNum(1.1), " 1.1 ", invalid, invalid),
// vector source values
newTestcase([]any{}, true, false, invalid, invalid, invalid, invalid, []any{}, map[string]any{}),
newTestcase([]any{""}, invalid, true, invalid, invalid, invalid, invalid, []any{""}, invalid),
newTestcase([]any{"foo"}, invalid, true, invalid, invalid, invalid, invalid, []any{"foo"}, invalid),
// object source values
newTestcase(map[string]any{}, true, false, invalid, invalid, invalid, invalid, []any{}, map[string]any{}),
newTestcase(map[string]any{"": ""}, invalid, true, invalid, invalid, invalid, invalid, invalid, map[string]any{"": ""}),
}
}
Loading

0 comments on commit 201842d

Please sign in to comment.