Skip to content

Commit

Permalink
Merge pull request #76 from skx/75-nth
Browse files Browse the repository at this point in the history
Move (nth) to stdlib.
  • Loading branch information
skx authored Nov 2, 2022
2 parents 2924190 + 7f102c3 commit 561aeaa
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 11 deletions.
32 changes: 32 additions & 0 deletions builtins/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func PopulateEnvironment(env *env.Environment) {
env.Set("ms", &primitive.Procedure{F: msFn, Help: helpMap["ms"]})
env.Set("nil?", &primitive.Procedure{F: nilFn, Help: helpMap["nil?"], Args: []primitive.Symbol{primitive.Symbol("object")}})
env.Set("now", &primitive.Procedure{F: nowFn, Help: helpMap["now"]})
env.Set("nth", &primitive.Procedure{F: nthFn, Help: helpMap["nth"],Args: []primitive.Symbol{primitive.Symbol("list"), primitive.Symbol("offset")}})
env.Set("ord", &primitive.Procedure{F: ordFn, Help: helpMap["ord"], Args: []primitive.Symbol{primitive.Symbol("char")}})
env.Set("os", &primitive.Procedure{F: osFn, Help: helpMap["os"]})
env.Set("print", &primitive.Procedure{F: printFn, Help: helpMap["print"], Args: []primitive.Symbol{primitive.Symbol("arg1..argN")}})
Expand Down Expand Up @@ -1169,6 +1170,37 @@ func nowFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive
return primitive.Number(time.Now().Unix())
}

// nthFn is the implementation of `(nth..)`
func nthFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {


// We need two arguments.
if len(args) != 2 {
return primitive.ArityError()
}

// The argument must be a list
lst, ok := args[0].(primitive.List)
if !ok {
return primitive.Error("argument not a list")
}

// The second argument must be a number
num, ok2 := args[1].(primitive.Number)
if !ok2 {
return primitive.Error("argument not a number")
}

n := int(num)

// Is it in bound?
if n >= 0 && n < len(lst) {
return lst[n]
}

return primitive.Error("out of bounds")
}

// ordFn is the implementation of (ord ..)
func ordFn(env *env.Environment, args []primitive.Primitive) primitive.Primitive {

Expand Down
114 changes: 114 additions & 0 deletions builtins/builtins_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2556,6 +2556,120 @@ func TestNow(t *testing.T) {

}

func TestNth(t *testing.T) {

// No arguments
out := nthFn(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:%s", out)
}

// we need to have "list" + "number"

// Not a list
out = nthFn(ENV, []primitive.Primitive{
primitive.Number(3),
primitive.Number(3),
})

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


// Not a number
out = nthFn(ENV, []primitive.Primitive{
primitive.List{},
primitive.List{},
})

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


// bound checking
var l primitive.List
l = append(l, primitive.String("one"))
l = append(l, primitive.String("two"))


// negative offset
out = nthFn(ENV, []primitive.Primitive{
l,
primitive.Number(-1),
})

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

// out of bounds.
// list has two entries: offset 0, then offset 1
// offset 2 is buffer overflow
out = nthFn(ENV, []primitive.Primitive{
l,
primitive.Number(2),
})

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


// valid access
str := nthFn(ENV, []primitive.Primitive{
l,
primitive.Number(0),
})

val, ok2 := str.(primitive.String)
if !ok2 {
t.Fatalf("expected string, got %v", str)
}
if val.ToString() != "one" {
t.Fatalf("got string, but wrong one %v", out)
}

// valid access
str = nthFn(ENV, []primitive.Primitive{
l,
primitive.Number(1),
})

val, ok2 = str.(primitive.String)
if !ok2 {
t.Fatalf("expected string, got %v", str)
}
if val.ToString() != "two" {
t.Fatalf("got string, but wrong one %v", out)
}

}

func TestOrd(t *testing.T) {

// no arguments
Expand Down
8 changes: 8 additions & 0 deletions builtins/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ now returns the number of seconds since the Unix Epoch.

See also: ms
%%
nth

nth returns an item from the specified list, at the given offset.

NOTE: The offset starts from 0, to access the first item.

Example: (print (nth '( 1 2 3 ) 0 ) )
%%
ord

ord returns the ASCII code for the character provided as the first input.
Expand Down
11 changes: 0 additions & 11 deletions stdlib/stdlib/mal.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,6 @@ Example: (apply-pairs (list 1 2 3 4) (lambda (a b) (print \"Called with %s %s\"

(alias count length)

;; Find the Nth item of a list
(set! nth (fn* (lst:list i:number)
"Return the Nth item of the specified list.
Note that offset starts from 0, rather than 1, for the first item."
(if (> i (length lst))
(error "Out of bounds on list-length")
(if (= 0 i)
(car lst)
(nth (cdr lst) (- i 1))))))


(set! map (fn* (lst:list fun:function)
"Return a list with the contents of evaluating the given function on every item of the supplied list.
Expand Down

0 comments on commit 561aeaa

Please sign in to comment.