From 65099be346d131aa969183a28dc9a75f2e77702b Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Mon, 17 Oct 2022 18:46:38 +0300 Subject: [PATCH 1/2] Allow (= ...) to accept multiple arguments. The numerical-equality function "(=..)" previously supported only two values. Now we support multiple values. This updates #53, but does not close it until we've implemented the corresponding inequality test. --- builtins/builtins.go | 46 +++++++++++++++++++++++++++++---------- builtins/builtins_test.go | 38 +++++++++++++++++++++++++++----- builtins/help.txt | 6 ++++- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/builtins/builtins.go b/builtins/builtins.go index e6163b6..6bdce66 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -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")}}) @@ -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" diff --git a/builtins/builtins_test.go b/builtins/builtins_test.go index f7ad59e..520abde 100644 --- a/builtins/builtins_test.go +++ b/builtins/builtins_test.go @@ -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 @@ -764,7 +764,7 @@ func TestEq(t *testing.T) { } } -// TestEquals tests "=" +// TestEquals tests "=" (numerical equality) func TestEquals(t *testing.T) { // No arguments @@ -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 @@ -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) @@ -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) diff --git a/builtins/help.txt b/builtins/help.txt index 7087193..d3ddbb3 100644 --- a/builtins/help.txt +++ b/builtins/help.txt @@ -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. From 32ca678938c64086c78b65ba4fbed0914156f66a Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Mon, 17 Oct 2022 18:49:35 +0300 Subject: [PATCH 2/2] Updated documentation too --- PRIMITIVES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PRIMITIVES.md b/PRIMITIVES.md index 6583793..7145830 100644 --- a/PRIMITIVES.md +++ b/PRIMITIVES.md @@ -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`