Skip to content

Commit

Permalink
Merge pull request #116 from skx/115-sort-by
Browse files Browse the repository at this point in the history
Added sort-by, string=, and string<
  • Loading branch information
skx authored Dec 19, 2022
2 parents 6165ff7 + 37f398e commit f310844
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 5 deletions.
10 changes: 10 additions & 0 deletions PRIMITIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,14 @@ Things you'll find here include:
* Generate a string, using a format-string.
* `str`
* Convert the specified parameter to a string.
* `string<`
* Return true if the first string is less than the second.
* `string<=`
* Return true if the first string is less than, or equal to the second.
* `string>`
* Return true if the first string is greater than the second.
* `string>=`
* Return true if the first string is greater than, or equal to the second.
* `time`
* Return values relating to the current time, as a list.
* Demonstrated in [time.lisp](time.lisp).
Expand Down Expand Up @@ -395,6 +403,8 @@ Functions here include:
* Return a list of numbers from 0 to N.
* `sign`
* Return the sign of the given number. (1 for positive, -1 for negative).
* `sort-by`
* Sort the given list, using the supplied comparison method.
* `sqrt`
* Return the square-root of the supplied number.
* `string?`
Expand Down
38 changes: 38 additions & 0 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("split", &primitive.Procedure{F: splitFn, Help: helpMap["split"], Args: []primitive.Symbol{primitive.Symbol("str"), primitive.Symbol("by")}})
env.Set("sprintf", &primitive.Procedure{F: sprintfFn, Help: helpMap["sprintf"], Args: []primitive.Symbol{primitive.Symbol("arg1..argN")}})
env.Set("str", &primitive.Procedure{F: strFn, Help: helpMap["str"], Args: []primitive.Symbol{primitive.Symbol("object")}})
env.Set("string=", &primitive.Procedure{F: stringEqualsFn, Help: helpMap["string="], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("string<", &primitive.Procedure{F: stringLtFn, Help: helpMap["string<"], Args: []primitive.Symbol{primitive.Symbol("a"), primitive.Symbol("b")}})
env.Set("time", &primitive.Procedure{F: timeFn, Help: helpMap["time"]})
env.Set("type", &primitive.Procedure{F: typeFn, Help: helpMap["type"], Args: []primitive.Symbol{primitive.Symbol("object")}})
env.Set("vals", &primitive.Procedure{F: valsFn, Help: helpMap["vals"], Args: []primitive.Symbol{primitive.Symbol("hash")}})
Expand Down Expand Up @@ -1570,6 +1572,42 @@ func strFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
return primitive.String(args[0].ToString())
}

// stringEqualsFn implements "string="
func stringEqualsFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
if len(args) != 2 {
return primitive.ArityError()
}

a, ok1 := args[0].(primitive.String)
if !ok1 {
return primitive.Error("argument not a string")
}

b, ok2 := args[1].(primitive.String)
if !ok2 {
return primitive.Error("argument not a string")
}
return primitive.Bool(a == b)
}

// stringLtFn implements "string<"
func stringLtFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
if len(args) != 2 {
return primitive.ArityError()
}

a, ok1 := args[0].(primitive.String)
if !ok1 {
return primitive.Error("argument not a string")
}

b, ok2 := args[1].(primitive.String)
if !ok2 {
return primitive.Error("argument not a string")
}
return primitive.Bool(a < b)
}

// timeFn returns the current (HH, MM, SS) as a list.
func timeFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {
var ret primitive.List
Expand Down
148 changes: 146 additions & 2 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func TestCdr(t *testing.T) {
}
}

// TestCharEquals tests "chare=" (character equality)
// TestCharEquals tests "char=" (character equality)
func TestCharEquals(t *testing.T) {

// No arguments
Expand Down Expand Up @@ -403,8 +403,8 @@ func TestCharLt(t *testing.T) {
if n != true {
t.Fatalf("got wrong result")
}

}

func TestChr(t *testing.T) {

// no arguments
Expand Down Expand Up @@ -3348,6 +3348,150 @@ func TestStr(t *testing.T) {
}
}

// TestStringEquals tests "string=" (character equality)
func TestStringEquals(t *testing.T) {

// No arguments
out := stringEqualsFn(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)
}

// One bogus argument
out = stringEqualsFn(ENV, []primitive.Primitive{
primitive.Number(33),
primitive.String("a"),
})

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

//
// Now a real one: equal
//
out = stringEqualsFn(ENV, []primitive.Primitive{
primitive.String("foo"),
primitive.String("foo"),
})

// 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 = stringEqualsFn(ENV, []primitive.Primitive{
primitive.String("a"),
primitive.String("b"),
})

// 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 = stringEqualsFn(ENV, []primitive.Primitive{
primitive.String("a"),
primitive.Number(32),
})

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

// test string<
func TestStringLt(t *testing.T) {

// No arguments
out := stringLtFn(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)
}

// Argument which isn't a string
out = stringLtFn(ENV, []primitive.Primitive{
primitive.Character("f"),
primitive.String("foo"),
})

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

