Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow (= ...) to accept multiple arguments. #58

Merged
merged 2 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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