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

Added /= function. #59

Merged
merged 1 commit 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 @@ -72,6 +72,8 @@ Things you'll find here include:
* `/`
* Division function.
* Note that if only a single value is specified the reciprocal is returned - i.e. "(/ 3)" is equal to "1/3".
* `/=`
* Numerical inequality test, if any argument is the same as another return false, otherwise if all arguments are unique return true.
* `<`
* Less-than function.
* `=`
Expand Down
50 changes: 49 additions & 1 deletion builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("*", &primitive.Procedure{F: multiplyFn, Help: helpMap["*"], Args: []primitive.Symbol{primitive.Symbol("N"), primitive.Symbol("arg1..argN")}})
env.Set("+", &primitive.Procedure{F: plusFn, Help: helpMap["+"], Args: []primitive.Symbol{primitive.Symbol("N"), primitive.Symbol("arg1..argN")}})
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: divideFn, Help: helpMap["/"], Args: []primitive.Symbol{primitive.Symbol("N"), primitive.Symbol("arg1..argN")}})
env.Set("/=", &primitive.Procedure{F: inequalityFn, 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("arg1"), primitive.Symbol("arg2 .. argN")}})
env.Set("arch", &primitive.Procedure{F: archFn, Help: helpMap["arch"]})
Expand Down Expand Up @@ -786,6 +787,53 @@ func helpFn(env *env.Environment, args []primitive.Primitive) primitive.Primitiv
return primitive.String(str)
}

// inequalityFn implements /=
func inequalityFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

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

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

// Now we'll loop over all other numbers
//
// If we got something that was already seen 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)

// Keep track of things we've seen here
seen := make(map[float64]bool)
seen[float64(nA)] = 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")
}

// Have we seen this?
_, found := seen[float64(nB)]
if found {
ret = primitive.Bool(false)
}
seen[float64(nB)] = true
}

return ret
}

// (join (1 2 3)
func joinFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

Expand Down
109 changes: 109 additions & 0 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1610,6 +1610,115 @@ func TestHelp(t *testing.T) {
}
}

// TestInequality tests /=
func TestInequality(t *testing.T) {

// No arguments
out := inequalityFn(ENV, []primitive.Primitive{})

// Will lead to an error
e, ok := out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if e != primitive.ArityError() {
t.Fatalf("got error, but wrong one %v", out)
}

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

// 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 - but multiple values
//
out = inequalityFn(ENV, []primitive.Primitive{
primitive.Number(1),
primitive.Number(2),
primitive.Number(3),
primitive.Number(4),
primitive.Number(5),
primitive.Number(6),
})

// 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 = inequalityFn(ENV, []primitive.Primitive{
primitive.Number(1),
primitive.Number(2),
primitive.Number(2),
primitive.Number(1),
})

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

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

e, ok = out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if !strings.Contains(string(e), "was not a number") {
t.Fatalf("got error, but wrong one '%v'", e)
}

//
// Now with wrong types
//
out = inequalityFn(ENV, []primitive.Primitive{
primitive.String("9"),
primitive.Number(9),
})

// Will report an error
e, ok = out.(primitive.Error)
if !ok {
t.Fatalf("expected error, got %v", out)
}
if !strings.Contains(string(e), "was not a number") {
t.Fatalf("got error, but wrong one %v", out)
}
}

// TestJoin tests join
func TestJoin(t *testing.T) {

Expand Down
4 changes: 4 additions & 0 deletions builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Multiplies all arguments present with the first number.
/
Divides all arguments present with the first number.
%%
/=
Numerical inequality testing. If any argument is identical
to any other argument return false. Otherwise return true.
%%
<

Return true if a is less than b.
Expand Down
9 changes: 4 additions & 5 deletions eval/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ a
//
{"(+ 3)", "3"},
{"(- 3)", "3"},
{"(/ 3)", "3"},
{"(* 3)", "3"},

// strings
Expand Down Expand Up @@ -358,12 +357,12 @@ func TestStandardLibrary(t *testing.T) {
output: "(1 4 9 16 25)"},

// range
{input: "(range -5 5 1)", output: "(-5 -4 -3 -2 -1 0 1 2 3 4)"},
{input: "(range 1 11 2)", output: "(1 3 5 7 9)"},
{input: "(range -5 5 1)", output: "(-5 -4 -3 -2 -1 0 1 2 3 4 5)"},
{input: "(range 1 11 2)", output: "(1 3 5 7 9 11)"},

// seq/nat
{input: "(seq 10)", output: "(0 1 2 3 4 5 6 7 8 9)"},
{input: "(nat 10)", output: "(1 2 3 4 5 6 7 8 9)"},
{input: "(seq 10)", output: "(0 1 2 3 4 5 6 7 8 9 10)"},
{input: "(nat 10)", output: "(1 2 3 4 5 6 7 8 9 10)"},

{input: "(join (reverse (split \"Steve\" \"\")))", output: "evetS"},
}
Expand Down