# Literals and strings ## Number literals Unlike traditional forths, Dusk has no DEC/HEX mode. Number literals are parsed using a prefix system. * "naked" numbers are parsed as decimal: 1234 -1234 * "$" is the prefix for hexadecimal notation: $12fe * "'" is the prefix for a character literal and must be closed: 'A' Whitespace characters cannot be represented this way. You can use constants such as SPC instead. ## Strings A string is an address to an area in memory starting with a length byte followed by that many bytes (also known as a "counted string"). When we refer to a "string", we refer to that address. For example, this code will yield a "hello" string to PS (Parameter Stack): here 5 c, 'h' c, 'e' c, 'l' c, 'l' c, 'o' c, The code above is the equivalent of: "hello" Strings in Dusk are not limited to textual data and can refer to binary data. It only refers to the "first byte length followed by that much data" structure. ### Frequent string patterns Except in C code, you won't see any reference to a "strlen" word, because getting a length for a string is as simple as c@, so c@ is used. Another frequent pattern is using c@+ on a string. This yields the range of a given string. ## String literals In most Forths, string literals are awkward because the word to construct them needs to be followed by a whitespace because of Forth's tokenization rules. Not in Dusk. In Dusk, string literal are created by enclosing the contents of the string in double quotes, without any requirement for a whitespace prefix. This is an example of a string literal: "hello" You can read more about how this works in [doc/usage/io]. For now, what you need to know is that this exception that Dusk has with regards to double quotes applies to all words ending with '"'. For example, to print a string, you do: ."hello" When a string handling word such as " ," or ." is used the following content is parsed in an almost verbatim manner until the closing " is reached. We say almost verbatim because we can write special characters with the '\' escape character: \b: backspace ($08) \t: tab ($09) \n: newline ($0a) \v: vertical tab ($0b) \f: form feed ($0c) \r: carriage return ($0d) \0: null byte (0) \\: '\' character \": '"' character A '\' followed by a "real" LF character discards the character, allowing string literals to span multiple lines. For example: "foo\ bar" is the same as: "foobar" Any other character following the '\' results in that character being parsed as- is, the preceding '\' being ignored. ## The string pool String literals in compiled words are interweaved in the code being compiled directly. For example, in: : foo "hello" ; you don't have to worry about the lifetime of the string, it's in permanent storage. However, when string literals are used in an interpret context, for example in: "hello" stype the string literal being parsed is placed in what we call the string pool. It is a rolling buffer very much like a scratchpad [mem/scratch], which goes back to its beginning when it reaches its end, thus overwriting previous string literals. Its default size is big enough to give you ample wiggle room to juggle many strings at once, but you must not forget that those strings are still in temporary storage. For example: "hello" const mystr is usually a bad idea unless "mystr" is used only for a short time. At some point, something is going to overwrite where mystr points to and you'll have a corrupt string. Even semi-permanent uses should avoid the pool. For example, if you write a word that needs to hold onto a string while it calls an arbitrary word, then it can't be a string from the pool because that arbitrary word could be compiling a big C source file that will eat through that pool like butter. In that case, you'll want to use str>r [doc/usage/mem]. ### Using the string pool directly The string pool is also useful as a generic scratchpad to place strings into and has an API for such usages, which is used in [lib/str] and [io/stream]. "strallot ( n -- a )" is exactly like "allot@" but for the string pool. "newstr ( -- a )" and "endstr ( a -- str )" offer a convenient API for string building. Calling "newstr" first ensures that we have at least STR_MAXSZ bytes in front of us in the pool, then allocates one byte for the string length and yields the following address. Then, it's your job to populate that memory with whatever contents you want. When you're finished, you call "endstr" with the address following the last byte of your contents. This will "finalize" (write the length byte and allocate that number of bytes in the pool) the string and yield it. For example, you build the string "foo", you'd do: : buildfoo newstr 'f' swap c!+ 'o' dup rot c!+ c!+ endstr ; Don't use those words in interpret mode, input stream processing will mess with your string. ## Ranges In Dusk, the word "range" refers to a pair of numbers (very often "a u") representing contiguous elements at a specific address. The first number is the starting address of the range, the second number is the length in units, which can be 1, 2 or 4 bytes. When a "range" word is binary modulable (that it has a "w" or "c" prefixed version) the word width determines the unit size. When it's not, the size is implicitly 1 byte. Examples: "rtype ( a u -- )" emits "u" characters (1 byte) starting at address "a". "c@+" applied to a string will yield a range for that string. "widx ( n a u -- )" search 16-bit number "n" in u*2 bytes range at "a". The symbol associated with ranges is "[]". For example, "[]= ( a1 a2 u -- f )" means "compare two ranges for equality". Many "range words" don't use this symbol though, for example "move" and "idx". A range with "u=0" is a null range and words dealing with ranges will generally deal gracefully with null ones. For example, "move" with a null range will be a noop, "cidx" will always yield "f=0", and so on. If a "range word" doesn't handle null ranges gracefully, it has to mention it in its description.