PLANC monitor-call rules - what a linter must check¶
Companion to monitor-calls.json, the machine-readable table of all 258
SINTRAN III monitor calls listed in the vendor manual ND-860228.2 EN SINTRAN III Monitor
Calls. This page holds the rules that do not fit in a table: which calls a PLANC program can
reach, in what order the libraries must be loaded, the shape of the error handler, the error
codes worth knowing by name, and the calls whose parameters people keep getting wrong.
Sources. Everything here is either quoted from a manual in this repo or marked as measured on a real machine (D100, SINTRAN III VSX/500 K, PLANC-100-F00). Anything that is neither is marked UNVERIFIED.
Reference-Manuals/ND-860228-2-EN SINTRAN III Monitor Calls.md- the call table, the parameters, the error-code appendix.Reference-Manuals/ND-60.117.5 EN PLANC Reference Manual.md- Appendix E, "Using SINTRAN Monitor Calls": which calls the PLANC runtime supplies and their exact signatures.SINTRAN/XMSG/SINTRAN-CHAT/CHAT.PLNCandCHATSV.PLNC- the programs that hit each of these traps in turn and carry the measurement notes.- Longer prose version:
PLANC-MONITOR-CALLS.md.
1. Two ways to issue a call, and only one of them needs no extra library¶
1.1 The 54 MONn routines the PLANC runtime supplies directly¶
The PLANC runtime carries one ordinary routine per call for 54 calls, and only these 54.
You IMPORT the routine and call it like any other routine. No extra library is needed.
The list, from ND-60.117.5 Appendix E section 0.2, in octal:
MON0 MON1 MON2 MON3 MON4 MON11 MON12 MON13 MON14 MON16
MON17 MON21 MON22 MON24 MON30 MON32 MON41 MON43 MON44 MON45
MON47 MON50 MON54 MON62 MON63 MON64 MON65 MON66 MON70 MON71
MON72 MON73 MON74 MON75 MON76 MON104 MON113 MON114 MON117 MON120
MON122 MON123 MON132 MON141 MON143 MON144 MON161 MON162 MON167 MON263
MON310 MON312 MON412 MON413
In monitor-calls.json this is the plancRoutine field: a string like "MON50" when the
runtime supplies one, null when it does not.
Linter rule. IMPORT (... : MONnnn) where MONnnn is not in this list is an error on a
plain build - the reference will not resolve. Measured on D100: MON221 and MON317 both come
out U (undefined) from LIST-ENTRIES-UNDEFINED, and MON43 and MON70, which are in the
list, both resolve. Four for four.
Three of the 54 are machine specific and the manual says so on their own pages:
| Routine | Only on |
|---|---|
MON45, MON47, MON132, MON141, MON167, MON310, MON312 |
ND-100 |
MON412, MON413 |
ND-500 |
(MON21 is on both, but the ND-100 and ND-500 forms take different parameters - the ND-100
form is (dev, w1, w2, w3, w4, num) with INTEGER WRITE words, the ND-500 form is
(dev, num, inbytes).)
The number is the identity. MON1 is call 1B is INBT is InByte. The short name and
the long name matter only to MONITOR_CALL.
1.2 MONITOR_CALL - reaches every call, but needs a library and a names file¶
MONITOR_CALL(221B, nameBuf, startAddr, noOfPages) % by NUMBER
MONITOR_CALL('CreateFile', nameBuf, startAddr, pages) % by NAME
MONITOR_CALL('ERMSG', ErrCode) % by SHORT NAME
| Form | What it needs | When it bites |
|---|---|---|
| by NUMBER | MON-CALL-1BANK:BRF linked (it carries the entry 5MON_P) |
link time |
| by NAME or SHORT NAME | the above plus a MON-CALL-NAMES file on the machine | compile time and link time |
Both forms were measured to fail on a bare D100 before the MON-CALL package was installed:
by name the compiler answered THE MON-CALL-NAMES FILE WAS NOT FOUND, and by number the link
ended 5MON_P ... U. With MON-CALL-1B-A00 installed, both work - CHATSV.PLNC uses
MONITOR_CALL(221B, ...) and MONITOR_CALL('TimeOut', ...) on that machine today.
Linter rule. A file that uses MONITOR_CALL in any form must have MON-CALL-1BANK:BRF
(or MON-CALL-2BANK:BRF, or MON-CALL-LIB:NRF on the ND-500) on its LOAD line. A file
that uses the name form additionally depends on the MON-CALL-NAMES file, which is a property
of the machine, not of the source - the linter can only warn.
The name to use is the longName key in monitor-calls.json. Where the manual prints
two spellings for the same call, the JSON notes field says so and the entry is marked
UNVERIFIED - HDLCFunction / HDLFunction is the only real one.
2. LOAD ORDER - your program, then MON-CALL, then the PLANC runtime¶
Verbatim from the Monitor Calls manual's own PLANC worked example (ND-100, one bank):
@PLANC-100
*COMPILE EX-PROG:SYMB, "EX-PROG:LIST", "EX-PROG:BRF"
*EXIT
@BRF-LINKER
Br1: PROGRAM-FILE "EX-PROG:PROG"
Br1: LOAD EX-PROG:BRF, MON-CALL-1BANK:BRF, PLANC-1BANK:BRF
Br1: EXIT
@EX-PROG
| Target | Monitor-call library | PLANC runtime | Extra |
|---|---|---|---|
| ND-100, one bank | MON-CALL-1BANK:BRF |
PLANC-1BANK:BRF |
- |
| ND-100, two bank (over 128 KB) | MON-CALL-2BANK:BRF |
PLANC-2BANK:BRF |
SEPARATE-DATA ON before COMPILE |
| ND-500 | MON-CALL-LIB:NRF |
PLANC-500 runtime | link with the ND-500 Linkage Loader |
The rule, and why. A library is searched to satisfy references that already exist. Loading it before there is anything to satisfy resolves nothing, and the linker accepts that in silence. The PLANC Reference Manual states the same rule for hand-written interface routines (Appendix E 0.1): "the user must load the interface routine before the PLANC runtime library." So: the thing that makes a reference goes first, the thing that satisfies it next, the runtime last.
What goes wrong if the order is wrong. Nothing visible. BRF-LINKER writes a runnable
:PROG with the entry still undefined and says nothing about it. The program then starts and
misbehaves in a way that looks like a bug in your logic. This happened on D100 with MON317
left undefined, and the resulting nonsense was mistaken for evidence about file creation.
Therefore LIST-ENTRIES-UNDEFINED is part of the build, and its output has to be READ.
Br1: LIST-ENTRIES-UNDEFINED
- a leftover
MNnn/MONnnunit means the PLANC runtime was not loaded, or you used a call outside the 54; - a leftover
5MON_Pmeans you usedMONITOR_CALLand did not loadMON-CALL-1BANK.
Two-step compile, not one. If you set PROG-FILE and let the compiler link, you cannot
control library order and the :BRF may come out empty. Compile to a real :BRF with no
PROG-FILE, then LOAD in BRF-LINKER in the order above. LIBRARY-MODE ON before a library
makes it contribute only the units actually referenced.
3. Every call needs ON ROUTINEERROR ... ENDON¶
A refused monitor call does not return a status. It raises. From the Monitor Calls manual, section on PLANC, verbatim:
"Error codes different from 0 cause ERRETURN from monitor calls. That is, the current ON - ENDON statement is executed."
"Error codes are automatically stored in the variable
ErrCode. This integer variableErrCodecan be read as any other variable. It should not be declared."
Unhandled, the raise unwinds the WHOLE program. There is no per-call status to ignore. A first run of a program that opens a file that does not exist yet dies on the spot unless the open is guarded.
3.1 The correct shape¶
ROUTINE VOID, INTEGER : openLog
INTEGER : dev
BOOLEAN : openFailed
0 =: dev
ON ROUTINEERROR DO
TRUE =: openFailed
ENDON
FALSE =: openFailed % <-- AFTER the ON block, never before
MON50(logName, logType, logAppend) =: dev
IF openFailed THEN
0 RETURN
ENDIF
dev RETURN
ENDROUTINE
Four rules the linter can check:
- The flag is cleared AFTER the
ON ... ENDONblock, not before. Measured on D100 2026-08-19: cleared before, the handler body had already run and every open reported failure while returning a good device number. AnON ROUTINEERROR DO ... ENDONthat is preceded by the initialisation of its own flag is a bug. - One flag per logical operation, not per call. A handler wrapped round a write loop that prints inside the handler prints once per character. Set a flag, test it after the loop.
ErrCodeis never declared.INTEGER : ErrCodeis an error - the runtime supplies it.- Test the flag, not the returned value. A failed
MON50leaves a stale number in the destination variable (46 has been seen), which reads as a perfectly good device number. Any code that tests the returned number instead of the flag is wrong.
Nesting note. The handler in force is the innermost enclosing one. To do something and deliberately ignore any failure, an empty handler is the idiom:
ON ROUTINEERROR DO
ENDON
makeLog % may fail; we test the next open instead
3.2 Which calls can raise¶
canRaise in the JSON. It is true for every call except the three that never return to the
caller at all: ExitFromProgram (0B), ExitRTProgram (134B) and ErrorReturn (400B).
This is a deliberate conservative default and is marked here as such. The manual's own section 2.2 has an ERR column, but in this scan that column is damaged on most rows and cannot be trusted. The safe lint rule is "guard every call"; a false "you need a handler here" costs four lines, a missing one costs the program.
4. Error codes a linter should know¶
ErrCode is DECIMAL. The manual's tables are OCTAL. Appendix A of ND-860228.2 prints both
columns side by side; the decimal column is the one that matches ErrCode. Getting this
backwards has already produced a "refused push reported success".
The ones worth naming:
| ErrCode (decimal) | Octal | Meaning | Where you meet it |
|---|---|---|---|
| 3 | 003 | End of file / nothing was typed | InByte on a terminal in no-wait mode. Not a fault - it means "no input yet". A program that treats 3 as fatal cannot poll a keyboard. |
| 2 | 002 | Bad file number | closing or reading a file number you never opened |
| 5 | 005 | Device not reserved | |
| 17 | 021 | Illegal character in parameter | a quoted local filespec handed to a SINTRAN command - see section 5 |
| 21 | 025 | You are not authorized to do this | |
| 46 | 056 | No such file name | first-run OpenFile before the file has been created. The manual's own PLANC example tests exactly this. |
| 47 | 057 | Ambiguous file name | an abbreviation that matches more than one file |
| 48 | 060 | Wrong password | |
| 55 | 067 | Contiguous space not available | |
| 60 | 074 | No such file version | |
| 61 | 075 | No more pages available for this user | |
| 62 | 076 | File already exists | CreateFile on a file that is already there. Usually means "carry on and open it", not "stop". |
| 63 | 077 | Attempt to create too many files | |
| 68 | 104 | No such access code | a bad access code to OpenFile |
| 69 | 105 | File already open | |
| 70 | 106 | Not write access | |
| 71 | 107 | Attempt to open too many files | at most 18 files open at a time |
| 73 | 111 | Not read access | |
| 79 | 117 | No such user index | |
| 80 | 120 | Not append access | opened with access 5 without append rights |
| 83 | 123 | Not open for sequential write | writing to a file opened for read |
| 84 | 124 | Not open for sequential read | reading from a file opened for write |
| 90 | 132 | No file opened with this number | |
| 99 | 143 | No such block | |
| 108 | 154 | Source empty | |
| 111 | 157 | Missing parameter | too few parameters in the call |
| 174 | 254 | Illegal floppy format | |
| 175 | 255 | File not open | |
| 178 | 262 | File-access connection aborted by file server | remote (COSMOS) file access |
| 182 | 266 | Unknown remote system name | remote file access to a name not in the routing table |
| 202 | 312 | No termination handling defined | |
| 211 | 323 | File-access internal error; invalid parameter value |
Codes above 511 (1000B and up) come only from programs running on the ND-500(0); 1003B =
515 decimal is "Error in monitor call" and 1015B = 525 decimal is "Wrong number of parameters
in monitor call" - the two a linter's own work is meant to prevent.
There is a second, separate numbering: the RT-program error numbers printed by messages of
the form ERROR 14 IN BAK07 AT 114721. Those are also decimal but they are a different table
(00 illegal monitor call, 14 outside segment bounds, 29 file-system error, 46 XMSG fatal error,
47 XMSG user error). Do not mix the two - RT error 46 is an XMSG failure, ErrCode 46 is "no
such file".
Two calls turn a code into text: WarningMessage (64B) prints it and continues,
ErrorMessage (65B) prints it and stops the program. GetErrorMessage (334B) hands
you the 128-byte text instead of printing it.
5. The calls people get wrong¶
5.1 CloseFile is 43B. DeleteFile is 54B.¶
MON43(dev) % CLOSE
MON54(name) % DELETE - destroys the file
They sit close together in the numbering, both take one parameter about a file, and one of them
destroys data. Getting them the wrong way round is silent until the file is gone. A linter
should flag MON54 anywhere the surrounding code looks like a close (immediately after a
write loop, at the end of a save routine).
5.2 OpenFile - the PLANC routine is not the manual's parameter order¶
The manual's table (and MONITOR_CALL) order is:
1 FileNumber (I/O) 2 AccessCode (I) 3 FileName (STR) 4 DefaultFileType (STR)
The PLANC MON50 routine is a different shape - it returns the file number as its
out-value and takes the other three in a different order:
IMPORT ( ROUTINE VOID, INTEGER (BYTES, BYTES, INTEGER) : MON50 )
MON50(fileName, defaultType, accessCode) =: dev
| Wrong | Right |
|---|---|
MON50(dev, 1, name, type) |
MON50(name, type, 1) =: dev |
IF dev > 0 THEN ... after a failed open |
test the ROUTINEERROR flag; dev holds rubbish |
MON50 cannot create a file. Quoting the name ('"CHAT"') does not mean "create it if
absent" from the PLANC routine - measured, it did nothing at all and every subsequent write
raised. Use CreateFile (221B) through MONITOR_CALL, or the CREATE-FILE command through
CallCommand.
A file opened for sequential write is truncated at CLOSE, not at OPEN. A program that opens and never closes leaves the OLD contents in place, and the save silently does nothing. Close on every path, including after a failed write. A half-written source file therefore still compiles - as its old contents.
Access codes: 0 sequential write, 1 sequential read, 5 sequential write append. The full
list is in the JSON notes for OpenFile.
5.3 CreateFile - one 64-byte buffer, and INT4 sizes cannot be constants¶
BYTES : nameBuf(0:63) % 64 bytes, SPACE padded
INTEGER4 : startAddr := 0
INTEGER4 : noOfPages := 0
...
MONITOR_CALL(221B, nameBuf, startAddr, noOfPages)
| Wrong | Right |
|---|---|
MONITOR_CALL(221B, 'CHATLG:SYMB', 0, 0) |
copy the filespec into a full 64-byte buffer, pass INT4 variables |
passing name and type separately, as OpenFile does |
CreateFile takes the WHOLE filespec, name and type together, in one buffer |
INTEGER4 parameters cannot be given as constants - the manual states this flatly for
PLANC. Put the value in a variable first. This applies to every INT4 parameter in the JSON,
not just CreateFile: SetMaxBytes, SetStartByte, ExpandFile, ReadFromFile,
WriteToFile, DeletePage, NewFileVersion, ReadDiskPage, WriteDiskPage,
DataTransfer, TransferData, DMAFunction, GetDeviceType.
5.4 CallCommand (70B) and ExecuteCommand (317B) - fixed-size buffers¶
| Number | PLANC buffer | On error | |
|---|---|---|---|
CallCommand (COMND) |
70B |
BYTES : Command(0:79) - eighty bytes |
may terminate your program |
ExecuteCommand (UECOM) |
317B |
BYTES : Command(0:35) - thirty-six bytes |
prints a message, program continues |
The manual recommends ExecuteCommand. But there is no MON317 in the PLANC runtime (it
is not in the 54), so from PLANC without the MON-CALL library, MON70 is the only route.
The buffer is a fixed size, not a string. A PLANC BYTES argument carries its own length,
so a short literal passes a short buffer where the call expects a full one. Fill with spaces,
copy the text over the front:
BYTES : cmdBuf(0:79)
BYTES : spaceChar := ' '
FOR i IN 1:80 DO spaceChar(0) =: cmdBuf(i - 1) ENDFOR
FOR i IN 1:cmdLen DO cmdText(i - 1) =: cmdBuf(i - 1) ENDFOR
MON70(cmdBuf)
The command text ends with an apostrophe, and carries no @. In PLANC an apostrophe inside
a literal is doubled, so a create is written 'CREATE-FILE MY:FILE,1''' - closing quote plus
one apostrophe character.
A LOCAL filespec takes NO quotes here, and this is the opposite of MON50. Measured on
D100:
@CREATE-FILE "CHAT:CNFG",1
ILLEGAL CHARACTER IN PARAMETER <- ErrCode 17
@CREATE-FILE CHAT:CNFG,1
@
Quotes are right only for a REMOTE filespec, which goes to another machine's command processor.
Two dependent commands need a wait between them. The manual names the case: "Use SuspendProgram to wait a specified time interval between two CallCommands which depend on each other, e.g. CreateFile and OpenFile." Create-then-open is a documented race.
5.5 OutByte - the byte is the IN-VALUE¶
IMPORT ( ROUTINE BYTE, VOID (INTEGER) : MON2 )
b MON2(dev) % right
MON2(dev, b) % wrong - will not compile
The same shape applies to the other in-value routines: MON17 (SetTerminalType), MON24
(Out8Bytes), MON64/MON65 (the error-message pair, where the error number is the in-value
and there are no parameters at all), MON73, MON74, MON76.
And the out-value routines return rather than fill: MON1 (InByte) gives a BYTE, MON50 an
INTEGER, MON62/MON75 an INTEGER4, MON11 an INTEGER4, MON30 an INTEGER.
5.6 Terminal input carries parity¶
Terminal input on these machines arrives with even parity set, so a carriage return reads as
141 decimal, not 13. The manual's own PLANC example loops WHILE Char > 215. Code that
compares an input byte against 13 or 10 without masking is looking for something that never
arrives. Mask to seven bits (value AND 127) before comparing, and mask again before writing
a byte into a file you want a person to read.
And a text file you WRITE needs even parity or LIST-FILE shows nothing at all, silently.
That is a property of SINTRAN's own tools, not of the monitor calls.
5.7 Arrays that are really records¶
Several calls take a fixed-size block whose length the manual gives in bytes: GetRTDescr 52,
ReadObjectEntry / GetObjectEntry / SetObjectEntry / GetUserEntry 64, GetDirEntry 42
(but WriteDirEntry 48 - that difference is what the manual prints), GetNameEntry 28,
GetSpoolingEntry 272, GetSystemInfo 24, TerminalStatus 44, GetErrorInfo 12,
GetUserRegisters 154, GetActiveSegment 2048, GetErrorMessage 128, GetOwnProcessInfo 34.
The manual says outright: "Monitor calls requiring large amounts of data may use records instead of strings." A linter should check the declared size of the array or record against the documented byte count - an undersized buffer is overwritten with no diagnostic, because PLANC does no array-bound checking.
5.8 A call outside the 54 and outside MON-CALL¶
Then you write an interface routine (a small MAC or NPL stub that issues the raw MON and
returns its status) and load it before the PLANC runtime. On a machine with no assembler
installed this route is closed, and the program has to be designed not to need the call.
6. What the JSON does and does not promise¶
monitor-calls.json is keyed by longName - the name MONITOR_CALL('...') takes. 258 entries.
| Field | Where it comes from | Confidence |
|---|---|---|
number, shortName, longName |
manual sections 2.1, 2.2, 2.15 cross-checked against each other | high; every disagreement between the three is recorded in notes |
params |
manual section 2.2, in order, with the manual's own types and directions | high for the documented calls; empty for the internal-use calls of section 2.14, which the manual deliberately does not document |
nd100, nd500 |
each call's own AVAILABILITY line in chapter 3 | high for 186 calls; for the rest the chapter-3 page did not survive the scan and the value comes from the section 2.2 dot columns, and notes says so and says UNVERIFIED |
canRaise |
conservative default (section 3.2 above) | deliberate over-approximation, not a measurement |
plancRoutine |
ND-60.117.5 Appendix E section 0.2 | high; confirmed four for four against the linker on D100 |
Not in the JSON, on purpose: calls that appear in the repo's Developer/MON/calls/ YAML
hub but in none of this manual's tables - 350B ReadWriteSegment, 351B OpenFileByIndex,
352B EventSystem, 441B PlaceSegment, 511B DVIO, 512B/513B XMSGCallA/B. They may be
real on later SINTRAN versions; this manual does not list them, so they are not here.
45B is DefineBreakpoint (DBRK) per this manual's section 2.14; the YAML hub calls it
GetTypeRing. The manual is followed.
The obsolete list. Section 2.16 names 27 numbers as no longer supported: the calls still
exist for old programs, but the monitor call package returns -2 when you use them, so
MONITOR_CALL cannot reach them at all. Their notes say so. The list includes 200B
(XMSGFunction) and 201B (HDLCFunction), which is why COSMOS work goes through the XMP
library and not through the monitor call directly.
See also¶
PLANC-MONITOR-CALLS.md- the same ground in prose, with the D100 measurement historyPLANC-DEVELOPER-GUIDE.md- the language and the compilerCOSMOS-XMP-LIBRARY.md- MON 200B, which PLANC reaches through the XMP library insteadDeveloper/MON/calls/- one YAML per call, with descriptions and worked examples (the language examples there are AI-generated; treat them as a hint, not authority)