Strings & text¶
This page covers text in d/BASIC: how to declare strings, join them, measure and slice them, search inside them, turn numbers into text and back, and show them safely. After reading it you will know the whole $ toolkit and the one rule that matters most: d/BASIC counts positions in a string in bytes, not characters.
Strings make small programs feel human. They also sit at every boundary a
program has: the console, forms, files, database values, network messages,
menus, and dragged items all carry text. d/BASIC keeps the familiar $
functions from classic BASIC, and is explicit about where text is bytes so that
a string can travel between machines safely.
Declare and join strings¶
DIM GivenName$
DIM FamilyName AS STRING
GivenName$ = "ADA"
FamilyName = "LOVELACE"
PRINT GivenName$ + " " + FamilyName
+ joins two strings. d/BASIC does not quietly turn a number into text, or
text into a number, in the middle of an expression; use STR$ or VAL where
you mean to convert.
String literals use double quotes and have no backslash escapes such as \n:
The working toolkit¶
| Need | Form | Result |
|---|---|---|
| byte length | LEN(Text$) |
INTEGER byte count |
| first byte value | ASC(Text$) |
0..255; an empty string is an error |
| one byte from a code | CHR$(Code%) |
one-byte string; the code must be 0..255 |
| left slice | LEFT$(Text$, Count%) |
at most Count% bytes |
| right slice | RIGHT$(Text$, Count%) |
at most Count% bytes |
| middle slice | MID$(Text$, Start% [, Count%]) |
slice starting at position Start% (the first byte is 1) |
| find | INSTR([Start%,] Text$, Needle$) |
position of the first match (counting from 1), or 0 |
| ASCII uppercase/lowercase | UCASE$, LCASE$ |
a changed copy |
| trim ASCII whitespace | LTRIM$, RTRIM$ |
a changed copy |
| spaces | SPACE$(Count%) |
that many spaces |
| repeated byte | STRING$(Count%, Pattern) |
the first byte of the pattern, repeated |
| number to text | STR$(Number) |
the number in its standard spelling |
| text to number | VAL(Text$) |
the numeric value |
The slice functions never reach past what exists. A start past the end gives an empty string; a length that runs past the end gives whatever is left.
Code$ = "DBASIC-ALPHA"
PRINT LEFT$(Code$, 6) ' DBASIC
PRINT RIGHT$(Code$, 5) ' ALPHA
PRINT MID$(Code$, 8, 99) ' ALPHA
PRINT INSTR(Code$, "-") ' 7
Search before splitting¶
INSTR makes a clear parser for small formats with a known shape:
Pair$ = "theme=amber"
EqualsAt% = INSTR(Pair$, "=")
IF EqualsAt% > 0 THEN
Key$ = LEFT$(Pair$, EqualsAt% - 1)
Value$ = MID$(Pair$, EqualsAt% + 1)
PRINT UCASE$(Key$); " -> "; Value$
END IF
Use the three-argument form when the search should begin later in the text:
An empty needle (the text being searched for) is found at whatever valid start
you asked for. A start below 1 is treated as 1. For an empty needle, the
position just after the final byte, LEN(Text$) + 1, also counts as valid; a
larger start returns 0. For a non-empty needle, a start beyond the end of the
text returns 0.
Replace without changing length¶
MID$ can also be the target of an assignment:
This overwrites the bytes that are already there; it does not insert bytes or change the length of the destination. The replacement is cut short by the length you give and by the room left in the destination.
That makes a pre-sized text buffer useful when you know its final size:
For ordinary prose, plain joining with + is usually clearer. Reach for a
pre-sized buffer when fixed columns are themselves part of the output.
Conversion should be visible¶
Count% = 12
Message$ = "COUNT=" + LTRIM$(STR$(Count%))
PRINT Message$
Entered$ = "41.5"
Reading! = VAL(Entered$)
PRINT Reading! + .5
STR$ follows the language's standard rules for signs and number formatting;
a positive number gets a leading space where a minus sign would go, which is
why compact labels often use LTRIM$. Use PRINT USING when the output needs
a declared layout rather than ad-hoc trimming.
Never rely on VAL to check input you do not trust, such as a structured
document from outside your program. Parse the exact format you accept and
reject anything left over or missing.
STRING values and fixed record text are different¶
An ordinary STRING is variable-length storage that the language manages for
you. A STRING * n field inside a TYPE record is exactly n bytes stored
in place:
Writing to a fixed field cuts longer input short and pads shorter input with
spaces. That is valuable in a packed record or a value shaped for a wire
format; it is rarely the right choice for prose someone will edit.
LEN(Row) measures the whole packed record, while LEN(Row.Title) measures
the declared field width once the field is loaded as text.
Bytes, UTF-8, and characters¶
d/BASIC's core string functions deliberately work in bytes:
LEN,LEFT$,RIGHT$,MID$, andINSTRcount byte positions;- comparison orders strings byte by byte;
ASC,CHR$, andSTRING$work with byte values; and- case conversion and trimming use ASCII rules.
This gives files, protocols, checksums, retro text, and every d/OS runtime (the program that runs your compiled code) one exact answer. It also means a byte slice can cut a multi-byte UTF-8 character in half.
For text people type, keep whole UTF-8 characters together. Let the
TextCommit and Composition events (what the window system sends when
someone finishes typing text, or is still composing it), widgets, DATA
values, and the bridges to native code carry text as text; do not use MID$ to
move a cursor through text in an arbitrary language. A runtime may draw the
text with a better font, but it may not change the stored bytes.
Limits, and what happens when you cross them¶
A string literal in the source may hold up to 32,767 bytes. A string in a
running program is limited by the string size the compiled program file,
.dbc, and the runtime agree on, and by the memory the program was accepted
with. A negative fill length, an empty STRING$ pattern, ASC(""), a byte
code outside 0..255, or a result larger than the string ceiling is declined,
and the program is told why, rather than wrapping around or quietly cutting
the result short.
ON REFUSAL GOTO BadCharacter
PRINT ASC("")
GOTO Done
BadCharacter:
PRINT "NO FIRST BYTE. RULE "; ERR
RESUME Done
Done:
END
ON REFUSAL GOTO names the line to jump to when an operation is declined, and
ERR holds the error code that says what went wrong.
A small text report¶
DIM Raw$(1 TO 3)
Raw$(1) = " amber"
Raw$(2) = "BLUE "
Raw$(3) = " Green "
FOR I% = LBOUND(Raw$) TO UBOUND(Raw$)
Clean$ = UCASE$(LTRIM$(RTRIM$(Raw$(I%))))
PRINT RIGHT$(" " + STR$(I%), 3); " "; Clean$
NEXT I%
END
The result is boring in the best way: every d/OS runtime performs the same byte operations and prints the same report.
Next: Expressions →