VX/BASIC - OpenVMS BASIC to ANSI C
Overview
VX/BASIC is a source-to-source translator that reads VAX and OpenVMS BASIC and emits ANSI C. It is not an interpreter and not a direct-to-machine-code compiler: the generated C is compiled by the host's own toolchain (gcc, clang, or cl) and linked against the VX/BASIC runtime library, producing a native executable for Linux, Windows, or macOS that keeps VAX BASIC semantics intact.
Because C is used as an intermediate language, the migrated application stays portable and can be maintained, built, and debugged with standard modern tools. Descriptor-based strings, RECORD and MAP memory layout, packed DECIMAL arithmetic, RMS record I/O, and ON ERROR handling are all preserved, so an application behaves the same way after conversion as it did on OpenVMS.
<!-- Webflow embed (min). Source: basic-page-technical/basic-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxbasicd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">Architecture and translation model</h2><p>
VX/BASIC is a translator plus a linked runtime library. The driver reads a BASIC source
file and emits ANSI C; that C is then compiled by the host C compiler and linked against
the VX/BASIC runtime (<code>libvxbas</code>) to produce a native executable. The translator
and the runtime are decoupled: the runtime is callable from any C program that includes the
master runtime header, so a hand-written runtime call produces the same result as a
translated statement.
</p><h6>Compilation pipeline</h6><p>By default the driver runs its preprocessor first, then translates to C. The generated
C is ordinary source that any modern toolchain can build and debug:</p><style>.s7-tech-embed .process-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 .process-endpoint {display: grid;grid-template-columns: 72px minmax(0, 1fr);gap: 14px;align-items: center;padding: 14px 16px;border-radius: 8px;background: var(--s7t-surface);color: var(--s7t-text);}.s7-tech-embed .process-output {border: 1px solid var(--s7t-note-bd);background: var(--s7t-note-bg);}.s7-tech-embed .process-label,.s7-tech-embed .process-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 .process-endpoint div,.s7-tech-embed .process-stage {min-width: 0;}.s7-tech-embed .process-endpoint strong,.s7-tech-embed .process-endpoint span:last-child,.s7-tech-embed .process-stage strong,.s7-tech-embed .process-stage span:last-child {display: block;}.s7-tech-embed .process-endpoint strong,.s7-tech-embed .process-stage strong {margin-bottom: 4px;color: var(--s7t-text);}.s7-tech-embed .process-endpoint span:last-child,.s7-tech-embed .process-stage span:last-child {color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}.s7-tech-embed .process-stages {display: grid;grid-template-columns: repeat(3, minmax(0, 1fr));gap: 10px;margin: 10px 0;}.s7-tech-embed .process-stage {padding: 15px;border-top: 3px solid var(--s7t-accent);border-radius: 6px;background: var(--s7t-surface2);color: var(--s7t-text);}.s7-tech-embed .process-stage .process-number {margin-bottom: 10px;}@media (max-width: 800px) {.s7-tech-embed .process-stages {grid-template-columns: 1fr;}.s7-tech-embed .process-endpoint {grid-template-columns: 1fr;gap: 5px;}}</style><div class="process-flow" aria-label="VX/BASIC compilation pipeline"><div class="process-endpoint"><span class="process-label">Input</span><div><strong><code>foo.bas</code></strong><span>VAX BASIC source</span></div></div><div class="process-stages" role="list" aria-label="Compilation stages"><div class="process-stage" role="listitem"><span class="process-number">01</span><strong><code>bpp</code></strong><span>Expands <code>%INCLUDE</code>, <code>%IF</code>, and <code>%LET</code>, producing <code>foo.bas.p</code>.</span></div><div class="process-stage" role="listitem"><span class="process-number">02</span><strong><code>vxbasic</code></strong><span>Tokenises the intermediate, builds the symbol table, and generates ANSI C as <code>foo.c</code>.</span></div><div class="process-stage" role="listitem"><span class="process-number">03</span><strong>Host C compiler</strong><span><code>cc</code>, <code>clang</code>, or <code>cl</code> compiles <code>foo.c</code> into <code>foo.o</code>.</span></div><div class="process-stage" role="listitem"><span class="process-number">04</span><strong>Link</strong><span><code>cc -lvxbas -ll -lm</code> links the object with the VX/BASIC runtime and support libraries.</span></div></div><div class="process-endpoint process-output"><span class="process-label">Output</span><div><strong><code>foo</code></strong><span>Native executable with VAX BASIC semantics intact</span></div></div></div><p>
The <code>-a</code> option turns off the separate preprocessor pass and lets the translator
handle <code>%</code> directives directly; <code>-g</code> and <code>-G</code> collapse the
whole pipeline by handing the generated C to the bundled host-compile scripts. Input is
normally <code>.bas</code> or <code>.BAS</code> (also <code>.sb</code> for embedded SQL and
<code>.bp2</code> for BASIC-PLUS-2); output is <code>.c</code> by default.
</p><h6>Component map</h6><div class="table-wrapper"><table><thead><tr><th>Component</th><th>Role</th></tr></thead><tbody><tr><td>Translator driver</td><td>Entry point, option scan, file open, and the top-level translate loop.</td></tr><tr><td>Reserved-word tables</td><td>Per-letter keyword tables mapping each BASIC keyword to its code-generation action, C name, and parameter shape.</td></tr><tr><td>Expression evaluator</td><td>Operator-precedence expression tree covering arithmetic, relational, logical, and concatenation operators.</td></tr><tr><td>Statement code generators</td><td>One action routine per keyword for control flow, <code>OPEN</code>/<code>GET</code>/<code>PUT</code>, <code>PRINT</code>, records, declarations, and structured error handling.</td></tr><tr><td>Name mangler</td><td>Rewrites BASIC identifiers (with <code>$</code>, <code>%</code>, <code>.</code>, and <code>::</code>) into legal C identifiers.</td></tr><tr><td>Preprocessor</td><td>Handles <code>%INCLUDE</code>, <code>%IF</code>/<code>%ELSE</code>, <code>%LET</code>, <code>%VARIANT</code>, and CDD <code>%FROM %CDD</code> extraction.</td></tr><tr><td>C runtime library</td><td>Descriptors, RMS record I/O, packed-decimal math, and the error stack, delivered as <code>libvxbas</code>.</td></tr><tr><td>Master runtime header</td><td>Type aliases, statement macros, and runtime prototypes that every generated C file includes.</td></tr></tbody></table></div><div class="callout"><strong>C as the intermediate language.</strong> Because the translator emits readable C
rather than opaque binary, the migrated application is portable to any platform with a C
compiler and can be inspected, built, and debugged with standard tools. The current line
is VX/BASIC V8.6.
</div><h2 id="source-language">Source language coverage</h2><p>
VX/BASIC targets the full VAX and OpenVMS BASIC language. Every keyword maps to a
code-generation action in the translator's reserved-word tables. The groups below summarise
the statement coverage; the one documented exclusion is the graphics family (see
Compatibility).
</p><h6>Statement groups</h6><div class="table-wrapper"><table><thead><tr><th>Group</th><th>Constructs</th></tr></thead><tbody><tr><td>Flow control</td><td><code>IF/THEN/ELSE/END IF</code>, <code>FOR/NEXT/STEP</code>, <code>WHILE</code>, <code>UNTIL</code>, <code>SELECT CASE/CASE/OTHERWISE/END SELECT</code>, <code>GOTO</code>/<code>GO TO</code>, <code>GOSUB</code>/<code>RETURN</code>, <code>ON n GOTO/GOSUB</code>, <code>STOP</code>, <code>END</code>, <code>EXIT</code>, <code>ITERATE</code>, <code>CONTINUE</code>.</td></tr><tr><td>Subprograms</td><td><code>SUB/SUBEND/SUBEXIT</code>, <code>FUNCTION/FUNCTIONEND/FUNCTIONEXIT</code>, <code>DEF</code>/<code>DEF*</code>/<code>FNEND</code>, <code>CALL</code>, <code>PROGRAM</code>.</td></tr><tr><td>Error handling</td><td><code>ON ERROR GOTO</code>, <code>ON ERROR GOTO 0</code>, <code>ON ERROR GO BACK</code>, <code>WHEN ERROR IN / END WHEN</code>, <code>WHEN ERROR USE</code>, <code>HANDLER / END HANDLER</code>, <code>RESUME</code>, <code>RETRY</code>, <code>CAUSE ERROR n</code>.</td></tr><tr><td>RMS file I/O</td><td><code>OPEN</code>, <code>CLOSE</code>, <code>GET</code>, <code>PUT</code>, <code>FIND</code>, <code>DELETE</code>, <code>UPDATE</code>, <code>READ</code>, <code>WRITE</code>, <code>FIELD</code>, <code>LSET</code>, <code>RSET</code>, <code>RESTORE</code>, <code>UNLOCK</code>, <code>NAME AS</code>, <code>KILL</code>. <code>OPEN ... ORGANIZATION</code> supports sequential, relative, indexed, and virtual.</td></tr><tr><td>Terminal I/O</td><td><code>PRINT</code>, <code>INPUT</code>, <code>LINPUT</code>, <code>INPUT LINE</code>, <code>INKEY$</code>, <code>MARGIN</code>, <code>NOMARGIN</code>, <code>EXTEND</code>, <code>NOEXTEND</code>, <code>SET</code>.</td></tr><tr><td>Storage</td><td><code>MAP</code>, <code>REMAP</code>, <code>COMMON</code>/<code>COM</code>, <code>DIM</code>/<code>DIMENSION</code>, <code>FREE</code>, <code>RECORD</code>, <code>RANDOMIZE</code>, <code>WAIT</code>, <code>SLEEP</code>.</td></tr><tr><td>Program control</td><td><code>CHAIN</code>, <code>CHANGE</code>, <code>RUN</code>, <code>MAT</code>, <code>MID$</code> (statement form).</td></tr></tbody></table></div><h6>Operator precedence</h6><p>
The expression evaluator preserves BASIC precedence, from highest to lowest:
</p><div class="kw-grid"><div>** / ^ (power)</div><div>unary - / + / NOT</div><div>* / /</div><div>+ / - / & (concat)</div><div>relational (= <> < > <= >= EQ NE LT GT LE GE)</div><div>AND</div><div>OR / XOR</div><div>IMP / EQV</div></div><p>
Integer bitwise operators (<code>EQV</code>, <code>DIV</code>, <code>IMP</code>,
<code>MOD</code>, <code>XOR</code>) are emitted as runtime macros that cast their operands
to 32-bit integers, matching VAX BASIC semantics.
</p><h6>Intrinsic functions</h6><p>
Around ninety intrinsic functions are supported, each mapped to a runtime entry point.
Representative coverage:
</p><div class="kw-grid"><div>LEFT$</div><div>RIGHT$</div><div>MID$</div><div>SEG$</div><div>LEN</div><div>INSTR</div><div>POS</div><div>EDIT$</div><div>FORMAT$</div><div>NUM$</div><div>STR$</div><div>STRING$</div><div>SPACE$</div><div>CHR$</div><div>ASCII</div><div>VAL</div><div>TRM$</div><div>XLATE$</div><div>DATE$</div><div>TIME$</div><div>INKEY$</div><div>ABS</div><div>SIN</div><div>COS</div><div>TAN</div><div>ATN</div><div>LOG</div><div>LOG10</div><div>EXP</div><div>SQR</div><div>INT</div><div>FIX</div><div>MOD</div><div>MAX</div><div>MIN</div><div>SGN</div><div>RND</div><div>ERR</div><div>ERL</div><div>ERT$</div><div>RMSSTATUS</div><div>VMSSTATUS</div><div>STATUS</div><div>LBOUND</div><div>UBOUND</div><div>LOC</div><div>SWAP%</div><div>GETRFA</div></div><h2 id="data-types">Data types and C mapping</h2><p>
Every VAX and OpenVMS BASIC data type maps to a fixed-width ANSI C type sized to match the
original storage, so record layouts and arithmetic keep their exact widths after
conversion. The table below is the accurate mapping used by the translator.
</p><div class="table-wrapper"><table><thead><tr><th>BASIC type</th><th>Bits</th><th>C type emitted</th></tr></thead><tbody><tr><td><code>BYTE</code></td><td>8</td><td><code>int8_t</code></td></tr><tr><td><code>WORD</code></td><td>16</td><td><code>int16_t</code></td></tr><tr><td><code>LONG</code></td><td>32</td><td><code>int32_t</code></td></tr><tr><td><code>INTEGER</code> (default width)</td><td>32</td><td><code>int32_t</code></td></tr><tr><td><code>QUAD</code></td><td>64</td><td><code>int64_t</code></td></tr><tr><td><code>SINGLE</code></td><td>32</td><td><code>float</code></td></tr><tr><td><code>DOUBLE</code></td><td>64</td><td><code>double</code></td></tr><tr><td><code>GFLOAT</code></td><td>64</td><td><code>double</code></td></tr><tr><td><code>HFLOAT</code></td><td>128</td><td><code>double</code> (converted, with a warning)</td></tr><tr><td><code>DECIMAL(p,q)</code></td><td>up to 31 digits</td><td>packed-BCD decimal struct</td></tr><tr><td><code>STRING</code> (dynamic)</td><td>descriptor + data</td><td>VMS string descriptor pointer</td></tr><tr><td><code>STRING(n)</code> (fixed)</td><td>descriptor + n</td><td>VMS string descriptor pointer (fixed class)</td></tr><tr><td><code>RFA</code></td><td>48</td><td><code>unsigned char[6]</code> (RMS Record File Address)</td></tr><tr><td><code>ADDRESS</code></td><td>64</td><td><code>void *</code></td></tr></tbody></table></div><div class="callout warn"><strong>LONG is 32-bit.</strong> <code>LONG</code> maps to <code>int32_t</code>, and
<code>INTEGER</code> defaults to the same 32-bit width (settable with <code>-ib</code>,
<code>-iw</code>, <code>-il</code> or <code>OPTION SIZE</code>). The 64-bit integer type is
<code>QUAD</code>, which maps to <code>int64_t</code>. Integer types are signed, matching
VAX BASIC integer semantics.
</div><h6>Strings and descriptors</h6><p>
Every BASIC string lives in a VMS string descriptor rather than a bare C buffer. The
translator emits a descriptor pointer for each string parameter, and the runtime owns the
descriptor pool and performs allocation and release. Both dynamic strings and fixed-length
<code>STRING(n)</code> strings are supported, which is what keeps migrated code compatible
with VAX BASIC string memory management.
</p><div class="callout"><strong>Floating-point note.</strong> <code>GFLOAT</code> and <code>HFLOAT</code> are
converted to 64-bit <code>double</code> on non-VMS hosts. <code>HFLOAT</code> loses
precision in this conversion and the translator emits a warning; there is no native
128-bit H-float on the target platforms.
</div><h2 id="records-maps">Records, maps, and parameter passing</h2><p>
RECORD, VARIANT, and MAP are the constructs that make VAX BASIC memory layout exact, and
they are the most load-bearing part of a faithful translation. VX/BASIC lowers each of them
into C types that reproduce the original byte layout, so a migrated program sees the same
bytes at the same offsets as it did on OpenVMS.
</p><h6>RECORD and VARIANT become struct and union</h6><p>
A <code>RECORD</code> becomes a typedef'd C struct. A <code>VARIANT</code> becomes a
<code>union</code>, and each <code>CASE</code> within it becomes a nested <code>struct</code>,
so overlapping fields share storage exactly as they do under VAX BASIC.
</p>
<pre><span class="kw">RECORD</span> ACCOUNT_REC
<span class="kw">VARIANT</span>
<span class="kw">CASE</span>
<span class="kw">WORD</span> BRANCH_ID, PRODUCT_CODE
<span class="kw">LONG</span> ACCOUNT_NUMBER, BALANCE_CENTS
<span class="kw">CASE</span>
<span class="kw">LONG</span> RAW_KEY
<span class="kw">END VARIANT</span>
<span class="kw">END RECORD</span></pre>
<p>translates to:</p>
<pre>typedef struct {
union {
struct {
_Word BRANCH_ID;
_Word PRODUCT_CODE;
_Long ACCOUNT_NUMBER;
_Long BALANCE_CENTS;
} case1;
struct {
_Long RAW_KEY;
} case2;
} var1;
} ACCOUNT_REC_2__;</pre>
<p>
Note that <code>_Word</code> is 2 bytes and <code>_Long</code> is 4 bytes, so the layout is
byte-for-byte what VAX BASIC produced.
</p><h6>MAP overlays share one backing store</h6><p>
Several <code>MAP</code> declarations that name the same program section describe the same
storage viewed different ways. VX/BASIC emits one struct per declaration and binds all of
them to a single backing block, so a write through one view is visible through the others.
</p>
<pre><span class="kw">MAP</span> (STMT) <span class="kw">STRING</span> STMT_RAW = 80
<span class="kw">MAP</span> (STMT) <span class="kw">LONG</span> STMT_ACCT, STMT_SEQ, STMT_AMOUNT</pre>
<pre>static struct Tag_MAP_STMT { char sSTMT_RAW[80]; } *MAP_STMT;
static struct Tag_MAP_STMT_1 { _Long pSTMT_ACCT; _Long pSTMT_SEQ; _Long pSTMT_AMOUNT; } *MAP_STMT_1;
union { char data[sizeof(struct Tag_MAP_STMT)]; long align; } STMT;
Vxb_PsectInit_1((void *)&MAP_STMT, "STMT", & STMT);
Vxb_PsectInit_1((void *)&MAP_STMT_1, "STMT", & STMT);</pre>
<p>
Both struct pointers are bound to the same <code>STMT</code> byte block, so writing through
<code>MAP_STMT_1</code> and reading through <code>MAP_STMT</code> sees the same bytes. This
reproduces VAX BASIC's MAP-overlay memory model exactly. <code>MAP DYNAMIC</code> allocates
on first reference and keeps the largest requested size; <code>MAP STATIC</code> becomes a
BSS allocation.
</p><h6>Parameter passing</h6><p>
BASIC defaults to calling by reference. VX/BASIC preserves the <code>BY VALUE</code> and
<code>BY REF</code> mechanism, passing literals by reference through a constant pool where
needed.
</p>
<pre><span class="kw">EXTERNAL LONG FUNCTION</span> CALC_INTEREST(<span class="kw">LONG BY VALUE</span>)
<span class="kw">EXTERNAL LONG FUNCTION</span> POST_LEDGER(<span class="kw">LONG BY REF</span>)
<span class="kw">EXTERNAL LONG FUNCTION</span> AUDIT_ENTRY(<span class="kw">LONG</span>) <span class="cmt">! unmarked LONG defaults to BY REF</span></pre>
<pre>result = calc_interest(1); /* BY VALUE: pass the int directly */
result = post_ledger(LCon(1, 1)); /* BY REF: pass address of pooled constant */
result = audit_entry(LCon(1, 1)); /* unmarked LONG defaults to BY REF */</pre>
<p><code>LCon(n, v)</code> writes value <code>v</code> into constant-pool slot <code>n</code> and
returns its address, which is how a literal is passed by reference. The <code>LONG</code>
result field is a 32-bit integer, consistent with the type mapping above.
</p><h2 id="runtime">Runtime model and error handling</h2><p>
The generated C calls into <code>libvxbas</code>, the VX/BASIC runtime. The runtime provides
descriptor-based strings, RMS record I/O, packed-decimal arithmetic, and the error stack.
Each BASIC intrinsic maps to a runtime entry point named <code>Vxb_</code> followed by the
routine name; translator-emitted helpers that never appear in user code use the
<code>Vxbf_</code> prefix.
</p><div class="table-wrapper"><table><thead><tr><th>Runtime entry</th><th>Purpose</th></tr></thead><tbody><tr><td><code>Vxbf_initialize</code></td><td>Master program initialisation: dialect selection, argument vector, decimal scale and rounding, and signal handlers.</td></tr><tr><td><code>Vxb_Open</code> / <code>Vxb_Close</code> / <code>Vxb_CloseAll</code></td><td>RMS channel open and close.</td></tr><tr><td><code>Vxb_Get</code> / <code>Vxb_Put</code> / <code>Vxb_Find</code> / <code>Vxb_Update</code> / <code>Vxb_Delete</code></td><td>RMS record operations.</td></tr><tr><td><code>Vxb_Print</code> / <code>Vxb_Input</code> / <code>Vxb_LineInput</code> / <code>Vxb_Inkey</code></td><td>Terminal I/O.</td></tr><tr><td><code>Vxb_Left</code> / <code>Vxb_Right</code> / <code>Vxb_Mid</code> / <code>Vxb_Instr</code> / <code>Vxb_Format</code></td><td>String intrinsics.</td></tr><tr><td><code>Vxb_Decimal</code> / <code>Vxb_Sum</code> / <code>Vxb_Prod</code> / <code>Vxb_Quo</code></td><td>Packed-decimal arithmetic.</td></tr><tr><td><code>Vxb_Err</code> / <code>Vxb_Erl</code> / <code>Vxb_Ert</code> / <code>Vxb_RmsStatus</code> / <code>Vxb_VmsStatus</code></td><td>Error introspection.</td></tr><tr><td><code>Vxbf_OnError_GoTo</code> / <code>Vxbf_Resume</code> / <code>Vxbf_Retry</code></td><td><code>ON ERROR</code> and <code>RESUME</code> machinery.</td></tr></tbody></table></div><p>
The runtime exposes just under two hundred distinct entry points. It is ordinary C, so the
same runtime call a translated program makes can be called directly from hand-written C
that includes the runtime header.
</p><h6>Error handling</h6><p>
VX/BASIC preserves VAX BASIC error semantics so a migrated program behaves identically when
it hits an error. <code>ON ERROR GOTO</code> is lowered onto the runtime's error machinery,
which maintains a parent-linked context chain, sets a jump target per subprogram, and
unwinds to the handler when an error is signalled. <code>RESUME</code> and <code>RETRY</code>
resume execution, and the structured <code>WHEN ERROR USE</code> and <code>HANDLER</code>
forms are supported with up to ten levels of nesting.
</p><p>
Programs introspect errors with <code>ERR</code>, <code>ERL</code>, <code>ERT$</code>,
<code>RMSSTATUS</code>, and <code>VMSSTATUS</code>, exactly as on OpenVMS. RMS status values
carry through as well: for example, the OpenVMS "file not found" condition (value
<code>98962</code>) is the same value a migrated program sees, so return-value checks on
system routines keep working without modification.
</p><div class="callout"><strong>System services.</strong> When a program calls OpenVMS system routines and checks
their return values, VX/BASIC backs that behaviour with the VX/RT runtime libraries and
the bundled system-symbol headers, so status codes and RMS behaviour match the original.
</div><h2 id="cli">Command-line reference</h2><p>
The translator is driven from the command line. Each short option has a DCL-style long form
for developers used to OpenVMS qualifier syntax. The highest-value flags are listed below.
</p><div class="table-wrapper"><table><thead><tr><th>Short</th><th>DCL form</th><th>Effect</th></tr></thead><tbody><tr><td><code>-a</code></td><td><code>/NOPREPROCESS</code></td><td>Do not run the preprocessor as a separate pass; the translator handles <code>%</code> directives directly.</td></tr><tr><td><code>-c</code></td><td><code>/BOUNDS</code></td><td>Emit runtime array-bounds checks.</td></tr><tr><td><code>-C</code></td><td><code>/CDD</code></td><td>Treat the data definition as new-CDD format.</td></tr><tr><td><code>-ds</code> / <code>-dd</code></td><td><code>/REAL_SIZE=SINGLE|DOUBLE</code></td><td>Set the default real type.</td></tr><tr><td><code>-ib</code> / <code>-iw</code> / <code>-il</code></td><td><code>/INT_SIZE=BYTE|WORD|LONG</code></td><td>Set the default integer width.</td></tr><tr><td><code>-g</code> / <code>-G</code></td><td><code>/GO</code> / <code>/EXEC_GO</code></td><td>After translating, run the bundled host-compile script.</td></tr><tr><td><code>-k</code></td><td><code>/NATIVE_ACCESS</code></td><td>Emit direct C MAP-field access instead of descriptor accessors.</td></tr><tr><td><code>-L</code></td><td><code>/LIST</code></td><td>Emit a <code>.lst</code> listing.</td></tr><tr><td><code>-nN</code></td><td><code>/SCALE=N</code></td><td>Set the DECIMAL scale factor.</td></tr><tr><td><code>-q</code></td><td><code>/SQL</code></td><td>Treat the module as embedded SQL and emit Pro*C / ESQL-C output.</td></tr><tr><td><code>-R</code></td><td><code>/ROUND_DECIMALS</code></td><td>Round rather than truncate DECIMAL values.</td></tr><tr><td><code>-T</code></td><td><code>/LARGE_KEYS</code></td><td>Allow RMS index keys longer than 255 bytes.</td></tr><tr><td><code>-u</code></td><td><code>/PREPROCESS_ONLY</code></td><td>Run only the preprocessor and exit.</td></tr><tr><td><code>-vN</code></td><td><code>/VARIANT=N</code></td><td>Set the preprocessor <code>%VARIANT</code> value.</td></tr><tr><td><code>-x</code></td><td><code>/EXTERNAL</code></td><td>Force an EXTERNAL module with no <code>main()</code> emitted.</td></tr></tbody></table></div><h6>Environment controls</h6><p>
Translator behaviour can also be set through environment variables, including
<code>VXB_OPT</code>, <code>VXB_GO</code>, <code>VXB_LOWER_FUNCTION_NAMES</code>,
<code>VXB_LOWER_MAP_NAMES</code>, <code>VXB_USE_RECORD_COPY</code>, and
<code>VXB_USE_DESCR64</code>.
</p><h6>Exit codes</h6><div class="table-wrapper"><table><thead><tr><th>Code</th><th>Meaning</th></tr></thead><tbody><tr><td><code>0</code></td><td>Success.</td></tr><tr><td><code>1</code></td><td>Completed with errors.</td></tr><tr><td><code>98962</code></td><td>Could not open the input source (the OpenVMS RMS "file not found" condition value).</td></tr></tbody></table></div><h2 id="compatibility">Compatibility and status</h2><p>
The core language, RMS I/O, descriptor strings, packed DECIMAL, and structured error
handling are fully supported. A small number of features are converted with a documented
trade-off, and a few OpenVMS-specific constructs are out of scope. The matrix below records
exactly one status per feature.
</p><div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Status</th><th>Detail</th></tr></thead><tbody><tr><td>Core language: types, flow control, SUB/FUNCTION, DEF, RECORD, MAP, COMMON</td><td><span class="ac-live">live</span></td><td>Full coverage with byte-exact layout.</td></tr><tr><td>RMS I/O: sequential, relative, indexed, virtual</td><td><span class="ac-live">live</span></td><td>All four file organizations supported.</td></tr><tr><td>Descriptor strings (dynamic and fixed)</td><td><span class="ac-live">live</span></td><td>VMS string descriptors preserved.</td></tr><tr><td>Packed DECIMAL arithmetic (up to 31 digits)</td><td><span class="ac-live">live</span></td><td>Scale and rounding controllable with <code>OPTION SCALE</code>, <code>-nN</code>, and <code>-R</code>.</td></tr><tr><td>Structured <code>WHEN ERROR USE</code> / <code>HANDLER</code></td><td><span class="ac-live">live</span></td><td>Up to ten levels of nesting.</td></tr><tr><td><code>HFLOAT</code> (128-bit H-float)</td><td><span class="ac-v1">partial</span></td><td>Converted to 64-bit <code>double</code> with a warning; precision is lost.</td></tr><tr><td><code>GFLOAT</code> on non-VMS hosts</td><td><span class="ac-v1">partial</span></td><td>Emitted as <code>double</code>; there is no native G-float on the target platforms.</td></tr><tr><td><code>MAT</code> matrix family</td><td><span class="ac-v1">partial</span></td><td>Basic forms work; mixed integer and decimal operands are rejected.</td></tr><tr><td>Embedded SQL (<code>-q</code>, Pro*C / ESQL-C)</td><td><span class="ac-v1">partial</span></td><td>Requires an Oracle Pro*C toolchain on the host.</td></tr><tr><td><code>OPTION ANGLE = DEGREES / RADIANS / GRADS</code></td><td><span class="ac-no">out of scope</span></td><td>Trigonometry is always in radians.</td></tr><tr><td>GRAPHICS family (<code>DRAW</code>, <code>PLOT</code>, <code>ROTATE</code>, ...)</td><td><span class="ac-no">out of scope</span></td><td>Recognised but not translated; this is the documented "all commands except graphics" exclusion.</td></tr></tbody></table></div><div class="callout"><strong>Maintain in place, or convert.</strong> Because the translation preserves
structure, variable names, and comments, an application can continue to be developed in
OpenVMS BASIC and re-translated on each release, or converted once and maintained as C.
Either path keeps the same behaviour on Linux and Windows.
</div><h2 id="quickref">Quick reference</h2><h6>Invoke</h6>
<pre>vxbasic file.bas [-options] <span class="cmt">! produces file.c</span>
vxbasic file.bas -g <span class="cmt">! translate then auto-compile</span>
vxbasic file.bas -L <span class="cmt">! also emit a .lst listing</span>
vxbasic file.bas -u <span class="cmt">! preprocess only</span></pre>
<h6>Type map</h6><div class="kw-grid"><div>BYTE = int8_t</div><div>WORD = int16_t</div><div>LONG = int32_t</div><div>QUAD = int64_t</div><div>SINGLE = float</div><div>DOUBLE = double</div><div>GFLOAT = double</div><div>HFLOAT = double</div><div>DECIMAL = packed BCD</div><div>STRING = descriptor</div><div>RFA = uchar[6]</div><div>ADDRESS = void *</div></div><h6>Translation rules</h6><div class="kw-grid"><div>RECORD -> typedef struct</div><div>VARIANT / CASE -> union / struct</div><div>MAP overlays -> parallel structs on one store</div><div>intrinsic X -> Vxb_Xxx</div><div>helper -> Vxbf_*</div><div>ON ERROR GOTO -> Vxbf_OnError_GoTo</div></div><h6>Error introspection</h6><div class="kw-grid"><div>ERR</div><div>ERL</div><div>ERT$</div><div>RMSSTATUS</div><div>VMSSTATUS</div><div>STATUS</div></div><h6>Link line</h6>
<pre>cc file.c -lvxbas -ll -lm -o file</pre>
</div>