In ChrysaLisp, :seq is the base class for all ordered collections, including
:array, :list, :str, :nums, :fixeds, and :reals. Because they share
this lineage, they share a unified set of manipulation functions and a unique
indexing logic designed for high-performance string and buffer manipulation.
To understand ChrysaLisp indexing, you only need to answer one question: "How do I slice a sequence to remove just the first and last elements, without calculating its length?"
The answer defines the system:
(slice seq 1 -2)
This single line implies the three fundamental rules of the coordinate system:
-
0 is the Start of the sequence.
-
-1 is the End Boundary (the Length), not the last element.
-
-2 is the Last Element.
Unlike many languages where -1 represents the last item, ChrysaLisp uses -1
to represent the "Fence Post" immediately following the last item. This allows
it to serve as an exclusive upper bound for slices.
Given a sequence of length 5 (e.g., "HELLO"):
Item: H E L L O <Boundary>
Positive: 0 1 2 3 4 5
Negative: -6 -5 -4 -3 -2 -1
When the system receives a negative index, it resolves the absolute index using this logic: RealIndex = Length + InputIndex + 1.
-
Input
-1: 5 + (-1) + 1 = 5 (The Length / End Boundary). -
Input
-2: 5 + (-2) + 1 = 4 (The Last Item).
The (slice seq start end) function returns a sequence representing the
requested range. The start is inclusive, and end is exclusive.
A critical feature of ChrysaLisp's slice implementation is Identity
Detection.
If you request a slice that covers the entire sequence range:
(slice seq 0 -1)
The system detects that the result is identical to the source. Instead of allocating new memory and copying data, it simply increments the reference count of the original object and returns it.
Because slice may return a reference, if you strictly require a new,
distinct copy of a sequence (e.g., to modify it without affecting the
original), you must use cat:
(defq my-copy (cat seq))
(cat) with a single argument guarantees the creation of a new container with
the same content.
-
Identity (Ref):
(slice seq 0 -1) -
Trim First/Last:
(slice seq 1 -2) -
Head (First 3):
(slice seq 0 3) -
Tail (All but first):
(slice seq 1 -1) -
Empty (No elements, but same type):
(slice seq 0 0)
The search functions are engineered to return indices that feed directly into
slice as arguments, eliminating the need for inc or dec math.
Scans from left to right.
-
Returns: The index of the start of the match.
-
Slicing utility: Perfect for the
startargument.
Scans from right to left.
-
Returns: The index of the element immediately following the match.
-
Slicing utility: Perfect for the
endargument.
Example: Parsing a File Path
Consider: (defq path "home/user/dev/main.lisp")
-
Get the Filename:
We want everything after the last slash.
rfindsearches backwards for/. It finds it at index 13, but returns 14 (the index of 'm').(defq path "lib/asm/file.inc") (slice path (rfind "/" path) -1)
-
Get the Directory:
We want everything up to the last slash.
(defq path "lib/asm/file.inc") (slice path 0 (dec (rfind "/" path)))
When accessing a single element via (elem-get seq idx), the index must point
to an actual item, not a boundary.
-
(first seq) is:
(elem-get seq 0) -
(last seq) is:
(elem-get seq -2)
Warning: Attempting (elem-get seq -1) is an Out of Bounds error, because
-1 resolves to the slot after the data ends.
When iterating using high-level functions like (map!), (each!), or
(filter!), the current index operator (!) provides the absolute hard
index.
-
It always returns a positive integer.
-
It starts at 0.
-
It allows you to cross-reference other arrays/lists during iteration without calculating relative offsets.
(map (lambda (item)
(list (cat item " ->") (!)))
"test")