STR$ - STRING Interface Routines
Overview
VX/STR$ is a call-compatible implementation of the OpenVMS STR$ string manipulation run-time library for x86 and ARM Linux. It is not a standalone product or daemon: it is a set of callable routines compiled into the shared VX/RT archive libvxrt.a. A migrated OpenVMS application, whether in translated C, COBOL, FORTRAN, BASIC, or Pascal, links against that archive and calls STR$TRIM, STR$CONCAT, and the rest exactly as it did on VMS.
The library covers string manipulation, searching, formatting, and decimal-string numeric operations across the OpenVMS VMS descriptor model, auto-detecting 32-bit and 64-bit descriptors so the same routine handles both. It preserves OpenVMS string handling behavior, including its condition-code return convention, so applications that depend on STR$ services migrate and run without rewriting the calling code.
<!-- Webflow embed (min). Source: str-page-technical/str-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxstrd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">Architecture: how a STR$ call reaches the code</h2><p>
VX/STR$ has no process model to administer. Each OpenVMS <code>STR$xxx</code> entry point
is implemented as its own C++ function body, compiled to an object file, and archived into
<code>libvxrt.a</code>. The relevant architecture is the symbol and entry-point plumbing
that lets a single C++ body answer to VMS-style names and to language-specific calling
conventions. The product surface is therefore one archive, roughly 40 built entry points,
four external symbol names each, and one descriptor model.
</p><div class="table-wrapper"><table><thead><tr><th>Layer</th><th>Mechanism</th></tr></thead><tbody><tr><td>Canonical C++ body</td><td><code>STR_D_<NAME>(...)</code> (for example <code>STR_D_TRIM</code>): the real implementation.</td></tr><tr><td>VMS-style aliases</td><td>An <code>__AliasF4()</code> macro emits assembler <code>.set</code> aliases mapping <code>STR$NAME</code> and <code>str$name</code> to the canonical body.</td></tr><tr><td>FORTRAN entry point</td><td><code>F4_STR_D_<NAME>(...)</code>: unpacks the hidden trailing string-length arguments, builds descriptors, then tail-calls the C++ body.</td></tr><tr><td>FORTRAN aliases</td><td>The same macro maps the trailing-underscore names <code>STR$NAME_</code> and <code>str$name_</code> to the FORTRAN entry.</td></tr><tr><td>Descriptor helpers</td><td><code>vxrt_strptr64</code>, <code>vxrt_dsc64t</code>, <code>vxrt_setlen</code>, and <code>vxrt_is_dsc64</code> resolve 32-bit, 64-bit, and varying descriptors.</td></tr></tbody></table></div><h6>Four external symbols per routine</h6><p>
A single alias line at the foot of each source file emits four external symbols. For
<code>STR$TRIM</code> the names are <code>STR$TRIM</code> and <code>str$trim</code> (aliases
of the C++ body <code>STR_D_TRIM</code>), plus <code>STR$TRIM_</code> and <code>str$trim_</code>
(trailing-underscore FORTRAN names, aliases of the FORTRAN thunk <code>F4_STR_D_TRIM</code>).
C, C++, and COBOL callers bind to <code>STR$TRIM</code>; FORTRAN callers bind to
<code>STR$TRIM_</code>. The alias macro:
</p>
<pre><span class="cmt">/* Linux build */</span>
<span class="kw">#define</span> __MkAlias(sym, alias) \
__asm__(<span class="str">".global "</span> alias); \
__asm__(<span class="str">".set "</span> alias <span class="str">", "</span> sym)
<span class="kw">#define</span> __AliasF4(sym, symF, aliasU, aliasL) \
__MkAlias2(#sym, #aliasU, #aliasL); \
__MkAlias2(#symF, #aliasU <span class="str">"_"</span>, #aliasL <span class="str">"_"</span>)</pre>
<div class="callout"><strong>One body, four names.</strong> <code>F4_STR_D_NAME</code> is the internal FORTRAN
thunk symbol, not itself a public alias. The four externally visible names are
<code>STR$NAME</code>, <code>str$name</code>, <code>STR$NAME_</code>, and
<code>str$name_</code>. On Apple and Cygwin builds the macro prepends a leading underscore
to every symbol; production Linux builds use the plain names.
</div><h6>The canonical call pattern</h6><p>
Every routine follows the same shape: validate the pointers, resolve the source string
through <code>vxrt_strptr64</code> (which is aware of 32-bit, 64-bit, and varying
descriptors), do the work into a scratch buffer, then funnel the result through
<code>STR$COPY_R64</code>, the universal class-aware copy primitive.
</p><style>.s7-tech-embed[data-vxstrd-body-embed] .call-flow {margin: 1.5rem 0;padding: 16px;border: 1px solid var(--s7t-border);border-radius: 10px;background: rgba(255, 255, 255, 0.01);}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint {display: grid;grid-template-columns: 76px minmax(0, 1fr);gap: 14px;align-items: center;padding: 14px 16px;border-radius: 8px;background: var(--s7t-surface);}.s7-tech-embed[data-vxstrd-body-embed] .call-return {border: 1px solid var(--s7t-note-bd);background: var(--s7t-note-bg);}.s7-tech-embed[data-vxstrd-body-embed] .call-label,.s7-tech-embed[data-vxstrd-body-embed] .call-number {font-family: var(--s7t-mono);color: var(--s7t-accent);font-size: 0.76em;font-weight: 700;letter-spacing: 0.08em;text-transform: uppercase;}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint strong,.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint span:last-child,.s7-tech-embed[data-vxstrd-body-embed] .call-stage strong,.s7-tech-embed[data-vxstrd-body-embed] .call-stage span:last-child {display: block;}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint strong,.s7-tech-embed[data-vxstrd-body-embed] .call-stage strong {margin-bottom: 4px;color: var(--s7t-text);}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint span:last-child,.s7-tech-embed[data-vxstrd-body-embed] .call-stage span:last-child {color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}.s7-tech-embed[data-vxstrd-body-embed] .call-stages {display: grid;grid-template-columns: repeat(4, minmax(0, 1fr));gap: 10px;margin: 10px 0;}.s7-tech-embed[data-vxstrd-body-embed] .call-stage {min-width: 0;padding: 15px;border-top: 3px solid var(--s7t-accent);border-radius: 6px;background: var(--s7t-surface2);}.s7-tech-embed[data-vxstrd-body-embed] .call-stage .call-number {margin-bottom: 10px;}@media (max-width: 900px) {.s7-tech-embed[data-vxstrd-body-embed] .call-stages {grid-template-columns: repeat(2, minmax(0, 1fr));}}@media (max-width: 600px) {.s7-tech-embed[data-vxstrd-body-embed] .call-stages,.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint {grid-template-columns: 1fr;}.s7-tech-embed[data-vxstrd-body-embed] .call-endpoint {gap: 5px;}}</style><div class="call-flow" role="group" aria-label="Canonical STR routine call pattern"><div class="call-endpoint"><span class="call-label">Caller</span><div><strong><code>STR$NAME</code></strong><span>Calls <code>STR_D_NAME(dest, source, ...)</code>.</span></div></div><div class="call-stages"><div class="call-stage"><span class="call-number">01</span><strong>Validate</strong><span>Return <code>SS$_ACCVIO</code> immediately if a required pointer is null.</span></div><div class="call-stage"><span class="call-number">02</span><strong>Resolve</strong><span><code>vxrt_strptr64</code> resolves a 32-bit, 64-bit, or varying descriptor.</span></div><div class="call-stage"><span class="call-number">03</span><strong>Transform</strong><span>Perform the routine's work in a scratch buffer.</span></div><div class="call-stage"><span class="call-number">04</span><strong>Copy</strong><span><code>STR_D_COPY_R64(dest, &len, buf)</code> writes through the shared class-aware primitive.</span></div></div><div class="call-endpoint call-return"><span class="call-label">Return</span><div><strong>Condition value returns to the caller</strong><span>The least-significant bit is 1 for success.</span></div></div></div><h2 id="descriptors">The descriptor model</h2><p>
The one data structure a caller must understand is the VMS descriptor. Every string
argument is a descriptor passed by reference. The runtime auto-detects 32-bit versus 64-bit
layout with <code>vxrt_is_dsc64</code>, and the class field selects the storage model. The
STR$ routines actively handle three classes: Static (1), Dynamic (2), and Varying (11).
Any other class returns <code>STR$_ILLSTRCLA</code>.
</p><div class="table-wrapper"><table><thead><tr><th>Class symbol</th><th>Value</th><th>Name</th><th>Storage model</th></tr></thead><tbody><tr><td><code>DSC_sK_CLASS_S</code></td><td>1</td><td>Static</td><td>Fixed buffer; the length field is both capacity and valid length.</td></tr><tr><td><code>DSC_sK_CLASS_D</code></td><td>2</td><td>Dynamic</td><td>Runtime-allocated buffer, resizable via <code>STR$GET1_DX</code>, owned by the descriptor.</td></tr><tr><td><code>DSC_sK_CLASS_VS</code></td><td>11</td><td>Varying</td><td>Pascal-style: buffer prefixed by a 16-bit current-length word.</td></tr><tr><td><code>DSC_sK_CLASS_SD</code></td><td>9</td><td>Scaled decimal</td><td>Present in headers; not a STR$ string class.</td></tr><tr><td><code>DSC_sK_CLASS_VSA</code></td><td>12</td><td>Varying array</td><td>Present in headers.</td></tr><tr><td><code>DSC_sK_CLASS_SB</code></td><td>15</td><td>Static bounded</td><td>Present in headers.</td></tr></tbody></table></div><p>
The datatype field is normally <code>DSC_sK_DTYPE_T</code> (value 14, text). The high bit
<code>0x80</code> of the datatype marks a temporary string handed out by
<code>STR$GET_TEMPSTR</code>; it is cleared by <code>STR$COPY_DX</code> when the temporary
is consumed.
</p><div class="callout"><strong>Descriptor auto-detection is transparent.</strong> Because the runtime detects
32-bit versus 64-bit descriptors at call time, the same routine handles both widths. A
64-bit variant such as <code>STR$COPY_R64</code> exists for callers that build 64-bit
descriptors directly, but the class-aware primitive underneath is shared.
</div><h2 id="routines">Callable routine catalogue</h2><p>
VX/STR$ builds 40 public STR$ entry points into <code>libvxrt.a</code>, covering
manipulation, searching, formatting, comparison, descriptor management, and decimal-string
arithmetic. Two further routines are present in the source but not built. The status column
marks the 40 built routines <span class="ac-live">live</span> and the two decimal-math
routines that are not linked into the archive <span class="ac-no">not built</span>.
</p><div class="table-wrapper"><table><thead><tr><th>STR$ API</th><th>Function</th><th>Status</th></tr></thead><tbody><tr><td><code>STR$ADD</code></td><td>Decimal-string addition on a sign, exponent, and digit-string triple.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$ANALYZE_SDESC</code></td><td>Extract length and data pointer from a 32-bit-length descriptor.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$ANALYZE_SDESC_64</code></td><td>64-bit-length variant; sets the descriptor-type flag.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$APPEND</code></td><td>In-place append onto a Dynamic or Varying destination.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$BYTES_COPIED</code></td><td>Resolve the number of bytes that fit, <code>min(origlen, dsclen)</code>.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$CASE_BLIND_COMPARE</code></td><td>Case-insensitive compare.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COMPARE</code></td><td>Space-padded byte compare; returns -1, 0, or 1.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COMPARE_EQL</code></td><td>Strict length and content equality (0 equal, 1 not equal).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COMPARE_MULTI</code></td><td>Flag-dispatched blind or exact compare for sorting and validation.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$CONCAT</code></td><td>Variadic append chain, terminated by a <code>-1</code> or NULL argument.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_DX</code></td><td>Descriptor-to-descriptor copy; clears the temporary bit.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_DX_TO_R</code></td><td>Descriptor to C buffer, NUL-terminated.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_R</code></td><td>Class-aware copy primitive (32-bit length).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_R64</code></td><td>64-bit-length copy primitive; the workhorse everything funnels through.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$COPY_R0</code></td><td>Copy a NUL-terminated C string into a descriptor.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$DIVIDE</code></td><td>Decimal-string long division, with a truncate or round flag.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$DUPL_CHAR</code></td><td>Fill a destination with N copies of one byte.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$ELEMENT</code></td><td>Extract the Nth delimiter-split token (0-based).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FIND_FIRST_IN_SET</code></td><td>1-based position of the first character present in a set.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FIND_FIRST_NOT_IN_SET</code></td><td>1-based position of the first character absent from a set.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FIND_FIRST_SUBSTRING</code></td><td>Leftmost match of any of several variadic substrings.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$FREE1_DX</code></td><td>Free a Dynamic buffer and zero the descriptor fields.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$GET1_DX</code></td><td>Allocate or reallocate a Dynamic buffer (16-bit length).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$GET1_DX_64</code></td><td>64-bit-length dynamic allocation or reallocation.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$GET_TEMPSTR</code></td><td>Pool-allocate a temporary Static descriptor (up to 2000 concurrent).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$LEFT</code></td><td>Copy the leftmost N characters (position clamped).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$LEN_EXTR</code></td><td>Extract <code>len</code> characters from a 1-based start.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$LENGTH</code></td><td>Effective string length (flag handling defaults only).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$MATCH_WILD</code></td><td>Wildcard match with <code>*</code> and <code>%</code> and backtracking.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$MUL</code></td><td>Decimal-string multiply (see the divergences section for the precision caveat).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$POINTER</code></td><td>Return the bare data pointer for a descriptor.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$POSITION</code></td><td>1-based position of a substring, with optional start.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$POS_EXTR</code></td><td>Copy the substring between a 1-based start and end.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$PREFIX</code></td><td>In-place insert a prefix at the front of a destination.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$REPLACE</code></td><td>Replace a substring: <code>source[..start-1] + repl + source[end+1..]</code>.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$RIGHT</code></td><td>Copy the substring from a 1-based position to the end.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$SETVSLEN</code></td><td>Set the current-length word of a Varying string.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$TRANSLATE</code></td><td>Per-byte translate via match and translation tables.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$TRIM</code></td><td>Copy while dropping trailing spaces and tabs.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$UPCASE</code></td><td>Copy folding lowercase to uppercase.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>STR$RECIP</code></td><td>Decimal-string reciprocal.</td><td><span class="ac-no">not built</span></td></tr><tr><td><code>STR$ROUND</code></td><td>Round or truncate a decimal string to N places.</td><td><span class="ac-no">not built</span></td></tr></tbody></table></div><p>
The count is 42 public STR$ names across the source, 40 built into <code>libvxrt.a</code>
and 2 not built (<code>STR$RECIP</code> and <code>STR$ROUND</code>). Calling either of the
two unbuilt routines produces a link error rather than a run-time failure.
</p><h2 id="status">Status and signal conventions</h2><p>
Return values are 32-bit VMS condition codes. The success test is the same as on OpenVMS:
the least significant bit set (<code>ret & 1</code>) means success. The two common
outcomes returned as ordinary values are <code>SS$_NORMAL</code> and the truncation warning
<code>STR$_TRU</code>. The table lists the condition codes the library returns, with their
VMS-visible names and values.
</p><div class="table-wrapper"><table><thead><tr><th>VMS name</th><th>Value</th><th>Meaning</th></tr></thead><tbody><tr><td><code>SS$_NORMAL</code></td><td><code>1</code></td><td>Success.</td></tr><tr><td><code>SS$_ACCVIO</code></td><td><code>12</code></td><td>Access violation (NULL pointer or no buffer).</td></tr><tr><td><code>STR$_TRU</code></td><td><code>0x248200</code></td><td>Truncation warning.</td></tr><tr><td><code>STR$_FATINTERR</code></td><td><code>0x248044</code></td><td>Fatal internal error.</td></tr><tr><td><code>STR$_DIVBY_ZER</code></td><td><code>0x24804C</code></td><td>Divisor is zero.</td></tr><tr><td><code>STR$_ILLSTRCLA</code></td><td><code>0x248054</code></td><td>Illegal string class.</td></tr><tr><td><code>STR$_WRONUMARG</code></td><td><code>0x248064</code></td><td>Wrong number of arguments.</td></tr><tr><td><code>STR$_INSVIRMEM</code></td><td><code>0x24806C</code></td><td>Insufficient virtual memory.</td></tr><tr><td><code>STR$_STRTOOLON</code></td><td><code>0x248074</code></td><td>Source longer than destination (internal).</td></tr><tr><td><code>STR$_ERRFREDYN</code></td><td><code>0x24807C</code></td><td>Error freeing a dynamic string.</td></tr><tr><td><code>STR$_NOMATCH</code></td><td><code>0x248208</code></td><td>Wildcard: no match.</td></tr><tr><td><code>STR$_INVDELIM</code></td><td><code>0x248210</code></td><td>Invalid delimiter.</td></tr><tr><td><code>STR$_NOELEM</code></td><td><code>0x248218</code></td><td>Element does not exist.</td></tr><tr><td><code>STR$_ILLSTRPOS</code></td><td><code>0x248409</code></td><td>Illegal string position.</td></tr><tr><td><code>STR$_ILLSTRSPE</code></td><td><code>0x248411</code></td><td>Illegal start or end specifier.</td></tr><tr><td><code>STR$_MATCH</code></td><td><code>0x248419</code></td><td>Wildcard: matched.</td></tr></tbody></table></div><div class="callout"><strong>Test the low bit, not equality.</strong> Success covers any odd value, so callers
should test <code>ret & 1</code> (or <code>ODD(ret)</code> in VMS languages) rather than
comparing against a single success constant. A truncation warning is a success value that
still delivered a result.
</div><h2 id="example">A real call path</h2><p><code>STR$TRIM</code> is small enough to show both entry points, and it is the template every
routine in the library follows. The canonical descriptor body validates its pointers,
resolves the source through <code>vxrt_strptr64</code>, scans off trailing spaces and tabs,
and hands the result to the copy primitive:
</p>
<pre><span class="cmt">/* canonical descriptor body */</span>
U32 STR_D_TRIM( VMS_DESCR *dest, VMS_DESCR *source, U16 *length )
{
U64 i, splen;
char *s_pointer;
<span class="kw">if</span> (!dest || !source) <span class="kw">return</span> SS$_ACCVIO;
s_pointer = (char *)vxrt_strptr64(source, &splen); <span class="cmt">/* 32/64/varying aware */</span>
<span class="kw">if</span> (!s_pointer) <span class="kw">return</span> SS$_ACCVIO;
i = splen;
<span class="kw">while</span> (i) { <span class="cmt">/* drop trailing ' ' and '\t' */</span>
i--;
<span class="kw">if</span> (s_pointer[i] != <span class="str">' '</span> && s_pointer[i] != <span class="str">'\t'</span>) { i++; <span class="kw">break</span>; }
}
Msw( length, i ); <span class="cmt">/* store length only if the pointer is non-NULL */</span>
<span class="kw">return</span> STR_D_COPY_R64(dest, &i, (void*)s_pointer);
}</pre>
<h6>The FORTRAN thunk</h6><p>
A separate FORTRAN-callable entry unpacks the hidden trailing length arguments the FORTRAN
compiler passes for each string, builds descriptors, and tail-calls the same C++ body. A
single <code>__AliasF4</code> line then wires up all four public names:
</p>
<pre>extern <span class="str">"C"</span>
U32 F4_STR_D_TRIM( F4STR(dest), F4STR(source), U16 *len,
F4STR_N(dest), F4STR_N(source) ) <span class="cmt">/* hidden length args */</span>
{
F4STR_DECL(dest); F4STR_DECL(source);
F4STR_INIT_2(dest, source); <span class="cmt">/* build DESCR64 from ptr + len pairs */</span>
<span class="kw">return</span> STR_D_TRIM(dest, source, len);
}
__AliasF4(STR_D_TRIM, F4_STR_D_TRIM, STR$TRIM, str$trim);</pre>
<h6>Variadic routines need a terminator</h6><p><code>STR$CONCAT</code> illustrates the one calling-convention gotcha a migration engineer
must know. The C, C++, and COBOL entry walks its argument list until it hits a
<code>-1</code> or NULL descriptor, so callers must terminate the list explicitly or the
loop reads past the call frame. The FORTRAN thunk instead guesses pointer versus length by
the numeric magnitude of each argument, treating values above 32768 as pointers:
</p>
<pre><span class="cmt">/* C/C++/COBOL entry: list terminated by (VMS_DESCR *)-1 or NULL */</span>
U32 STR_D_CONCAT(VMS_DESCR *dst, ...) {
va_list ap; U32 ret = SS$_ACCVIO;
va_start(ap, dst);
<span class="kw">while</span> (1) {
void *tstr = va_arg(ap, VMS_DESCR *);
<span class="kw">if</span> (tstr == (VMS_DESCR *)-1 || tstr == (VMS_DESCR *)0) <span class="kw">break</span>;
ret = STR_D_APPEND(dst, tstr); <span class="cmt">/* APPEND handles Dynamic and Varying */</span>
<span class="kw">if</span> (!(ret & 1)) <span class="kw">break</span>; <span class="cmt">/* stop on first failure */</span>
}
<span class="kw">return</span> ret;
}
__AliasF4(STR_D_CONCAT, F4_STR_D_CONCAT, STR$CONCAT, str$concat);</pre>
<p>
The same pattern appears in a migrated application. A translated program links this
unchanged against <code>libvxrt.a</code> and tests each result with the VMS
<code>ODD()</code> convention:
</p>
<pre><span class="cmt">! trim an account holder name field and build a display label</span>
status = STR$TRIM (TRIMMED_NAME, ACCT_HOLDER_NAME)
<span class="kw">IF</span> (.NOT. ODD(status)) <span class="kw">CALL</span> LIB$SIGNAL(status)
status = STR$CONCAT (DISPLAY_LABEL,
<span class="str">'ACCOUNT: '</span>,
ACCT_NUMBER,
<span class="str">' '</span>,
TRIMMED_NAME) <span class="cmt">! FORTRAN thunk pads to a fixed slot count</span>
<span class="kw">IF</span> (.NOT. ODD(status)) <span class="kw">CALL</span> LIB$SIGNAL(status)</pre>
<h2 id="behavior">Behavior notes and divergences</h2><p>
VX/STR$ aims for bug-for-bug OpenVMS compatibility, and in a few places it deliberately or
knowingly diverges from the original specification. These are the behaviors a migration
engineer should confirm against their own workload before cutover.
</p><div class="table-wrapper"><table><thead><tr><th>Area</th><th>Behavior</th><th>Impact</th></tr></thead><tbody><tr><td><code>STR$RECIP</code>, <code>STR$ROUND</code></td><td>Present in source but not built into the archive.</td><td>Calling either produces a link error. Decimal-math reciprocal and rounding are deferred.</td></tr><tr><td><code>STR$MUL</code></td><td>Uses 64-bit integer multiply rather than digit-by-digit decimal math.</td><td>Loses precision beyond roughly 18 significant digits. Confirm before relying on wide decimal operands.</td></tr><tr><td><code>STR$LENGTH</code></td><td>Flag bits are parsed but only the default length behavior is implemented.</td><td>Non-default flag combinations behave as the default rather than raising an error.</td></tr><tr><td>NULL-pointer paths</td><td>Several routines return <code>SS$_ACCVIO</code> where VMS would raise <code>LIB$_INVARG</code> or <code>STR$_ILLSTRCLA</code>.</td><td>Error codes differ on invalid input, though both are failure values under the odd-success test.</td></tr><tr><td><code>STR$TRANSLATE</code></td><td>Uses a fixed stack scratch buffer of about 100000 bytes with no allocation fallback.</td><td>Source strings larger than roughly 100 KB overflow the stack. Other routines fall back to allocation above the fast path.</td></tr><tr><td><code>STR$ADD</code></td><td>A Varying zero result writes a single-byte Ctrl-A (<code>0x01</code>).</td><td>Intentional bug-for-bug VMS compatibility; matches the original behavior.</td></tr><tr><td><code>STR$COMPARE</code></td><td>Returns a three-valued -1, 0, or 1 with space-padding of the shorter operand.</td><td>Callers that assumed a two-valued result on some historical documentation should test the sign.</td></tr></tbody></table></div><div class="callout warn"><strong>Decimal-math caveat.</strong> The decimal-string arithmetic routines are the area
most worth validating: <code>STR$MUL</code> is integer-backed and loses precision on wide
operands, and <code>STR$RECIP</code> and <code>STR$ROUND</code> are not built. Workloads
that lean on full-precision decimal math should be reviewed as part of the migration.
</div><h2 id="limits">Constants, limits, build and test</h2><h6>Real constants and limits</h6><div class="table-wrapper"><table><thead><tr><th>Constant</th><th>Value</th><th>Applies to</th></tr></thead><tbody><tr><td>Max decimal digits per operand</td><td><code>250</code></td><td>Decimal operands in <code>STR$ADD</code> and <code>STR$DIVIDE</code>; over-length returns <code>STR$_TRU</code>.</td></tr><tr><td>Temporary-string pool size</td><td><code>2000</code></td><td>Maximum concurrent <code>STR$GET_TEMPSTR</code> descriptors.</td></tr><tr><td>Stack scratch buffer</td><td><code>100000</code> bytes</td><td>Fast path before allocation fallback (except <code>STR$TRANSLATE</code>, which has no fallback).</td></tr><tr><td>Temporary datatype bit</td><td><code>0x80</code></td><td>Marks a temporary string; cleared by <code>STR$COPY_DX</code>.</td></tr><tr><td>FORTRAN pointer or length threshold</td><td><code>32768</code></td><td>The <code>STR$CONCAT</code> FORTRAN thunk treats values above this as pointers.</td></tr></tbody></table></div><h6>Build</h6><p>
The library is built with <code>make</code> in the STR$ source directory, which compiles
each source file to an object and archives it into <code>libvxrt.a</code>. A migrated
application links against that archive alongside the rest of VX/RT. There is no separate
run-time service to install or configure.
</p><h6>Regression testing</h6><p>
A standalone regression driver exercises the major routines against both 32-bit and 64-bit
descriptor classes. It is a separate test program and is not part of the shipped archive.
The upstream reference is the OpenVMS RTL String Manipulation Reference Manual, and where
the historical documentation disagrees with the current implementation, the implementation
is authoritative.
</p><h2 id="quickref">Quick reference</h2><h6>Manipulation and formatting</h6><div class="kw-grid"><div>STR$APPEND</div><div>STR$CONCAT</div><div>STR$PREFIX</div><div>STR$REPLACE</div><div>STR$TRIM</div><div>STR$UPCASE</div><div>STR$TRANSLATE</div><div>STR$DUPL_CHAR</div><div>STR$LEFT</div><div>STR$RIGHT</div><div>STR$POS_EXTR</div><div>STR$LEN_EXTR</div><div>STR$ELEMENT</div></div><h6>Searching and comparison</h6><div class="kw-grid"><div>STR$COMPARE</div><div>STR$COMPARE_EQL</div><div>STR$COMPARE_MULTI</div><div>STR$CASE_BLIND_COMPARE</div><div>STR$POSITION</div><div>STR$FIND_FIRST_SUBSTRING</div><div>STR$FIND_FIRST_IN_SET</div><div>STR$FIND_FIRST_NOT_IN_SET</div><div>STR$MATCH_WILD</div><div>STR$LENGTH</div></div><h6>Descriptor and memory management</h6><div class="kw-grid"><div>STR$GET1_DX</div><div>STR$GET1_DX_64</div><div>STR$FREE1_DX</div><div>STR$GET_TEMPSTR</div><div>STR$ANALYZE_SDESC</div><div>STR$ANALYZE_SDESC_64</div><div>STR$COPY_DX</div><div>STR$COPY_DX_TO_R</div><div>STR$COPY_R</div><div>STR$COPY_R64</div><div>STR$COPY_R0</div><div>STR$POINTER</div><div>STR$SETVSLEN</div><div>STR$BYTES_COPIED</div></div><h6>Decimal-string arithmetic</h6><div class="kw-grid"><div>STR$ADD</div><div>STR$DIVIDE</div><div>STR$MUL</div></div><h6>Things to remember</h6><div class="table-wrapper"><table><thead><tr><th>Topic</th><th>Rule</th></tr></thead><tbody><tr><td>Link against</td><td><code>libvxrt.a</code>, the shared VX/RT archive.</td></tr><tr><td>Call any of</td><td><code>STR$NAME</code> or <code>str$name</code> from C, C++, and COBOL; <code>STR$NAME_</code> or <code>str$name_</code> from FORTRAN.</td></tr><tr><td>Copy primitive</td><td>Everything funnels through <code>STR$COPY_R64(dest, &len, src)</code>.</td></tr><tr><td>Classes handled</td><td>Static (1), Dynamic (2), Varying (11); anything else returns <code>STR$_ILLSTRCLA</code>.</td></tr><tr><td>Success test</td><td><code>ret & 1</code>: the low bit set means success.</td></tr><tr><td>Variadic terminator</td><td><code>STR$CONCAT</code> and <code>STR$FIND_FIRST_SUBSTRING</code> need a <code>-1</code> terminator.</td></tr><tr><td>Not built</td><td><code>STR$RECIP</code> and <code>STR$ROUND</code> cause a link error if called.</td></tr><tr><td>Decimal-math caveat</td><td><code>STR$MUL</code> is integer-backed, not full-precision.</td></tr></tbody></table></div></div>