Forth on a VAX

Forth on a VAX

I now have pForth 2.0.1, Phil Burk’s portable Forth, running on my VAXstations under OpenVMS VAX 7.3. This is an ANS-style Forth with 32-bit cells, double-cell words, local variables, floating point, file words and a SYSTEM word that runs a DCL command. It is built with DEC C, installed for every user with an installer and an uninstaller, and runs as an ordinary unprivileged user. Alongside it is the VAX-11 fig-Forth of 1985, assembled from its MACRO-32 source without a change.

Claude Code assisted, which is to say, did much of the hard work. (This seems to be my tagline these days.)

This one needed small source changes, so the download folder has the whole story: the upstream archive with its hash, five patches of a few lines each (kept apart, with the reason for each), and a recipe to recreate the kit from nothing. No existing Forth word was changed (three new ones were added); the patches are about the C compiler and C library of a VAX.

Downloads, with SHA-256 sums in SHA256SUMS.TXT:

Licence

pForth is under the zero-clause BSD licence (“Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted”), so the kit can be passed on freely. The installer, uninstaller, setup procedure, patches and examples are mine, under the same terms. The 1985 VAX-11 fig-Forth was released into the public domain by the Central Iowa Forth Interest Group; its notices are kept in the source.

Which Forth

I went looking for a Forth that builds on a VAX under VMS 7.3 with DEC C 6.4 (there is no VAX C, and no GCC here), is reasonably standard, and is still maintained. All checked 14-OCT-2026:

VersionWhereWhat I did
pForth 2.0.1 (Jan 2023, “ANS-like”, ANSI C, 8,400 lines)github.com/philburk/pforthbuilt: the only candidate that needs neither GCC nor Unix
fig-Forth 2.0 for VAX-11 (1985, MACRO-32)DECUS VAX85 collectionbuilt unchanged, installed as FIGFORTH
pfe 0.30 / 0.32 (Portable Forth Environment)DECUS CDsnot built: no VMS port, autoconf and libtool, dlopen, GCC-isms
Gforth 0.2 - 0.4DECUS CDsnot built: needs GCC; its own to-do list names VMS as unported
TILE Forth 2.1 (1990, Forth-83)DECUS CDsnot built: Forth-83 only, Unix-oriented
C-ForthDECUS CDsnot built: no DOES>

I found no Forth with a VMS build anywhere on the web.

What was built

  • Source: pforth-2.0.1.tar.gz from the v2.0.1 tag (the newest release when I looked, 15-OCT-2026), 139,439 bytes, SHA-256 f4c417d7d1f2c187716263484bdc534d3224b6d159e049d00828a89fa5d6894d.
  • Compiler: DEC C V6.4, CC/NAMES=AS_IS/FLOAT=G_FLOAT/OPTIMIZE, a short DCL procedure instead of the Unix makefile.
  • Measured on the VAXstation 4000/90 (71 MHz KA49), in a batch job: 15 modules, 0 compile failures, 1 minute 6 seconds; link 1.4 seconds; PFORTH -i system.fth builds the 125-block dictionary in 7 seconds. PFORTH.EXE is 106 blocks. The whole thing: 1 minute 13 seconds. Compiling without optimisation took one minute: the build is not the bottleneck.
  • The kit: 157 files, 1,552 blocks installed, the BACKUP saveset 1,890 blocks; the lifecycle test (install, refuse a second install, uninstall with a changed file kept, reinstall, replace twice: 21 checks) takes 2 minutes 16 seconds and the install itself 19 seconds on the 4000/90; 3 minutes 47 seconds and 33 seconds on the 4000/60.

The source changes

Five patches, all small, all guarded so that the Unix build is unchanged:

  1. No <stdint.h> (17 lines, pforth.h). DEC C for the VAX has no such header and no 64-bit integer type. PF_NO_STDINT supplies the types; pForth tests sizeof where it matters, so uint64_t being 32 bits is handled.
  2. Three new words (38 lines, pfcustom.c): SYSTEM ( c-addr u -- n ) runs a DCL command in a subprocess and returns its status; CLOCK ( -- ticks ) is the process CPU time in 10 ms ticks and CLOCKS/SEC is 100. The portable platform has no shell or timer word, and a Forth on VMS wants both.
  3. sdSleepMillis (20 lines). The “stdio” platform I/O file of pForth 2.0.1 does not define it, so that platform does not link; on VMS it is LIB$WAIT.
  4. No round() (5 lines). The VAX C library lacks the C99 function that FROUND uses; half away from zero, like round().
  5. RESIZE-FILE cannot shrink a file (8 lines). Making a file shorter in ANSI C is done with freopen(NULL, ...), which crashes the DEC C run-time library with an access violation. Extending a file works; shrinking reports an error.

Two things in the build that were not code: DEC C resolves #include "../x.h" against the current default directory rather than the including file’s directory (the two files in csrc/stdio are compiled from their own directory), and /NAMES=AS_IS means the VMS routine must be written LIB$WAIT in capitals.

pForth’s own tests

pForth ships test files, and I ran them on the VAX under the same procedure as on a Linux PC:

