Strings & text¶
Strings make small programs feel human. They also sit at important boundaries:
the console, forms, files, database values, network messages, menus, and drag
items all carry text. D/BASIC keeps the familiar $ toolkit while making the
byte/text boundary explicit enough to travel safely.
Declare and join strings¶
DIM GivenName$
DIM FamilyName AS STRING
GivenName$ = "ADA"
FamilyName = "LOVELACE"
PRINT GivenName$ + " " + FamilyName
+ concatenates two strings. D/BASIC does not silently turn a number into text
or text into a number inside an expression; use STR$ or VAL where conversion
is intentional.
String literals use double quotes and have no backslash escape language:
The working toolkit¶
| Need | Form | Result |
|---|---|---|
| byte length | LEN(Text$) |
INTEGER byte count |
| first byte value | ASC(Text$) |
0..255; empty text refuses |
| one byte from a code | CHR$(Code%) |
one-byte string; 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%]) |
1-based bounded slice |
| find | INSTR([Start%,] Text$, Needle$) |
1-based position, or 0 |
| ASCII uppercase/lowercase | UCASE$, LCASE$ |
transformed copy |
| trim ASCII whitespace | LTRIM$, RTRIM$ |
transformed copy |
| spaces | SPACE$(Count%) |
repeated spaces |
| repeated byte | STRING$(Count%, Pattern) |
first byte of pattern repeated |
| number to text | STR$(Number) |
canonical numeric presentation |
| text to number | VAL(Text$) |
numeric value |
The slice functions clamp to what exists. A start past the end produces an empty string; a requested length past the end returns the available suffix.
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 bounded formats:
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 searching should begin later:
An empty needle is found at the requested valid start. A start below 1 is
treated as 1. For an empty needle, the position immediately after the final
byte—LEN(Text$) + 1—is also valid; a larger start returns 0. For a nonempty
needle, a start beyond the text returns 0.
Replace without changing length¶
MID$ can also be an assignment target:
This overwrites the available span; it does not insert bytes or change the destination length. The replacement is clipped by the explicit length and the remaining room in the destination.
That makes a pre-sized text buffer useful when its final bound is known:
For ordinary prose, clear concatenation is usually better. Reach for a pre-sized buffer when the fixed columns are themselves part of the output format.
Conversion should be visible¶
Count% = 12
Message$ = "COUNT=" + LTRIM$(STR$(Count%))
PRINT Message$
Entered$ = "41.5"
Reading! = VAL(Entered$)
PRINT Reading! + .5
STR$ retains the language's canonical sign and numeric formatting rules; a
positive result may include a leading sign space, which is why compact labels
often use LTRIM$. Use PRINT USING when the presentation needs a declared
picture rather than ad-hoc trimming.
Never use VAL as validation for an untrusted structured document. Parse the
bounded format you actually accept and reject leftover or missing fields.
STRING values and fixed record text are different¶
An ordinary STRING is variable-length managed storage. A STRING * n field
inside a TYPE is exactly n inline bytes:
Writing a fixed field truncates longer input and space-pads shorter input. That
is valuable in a packed record or wire-shaped value; it is rarely the right
choice for editable prose. LEN(Row) measures the complete packed record, while
LEN(Row.Title) measures the declared field width after it is loaded as text.
Bytes, UTF-8, and characters¶
D/BASIC's core string operators are deliberately byte-oriented:
LEN,LEFT$,RIGHT$,MID$, andINSTRcount byte positions;- comparison orders byte sequences;
ASC,CHR$, andSTRING$work with byte values; and- case conversion and trimming use ASCII rules.
This gives files, protocols, digests, retro text, and every runtime one exact answer. It also means a byte slice can split a multi-byte UTF-8 character.
For human text, keep committed UTF-8 whole. Let TextCommit and Composition
events, widgets, DATA values, and native bridges carry semantic text; do not use
MID$ as a cursor engine for an arbitrary language. A runner may upgrade the
font used to draw that text, but it may not change the stored bytes.
Limits and refusal¶
A source string literal is bounded to 32,767 bytes. A live string is bounded by
the DBC/runtime string contract and available admitted memory. Negative fill
lengths, an empty STRING$ pattern, ASC(""), byte codes outside 0..255, or a
result beyond the string ceiling refuse instead of wrapping or truncating
silently.
ON REFUSAL GOTO BadCharacter
PRINT ASC("")
GOTO Done
BadCharacter:
PRINT "NO FIRST BYTE. RULE "; ERR
RESUME Done
Done:
END
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 intentionally boring in the best way: every runner performs the same byte operations and produces the same report.
Next: Expressions →