🚨 Oracle not porting Rdb to x86 and EOL on Itanium 12/2027 🚨
Technical Capabilities
Having trouble finding what you need?
Get in touch with us, so we can answer your specific questions directly.
Get In Touch

VX/TDMS - Terminal Data Management System

Overview

VX/TDMS is a native reimplementation of DEC TDMS (Terminal Data Management System) for Linux. It provides source-compatible form handling for migrated OpenVMS applications: existing FDU form listings, RDU request definitions, LDF request libraries, and CDD record layouts compile unchanged through the VX/TDMS compiler, and existing calls to TSS$ services link and run without modification.

The compiler emits reviewable, versioned JSON p-code rather than opaque binary. Every mapping is semantically validated against compiled form and record layouts, and every construct it cannot represent is counted and named rather than silently dropped. VX/TDMS also feeds two modernization paths that reuse the same compiled inventory: conversion to VSI DECforms, and conversion to VX/DECFORMS on Linux.

<div class="s7-tech-embed" data-vxtdmsd-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">Architecture and components</h2><p> VX/TDMS is a compiler and a linked runtime library, not a long-running daemon. There is no server process to administer: the "runtime" is <code>libvtdms_forms</code>, linked directly into the migrated application, and it reads compiled form/request/library/record definitions from disk the moment a program opens a request library. The table maps each OpenVMS TDMS component to its VX/TDMS counterpart. </p><div class="table-wrapper"><table><thead><tr><th>OpenVMS TDMS component</th><th>VX/TDMS counterpart</th><th>Role</th><th>Status</th></tr></thead><tbody><tr><td>FDU (Form Definition Utility)</td><td><code>tdmsc compile</code> (form path)</td><td>Parses FDU <code>LIST FORM</code> listings into form p-code: geometry, fields, attributes, validators, access order.</td><td><span class="ac-live">live</span></td></tr><tr><td>RDU (Request Definition Utility)</td><td><code>tdmsc compile</code> (request path) + C++ front end</td><td>Parses <code>.rdf</code> request definitions into request p-code.</td><td><span class="ac-live">live</span></td></tr><tr><td>CDD / DDL record definitions</td><td><code>tdmsc compile</code> (record path)</td><td>Parses CDD DDL into record p-code with exact byte layout, <code>OCCURS</code>/<code>ARRAY</code>, and variants.</td><td><span class="ac-live">live</span></td></tr><tr><td>LDF / RLB request library</td><td><code>tdmsc compile</code> (library path)</td><td>Parses <code>.ldf</code> library definitions into library p-code; models request-lookup membership.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$</code> form manager (linked runtime)</td><td><code>libvtdms_forms</code></td><td>C++ runtime exporting the <code>TSS$</code> call surface; loads p-code, binds records, renders forms, executes requests.</td><td><span class="ac-live">live</span></td></tr><tr><td>FDU interactive form editor</td><td>Browser-based form editor</td><td>Edits VX/TDMS form p-code (form / layout / assign / order phases).</td><td><span class="ac-v1">partial: p-code editor, not a terminal emulator</span></td></tr><tr><td>DECforms conversion (VSI OpenVMS)</td><td>IFDL translation path</td><td>Translates TDMS assets to VSI DECforms IFDL; rewrites <code>TSS$</code> calls to <code>FORMS$</code>.</td><td><span class="ac-v1">partial</span></td></tr><tr><td>VX/DECFORMS target (Linux)</td><td>VX/DECFORMS compiler, extended</td><td>Lowers TDMS request syntax directly into VX/DECFORMS.</td><td><span class="ac-v1">v1</span></td></tr></tbody></table></div><div class="callout"><strong>No server to administer.</strong> Unlike ACMS-style multi-process runtimes, VX/TDMS has no daemon and no IPC substrate to configure. The compiled p-code is read directly by the runtime library at <code>TSS$OPEN_RLB</code> time. </div><h6>Call lifecycle / data flow</h6><p>The <strong>compile-time</strong> flow runs offline, once per estate:</p> <pre>FDU LIST FORM (.lis) ─┐ RDU request (.rdf) ─┼─▶ compile ──▶ JSON p-code ──▶ validate-semantics ──▶ (optional) bindings LDF library (.ldf) ─┤ (compile-all) (4 formats) (mappings vs form+record) CDD DDL (.ddl) ─┘</pre> <p>The <strong>run-time</strong> flow happens per interactive session, inside the migrated program:</p> <pre>program ──TSS$OPEN──▶ channel ──TSS$OPEN_RLB(lib-file)──▶ library-id (runtime loads request/form/record p-code) ──TSS$REQUEST(ch, lib, name, %REF rec1, rec2)──▶ decode record buffers → apply OUTPUT mappings → render form (ANSI) → collect INPUT → validate → write values back into record buffers → run request actions (RESET / REVERSE / RING BELL / …) ──TSS$CLOSE_RLB──▶ ──TSS$CLOSE(ch, clear)──▶ done</pre> <p> The record buffers passed to <code>TSS$REQUEST</code> are opaque to the caller and shared with the runtime by reference; the runtime reads and writes fields at the byte offsets recorded in that request's record p-code. This is the crux of source-level compatibility: the program's <code>RECORD</code> variable is the same bytes the runtime edits. </p><h2 id="languages">Source languages</h2><p> VX/TDMS compiles four related definition file types. Only the RDF request language is a full imperative form language; the others are structured metadata. </p><div class="table-wrapper"><table><thead><tr><th>File type</th><th>OpenVMS origin</th><th>Contents</th></tr></thead><tbody><tr><td><code>*_form.lis</code></td><td>FDU <code>LIST FORM</code> output</td><td>Form name, formwide attributes, screen width, help form, field access order, per-field name / picture / length / default / help / attributes / validators / video / scrolling / indexing.</td></tr><tr><td><code>*_req.rdf</code></td><td>RDU request source</td><td>Form binding, record binding, mappings, control fields, program keys, video actions, waits.</td></tr><tr><td><code>*_lib.ldf</code></td><td>LDF library source</td><td><code>REQUEST IS</code> / <code>REQUESTS ARE</code>, <code>FILE IS</code>, membership.</td></tr><tr><td><code>*.ddl</code></td><td>CDD/DDL record source</td><td><code>DEFINE RECORD</code>, <code>STRUCTURE</code>, nested structures, <code>OCCURS</code>/<code>ARRAY</code>, variants, field types.</td></tr></tbody></table></div><h6>The RDF request language</h6><p> A request binds one or more program records to a form and moves data between them. The example below is a representative request showing the constructs VX/TDMS handles end to end: record binding with an alias, a program key, top-level and nested <code>CONTROL FIELD</code> cases, subscripted array mappings, and <code>%LINE</code> loop variables. </p> <pre><span class="kw">FORM IS</span> ACCOUNT_INQUIRY_FORM; <span class="kw">RECORDS ARE</span> ACCOUNT_REC <span class="kw">WITH NAME</span> ACCTREC, CDD$TOP.BANKING.DATA.SYST_STNDFORM_REC <span class="kw">WITH NAME</span> STNDFORM; <span class="kw">DESCRIPTION</span> <span class="cmt">/* Displays outstanding statements for an account. Control field is FLAG. */</span>; <span class="kw">RESET FIELD</span> %ALL; <span class="kw">USE FORM</span> ACCOUNT_INQUIRY_FORM; <span class="kw">PROGRAM KEY IS GOLD</span> <span class="str">"E"</span> <span class="kw">NO CHECK</span>; <span class="kw">RETURN</span> <span class="str">"ABORT "</span> <span class="kw">TO</span> STNDFORM.RECORD_LOCK; <span class="kw">END PROGRAM KEY</span>; <span class="kw">OUTPUT</span> ACCTREC.ACCOUNT_ID <span class="kw">TO</span> ACCOUNT_ID, ACCTREC.STATEMENT_NUMBER[1 TO 30] <span class="kw">TO</span> STATEMENT_NUMBER[1 TO 30], ACCTREC.SELECT_STATEMENT[1 TO 30] <span class="kw">TO</span> SELECT_STATEMENT[1 TO 30]; <span class="kw">CONTROL FIELD IS</span> FLAG <span class="str">'START '</span>: <span class="kw">OUTPUT</span> STNDFORM.PGM_NAME <span class="kw">TO</span> PGM_NAME, <span class="str">""</span> <span class="kw">TO</span> STATEMENT_DATE[1 TO 30]; <span class="kw">INPUT</span> ACCOUNT_ID <span class="kw">TO</span> ACCTREC.ACCOUNT_ID; <span class="str">'ACCT '</span>: <span class="kw">REVERSE FIELD</span> ACCOUNT_ID; <span class="kw">INPUT</span> ACCOUNT_ID <span class="kw">TO</span> ACCTREC.ACCOUNT_ID; <span class="str">'DISPLAY'</span>: <span class="kw">CONTROL FIELD IS</span> DATE_FLAG[%LINE = 1 TO 30] <span class="str">"X"</span>: <span class="kw">OUTPUT</span> ACCTREC.STATEMENT_DATE[%LINE] <span class="kw">TO</span> STATEMENT_DATE[%LINE]; <span class="kw">INPUT</span> SELECT_STATEMENT[%LINE] <span class="kw">TO</span> ACCTREC.SELECT_STATEMENT[%LINE]; <span class="kw">END CONTROL FIELD</span>; <span class="kw">END CONTROL FIELD</span>; <span class="kw">END DEFINITION</span>;</pre> <p> An empty string literal (<code>""</code>) as an <code>OUTPUT</code> source clears the receiving field. A <code>CONTROL FIELD</code> nests arbitrarily and its cases are matched against the named field's value. <code>%LINE</code> and <code>%ENTRY</code> are loop variables over subscript ranges, and field references may be dotted CDD record paths. </p><h6>RDF instruction set</h6><div class="table-wrapper"><table><thead><tr><th>Instruction</th><th>Meaning</th></tr></thead><tbody><tr><td><code>FORM IS &lt;name&gt;</code> / <code>USE FORM</code> / <code>DISPLAY FORM</code></td><td>Bind / select / paint a form (name may be a dotted CDD path).</td></tr><tr><td><code>RECORD IS</code> / <code>RECORDS ARE … WITH NAME</code></td><td>Bind one or more program records, with an optional alias.</td></tr><tr><td><code>DESCRIPTION /* … */</code></td><td>Comment block.</td></tr><tr><td><code>RESET FIELD %ALL | &lt;list&gt;</code></td><td>Clear all or listed fields.</td></tr><tr><td><code>PROGRAM KEY IS GOLD "k" [NO CHECK] … END PROGRAM KEY</code></td><td>GOLD-prefixed key handler.</td></tr><tr><td><code>OUTPUT &lt;src&gt; TO &lt;formfield&gt;, …</code></td><td>Move record or literal value to the form.</td></tr><tr><td><code>INPUT &lt;formfield&gt; TO &lt;recfield&gt;, …</code></td><td>Move form value to the record.</td></tr><tr><td><code>RETURN &lt;lit&gt; TO &lt;field&gt;</code></td><td>Assign a literal (also stands alone in control-field arms and program keys).</td></tr><tr><td><code>CONTROL FIELD IS f[subscript] … END CONTROL FIELD</code></td><td>Case dispatch on a field value; nests arbitrarily.</td></tr><tr><td><code>[NO] REVERSE / BLINK / BOLD / UNDERLINE FIELD &lt;list&gt;</code></td><td>Video attribute on listed fields.</td></tr><tr><td><code>RING BELL</code></td><td>Sound the terminal bell.</td></tr><tr><td><code>SIGNAL OPERATOR</code> / <code>SIGNAL MODE IS REVERSE</code></td><td>Operator signal / signal mode.</td></tr><tr><td><code>WAIT</code> / <code>NO WAIT</code></td><td>Block / don't block for input.</td></tr><tr><td><code>CHECK</code> / <code>NO CHECK</code></td><td>Enable / skip field validation.</td></tr><tr><td><code>CLEAR SCREEN</code></td><td>Clear the screen.</td></tr><tr><td><code>END DEFINITION</code></td><td>Request terminator.</td></tr></tbody></table></div><p> Subscripts are multi-dimensional with loop variables: <code>[1 TO 30]</code>, <code>[%LINE]</code>, <code>[%LINE = 1 TO 100, %ENTRY = 1 TO 25]</code>. </p><div class="callout"><strong>Parenthesised multi-receiver mappings.</strong> Some DEC documentation mentions a parenthesis form for multiple mapping receivers, e.g. <code>(field1, field2) …</code>. VX/TDMS's compiler accepts the parenthesis-free list form throughout, which is the canonical RDU form. We have not yet seen a production request that requires the parenthesised variant. </div><h6>FDU form field attributes</h6><p>All represented in form p-code:</p><ul><li><strong>Display Only:</strong> field not mapped for input.</li><li><strong>No Echo:</strong> data entered is not displayed.</li><li><strong>Autotab:</strong> cursor advances automatically when the field fills.</li><li><strong>Default value:</strong> shown at run time unless a request overrides it.</li><li><strong>Field help:</strong> message shown on the HELP key.</li><li><strong>Clear character:</strong> the fill character in empty positions.</li><li><strong>Clear / zero suppression:</strong> suppress a leading zero for a null numeric field.</li><li><strong>Validation:</strong><code>RANGE</code>, <code>CHOICE</code>, <code>SIZE</code>, <code>CHECK DIGIT</code>.</li><li><strong>Video attributes:</strong><code>BOLD</code>, <code>REVERSE</code>, <code>BLINK</code>, <code>UNDERLINE</code>.</li><li><strong>Access order:</strong> left-to-right, top-to-bottom field visitation.</li></ul><p> Reserved keys: <code>HELP</code> (PF2/F15), <code>MENU</code> (GOLD-KP7), <code>RETURN</code>/<code>ENTER</code> saves field values, <code>GOLD</code> is the prefix for program keys. </p><h2 id="runtime-api">The TSS$ runtime API</h2><p> VX/TDMS ships a C++ runtime (<code>libvtdms_forms</code>) that exports the OpenVMS <code>TSS$</code> call surface so recompiled application code links unchanged. Each routine is exported under three names: the VMS dollar-sign name (via an assembler alias, so existing object references resolve), a portable <code>TSS_</code> C alias, and a C++ class behind both. </p><h6>VMS ABI</h6><p> Return convention: the function result is a VMS condition value <strong>returned by value</strong>: odd means success, even means failure. Callers test <code>ODD(...)</code>. Canonical signatures: </p> <pre>ret = TSS$OPEN (channel.wlu.r [, device.rt.dx]) ret = TSS$CLOSE (channel.rlu.r [, clear-screen.rlu.r]) ret = TSS$OPEN_RLB (rlb-file-spec.rt.dx, library-id.wlu.r) ret = TSS$CLOSE_RLB (library-id.rlu.r) ret = TSS$REQUEST (channel.rlu.r, library-id.rlu.r, request-name.rt.dx [, record1.mz.r [, ... recordN.mz.r]]) ret = TSS$READ_MSG_LINE (channel.rlu.r, response-text.wt.dx [, message-prompt.rt.dx] [, response-length.wwu.r]) ret = TSS$WRITE_MSG_LINE(channel.rlu.r, message-text.rt.dx) ret = TSS$SIGNAL <span class="cmt">; signal last condition, no args</span> ret = TSS$COPY_SCREEN (channel.rlu.r [, file-spec.rt.dx] [, append-flag.rlu.r]) ret = TSS$DECL_AFK (channel.rlu.r, key-id.rlu.r, key-efn.rlu.r, key-astadr.szem.r [, key-astprm.rlu.v])</pre> <h6>Routine table</h6><div class="table-wrapper"><table><thead><tr><th>Routine</th><th>Purpose</th><th>Status</th></tr></thead><tbody><tr><td><code>TSS$OPEN</code></td><td>Open a terminal channel (writes channel out).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$CLOSE</code></td><td>Close channel, optional clear-screen.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$OPEN_RLB</code></td><td>Open a request library by file spec (writes library-id out).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$CLOSE_RLB</code></td><td>Close a request library.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$REQUEST</code></td><td>Execute a named request against zero or more record buffers (variadic, by reference).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$SIGNAL</code></td><td>Signal the last condition value.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$READ_MSG_LINE</code></td><td>Read a response from the message line.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$WRITE_MSG_LINE</code></td><td>Write text to the message line.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$CANCEL</code></td><td>Cancel outstanding I/O on a channel.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$COPY_SCREEN</code></td><td>Copy screen to a hardcopy file (honours the <code>TSS$HARDCOPY</code> logical).</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$STRACE_ON</code> / <code>TSS$STRACE_OFF</code></td><td>Screen-trace to a portable trace file.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$DECL_AFK</code> / <code>TSS$UNDECL_AFK</code></td><td>Declare / remove an async function-key handler.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>TSS$OPEN_A</code> / <code>TSS$REQUEST_A</code> / <code>TSS$CLOSE_A</code></td><td>Async variants.</td><td><span class="ac-v1">partial: complete synchronously, status delivered through the RSB pointer</span></td></tr></tbody></table></div><p><code>TSS$HARDCOPY</code> is honoured as the default <code>TSS$COPY_SCREEN</code> destination when set as a logical name / environment variable. <code>TSS_TRIGGER_AFK</code> (and interactive <code>AFK:&lt;key-id&gt;</code> input) fires a declared async function-key callback with the channel, key-id, and the caller's AST parameter. </p><h6>Request-execution semantics</h6><p><code>TSS$REQUEST</code> does the real work. On every call it:</p><ul><li>Loads the request / form / record p-code for the named request.</li><li>Decodes raw record buffers at compiled offsets: <code>TEXT</code>, signed/unsigned integer, <code>DATE</code>, <code>F_FLOATING</code>, <code>G_FLOATING</code>, <code>UNSIGNED QUADWORD</code>, and <code>BIT</code> all decode to display text and encode back to native storage bytes on input.</li><li>Applies RDF <code>OUTPUT</code> mappings: record/literal to form, including control-field cases and indexed repeated fields.</li><li>Renders the form: live terminal output uses ANSI cursor addressing and SGR video attributes; copy-screen output stays plain text for deterministic hardcopy comparison.</li><li>Applies RDF <code>INPUT</code> mappings, validates the value (required, uppercase, numeric, choice, range, size, check digit, length), and writes it back into the raw application record buffer.</li><li>Runs request actions: field reset, video attributes, <code>RING BELL</code>, <code>SIGNAL OPERATOR</code>, <code>WAIT</code>, <code>NO WAIT</code>.</li></ul><h6>Real call path (VAX Pascal)</h6><p> A migrated program links this unchanged against <code>libvtdms_forms</code>; note the <code>ODD()</code> status test and <code>%REF</code> (by-reference) record passing: </p> <pre><span class="cmt">{ open channel + request library }</span> IF NOT ODD (TSS$OPEN(TDMS_CHANNEL)) THEN TDMS_STATUS := TSS$SIGNAL; IF NOT ODD (TSS$OPEN_RLB(<span class="str">'BANKING_RLB:ACCT_INQUIRY_LIB'</span>, TDMS_LIB_ID)) THEN TDMS_STATUS := TSS$SIGNAL; <span class="cmt">{ run a request against two program records, passed by reference }</span> IF NOT ODD (TSS$REQUEST(TDMS_CHANNEL, TDMS_LIB_ID, <span class="str">'ACCOUNT_INQUIRY_REQ'</span>, (STNDFORM), <span class="cmt">{ %REF record 1 }</span> (ACCTREC))) <span class="cmt">{ %REF record 2 }</span> THEN TDMS_STATUS := TSS$SIGNAL; <span class="cmt">{ … application reads/writes fields of ACCTREC, which the runtime just filled … }</span> <span class="cmt">{ tear down }</span> IF NOT ODD (TSS$CLOSE(TDMS_CHANNEL, CLEAR_SCREEN)) THEN TDMS_STATUS := TSS$SIGNAL; IF NOT ODD (TSS$CLOSE_RLB(TDMS_LIB_ID)) THEN TDMS_STATUS := TSS$SIGNAL;</pre> <p> The equivalent portable C entry point is declared as <code>extern "C" int TSS_REQUEST(int channel, int library_id, const char* request_name, ...);</code>, returning the condition value by value. </p><h2 id="pcode">Data model: JSON p-code</h2><p> The compiler emits versioned JSON p-code, one file per source artifact, in four formats. Every file carries <code>format</code>, <code>version</code>, <code>source</code>, <code>diagnostics</code>, and <code>summary</code> keys; readers ignore unknown future keys and error on missing required keys. This is deliberate: reviewable p-code is what makes a migration regression-testable against the original OpenVMS behaviour, instead of trusting an opaque binary translation. </p><div class="table-wrapper"><table><thead><tr><th>Format</th><th>From</th><th>Key contents</th></tr></thead><tbody><tr><td><code>VXTDMS-FORM-PCODE</code></td><td><code>.lis</code></td><td><code>meta</code> (form name, line range, screen width), <code>access_order</code>, <code>fields</code>, <code>ops</code>.</td></tr><tr><td><code>VXTDMS-REQUEST-PCODE</code></td><td><code>.rdf</code></td><td><code>name</code>, <code>form_name</code>, <code>records</code> (in TSS argument order), <code>actions</code>, <code>mappings</code>, <code>program_keys</code>.</td></tr><tr><td><code>VXTDMS-LIBRARY-PCODE</code></td><td><code>.ldf</code></td><td><code>name</code>, <code>file</code>, <code>requests</code>.</td></tr><tr><td><code>VXTDMS-RECORD-PCODE</code></td><td><code>.ddl</code></td><td><code>name</code>, <code>root</code>, <code>byte_size</code>, <code>fields</code> (flattened with byte layout).</td></tr></tbody></table></div><p> A form <strong>field entry</strong> carries name, source line, row/col, length, display length, datatype, picture type, attributes, occurs count, index direction, scroll flag, and validators. An <strong>op entry</strong> is <code>clear</code>, <code>draw_text</code>, or <code>field</code>. A request <strong>mapping entry</strong> carries direction (<code>OUTPUT</code>/<code>INPUT</code>/<code>RETURN</code>), source, target, source line, and the active <code>CONTROL FIELD</code> conditions. A record <strong>field entry</strong> carries name, full nested CDD path, datatype, element size, byte offset, byte size, and dimensions (from <code>OCCURS</code>/<code>ARRAY</code>) with per-element offsets. </p><h6>Integrity properties enforced by the compiler</h6><ul><li><strong>Per-element offsets</strong>, not contiguous-array assumptions, so nested <code>STRUCTURE OCCURS</code> and CDD variant overlays are represented faithfully. Fields from different variant branches may legally share the same offset.</li><li><strong>Fixed record byte size</strong> is compiled and used to size the raw buffer the runtime decodes and writes.</li><li><strong>Semantic validation</strong> resolves every mapping operand against compiled form fields and record paths, accepting dotted paths and unique nested-suffix references, and reports form/record reference errors, missing fields, static subrange violations, occurrence-count mismatches, and type-category mismatches, each with source path, RDF line, and severity.</li></ul><div class="callout"><strong>Not a black box.</strong> Every construct the compiler cannot represent is counted and named on stderr with filename, line, and construct text, never silently dropped. Duplicate form names are uniquified with a warning, not silently. </div><h2 id="build-pipeline">Build and compile pipeline</h2><p> The compiler driver is <code>tdmsc</code>. The C++ runtime side builds with CMake ≥ 3.16 and C++17. </p><h6>Pipeline steps</h6><div class="table-wrapper"><table><thead><tr><th>Step</th><th>Command</th><th>Input</th><th>Output</th></tr></thead><tbody><tr><td>Compile one artifact</td><td><code>tdmsc compile &lt;file&gt; [-o out.json]</code></td><td>One <code>.lis</code> / <code>.rdf</code> / <code>.ldf</code> / <code>.ddl</code> (type by extension)</td><td>One <code>*.tdms.json</code></td></tr><tr><td>Compile a whole estate</td><td><code>tdmsc compile-all &lt;root&gt; -o &lt;dir&gt; [--validate-semantics]</code></td><td>Tree of forms / requests / libraries / records</td><td>Directory of p-code + counters</td></tr><tr><td>Validate semantics</td><td><code>tdmsc validate-semantics &lt;pcode-dir&gt;</code></td><td>Compiled p-code directory</td><td>Mapping diagnostics + counters</td></tr><tr><td>Render p-code</td><td><code>tdmsc render &lt;pcode&gt; [--values v.json] [--html f.html]</code></td><td>Form p-code</td><td>Text or HTML screen</td></tr><tr><td>Render a request</td><td><code>tdmsc render-request --form F --request R [--records recs.json]</code></td><td>Form + request p-code + record data</td><td>Rendered screen after OUTPUT mappings</td></tr><tr><td>Validate values</td><td><code>tdmsc validate &lt;pcode&gt; &lt;values.json&gt;</code></td><td>Form p-code + field values</td><td>Validation messages</td></tr><tr><td>Generate bindings</td><td><code>tdmsc gen-bindings &lt;record-pcode&gt; -o &lt;dir&gt; [--language c|pascal|all]</code></td><td>Record p-code</td><td><code>.h</code> / <code>.inc</code> byte-layout bindings</td></tr></tbody></table></div><h6>Worked example</h6> <pre><span class="cmt"># 1: compile the whole estate</span> tdmsc compile-all /opt/tdms/banking/cdd -o /tmp/tdms_pcode <span class="cmt"># → compiled=500 fields=8740 ops=22087 warnings=3 unsupported=0 errors=0</span> <span class="cmt"># 2: validate every mapping against compiled forms + records</span> tdmsc validate-semantics /tmp/tdms_pcode <span class="cmt"># → semantic_requests=109 semantic_mappings=5030 semantic_warnings=2 semantic_errors=0</span> <span class="cmt"># 3: generate C + Pascal byte-layout bindings for the record p-code</span> tdmsc gen-bindings /tmp/tdms_pcode -o /tmp/tdms_bindings --language all</pre> <p> On a representative production-scale estate this breaks down as 105 forms, 109 requests, 64 libraries, and 222 records: 500 files, 8,740 fields, 22,087 rendering ops, and 5,030 request mappings, all validated with zero semantic errors. </p><div class="callout"><strong>Skip/fail discipline.</strong> The compiler is not silent best-effort. Every run prints <code>warnings=</code>, <code>unsupported=</code>, and <code>errors=</code> counters; an unrepresentable construct is named on stderr with filename, line, and construct text. Exit code is non-zero on errors. </div><h6>CLI reference</h6><div class="table-wrapper"><table><thead><tr><th>Subcommand</th><th>Args / flags</th><th>Notes</th></tr></thead><tbody><tr><td><code>compile</code></td><td><code>input</code>, <code>-o/--output</code></td><td>Type chosen by extension; exit 1 on errors.</td></tr><tr><td><code>compile-tree</code></td><td><code>root</code>, <code>-o</code> (required), <code>--pattern</code></td><td>Forms only.</td></tr><tr><td><code>compile-all</code></td><td><code>root</code>, <code>-o</code> (required), <code>--validate-semantics</code></td><td>Forms + RDF + LDF + DDL; uniquifies duplicate output names with a warning.</td></tr><tr><td><code>render</code></td><td><code>pcode</code>, <code>--values</code>, <code>--html</code>, <code>--no-fields</code>, <code>--no-defaults</code></td><td></td></tr><tr><td><code>render-request</code></td><td><code>--form</code> (required), <code>--request</code> (required), <code>--records</code></td><td></td></tr><tr><td><code>validate</code></td><td><code>pcode</code>, <code>values</code></td><td>Exit 1 if any messages.</td></tr><tr><td><code>validate-semantics</code></td><td><code>pcode_dir</code></td><td>Exit 1 on semantic errors.</td></tr><tr><td><code>gen-bindings</code></td><td><code>record_pcode</code>, <code>-o</code> (required), <code>--language {c,pascal,all}</code></td><td></td></tr></tbody></table></div><h6>Incremental rebuilds</h6><p><code>compile</code> recompiles a single artifact to a single p-code file. There is no daemon to reload. The runtime reads p-code fresh at <code>TSS$OPEN_RLB</code> time, so a recompiled p-code file takes effect on the next run. The browser form editor writes a timestamped backup beside the file before replacing it. </p><h2 id="modernization">Modernization paths and divergences</h2><p> VX/TDMS supports three target runtimes off the <strong>same compiled inventory</strong>: form listings, requests, libraries, and record metadata. Path 1 keeps the application on VX/TDMS's own <code>TSS$</code> runtime on Linux; Paths 2 and 3 convert the application to a DECforms-family target. </p><div class="table-wrapper"><table><thead><tr><th>Item</th><th>Path 1: VX/TDMS runtime</th><th>Path 2: TDMS → VSI DECforms</th><th>Path 3: TDMS → VX/DECFORMS (Linux)</th></tr></thead><tbody><tr><td>Compile forms/requests/libraries/records to reviewable p-code</td><td><span class="ac-live">live</span></td><td><span class="ac-live">live (source for the translator)</span></td><td><span class="ac-live">live</span></td></tr><tr><td>Run original <code>TSS$</code> calls unchanged</td><td><span class="ac-live">live</span></td><td><span class="ac-no">n/a: calls rewritten to FORMS$</span></td><td><span class="ac-no">n/a: calls rewritten to FORMS$</span></td></tr><tr><td>Semantic validation of all mappings</td><td><span class="ac-live">live</span></td><td><span class="ac-live">live</span></td><td><span class="ac-live">live</span></td></tr><tr><td>Translate to DECforms IFDL</td><td><span class="ac-no">out of scope</span></td><td><span class="ac-v1">partial</span></td><td>via VX/DECFORMS TDMS extensions</td></tr><tr><td><code>TSS$</code> → <code>FORMS$</code> program conversion</td><td><span class="ac-no">out of scope</span></td><td><span class="ac-v1">partial</span></td><td><span class="ac-v1">partial</span></td></tr><tr><td>TDMS request syntax lowered directly by the forms compiler</td><td>n/a</td><td><span class="ac-no">out of scope: VSI DECforms is native</span></td><td><span class="ac-v1">v1: design target 99.99% automated</span></td></tr><tr><td>Async <code>TSS$*_A</code> fidelity</td><td><span class="ac-v1">partial: synchronous completion via RSB</span></td><td>reported</td><td>reported</td></tr><tr><td>Interactive FDU terminal-keypad editing</td><td><span class="ac-no">out of scope: p-code editor only</span></td><td><span class="ac-no">out of scope</span></td><td><span class="ac-no">out of scope</span></td></tr></tbody></table></div><h6>What DEC's own converter drops, and what VX/TDMS preserves</h6><p> Path 2 targets the native DECforms product and inherits DEC's own Converter limitations, documented in the <em>DECforms TDMS Conversion Guide</em>. That guide states plainly that the Converter converts forms and requests but does <strong>not</strong> convert the program, and, even for forms and requests, does not convert eight specific things. VX/TDMS was built to preserve every one of them, because it compiles from source rather than from a built <code>.RLB</code> file. </p><div class="table-wrapper"><table><thead><tr><th>DEC Converter will <strong>not</strong> convert</th><th>Why DEC drops it</th><th>VX/TDMS status</th></tr></thead><tbody><tr><td>CHECK DIGIT field validator</td><td>DECforms has no CHECK DIGIT validator.</td><td><span class="ac-live">Preserved:</span> VX/TDMS compiles and executes the validator.</td></tr><tr><td>Default key definitions</td><td>DECforms binds built-in functions to keys; only <code>ERROR</code>/<code>GOLD</code>/<code>HARDCOPY</code> are emulated.</td><td><span class="ac-live">Modelled:</span><code>PROGRAM KEY IS GOLD "k" … END PROGRAM KEY</code> is represented natively.</td></tr><tr><td>Dependent ranges + nested <code>CONTROL FIELD IS</code></td><td>DECforms has no looping mechanism and no case structure, so you redesign by hand.</td><td><span class="ac-live">Preserved: the biggest single differentiator.</span> Nested control fields and <code>%LINE</code>/<code>%ENTRY</code> dependent ranges parse and validate cleanly.</td></tr><tr><td><code>DESCRIPTION</code> instruction</td><td>DEC reads the built <code>.RLB</code>, which does not retain comment text.</td><td><span class="ac-live">Available:</span> VX/TDMS compiles from RDF source, so the text is present.</td></tr><tr><td><code>LIGHT LIST</code> instruction</td><td>Not every terminal has the referenced LEDs.</td><td><span class="ac-v1">Path-3 mapping to an operator message is a planned equivalent.</span></td></tr><tr><td>Operator input order (ORDER phase)</td><td>The Converter doesn't maintain the field order you specify.</td><td><span class="ac-live">Preserved:</span> VX/TDMS records <code>access_order</code> in every form p-code.</td></tr><tr><td>Non-default scale factors</td><td>The Converter converts only the default scale factor from the picture.</td><td><span class="ac-live">Preserved:</span> VX/TDMS emits the compiled <code>scale_factor</code> into form p-code fields.</td></tr><tr><td>Values in <code>RING BELL</code> (up to 255)</td><td>DECforms <code>SIGNAL</code> takes no count.</td><td>Not yet exercised against a request that specifies a count.</td></tr></tbody></table></div><div class="callout warn"><strong>DEC's own prerequisites for Path 2.</strong> The Converter only handles forms and requests built with TDMS ≥ 1.7. It also does not support CDD/Plus record variants or all CDD/Plus data types. DEC requires removing variants and changing unsupported field types by hand before conversion. VX/TDMS represents variant branches as overlay offsets and decodes the full datatype set directly, so this prerequisite does not apply on the VX paths. </div><h6>Path 3: the compatibility superset</h6><p> Rather than force every TDMS concept into standard DECforms, VX/DECFORMS is extended to understand TDMS request syntax and runtime behaviour directly, and the same migration rewrites <code>TSS$</code> calls to <code>FORMS$</code>. Because the compiler lowers TDMS syntax natively, most of the limitations above simply don't arise on this path: nested <code>CONTROL FIELD IS</code>, dependent ranges, program keys, explicit and subrange mappings, video attributes, and operator signals are all recognized constructs. Where a behaviour still needs project-specific policy, the compiler emits a compatibility diagnostic rather than silently changing meaning. </p><div class="callout"><strong>Scope honesty.</strong> Path 3's DECforms-lowering and the <code>TSS$</code> → <code>FORMS$</code> rewrite are under active development today. The claim above is grounded in VX/TDMS's verified p-code coverage of these constructs, not yet in a measured end-to-end Path 3 conversion of a full production estate. </div><h6>Deliberate divergences (Path 1 runtime behaviour)</h6><div class="table-wrapper"><table><thead><tr><th>Divergence</th><th>Reason</th></tr></thead><tbody><tr><td><code>TSS$OPEN_A</code> / <code>TSS$REQUEST_A</code> / <code>TSS$CLOSE_A</code> complete synchronously, writing status through the RSB pointer</td><td>Linux has no VMS AST/<code>$WAITFR</code> kernel primitive; the caller's <code>$WAITFR</code>/RSB sequence still observes completion correctly.</td></tr><tr><td>Live rendering uses ANSI/SGR; <code>TSS$COPY_SCREEN</code> output is plain text</td><td>Deterministic hardcopy and byte-stable regression comparison against reference captures.</td></tr><tr><td><code>TSS$STRACE_ON/OFF</code> writes a portable trace file</td><td>No VMS screen-trace facility exists on Linux; the trace is logged messages, screens, and copies.</td></tr><tr><td>The form editor edits VX/TDMS JSON p-code, not byte-for-byte CDD/FDU storage</td><td>It is a p-code editor, not an FDU terminal emulator; full FDU keypad equivalence is future work.</td></tr><tr><td>Validation-failure behaviour, message-line geometry, and <code>.RLB</code>/<code>.FRM</code> binary layouts are VX-defined in places</td><td>The most detailed DEC Request/Programming/Forms/Reference manuals are unrecoverable; where public documentation is silent, VX/TDMS defines and documents its own behaviour.</td></tr></tbody></table></div><h2 id="operator">Operator and runtime management</h2><p> There is no server to administer. Runtime behaviour is controlled by logical names (honoured as environment variables) and a handful of files. </p><div class="table-wrapper"><table><thead><tr><th>Control</th><th>Effect</th></tr></thead><tbody><tr><td><code>TSS$HARDCOPY</code> / <code>TSS_HARDCOPY</code></td><td>Default destination file for <code>TSS$COPY_SCREEN</code> when no file argument is given.</td></tr><tr><td><code>VXTDMS_PCODE_DIR</code></td><td>Directory the runtime loads compiled p-code from.</td></tr><tr><td><code>VXTDMS_INPUT_&lt;FIELD&gt;</code></td><td>Supplies an <code>INPUT</code> field value in non-interactive runs (otherwise the current form value; interactive runs prompt on stdin).</td></tr><tr><td><code>AFK:&lt;key-id&gt;</code> (interactive input) / <code>TSS_TRIGGER_AFK</code></td><td>Fires a declared async function-key callback.</td></tr><tr><td>Screen-trace file (<code>TSS$STRACE_ON/OFF</code>)</td><td>Portable log of rendered request screens, messages, and copy-screen operations.</td></tr></tbody></table></div><h6>Form editor</h6><p> The browser-based form editor moves through a form → layout → assign → order phase model. It loads and saves the same JSON p-code the runtime consumes, validates on save, and writes a timestamped backup before replacing the file. It lists valid form p-code files in the current directory and counts and reports files that fail validation rather than hiding them. It binds to a configurable local address and port, with a no-browser flag for headless use. </p><div class="callout"><strong>Verified against a real DEC TDMS reference install.</strong> The runtime's test suite executes DEC's own Installation Verification Procedure: add, display, change, display, delete, negative-message, exit, all through real <code>TSS_REQUEST</code> calls, and compares the rendered screens against a golden capture taken from a live OpenVMS TDMS install, field for field, with zero mismatches. </div><h2 id="quickref">Quick reference</h2><h6><code>tdmsc</code> subcommands</h6><div class="kw-grid"><div>compile</div><div>compile-tree</div><div>compile-all</div><div>corpus</div><div>render</div><div>render-request</div><div>validate</div><div>validate-semantics</div><div>gen-bindings</div></div><h6>RDF verbs</h6><div class="kw-grid"><div>FORM IS</div><div>USE FORM</div><div>DISPLAY FORM</div><div>RECORD IS</div><div>RECORDS ARE</div><div>WITH NAME</div><div>RESET FIELD</div><div>PROGRAM KEY IS GOLD</div><div>NO CHECK</div><div>END PROGRAM KEY</div><div>OUTPUT</div><div>INPUT</div><div>RETURN</div><div>CONTROL FIELD IS</div><div>END CONTROL FIELD</div><div>REVERSE FIELD</div><div>BLINK FIELD</div><div>BOLD FIELD</div><div>UNDERLINE FIELD</div><div>RING BELL</div><div>SIGNAL OPERATOR</div><div>SIGNAL MODE IS REVERSE</div><div>WAIT</div><div>NO WAIT</div><div>CHECK</div><div>CLEAR SCREEN</div><div>END DEFINITION</div></div><h6><code>TSS$</code> routines</h6><div class="kw-grid"><div>TSS$OPEN</div><div>TSS$CLOSE</div><div>TSS$OPEN_RLB</div><div>TSS$CLOSE_RLB</div><div>TSS$REQUEST</div><div>TSS$SIGNAL</div><div>TSS$READ_MSG_LINE</div><div>TSS$WRITE_MSG_LINE</div><div>TSS$CANCEL</div><div>TSS$COPY_SCREEN</div><div>TSS$STRACE_ON</div><div>TSS$STRACE_OFF</div><div>TSS$DECL_AFK</div><div>TSS$UNDECL_AFK</div><div>TSS$OPEN_A</div><div>TSS$REQUEST_A</div><div>TSS$CLOSE_A</div></div><p>Return convention: <strong>odd = success, even = failure</strong>; test <code>ODD(...)</code>.</p><h6>P-code formats</h6><div class="kw-grid"><div>VXTDMS-FORM-PCODE</div><div>VXTDMS-REQUEST-PCODE</div><div>VXTDMS-LIBRARY-PCODE</div><div>VXTDMS-RECORD-PCODE</div></div><h6>CDD datatypes decoded / written by the runtime</h6><div class="kw-grid"><div>TEXT</div><div>signed integer</div><div>unsigned integer</div><div>DATE</div><div>F_FLOATING</div><div>G_FLOATING</div><div>UNSIGNED QUADWORD</div><div>BIT</div></div><h6>Validators</h6><div class="kw-grid"><div>RANGE</div><div>CHOICE</div><div>SIZE</div><div>CHECK DIGIT</div><div>required</div><div>uppercase</div><div>numeric</div><div>length</div></div><h6>Key file paths</h6><div class="table-wrapper"><table><thead><tr><th>Path</th><th>Contents</th></tr></thead><tbody><tr><td><code>compiler/tdmsc</code>, <code>compiler/tdms_forms</code></td><td>Compiler driver and per-format parsers.</td></tr><tr><td><code>src/form_runtime.cpp</code>, <code>include/vtdms/form_runtime.hpp</code></td><td>The <code>TSS$</code> runtime.</td></tr><tr><td><code>tools/tdms_form_editor</code></td><td>Browser-based form p-code editor.</td></tr><tr><td><code>docs/PCODE_SCHEMA.md</code></td><td>P-code format schema reference.</td></tr><tr><td><code>docs/SEMANTICS.md</code></td><td>Semantic validation rules reference.</td></tr></tbody></table></div></div>

Frequently Asked Questions

Curious about how Sector7 can facilitate your application migration? Explore our FAQs for expert insights.

No items found.

Transform Your Legacy Software Today!

Get In Touch
Unlock the potential of your legacy software with our expert migration services.