Skip to content

Commit

Permalink
Merge pull request #58 from skx/53-numerical-equality
Browse files Browse the repository at this point in the history
Allow (= ...) to accept multiple arguments.
  • Loading branch information
skx authored Oct 17, 2022
2 parents 1c7643f + 32ca678 commit ec0c2d6
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 19 deletions.
2 changes: 2 additions & 0 deletions PRIMITIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ Things you'll find here include:
* Subtraction function.
* `/`
* Division function.
* Note that if only a single value is specified the reciprocal is returned - i.e. "(/ 3)" is equal to "1/3".
* `<`
* Less-than function.
* `=`
* Numerical comparison function.
* Note that multiple arguments are supported, not just two.
* `arch`
* Return the operating system architecture.
* `car`
Expand Down
46 changes: 34 additions & 12 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("-", &primitive.Procedure{F: minusFn, Help: helpMap["-"], Args: []primitive.Symbol{primitive.Symbol("N"), primitive.Symbol("arg1..argN")}})
env.Set("/", &primitive.Procedure{F: divideFn, Help: helpMap["-"], Args: []primitive.Symbol{primitive.Symbol("N"), primitive.Symbol("arg1..argN")}})
env.Set("<", &primitive.Procedure{F: ltFn, Help: helpMap["<"], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("=", &primitive.Procedure{F: equalsFn, Help: helpMap["="], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("=", &primitive.Procedure{F: equalsFn, Help: helpMap["="], Args: []primitive.Symbol{primitive.Symbol("arg1"), primitive.Symbol("arg2 .. argN")}})
env.Set("arch", &primitive.Procedure{F: archFn, Help: helpMap["arch"]})
env.Set("car", &primitive.Procedure{F: carFn, Help: helpMap["car"], Args: []primitive.Symbol{primitive.Symbol("list")}})
env.Set("cdr", &primitive.Procedure{F: cdrFn, Help: helpMap["cdr"], Args: []primitive.Symbol{primitive.Symbol("list")}})
Expand Down Expand Up @@ -390,23 +390,45 @@ func eqFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive

// equalsFn implements "="
func equalsFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
if len(args) != 2 {

// We need at least two arguments
if len(args) < 2 {
return primitive.ArityError()
}

a := args[0]
b := args[1]

if a.Type() != "number" {
return primitive.Error("argument was not a number")
}
if b.Type() != "number" {
// First argument must be a number.
nA, ok := args[0].(primitive.Number)
if !ok {
return primitive.Error("argument was not a number")
}
if a.(primitive.Number) == b.(primitive.Number) {
return primitive.Bool(true)

// Now we'll loop over all other numbers
//
// If we got something that was NOT the same as our
// initial number we can terminate early but we don't
// because it is important to also report on failures to
// validate types - which we can't do if we bail.
//
ret := primitive.Bool(true)

for _, i := range args[1:] {

// check we have a number
nB, ok2 := i.(primitive.Number)

if !ok2 {
return primitive.Error("argument was not a number")
}

// Record our failure, but keep testing in case
// we have a type violation to report in a later
// argument.
if nB != nA {
ret = primitive.Bool(false)
}
}
return primitive.Bool(false)

return ret
}

// errorFn implements "error"
Expand Down
38 changes: 32 additions & 6 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -697,7 +697,7 @@ func TestEnsureHelpPresent(t *testing.T) {
}
}

// TestEq tests "eq"
// TestEq tests "eq" (non-numerical equality)
func TestEq(t *testing.T) {

// No arguments
Expand Down Expand Up @@ -764,7 +764,7 @@ func TestEq(t *testing.T) {
}
}

// TestEquals tests "="
// TestEquals tests "=" (numerical equality)
func TestEquals(t *testing.T) {

// No arguments
Expand Down Expand Up @@ -796,12 +796,35 @@ func TestEquals(t *testing.T) {
t.Fatalf("got wrong result")
}

//
// Now a real one: equal - but multiple values
//
out = equalsFn(ENV, []primitive.Primitive{
primitive.Number(9),
primitive.Number(9),
primitive.Number(9),
primitive.Number(9),
primitive.Number(9),
primitive.Number(9),
})

// Will work
n, ok2 = out.(primitive.Bool)
if !ok2 {
t.Fatalf("expected bool, got %v", out)
}
if n != true {
t.Fatalf("got wrong result")
}

//
// Now a real one: unequal values
//
out = equalsFn(ENV, []primitive.Primitive{
primitive.Number(99),
primitive.Number(9),
primitive.Number(99),
primitive.Number(9),
})

// Will work
Expand All @@ -814,11 +837,14 @@ func TestEquals(t *testing.T) {
}

//
// Now with wrong types
// Now with wrong types - last one is wrong
//
out = equalsFn(ENV, []primitive.Primitive{
primitive.Number(9),
primitive.String("9"),
primitive.Number(1),
primitive.Number(2),
primitive.Number(3),
primitive.Number(4),
primitive.String("5"),
})

e, ok = out.(primitive.Error)
Expand All @@ -837,7 +863,7 @@ func TestEquals(t *testing.T) {
primitive.Number(9),
})

// Will work
// Will report an error
e, ok = out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
Expand Down
6 changes: 5 additions & 1 deletion builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ Divides all arguments present with the first number.
Return true if a is less than b.
%%
=
returns true if the numerical values a and b are equal.
returns true if the numerical values supplied are all equal to each other.

Note that multiple values may be specified, so it is possible to compare
three, or more, values as per the second example below.

See also: eq
Example : (print (= 3 a))
Example : (print (= 3 a b))
%%
abs
Return the absolute value of the supplied number.
Expand Down

0 comments on commit ec0c2d6

Please sign in to comment.