// Argument which isn't a string
out = stringLtFn(ENV, []primitive.Primitive{
primitive.String("a"),
primitive.Number(332),
})

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

//
// Now a real one
//
out = stringLtFn(ENV, []primitive.Primitive{
primitive.String("a"),
primitive.String("b"),
})

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

func TestType(t *testing.T) {

// No arguments
Expand Down
20 changes: 18 additions & 2 deletions builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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: char=, eq
See also: char=, eq, string=
Example : (print (= 3 a))
Example : (print (= 3 a b))
%%
Expand Down Expand Up @@ -62,7 +62,7 @@ char=

char= returns true if the supplied parameters were characters, and were equal.

See also: = char<
See also: = char< string=
%%
char<

Expand Down Expand Up @@ -328,6 +328,10 @@ sort

sort will sort the items in the list specified as the single argument, and return them as a new list.

Note that the sort is naive; numbers will be sorted correctly, any other type
will be converted to a string and sorted that way. If you want more flexibility
see also sort-by.

Example: (print (sort 3 43 1 "Steve" "Adam"))
%%
split
Expand Down Expand Up @@ -361,6 +365,18 @@ str converts the parameter supplied to a string, and returns it.
Example: (print (str 3))
See also: base, number
%%
string=

string= returns true if the supplied parameters were both strings, and have equal values.

See also: = char= string<
%%
string<

string< returns true if the supplied parameters were both strings, and the first is less than the second.

See also: < char< string=
%%
time

time returns a list containing time-related entries; the current hour, the current minute past the hour, and the current value of the seconds.
Expand Down
73 changes: 73 additions & 0 deletions examples/lisp-tests.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ If the name of the test is not unique then that will cause an error to be printe
))


;;
;; Some data for testing-purposes
;;
;; Define a "person" object
(struct person forename surname)

;; Create some people
(set! people (list (person "Ralph" "Wiggum")
(person "Lisa" "Simpson")
(person "Apu" "Nahasapeemapetilon")
(person "Marge" "Bouvier")
(person "Artie" "Ziff")
(person "Edna", "Crabapple")
(person "Homer" "Simpson")))




;;
;; Test cases now follow, defined with the macro above.
Expand Down Expand Up @@ -352,6 +369,24 @@ If the name of the test is not unique then that will cause an error to be printe
(deftest append:2 (list (append (list 2) "2") (list 2 "2")))
(deftest append:3 (list (append (list 2 3) 5) (list 2 3 5)))

;; string<
(deftest string<:1 (list (string< "a" "b") true))
(deftest string<:2 (list (string< "b" "a") false))

;; string<=
(deftest string<=:1 (list (string<= "a" "b") true))
(deftest string<=:2 (list (string<= "b" "a") false))
(deftest string<=:3 (list (string<= "b" "b") true))

;; string>
(deftest string>:1 (list (string> "a" "b") false))
(deftest string>:2 (list (string> "B" "A") true))

;; string>=
(deftest string>=:1 (list (string>= "a" "b") false))
(deftest string>=:2 (list (string>= "b" "a") true))
(deftest string>=:3 (list (string>= "b" "b") true))

;; strlen
(deftest strlen:1 (list (strlen "") 0))
(deftest strlen:2 (list (strlen "steve") 5))
Expand Down Expand Up @@ -380,6 +415,44 @@ If the name of the test is not unique then that will cause an error to be printe
"me"))



;; Define two helpers for sorting, by one/other field.
(set! people-surname-sort (fn* (a b) (string< (person.surname a) (person.surname b))))
(set! people-forename-sort (fn* (a b) (string< (person.forename a) (person.forename b))))


;; sort-by
(deftest sort-by:1 (list (type (person "foo" "bar")) "person"))
(deftest sort-by:2 (list (type (car people)) "person"))
(deftest sort-by:3 (list (type people-surname-sort) "procedure(lisp)"))
(deftest sort-by:4 (list (type people-forename-sort) "procedure(lisp)"))

;; forename-sort, first and last
(deftest sort-by:5 (list (let* (sorted (sort-by people-forename-sort people))
(sprintf "%s %s"
(person.forename (car sorted))
(person.surname (car sorted))))
"Apu Nahasapeemapetilon"))
(deftest sort-by:6 (list (let* (sorted (sort-by people-forename-sort people))
(sprintf "%s %s"
(person.forename (last sorted))
(person.surname (last sorted))))
"Ralph Wiggum"))

;; surname-sort, first and last
(deftest sort-by:7 (list (let* (sorted (sort-by people-surname-sort people))
(sprintf "%s %s"
(person.forename (car sorted))
(person.surname (car sorted))))
"Marge Bouvier"))
(deftest sort-by:8 (list (let* (sorted (sort-by people-surname-sort people))
(sprintf "%s %s"
(person.forename (last sorted))
(person.surname (last sorted))))
"Artie Ziff"))



;;
;; Define a function to run all the tests, by iterating over the hash.
;;
Expand Down
Loading

0 comments on commit f310844

Please sign in to comment.