cstr
byte primitives on raw :cstr values.
Small pure-Turmeric surface for reading the length of a NUL-terminated C string and the byte at a given index. Sits below stdlib/str.tur, which wraps a :cstr in a length-tagged view struct. Use these when you just want to walk a literal like "1+2*(3+4)" byte by byte without allocating a view.
Not autoloaded: (import cstr :refer [cstr-len cstr-nth]) explicitly. See docs/archive/history/no-cstr-byte-primitives-pure-turmeric.md for why (an earlier attempt to autoload perturbed Vec element-type inference).
Since: 2026-07-01
cstr-len
(cstr-len [s :cstr] :int)
byte length of a NUL-terminated C string.
| s | NUL-terminated C string pointer |
Length in bytes, excluding the trailing NUL.
(cstr-len "hello") ; => 5
cstr-nth
(cstr-nth [s :cstr i :int] :int)
byte at index i in a NUL-terminated C string.
| s | NUL-terminated C string pointer | |
| i | zero-based byte index; caller must ensure 0 <= i < (cstr-len s) |
The unsigned byte value at position i.
(cstr-nth "1+2" 1) ; => 43 ('+')
cstr-byte-is?
(cstr-byte-is? [s :cstr i :int c :Char] :bool)
is the byte at index i of s the character c?
Unlike cstr-nth this checks the index: it is false when i is negative or at or past the terminator, so a scanner needs no separate length test and no magic numbers. c should be ASCII, as for string/byte-is?. A cstr stores no length, so every call measures s: cheap for a line or a token, but a byte-by-byte walk over a long text should make it a String once and use string/byte-is?.
| s | NUL-terminated C string pointer | |
| i | a byte index; out of range is not an error | |
| c | the character to compare with |
true iff 0 <= i < (cstr-len s) and the byte at i is c's code.
(cstr-byte-is? "a,b" 1 #\,) ; => true (cstr-byte-is? "a,b" 3 #\,) ; => false
Since: markdown-library-plan S1
cstr-char-at
(cstr-char-at [s :cstr i :int] :(Option Char))
the byte at index i of s as a Char, or (none).
The checked counterpart of cstr-nth, for comparing with character literals. Measures s on every call, like cstr-byte-is?.
| s | NUL-terminated C string pointer | |
| i | a byte index; out of range is not an error |
(some c) holding the byte at i, or (none) when i is negative or at or past the terminator.
(cstr-char-at "hi" 1) ; => (some #\i)
Since: markdown-library-plan S1
cstr-starts-with?
(cstr-starts-with? [s :cstr prefix :cstr] :bool)
true iff s begins with prefix.
| s | NUL-terminated C string pointer | |
| prefix | NUL-terminated C string pointer |
true iff the first (cstr-len prefix) bytes of s are prefix's. Every string starts with "".
(cstr-starts-with? "# Title" "# ") ; => true
Since: markdown-library-plan S2
cstr-ends-with?
(cstr-ends-with? [s :cstr suffix :cstr] :bool)
true iff s ends with suffix.
| s | NUL-terminated C string pointer | |
| suffix | NUL-terminated C string pointer |
true iff the last (cstr-len suffix) bytes of s are suffix's. Every string ends with "".
(cstr-ends-with? "guide.md" ".md") ; => true
Since: markdown-library-plan S2
cstr-index-of
(cstr-index-of [s :cstr needle :cstr] :(Option int))
byte offset of the first occurrence of needle in s.
| s | NUL-terminated C string pointer to search | |
| needle | NUL-terminated C string pointer to find |
(some i) for the lowest i where needle occurs, or (none). The empty needle is found at 0.
(cstr-index-of "key: value" ": ") ; => (some 3)
Since: markdown-library-plan S2
cstr-index-of-char
(cstr-index-of-char [s :cstr c :Char] :(Option int))
byte offset of the first c in s.
| s | NUL-terminated C string pointer to search | |
| c | the character to find (ASCII; see cstr-byte-is?) |
(some i) for the lowest i holding c, or (none). The terminator is not searched, so a NUL byte is never found.
(cstr-index-of-char "a=b" #\=) ; => (some 1)
Since: markdown-library-plan S2
cstr-span-while
(cstr-span-while [s :cstr start :int pred :(fn [Char] bool)] :int)
the index where a run of bytes satisfying pred ends.
string/span-while for a cstr: scans from start while pred holds.
| s | NUL-terminated C string pointer to scan | |
| start | the byte offset to start at (clamped into [0, (cstr-len s)]) | |
| pred | the test each byte must pass to continue the run |
The end of the run: an index in [start, (cstr-len s)].
(cstr-span-while " - item" 0 space?) ; => 2
Since: markdown-library-plan S2
cstr-sub
(cstr-sub [s :cstr start :int end :int] :cstr)
substring [start, end) of a NUL-terminated C string.
| s | NUL-terminated C string pointer | |
| start | zero-based start byte index (clamped into [0, len]) | |
| end | exclusive end byte index (clamped into [start, len]) |
A freshly heap-allocated cstr holding the bytes of s in [start, end), NUL-terminated. Out-of-range indices are clamped, so the result is always a valid (possibly empty) string. The caller frees the result.
(cstr-sub "hello" 1 4) ; => "ell"
cstr-eq?
(cstr-eq? [a :cstr b :cstr] :bool)
content equality on two NUL-terminated C strings.
The = operator has no cstr overload at all (comparing two cstr values with it is a TUR-E0006 operator-lookup error), so a byte-wise comparison needs a named function. (eq? a b) from the auto-loaded Eq[cstr] instance is the same comparison reached through typeclass dispatch; this is the direct call, for code that walks bytes with the rest of this module and does not otherwise want typeclass machinery.
| a | NUL-terminated C string pointer, or NULL | |
| b | NUL-terminated C string pointer, or NULL |
true when both are NULL, or both are non-NULL and hold the same bytes up to the terminator. false otherwise -- in particular a NULL never equals a non-NULL, including the empty string.
(cstr-eq? "abc" (cstr-sub "abcdef" 0 3)) ; => true (cstr-eq? "abc" "abd") ; => false
cstr-free
(cstr-free [s :cstr] :void)
release a cstr this module heap-allocated.
cstr-sub hands back a malloc'd string and documents that the caller frees it, but drop! wants a ref<T> and rejects a bare cstr, so without this there is no way to honor that contract from Turmeric -- every caller had to drop into inline C. Pass only a pointer this module allocated: a string literal is static, and freeing one is undefined. NULL is accepted and ignored, matching free(3).
| s | a cstr returned by cstr-sub, or NULL |
(let [t (cstr-sub "hello world" 0 5)] (println t) (cstr-free t))
Internal definitions
cstr-match-at-- internal: do bytes [k, n) of p equal s's from at+k on?cstr-index-of-loop-- internal: first match of needle (length m) at i..last.cstr-index-of-byte-loop-- internal: first i in [i, n) holding byte b.cstr-span-loop-- internal: first i in [i, n) whose byte fails pred, else n.