Skip to content

Commit 2ba80ba

Browse files
authored
feat: objects (#13)
* add support for objects properties * resolved some TODOs * added ObjectGetMethod in prep for object function support
1 parent b387e61 commit 2ba80ba

15 files changed

+726
-152
lines changed

.golangci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ linters-settings:
207207
gocognit:
208208
# Minimal code complexity to report
209209
# Default: 30 (but we recommend 10-20)
210-
min-complexity: 15
210+
min-complexity: 20
211211

212212
gocritic:
213213
# Which checks should be enabled; can't be combined with 'disabled-checks'.
@@ -300,7 +300,7 @@ linters-settings:
300300
gocyclo:
301301
# Minimal code complexity to report.
302302
# Default: 30 (but we recommend 10-20)
303-
min-complexity: 15
303+
min-complexity: 20
304304

305305
godot:
306306
# Comments to be checked: `declarations`, `toplevel`, or `all`.

README.md

+44-2
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ Escapes are supported:
126126

127127
## Bools
128128

129-
In addition to boolean expressions, sepcial contants `True` and `False` may be used.
129+
In addition to boolean expressions, special contants `True` and `False` may be used.
130130

131131
Do not double-quote them, or they will become plain strings!
132132

@@ -148,7 +148,7 @@ This is container `Value`. It can contain zero or any number of `Value`'s. Curre
148148
* Go classifies bit shift operators with the higher `*`.
149149
* `&&` is synonymous of `And`.
150150
* `||` is synonymous of `Or`.
151-
* Worded operators such as `And` and `Or` are **case-sensitive** and must be followed by a blank character. `True Or (False)` is a Bool expression with the `Or` operator but `True Or(False)` is an invalid expression attempting to call a user-defined function called `Or()`.
151+
* Worded operators such as `And` and `Or` are **case-sensitive** and must be followed by a blank character. `True Or (False)` is a Bool expression with the `Or` operator but `True Or(False)` is an expression attempting to call a user-defined function called `Or()`.
152152
* Types: String, Number, Bool, MultiValue
153153
* Associativity with parentheses: `(` and `)`
154154
* Functions:
@@ -158,6 +158,8 @@ This is container `Value`. It can contain zero or any number of `Value`'s. Curre
158158

159159
## Functions
160160

161+
(See also Objects)
162+
161163
A function is defined as a Go type: `type FunctionalValue func(...Value) Value`
162164

163165
Function names are case-insensitive.
@@ -172,12 +174,52 @@ This allows parsing the expression once with `Parse` and run `Tree`.`Eval` multi
172174

173175
## Variables
174176

177+
(See also Objects)
178+
175179
Variable names are case-sensitive.
176180

177181
Values are passed as a `map[string]Value` using `WithVariables` when calling `Eval` from `Tree`.
178182

179183
This allows parsing the expression once with `Parse` and run `Tree`.`Eval` multiple times with different variable values.
180184

185+
## Objects
186+
187+
Objects are Go `struct`'s which **properties** act as **gal variables** and **methods** as **gal functions**.
188+
189+
Object definitions are passed as a `map[string]Object` using `WithObjects` when calling `Eval` from `Tree`.
190+
191+
This allows parsing the expression once with `Parse` and run `Tree`.`Eval` multiple times with different instances of an object.
192+
193+
Object methods generally follow the same rules as **gal Functions**:
194+
- methods can optionally accept one or more arguments
195+
- methods must return a single value (which can be a `MultiValue` to emulate multiple return values)
196+
- arguments and return value are preferred to be of `gal.Value` type.
197+
However, **gal** will attempt to convert Go types to `gal.Value` types on best endeavour:
198+
- A method signature of `MyMethod(arg1 int64) bool` will translate the supplied `gal.Value`'s and attempt to map them to `int64` and `bool` using `gal.Numberer` and `gal.Booler`.
199+
- Type conversion may lead to a panic when the type cannot be interpreted.
200+
201+
Example:
202+
203+
`type Car struct` has several properties and methods - one of which is `func (c *Car) CurrentSpeed() gal.Value`.
204+
205+
```go
206+
expr := `aCar.MaxSpeed - aCar.CurrentSpeed()`
207+
parsedExpr := gal.Parse(expr)
208+
209+
got := parsedExpr.Eval(
210+
gal.WithObjects(map[string]gal.Object{
211+
"aCar": Car{
212+
Make: "Lotus Esprit",
213+
Mileage: gal.NewNumberFromInt(2000),
214+
Speed: 100,
215+
MaxSpeed: 250,
216+
},
217+
}),
218+
)
219+
// result: 150 == 250 - 100
220+
221+
```
222+
181223
## High level design
182224

183225
Expressions are parsed in two stages:

function.go

+4-9
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ var builtInFunctions = map[string]FunctionalValue{
7171
// It returns `nil` when no built-in function exists by the specified name.
7272
// This signals the Evaluator to attempt to find a user defined function.
7373
func BuiltInFunction(name string) FunctionalValue {
74+
if builtInFunctions == nil {
75+
return nil
76+
}
77+
7478
// note: for now function names are arbitrarily case-insensitive
7579
lowerName := strings.ToLower(name)
7680

@@ -82,15 +86,6 @@ func BuiltInFunction(name string) FunctionalValue {
8286
return nil
8387
}
8488

85-
// UserDefinedFunction is a helper function that returns the definition of the
86-
// provided function name from the supplied userFunctions.
87-
func UserDefinedFunction(name string, userFunctions Functions) FunctionalValue {
88-
// note: for now function names are arbitrarily case-insensitive
89-
lowerName := strings.ToLower(name)
90-
91-
return userFunctions.Function(lowerName)
92-
}
93-
9489
// Pi returns the Value of math.Pi.
9590
func Pi(args ...Value) Value {
9691
if len(args) != 0 {

function_test.go

+12-12
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ func TestPiLong(t *testing.T) {
2121
}
2222

2323
func TestFactorial(t *testing.T) {
24-
val := gal.Factorial(gal.NewNumber(0))
25-
assert.Equal(t, gal.NewNumber(1).String(), val.String())
24+
val := gal.Factorial(gal.NewNumberFromInt(0))
25+
assert.Equal(t, gal.NewNumberFromInt(1).String(), val.String())
2626

27-
val = gal.Factorial(gal.NewNumber(1))
28-
assert.Equal(t, gal.NewNumber(1).String(), val.String())
27+
val = gal.Factorial(gal.NewNumberFromInt(1))
28+
assert.Equal(t, gal.NewNumberFromInt(1).String(), val.String())
2929

30-
val = gal.Factorial(gal.NewNumber(10))
31-
assert.Equal(t, gal.NewNumber(3_628_800).String(), val.String())
30+
val = gal.Factorial(gal.NewNumberFromInt(10))
31+
assert.Equal(t, gal.NewNumberFromInt(3_628_800).String(), val.String())
3232

33-
val = gal.Factorial(gal.NewNumber(-10))
33+
val = gal.Factorial(gal.NewNumberFromInt(-10))
3434
assert.Equal(t, "undefined: Factorial: requires a positive integer, cannot accept -10", val.String())
3535
}
3636

@@ -60,21 +60,21 @@ func TestFloor(t *testing.T) {
6060
}
6161

6262
func TestLn(t *testing.T) {
63-
val := gal.Ln(gal.NewNumber(123456), gal.NewNumber(5))
63+
val := gal.Ln(gal.NewNumberFromInt(123456), gal.NewNumberFromInt(5))
6464
assert.Equal(t, "11.72364", val.String())
6565

66-
val = gal.Ln(gal.NewNumber(-123456), gal.NewNumber(5))
66+
val = gal.Ln(gal.NewNumberFromInt(-123456), gal.NewNumberFromInt(5))
6767
assert.Equal(t, "undefined: Ln:cannot calculate natural logarithm for negative decimals", val.String())
6868
}
6969

7070
func TestLog(t *testing.T) {
71-
val := gal.Log(gal.NewNumber(123456), gal.NewNumber(5))
71+
val := gal.Log(gal.NewNumberFromInt(123456), gal.NewNumberFromInt(5))
7272
assert.Equal(t, "5.09151", val.String())
7373

74-
val = gal.Log(gal.NewNumber(-123456), gal.NewNumber(5))
74+
val = gal.Log(gal.NewNumberFromInt(-123456), gal.NewNumberFromInt(5))
7575
assert.Equal(t, "undefined: Log:cannot calculate natural logarithm for negative decimals", val.String())
7676

77-
val = gal.Log(gal.NewNumber(10_000_000), gal.NewNumber(0))
77+
val = gal.Log(gal.NewNumberFromInt(10_000_000), gal.NewNumberFromInt(0))
7878
assert.Equal(t, "7", val.String())
7979
}
8080

0 commit comments

Comments
 (0)