# Strings String operations are bare stdlib verbs available in every runtime. Indexing is codepoint-based: `len` counts characters, and `$s(a..b)` slicing, `nth`, `char-at`, and `index-of` all operate on whole characters — slicing can never land mid-character. Internally strings are UTF-8 byte arrays and binary-safe; the `bytes/*` verbs give raw access for binary data. Display width (East Asian Wide glyphs take 2 terminal cells) is a separate concern: use the width verbs, not `len`, whenever you position text in a terminal grid. Full story: [String Representation](../10-System-Design/string-representation). ## Functions Core string operations: | Function | Purpose | |---|---| | `str $xs...` | Concatenate values to a string | | `$s << x` | Concatenate onto a string local in place (sugar for `set s [str $s x]`) | | `append s v` | New string with `v` concatenated (pure — also works on nil) | | `format fmt args...` | Printf-style formatting (`%d %x %f %s ...` with flags/width/precision) | | `upper s`, `lower s` | Case conversion | | `trim s` | Strip leading and trailing whitespace | | `split s sep` | Split into a list (empty `sep` splits into characters) | | `join xs sep` | Join a list into a string | | `replace s old new` | Replace all occurrences | | `replace-first s old new` | Replace the first occurrence only | | `starts-with? s prefix`, `ends-with? s suffix` | Prefix / suffix predicates | | `repeat s n` | Repeat the string `n` times | | `lines s` | Split on newlines | | `words s` | Split on whitespace | | `contains s substr` | Substring check | | `reverse s` | Reverse by codepoint (multi-byte characters stay intact) | | `char-at s i` | Character at a codepoint index (nil out of range) | | `index-of s sub` | Find a substring at a codepoint index, or -1 | | `chars/count s`, `chars/slice s a b` | Explicit character count / range | Layout (display width) — use these, not `len`, when positioning text in a terminal-cell UI: | Function | Purpose | |---|---| | `width s` | Display width in cells (East Asian Wide = 2) | | `cells s` | Per-character `@{char-index char-string width}` triples | | `truncate-width s w ?tail` | Longest prefix fitting `w` cells (optional "…" tail) | | `pad-width-left/right s w ?fill` | Pad to a display width | | `wrap-width s w` | Word wrap at display width into a list of lines | | `pad-left s n ch`, `pad-right s n ch` | Pad to a character count | Bytes (binary data) — for `fs/read` bodies, crypto, and protocol offsets, where chunk boundaries must not drift: | Function | Purpose | |---|---| | `bytes/len s` | Byte length | | `bytes/slice s a b` | Raw byte range | | `byte-at s i` | Byte value (0-255) | The codepoint verbs are the text layer: `char-at` and `index-of` never return a raw byte fragment, and `split` with an empty separator yields whole characters. The byte verbs are the data layer: use them for encoded blobs, protocol offsets, and any body that merely happens to contain UTF-8 runs. `width` measures terminal cells; invalid UTF-8 bytes count 1 each. For character classification (`chars/alpha?`, `chars/digit?`, ...) see Chars; for pattern matching see Regex. ## Examples ### Codepoints vs bytes vs display cells
set s "a中c"
echo "chars:" $s(len)                 # 3
echo "bytes:" [bytes/len $s]          # 5
echo "cells:" [width $s]              # 4
### Core operations
set parts [split "a,b,c" ","]
echo [join $parts " | "]              # a | b | c
echo [upper [trim "  hi  "]]          # HI
echo [replace "foo bar foo" "foo" "baz"]
echo [char-at "a中c" 1]               # 中
echo [index-of "a中b文c" "文"]        # 3
echo [repeat "=" 20]
### Building strings For one-shot concatenation use `str` (or interpolation). To build a string across a loop, accumulate with `<<` — it renders values exactly like `str` and reuses one buffer, so the loop stays linear:
set s "count: "
$s << 42                              # count: 42 — any value str-ifies
$s << " items"                        # count: 42 items

def render-rows {|rows|               # nil starts an empty string
    set out nil
    for r in $rows { $out << [str "|" $r~name "|" $r~qty "\n"] }
    $out
}

[append "a" "b"]                      # => "ab"  (pure — new string)
[append nil "b"]                      # => "b"
For joining an existing list, `join` is still the right verb: `[join @{a b c} "-"]` → `"a-b-c"`. Keep hot `<<` loops inside a function — the local then gets the fast in-place path (a global receiver re-copies the string each time). See Syntax and Style. ### Layout: width-aware padding, truncation, wrapping
echo [pad-width-right "中文" 8 "-"]         # 中文----
echo [truncate-width "hello world" 8 "…"]   # hello w…
for line [wrap-width "the quick brown fox" 9] {
    echo $line
}
### Bytes: raw access for binary data
set blob [bytes/slice "a中c" 0 3]     # 3 raw bytes
echo [byte-at "a中c" 1]               # 228 (0xE4, lead byte of 中)