TestVAX 7.3Linux (same patched sources)
T_COREX.FTH124 passed, 0 failed124 / 0
T_STRINGS.FTH58 / 058 / 0
T_LOCALS.FTH9 / 09 / 0
T_ALLOC.FTH1 / 01 / 0
T_FLOATS.FTH59 / 160 / 0
T_FILE.FTH88 / 694 / 0
  • The float failure is 0.0 -0.0 0.0 F~. VAX floating point has no negative zero; -0.0 is just 0.0. A difference of the machine, not a bug.
  • The six file failures are RESIZE-FILE to a smaller size (patch 5) and the checks that depend on the shrunk file.

One thing to know

SAVE-INPUT and RESTORE-INPUT need Stream_LF files. I found this because T_COREX.FTH stopped silently halfway on the VAX. pForth’s RESTORE-INPUT works out the position of the start of a line by subtracting the line length from the file position, which is right when a position is a byte offset (a Stream_LF file) and wrong for the variable-length record files that UNZIP, EDT and TYPE redirection produce. Plain INCLUDE and INCLUDED of such a file work: I tested a record file with definitions, blank lines and both kinds of comment, and everything loaded.

So: ordinary Forth source in any VMS format is fine; a program that uses SAVE-INPUT/RESTORE-INPUT (the Hayes test suite does) needs its source in Stream_LF. The kit’s own .FTH files are Stream_LF already, and FORTH_STMLF.COM converts yours:

$ @SYS$COMMON:[SYSLIB.FORTH]FORTH_STMLF MYPROG.FTH

I did not change pForth’s file-position logic.

Installing and using

You need OpenVMS VAX 7.3 and SYSTEM (or SYSPRV). About 3,500 free blocks.

  1. Put the saveset and the two .COM files in one directory on the VAX (.BCK in binary, .COM in ASCII; the installer puts back the saveset’s record attributes if your transfer lost them).
  2. $ @FORTH_INSTALL installs into SYS$COMMON:[SYSLIB.FORTH], writes a manifest and runs a smoke test in a subprocess. @FORTH_INSTALL "" REPLACE replaces an installation. Nothing outside that directory is touched: no INSTALL, no rights identifier, no startup file, no system logical name.
  3. Each user: $ @SYS$COMMON:[SYSLIB.FORTH]FORTH_SETUP (or put it in LOGIN.COM), then $ PFORTH. Quote switches, because DCL upper-cases them: PFORTH "-q" MYFILE.FTH.
  4. $ @FORTH_EXAMPLES:RUN_EXAMPLES runs the five examples and compares them with their expected output.
  5. To remove it: $ @SYS$COMMON:[SYSLIB.FORTH]FORTH_UNINSTALL deletes only the files its manifest recorded, and keeps any that you changed.
$ PFORTH
PForth V2.0.0, LE/32, built Oct 14 2026 20:26:43
pForth loading dictionary from file PFORTH_DIC:PFORTH.DIC
     Cell Size       = 4
: SQUARE DUP * ; 7 SQUARE . CR 49
   ok
Stack<10> s\" WRITE SYS$OUTPUT F$GETSYI(\"VERSION\")" system . cr
V7.3    65537
   ok
Stack<10> bye

(The banner says V2.0.0 although this is the v2.0.1 tag, commit 3956a7fd: upstream left PFORTH_VERSION_NAME at “2.0.0” in csrc/pf_guts.h, so this is not the wrong source.)

The fig-Forth is FIGFORTH after the same setup. It is the real thing: upper case, no recursion by name inside a definition, loops only inside definitions, and a HELP word command that reads its own help library.

Examples

The five programs in examples/ are a tour of what is different on a 32-bit VAX with VMS file names:

  • STACK.FTH: factorial, Fibonacci and GCD, the cell size (4 bytes, 32 bits, -1 U. is 4294967295) and double-cell arithmetic: 20! = 2,432,902,008,176,640,000 in two cells, with UM*.
  • DEFWORDS.FTH: CREATE ... DOES>: an array that checks its index and throws, a table-defining word and a traffic-light state machine.
  • SIEVE.FTH: the Byte magazine sieve, 8,190 flags, 10 passes. Measured on the 4000/90: 417 ticks = 4.17 seconds of CPU; on the 4000/60: 680 ticks = 6.80 seconds, 1,899 primes per pass.
  • FILEIO.FTH: writes a file in SYS$SCRATCH:, reads it back with READ-LINE, counts 5 lines, 20 words and 107 characters, deletes it.
  • VMSDCL.FTH: DCL from Forth: F$GETSYI("VERSION"), TYPE, SEARCH, DELETE, each with its status.

All five pass as USER1 (a test account with only standard user privileges) on the 4000/90 and the 4000/60, from the installed kit (10 seconds for all five on the 4000/90, 17 seconds on the 4000/60).

Findings from writing them: an interpreted S" in pForth is a transient buffer, so a 2CONSTANT of one holds garbage (I got CREATE-FILE to make a file called .; use a colon definition); S" does not understand \", S\" does; and BYE inside an included file prints an INCLUDE error banner, so the examples simply end and let the end of input exit.

What I did not do

No test on a simulator or an Alpha; no VAX C build; no saved user dictionary (SAVE-FORTH) on VMS; the Hayes coretest.fth suite was not run (pForth’s own tests were).