Arrays
GameLisp's general-purpose sequential data structure is called an "array", but it's actually
a VecDeque
.
A VecDeque
is a growable ring buffer. It's very similar to a Vec
, but with the added ability
to push and pop items from the start of the sequence in constant time. (Note that a VecDeque
is very different from a C++ std::deque
,
which has an odd memory-allocation pattern - VecDeque
allocates all of its elements
in a single buffer, just like a Vec
.)
Basic Functions
The arr
function will return a new array which contains all of its arguments.
When one of the arguments is an array, and that argument is prefixed with ..
, all of that
array's elements are copied into the array which is being constructed.
(prn (arr)) ; prints ()
(prn (arr 1 2 3 4)) ; prints (1 2 3 4)
(let src '(x y z))
(prn (arr 1 2 src 3 4)) ; prints (1 2 (x y z) 3 4)
(prn (arr 1 2 ..src 3 4)) ; prints (1 2 x y z 3 4)
(prn (arr ..src ..src)) ; prints (x y z x y z)
len
returns the array's length, and empty?
tests whether it
has a length of 0.
(prn (len '())) ; prints 0
(prn (len (arr 'a 'b))) ; prints 2
(prn (empty? (arr))) ; prints #t
(prn (empty? '(1))) ; prints #f
An array constructed using the arr
function will be mutable. You can add any number of elements
to the start or end of an array using the functions push-start!
and
push!
, or you can remove one element at a time using
pop-start!
and pop!
.
(let metals (arr 'pewter 'silver 'copper))
(push! metals 'iron 'bronze)
(prn metals) ; prints (pewter silver copper iron bronze)
(prn (pop! metals)) ; prints bronze
(prn (pop! metals)) ; prints iron
(prn (pop-start! metals)) ; prints pewter
(prn metals) ; prints (silver copper)
(push-start! metals 'titanium 'electrum)
(prn metals) ; prints (titanium electrum silver copper)
Many other useful functions are described in the standard library documentation.
Indexing
The macro for looking up an element in any collection is called access
.
To get the first element of an array, you might call (access the-array 0)
.
Because access
is such a fundamental operation, it can be
abbreviated using square brackets. (access the-array 0)
would normally be written as [the-array 0]
instead. Notice that this resembles the equivalent
Rust syntax: the_array[0]
.
Negative indexes count backwards from the end of the array. [names -1]
returns the last element
in the names
array, and [names -2]
returns its second-to-last element.
Array elements are places, so they can be mutated using the
=
macro.
(let blues (arr 'azure 'sapphire 'navy))
(prn [blues 2]) ; prints navy
(prn [blues -3]) ; prints azure
(= [blues 0] 'cerulean)
(= [blues -2] 'cobalt)
(prn blues) ; prints (cerulean cobalt navy)
Slicing
The access
macro, and therefore the []
abbreviation, both support special syntax for accessing
multiple consecutive elements in an array. They use the :
symbol, similar to Python's slice
syntax.
(let alphabet '(a b c d e f g h i j k l m n o p q r s t u v w x y z))
; elements from n to m are sliced using `n : m`
(prn [alphabet 3 : 8]) ; prints (d e f g h)
(prn [alphabet -10 : 21]) ; prints (q r s t u)
(prn [alphabet 5 : 5]) ; prints ()
; elements from 0 to n are sliced using `: n`
(prn [alphabet : 5]) ; prints (a b c d e)
(prn [alphabet : -23]) ; prints (a b c)
(prn [alphabet : 30]) ; an error
; elements from n to the end are sliced using `n :`
(prn [alphabet 23 :]) ; prints (x y z)
(prn [alphabet -1 :]) ; prints (z)
; the entire array can be sliced using `:`
(prn (eq? [alphabet :] alphabet)) ; prints #t
; the `:` symbol is whitespace-sensitive
(prn [alphabet 2:5]) ; an error
(prn [alphabet 3:]) ; an error
To keep things simple, all of these slicing operations will allocate, and return, a new array. Unlike Rust, there's no way to produce a reference which points into an array's interior.
The del!
and remove!
macros also support the same
slicing syntax.
(del! names 3) ; delete element 3
(del! names 2 : 5) ; delete elements 2, 3 and 4
(del! names :) ; delete every element
A slice is a place. Assigning an array to a slice will overwrite all of the elements stored there, changing the size of the array if necessary.
(let numbers (arr 0 1 2 3 4 5 6 7 8 9))
(= [numbers : 6] '())
(prn numbers) ; prints (6 7 8 9)
(= [numbers -2 :] (arr 42 42 42))
(prn numbers) ; prints (6 7 42 42 42)
(= [numbers :] '(5 5 5))
(prn numbers) ; prints (5 5 5)
GameLisp doesn't include some of the traditional Lisp functions for processing sequences, like
rest
, butlast
, take
and drop
. All of those functions can be expressed in a more versatile
and general way using the slice syntax.
Arrows
Deeply-nested use of the []
syntax can sometimes be visually confusing.
[[[the-array index] 0] start-index :]
"Arrow macros" were discussed in the previous chapter. The []
syntax works well when used with the ->
macro:
(-> the-array [index] [0] [start-index :])
This is similar to the equivalent Rust syntax:
the_array[index][0][start_index..]