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..]