# String utilities

This unit lives at /lib/str.fs and provides a few strings an range related
words.

Most strings in Dusk are regular strings as doc/usage describes. However, we
sometimes have to deal with null-terminated strings and this unit also has words
to deal with them. We refer to null-terminated strings as "zstr" (z for zero).

This unit also defines the concept of "string lists". Those are simply strings
following each other in memory, ending with a null string. To iterate a string
list, all we need to do is "skip" the string using its size byte.

## Internal buffer

Some words need to copy string contents to memory. For this they use an internal
static buffer and yield that buffer as a result. When a word mentions that it
uses the internal buffer, this means that the result must be used (or copied)
straight away before another word from lib/str is used.

## String API

Constants:

NULLSTR     An empty string

Words:

ws? ( c -- f )
  Returns whether character "c" is a whitespace.

s) ( str -- a )
  Skip "str", that is, return the address following its last char.

s, ( str -- )
  Write "str" to here.

strmove" ( dst -- )
  Read quoted string and copy it to "dst", as a string.

#[]>str ( a u -- a u )
  Assert that range "a u" fits in a string ("u" < STR_MAXSZ).

[]>str ( a u -- str )
  Copy range "a u" in the internal buffer and return the corresponding string.

str>zstr ( str -- zstr )
  Copy "str" to internal buffer, add a null terminator and yield zstr.

strcat ( s1 s2 -- str )
  Concatenates s1 and s2 in the internal buffer and return the corresponding
  string. "s1" can be the internal buffer (in other words, "strcat" can be
  chained), but not "s2".

s[]= ( str a u -- f )
  Compares a string to a range.

cmove+ ( src dst u -- dst+ )
  Like "cmove", but also yields "dst+u".

zstrlen ( ztr -- len )
  Returns the length of "zstr".

zmove ( zsrc dst -- dst+ )
  Copies "u" bytes from zsrc where u is zstr's length, including the null byte.
  Yields "dst+u".

[str]? ( str a u -- idx )
  Search for the first occurrence of "str" in range "a u" and return its index.
  Returns -1 if not found.

sfind ( str list -- idx? f )
  Find "str" in "list" and return its index and f=1 if found, f=0 otherwise.

slistiter ( idx list -- str )
  Given a string list "list", iterate to its "idx" element and return the str.

toword ( -- c )
  Read stdin until next non WS and yield it.

expectchar ( c -- )
  Read stdin, discarding whitespaces and expect the next char to be "c"

stringlist ( n "name" "..." -- )
  Create a list of strings (same format as sfind above) with the specified
  number of elements. Elements are accumulated using "wordorquote"
  [doc/usage/io].

  Example: 3 stringlist mylist foo bar "hello world!"

rfor ( a u -- ) i=a j=idx k=u
  An iterator over the range "a u". It iterates "u" time, yielding an increased
  "a" as "i". Additionally, an index starting at 0 is maintained at "j".

  Example: " hello" c@+ rfor i c@ emit j . next --> h0e1l2l3o4

startswith? ( substr str -- f )
  Yields f=1 if "str" begins with "substr".

endswith? ( substr str -- f )
  Yields f=1 if "str" ends with "substr".

upcase ( c -- c )
  If c is between "a" and "z", yield their upcase version. Otherwise, yield c.

0-9? ( c -- f )
  Returns whether "c" is a digit.

A-Za-z? ( c -- f )
  Returns whether "c" is an ASCII letter.

alnum? ( c -- f )
  Returns whether "c" is a letter or digit.

## Range API

bounds[] ( a u -- lo hi ) *B*
  Yields the low and high addresses of the bounds represented by range "a u".

cintersect[] ( a1 u1 a2 u2 -- ?a u )
  Compute the intersection between the two given ranges and return the part
  common to both. If there is no intersection, u=0 and a isn't present.

cmove- ( src dst u -- )
  Like "cmove", but operates backwards. You'll want to use this when copying
  overlapping memory area "forward" (dst > src).

rmatch" ( "..." -- ) ( c -- f ) *I*
  Given a list of character ranges following the opening quote, compile code
  with signature "c -- f" that checks whether input character is in that range.
  For example, ": foo rmatch"azAZ" ;" compiles the word "foo ( c -- f )" that
  yields f=1 if c is within a-z or A-Z.

  Input character list must be even (in pairs).

rfind" ( "..." -- ) ( a u -- ?idx f ) *I*
  Piggy-backs on rmatch" and compiles code that looks in range "a u" for the
  first character fitting in the specified character ranges. If found, f=1 and
  "idx" is the index of the found character. Otherwise, f=0 and "idx" is absent.

rslide+ ( by a u -- )
  Move range "a u" forward in memory by "by" bytes.

rtrimleft ( n a u -- a u )
  Trim "n" characters at the left of range "a u" and return the resulting range.

rtrimright ( n a u -- a u )
  Trim "n" characters at the right of range "a u" and return the resulting
  range.

swap[] ( a u -- ) *B*
  Reverse the order of the elements in the range. For example, a range with
  3 elements "1 2 3" becomes a range with elements "3 2 1".
