Skip to content
This repository was archived by the owner on Mar 16, 2025. It is now read-only.

Commit 201842d

Browse files
committed
use generic algorithm to base equality checks solely on the coalescer
1 parent ba5f243 commit 201842d

18 files changed

Lines changed: 1231 additions & 556 deletions

aliases.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,3 @@ func NewVariables() Variables {
5959
func NewDocument(data any) (Document, error) {
6060
return types.NewDocument(data)
6161
}
62-
63-
// Unwrap returns the native Go value for either native Go values or an
64-
// Rudi AST node (like turning an ast.Number into an int64).
65-
func Unwrap(val any) (any, error) {
66-
return types.UnwrapType(val)
67-
}
68-
69-
// WrapNative returns the Rudi node equivalent of a native Go value, like turning
70-
// a string into ast.String.
71-
func WrapNative(val any) (any, error) {
72-
return types.WrapNative(val)
73-
}

pkg/coalescing/coalescer.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import (
1010
)
1111

1212
type Coalescer interface {
13+
ToNull(val any) (bool, error)
1314
ToBool(val any) (bool, error)
14-
ToFloat64(val any) (float64, error)
1515
ToInt64(val any) (int64, error)
16+
ToFloat64(val any) (float64, error)
1617
ToNumber(val any) (ast.Number, error)
1718
ToString(val any) (string, error)
1819
ToVector(val any) ([]any, error)
1920
ToObject(val any) (map[string]any, error)
20-
ToNull(val any) (bool, error)
2121
}
2222

2323
func deliteral(val any) any {

pkg/coalescing/humane.go

Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package coalescing
55

66
import (
7+
"errors"
78
"fmt"
89
"strconv"
910
"strings"
@@ -24,14 +25,59 @@ func (humane) ToNull(val any) (bool, error) {
2425
case nil:
2526
return true, nil
2627
case bool:
27-
return !v, nil
28+
if v {
29+
return false, fmt.Errorf("cannot coalesce true into null")
30+
}
31+
return true, nil
32+
case int:
33+
if v != 0 {
34+
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
35+
}
36+
return true, nil
37+
case int32:
38+
if v != 0 {
39+
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
40+
}
41+
return true, nil
42+
case int64:
43+
if v != 0 {
44+
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
45+
}
46+
return true, nil
47+
case float32:
48+
if v != 0 {
49+
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
50+
}
51+
return true, nil
52+
case float64:
53+
if v != 0 {
54+
return false, fmt.Errorf("cannot coalesce %v (%T) into null", v, v)
55+
}
56+
return true, nil
57+
case string:
58+
if len(v) != 0 {
59+
return false, fmt.Errorf("cannot coalesce %q (%T) into null", v, v)
60+
}
61+
return true, nil
62+
case []any:
63+
if len(v) != 0 {
64+
return false, errors.New("cannot coalesce non-empty vector into null")
65+
}
66+
return true, nil
67+
case map[string]any:
68+
if len(v) != 0 {
69+
return false, errors.New("cannot coalesce non-empty object into null")
70+
}
71+
return true, nil
2872
default:
2973
return false, fmt.Errorf("cannot coalesce %T into null", v)
3074
}
3175
}
3276

3377
func (humane) ToBool(val any) (bool, error) {
3478
switch v := deliteral(val).(type) {
79+
case nil:
80+
return false, nil
3581
case bool:
3682
return v, nil
3783
case int:
@@ -54,15 +100,21 @@ func (humane) ToBool(val any) (bool, error) {
54100
return len(v) > 0, nil
55101
case map[string]any:
56102
return len(v) > 0, nil
57-
case nil:
58-
return false, nil
59103
default:
60104
return false, fmt.Errorf("cannot coalesce %T into bool", v)
61105
}
62106
}
63107

64108
func (humane) ToFloat64(val any) (float64, error) {
65109
switch v := deliteral(val).(type) {
110+
case nil:
111+
return 0, nil
112+
case bool:
113+
if v {
114+
return 1, nil
115+
} else {
116+
return 0, nil
117+
}
66118
case int:
67119
return float64(v), nil
68120
case int32:
@@ -74,46 +126,64 @@ func (humane) ToFloat64(val any) (float64, error) {
74126
case float64:
75127
return v, nil
76128
case string:
129+
v = strings.TrimSpace(v)
130+
if v == "" {
131+
return 0, nil
132+
}
133+
77134
parsed, err := strconv.ParseFloat(v, 64)
78135
if err != nil {
79-
return 0, fmt.Errorf("cannot convert %q losslessly to float64", v)
136+
return 0, fmt.Errorf("cannot coalesce %T into float64", v)
80137
}
81138
return parsed, nil
82-
case bool:
83-
if v {
84-
return 1, nil
85-
} else {
86-
return 0, nil
87-
}
88-
case nil:
89-
return 0, nil
90139
default:
91140
return 0, fmt.Errorf("cannot coalesce %T into float64", v)
92141
}
93142
}
94143

95144
func (humane) ToInt64(val any) (int64, error) {
96145
switch v := deliteral(val).(type) {
146+
case nil:
147+
return 0, nil
148+
case bool:
149+
if v {
150+
return 1, nil
151+
} else {
152+
return 0, nil
153+
}
97154
case int:
98155
return int64(v), nil
99156
case int32:
100157
return int64(v), nil
101158
case int64:
102159
return v, nil
160+
case float32:
161+
if v == float32(int32(v)) {
162+
return int64(v), nil
163+
}
164+
return 0, fmt.Errorf("cannot convert %s losslessly to int64", formatFloat(float64(v)))
165+
case float64:
166+
if v == float64(int64(v)) {
167+
return int64(v), nil
168+
}
169+
return 0, fmt.Errorf("cannot convert %s losslessly to int64", formatFloat(v))
103170
case string:
171+
v = strings.TrimSpace(v)
172+
if v == "" {
173+
return 0, nil
174+
}
175+
104176
parsed, err := strconv.ParseInt(v, 10, 64)
105177
if err != nil {
106-
return 0, fmt.Errorf("cannot convert %q losslessly to int64", v)
178+
// allows "2.0" to turn into int64(2)
179+
parsed, err := strconv.ParseFloat(v, 64)
180+
if err == nil && parsed == float64(int64(parsed)) {
181+
return int64(parsed), nil
182+
}
183+
184+
return 0, fmt.Errorf("cannot coalesce %T into int64", v)
107185
}
108186
return parsed, nil
109-
case bool:
110-
if v {
111-
return 1, nil
112-
} else {
113-
return 0, nil
114-
}
115-
case nil:
116-
return 0, nil
117187
default:
118188
return 0, fmt.Errorf("cannot coalesce %T into int64", v)
119189
}
@@ -125,8 +195,8 @@ func (h humane) ToNumber(val any) (ast.Number, error) {
125195

126196
func (humane) ToString(val any) (string, error) {
127197
switch v := deliteral(val).(type) {
128-
case string:
129-
return v, nil
198+
case nil:
199+
return "", nil
130200
case bool:
131201
return strconv.FormatBool(v), nil
132202
case int:
@@ -137,8 +207,8 @@ func (humane) ToString(val any) (string, error) {
137207
return strconv.FormatInt(v, 10), nil
138208
case float64:
139209
return formatFloat(v), nil
140-
case nil:
141-
return "", nil
210+
case string:
211+
return v, nil
142212
default:
143213
return "", fmt.Errorf("cannot coalesce %T into string", v)
144214
}
@@ -159,6 +229,12 @@ func (humane) ToVector(val any) ([]any, error) {
159229
return []any{}, nil
160230
case []any:
161231
return v, nil
232+
case map[string]any:
233+
if len(v) == 0 {
234+
return []any{}, nil
235+
} else {
236+
return nil, fmt.Errorf("cannot coalesce %T into vector", v)
237+
}
162238
default:
163239
return nil, fmt.Errorf("cannot coalesce %T into vector", v)
164240
}
@@ -168,6 +244,12 @@ func (humane) ToObject(val any) (map[string]any, error) {
168244
switch v := deliteral(val).(type) {
169245
case nil:
170246
return map[string]any{}, nil
247+
case []any:
248+
if len(v) == 0 {
249+
return map[string]any{}, nil
250+
} else {
251+
return nil, fmt.Errorf("cannot coalesce %T into object", v)
252+
}
171253
case map[string]any:
172254
return v, nil
173255
default:

pkg/coalescing/humane_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// SPDX-FileCopyrightText: 2023 Christoph Mewes
2+
// SPDX-License-Identifier: MIT
3+
4+
package coalescing
5+
6+
import (
7+
"testing"
8+
)
9+
10+
func TestHumaneCoalescer(t *testing.T) {
11+
testCoalescer(t, NewHumane(), getHumaneTestcases())
12+
}
13+
14+
func getHumaneTestcases() []testcase {
15+
return []testcase{
16+
// (source, canBeNull, toBool, toInt, toFloat, toNumber, toString, toVector, toObject)
17+
// nil source value
18+
newTestcase(nil, true, false, int64(0), 0.0, newNum(int64(0)), "", []any{}, map[string]any{}),
19+
// boolean source values
20+
newTestcase(true, invalid, true, int64(1), 1.0, newNum(int64(1)), "true", invalid, invalid),
21+
newTestcase(false, true, false, int64(0), 0.0, newNum(int64(0)), "false", invalid, invalid),
22+
// numeric source values
23+
newTestcase(0, true, false, int64(0), 0.0, newNum(int64(0)), "0", invalid, invalid),
24+
newTestcase(0.0, true, false, int64(0), 0.0, newNum(int64(0)), "0", invalid, invalid),
25+
newTestcase(0.1, invalid, true, invalid, 0.1, newNum(0.1), "0.1", invalid, invalid),
26+
newTestcase(1, invalid, true, int64(1), 1.0, newNum(int64(1)), "1", invalid, invalid),
27+
newTestcase(1.0, invalid, true, int64(1), 1.0, newNum(int64(1)), "1", invalid, invalid),
28+
newTestcase(-3.14, invalid, true, invalid, -3.14, newNum(-3.14), "-3.14", invalid, invalid),
29+
// string source values
30+
newTestcase("", true, false, int64(0), 0.0, newNum(int64(0)), "", invalid, invalid),
31+
newTestcase(" ", invalid, true, int64(0), 0.0, newNum(int64(0)), " ", invalid, invalid),
32+
newTestcase("\n", invalid, true, int64(0), 0.0, newNum(int64(0)), "\n", invalid, invalid),
33+
newTestcase("0", invalid, false, int64(0), 0.0, newNum(int64(0)), "0", invalid, invalid),
34+
newTestcase("000", invalid, true, int64(0), 0.0, newNum(int64(0)), "000", invalid, invalid),
35+
newTestcase(" 0 ", invalid, true, int64(0), 0.0, newNum(int64(0)), " 0 ", invalid, invalid),
36+
newTestcase(" 000 ", invalid, true, int64(0), 0.0, newNum(int64(0)), " 000 ", invalid, invalid),
37+
newTestcase("1", invalid, true, int64(1), 1.0, newNum(int64(1)), "1", invalid, invalid),
38+
newTestcase("001", invalid, true, int64(1), 1.0, newNum(int64(1)), "001", invalid, invalid),
39+
newTestcase("1.0", invalid, true, int64(1), 1.0, newNum(int64(1)), "1.0", invalid, invalid),
40+
newTestcase(" 1.1 ", invalid, true, invalid, 1.1, newNum(1.1), " 1.1 ", invalid, invalid),
41+
// vector source values
42+
newTestcase([]any{}, true, false, invalid, invalid, invalid, invalid, []any{}, map[string]any{}),
43+
newTestcase([]any{""}, invalid, true, invalid, invalid, invalid, invalid, []any{""}, invalid),
44+
newTestcase([]any{"foo"}, invalid, true, invalid, invalid, invalid, invalid, []any{"foo"}, invalid),
45+
// object source values
46+
newTestcase(map[string]any{}, true, false, invalid, invalid, invalid, invalid, []any{}, map[string]any{}),
47+
newTestcase(map[string]any{"": ""}, invalid, true, invalid, invalid, invalid, invalid, invalid, map[string]any{"": ""}),
48+
}
49+
}

0 commit comments

Comments
 (0)