6. RPN compiler¶
The RPN compiler makes it possible to compile HP-41 RPN programs into the same output format as the Nut assembler. That is, it will output a relocatable object file and optionally a list file. The relocatable object file is suitable to be linked into a HP-41 module.
The RPN compiler is a command line tool named rpncomp. Running it from the command line with the ``–help`` option will give a sign-on message and display accepted options:
Calypsi RPN compiler for Hewlett-Packard Nut version 5.16
Usage: rpncomp [--version] [-o|--output-file OUTPUT-FILE] [-l]
[--list-file LIST-FILE] [-I DIRECTORY]
[--xeq-from-rom LABEL-LIST] [--rpn-output] [--raw-output]
[--no-fat] [--prefix-labels PREFIX] FILE
use 'rpncomp --help' for detailed help
Available options:
--version Display version number
-o,--output-file OUTPUT-FILE
Name of output file
-l Generate a list file, named by appending '.lst' to
input file
--list-file LIST-FILE Generate list file, using given name
-I DIRECTORY Include directory
--xeq-from-rom LABEL-LIST
Specify XEQ global labels to keep as is (not change
to XROM calls, space separated list)
--rpn-output Expecting a .raw file, generate RPN source output
--raw-output generate .raw output
--no-fat Omit auto generation of function address table entry
(fat)
--prefix-labels PREFIX Prefix created assembler labels
-h,--help Show this help text
The input source file can either be a source file or a raw binary RPN
image (using .raw file extension).
The default behavior is to convert XEQ to a global alpha label, to be
its corresponding ROM based XROM instruction. The XROM variant
will always call the same function and uses less memory, see
Global alpha labels.
Normally you do not need to use any particular option, unless you want
to preserve an XEQ to a global alpha label which may be used as a
mechanism to call a user written function stored in RAM. A more
flexible way to make such call, is to store the global alpha label name in a
register and call it indirectly.
In addition to creating relocatable files, the RPN compiler can
convert .raw files to readable .rpn source files. Use the
--rpn-output for this, see Converting .raw files for more
details.
6.1. RPN language¶
The language used is similar to how it appears in the HP-41 with some minor differences due to special characters.
The RPN compiler expects the input file to be encoded in UTF-8, which
means that ordinary ASCII characters are encoded as (7-bit) ASCII
characters, while non-ASCII characters are encoded in sequences of
multiple bytes (with the highest bit set, making them different from
all ASCII characters). Thus, it is possible to type an instruction
such as ΣREG in the way it appears in the HP-41, which is
impossible to do in ASCII.
In order to help typing programs, some synonyms are also accepted:
RPN |
Synonyms |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6.2. Preprocessor¶
The RPN compiler uses a full featured C preprocessor to handle the input source file. The preprocessor provides the normal features you will find in a C preprocessor, the ability to include header files, macro expansions, conditional compilation and use of C style comments.
Wikipedia is a good place to look for an introduction with many examples on how to use the C preprocessor.
When used in the RPN compiler, the following macros are predefined:
Preprocessor symbol |
Description |
|---|---|
|
An integer that is 1 when the Nut target (including NEWT variant) is used. |
|
An integer that is 1 when the RPN compiler is used. |
|
An integer that is 1 when generating code for module use. |
|
An integer that is 1 when
generating code for RAM use.
( |
Comments¶
Comments start with a semi-colon and remains active for the rest of the line. Using C style comments are also possible thanks to the preprocessor.
Line numbers¶
HP-41 RPN programs have line numbers as listed. The RPN compiler will accept programs as input either with or without line numbers. Whether the program have line numbers is sensed automatically by looking at what appears to be the first instruction in the program. If that is a number, it is assumed that there will be a line number before each instruction. 1
Line numbers are just read and ignored and there is no requirement that they make any sense, such as being in a strict increasing order.
Indentation¶
Spaces can be used to indent code, which can be useful to increase readability. Using empty lines and comments blocks may also be useful to make the program easier to read.
Alpha strings¶
As the input file is assumed to be in UTF-8, it allows you to use certain special characters not available in ASCII and type them as they look. Refer to the Unicode table in HP-41 character set.
Append alpha¶
If the append character appears first in an alpha literal, it means that the string literal will be appended to the alpha register rather than replacing it.
The append character is 127 which correspond to the delete character
in ASCII. While it is possible to type it using the ├ Unicode
character, or a hexadecimal or octal escape sequence (see below), the
easiest way is to precede the string literal with the greater than
sign:
>"FOO" ; Append FOO
Control characters¶
Control or individual special characters inside an otherwise normal string literal, can be inserted using escape sequences in the same way as in the C language:
"ABC\r" ; followed by CR
"ABC\x0d" ; also followed by CR (hex 0D)
"FOO\\BAR" ; \ itself must be escaped
Synthetic alpha strings¶
If you want to enter an string literal where the actual numeric character codes are more important than readable characters, the alternative syntax with a bracketed list of character codes can be used:
[128,255,0,3] ; 4 characters in decimal form
Numeric constants¶
Numeric constants are written just as they appears in an HP-41 program. You need to separate the exponent field from the number with a space. Synthetic exponent constants are also allowed:
-1.303 E-13
E
E3
-E
NULL separation of numeric constants¶
If the source program contains two numeric constants immediately after each other, an invisible NULL will be inserted between them. This is done automatically. Such NULL instruction will not get an RPN line number, as it is invisible when examining the program in the HP-41. The NULL instruction will however be visible in the list file, without any line number will be assigned to it.
6.3. A basic example¶
A simple RPN program to calculate the area of a circle can be implemented as follows:
;; Calculate the area of a circle given radius in X
LBL "AREA"
X^2
PI
*
END
Running the RPN compiler on it produces a list file with the following contents:
###############################################################################
# #
# Calypsi RPN compiler for Hewlett-Packard Nut version 5.16 #
# 14/Apr/2026 16:42:38 #
# Command line: -l area.rpn #
# #
###############################################################################
0000 .section FAT
0000 ...... .fatrpn AREA
0000 .section RPN
0000 002270 ;; COPY header
0002 1c6000 01 LBL "AREA"
0004 0f5000
0006 041052
0008 045041
000a 151 02 X^2
000b 172 03 PI
000c 142 04 *
000d 1c8001 05 END
000f 22f
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 18 words
As can be seen from the list file, there are two sections generated. The first one (FAT) is meant to go into the function address table. We are after all going to produce a module and the global alpha label needs to be included there in order to be accessible. The actual word values to be put into the image cannot be determined at this point, as we do not know where the program is going to be located in the module.
The second section (RPN) is the actual program. It starts with two
words that describe some properties of the program meant to be used by
the COPY instruction, which ensures that the program can be
properly copied to RAM memory. The rest are the actual RPN
instructions encoded for ROM use.
Also note that RPN line numbers are generated before each RPN instruction. The leftmost column of hex numbers are the offset for the instruction within its section.
6.4. Branches and labels¶
Relative branches are automatically compiled for ROM use. The HP-41 can make use of short and long branches and the smallest one that can reach the destination is used. 2
6.5. Global alpha labels¶
Global alpha labels are automatically found and a suitable information is generated for populating the FAT for each label. By default you will get a small FAT section fragment with this program’s contribution to the final FAT. You may want to control this by hand, see FAT order.
A call to a global alpha label using XEQ is assumed to be within the
current module. It is converted to a 2-word XROM instruction, where
the generated words depends on the final location in the FAT. The
actual values of these words will be determined at link time.
In some cases you may actually want to call a global alpha label and avoid
the automatic translation to XROM. You can do this using the
--xeq-from-rom command line switch:
LBL "FOO"
XEQ "AREA"
XEQ "FN"
END
To prevent FN from being converted to an XROM call, use the
following command line:
$ rpncomp foo.rpn --xeq-from-rom=FN
In the resulting list file, FN has been preserved as an XEQ to
a global alpha label. This will cause the operating system to search for it
starting in catalog 1.
###############################################################################
# #
# Calypsi RPN compiler for Hewlett-Packard Nut version 5.16 #
# 14/Apr/2026 16:42:38 #
# Command line: -l call.rpn --xeq-from-rom=FN #
# #
###############################################################################
0000 .section FAT
0000 ...... .fatrpn FOO
0000 .section RPN
0000 003220 ;; COPY header
0002 1c2001 01 LBL "FOO"
0004 0f4000
0006 04604f
0008 04f
0009 ...... 02 XROM "AREA"
000b 11e0f2 03 XEQ "FN"
000d 04604e
000f 1cc001 04 END
0011 22f
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 20 words
If you want to prevent multiple routines from being converted to XROM
calls, either use the --xeq-from-rom option multiple times, or
specify a double quoted space separated alpha label list:
$ rpncomp foo.rpn --xeq-from-rom="FN AREA"
Note
The default behavior when calling a global alpha label with XEQ is to
convert it into an XROM call instruction for the module being
built. If that global alpha label is not defined in the same RPN program,
the RPN compiler will silently create an external dependency on
it. The linker will later take care of fixing the XROM call,
provided that the global alpha label exists in another RPN program linked
together in the same module.
6.6. Using other plug-in modules¶
An RPN program can use instructions and functions defined in other
plug-in modules. Such modules are not known to the RPN compiler from
start, but they can be made available using an .import directive
in the source file.
In this example the optional HP-41CX instructions and the Math Pac
module is imported. The CLRGX instruction is part of the HP-41CX
and the RPN program ACOSH is defined in Math Pac:
.import 41cx, math
01 LBL "TEST"
02 3.065
03 CLRGX
04 XROM "ACOSH"
05 END
Note that instructions are just typed as any instruction, while the
function ACOSH in Math Pac is an RPN program so it needs to be
preceded by XROM, just as it appears when the HP-41 displays the
program steps.
Compiling this program produces the following list file output:
###############################################################################
# #
# Calypsi RPN compiler for Hewlett-Packard Nut version 5.16 #
# 14/Apr/2026 16:42:38 #
# Command line: -l import.rpn #
# #
###############################################################################
0000 .section FAT
0000 ...... .fatrpn TEST
0000 .section RPN
0000 003260 ;; COPY header
0002 1c8000 01 LBL "TEST"
0004 0f5000
0006 054045
0008 053054
000a 11301a 02 3.065
000c 010016
000e 015
000f 1a6072 03 CLRGX
0011 1a0066 04 XROM "ACOSH"
0013 1c6002 05 END
0015 22f
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 24 words
As the XROM codes for these optional functions are known at compile time (the modules already exists, it is nothing we are generating now), the actual words are resolved at compile time.
The imported modules have to be supplied as separate module export files. How the imports can be generated is described in the Module tool chapter.
The installation contains a set of already generated module export
files in the module-export directory. If the desired module is
available there, you can just use it as the RPN provides contains this
directory in its predefined search path.
The search path also includes the current directory. If the module
export file is located elsewhere, you need to use the -I command
line option to add that directory to the search path.
6.7. Invoking local MCODE instructions¶
A special case is if you are building a mixed RPN and MCODE module. There are a couple of ways of doing this. In general it boils down to whether:
you decide up front on the XROM numbers for your MCODE instructions, or
you want to let the tool help you in a similar way to local RPN functions.
If you want to have fixed numbers, you can define a module exports file and let the RPN functions import it. Then you just need to ensure that you link the code in such order that the MCODE instructions end up at the specified location in the FAT.
The alternative is to let the tools figure it out using a technique that piggybacks on the same mechanism that allows automatic calls of RPN functions in the same module.
To do this you need to tell the RPN compiler that there are local
MCODE instructions using the .local directive. 3 It looks
syntactically similar to the .import directive, but instead of
reading a module exports file, it specifies individual MCODE
instruction names. In the Poker game that is part of the supplied
example games module (see RPN games module), there are three MCODE
support instructions called CARD, MV and TRI. The start of
the source file may then be as follows:
.import 41cx
#ifdef __CALYPSI_MODULE_USE__
.local CARD, MV, TRI
#else
.import exgames
#endif
01 LBL "POKER+"
02 10
...
54 CARD
Note
Using .local is only needed when importing MCODE instruction
names. It is not needed for RPN programs. The reason is that RPN
programs are wth an XROM prefix, so the compiler have no
problem understanding what is going on.
This makes the MCODE instructions available to the RPN program. The actual XROM code used is unknown at compile time and the mechanism works as follows. It is expected that for each such MCODE instruction, there is a special named public label at each FAT entry. In the MCODE source file you will need to do something like: 4
.section FAT
`FAT entry: CARD`:
.fat entry_CARD
`FAT entry: MV`:
.fat entry_MV
`FAT entry: TRI`:
.fat entry_TRI
.public `FAT entry: CARD`, `FAT entry: MV`, `FAT entry: TRI`
.section CODE
.name "TRI"
entry_TRI: c=regn c
rcr 3
The FAT labels are expected to be named like `FAT entry: XX` where
XX is the name of the MCODE instruction as given to the .local
directive. Since this label contains non-alphanumeric characters, it
needs to be surrounded by back quotes.
If you expect your functions and instructions to be used from other programs or modules, you may feel that you need control the order in which the entries go into the FAT. This is possible by achieve by using different section names and by the order in which the object files are specified to the linker on the command line.
6.8. Converting .raw files¶
Since .raw files are just raw binary data, they are not human
readable. Sometimes it makes sense to convert such programs into
readable text, for inspection or editing. You can perform such
conversion using the --rpn-output option. In addition to this
option, you need to specify a .raw file. The output file is
written to the current directory, using the same base name as the
.raw file, but with a .rpn file extension instead.
Calls to external ROMs currently shows up as XROM instructions
with numbers.
If you have many .raw files you want to convert, here is a way
using xargs which is available if you are using a POSIX.2
compliant system:
$ ls path-to-raw-files/*.raw | xargs -n1 -J % rpncomp --rpn-output %
Footnotes
- 1
This means that a program which does not have line numbers but where the first instruction is a numeric constant, cannot be handled properly. In reality, this is a minor limitation as most programs start with a global alpha label.
- 2
Short jump reachability differs slightly in ROM and RAM on the HP-41. In a ROM the distance covered by a short branch is slightly longer compared to RAM. This means that while a short branch may be compiled properly in ROM, it may not be compilable when copied to RAM. This will only affect the execution speed of the program, it will otherwise work in the same way.
- 3
The
.localdirective only works when building a module, as there is no FAT in RAM memory. In order to make the RPN program work in both RAM and a module, use conditional preprocessor#ifdefon the__CALYPSI_MODULE_USE__symbol around it and provide a module exports file for RAM use, see the example.- 4
This manual work is not needed when using RPN functions, as the compiler will create the FAT entries and public labels automatically.
6.9. Merged XROM instructions¶
Some modules allow XROM instructions to have postfix operands. Such combined instructions are not supported by the HP-41 operating system, but is possible using some special techniques. There are a couple of variants available, and they rely on that the XROM instruction consumes the following step (which is an ordinary stand alone RPN instruction) and use it as an argument of some kind.
Current support for this is limited and only supports the style used by the Ladybug module.
The .postfix directive tells the RPN compiler that you have an
XROM with some kind of postfix operand. It takes a list of
instructions it applies to, followed by the operand style they use
inside parentheses:
.import ladybug
.postfix DSZI, DECI, INCI, LDI, STI (semi-merged-postfix 00)
.postfix CLRI, CB, SB, VIEWI, B? (semi-merged-postfix 00)
.postfix TST (semi-merged-postfix 00)
.postfix SL, SR, ASR, RL, RR (semi-merged-postfix 01)
.postfix RLC, RRC (semi-merged-postfix 01)
.postfix MASKL, MASKR (semi-merged-postfix 8)
.postfix SEX, WSIZE (semi-merged-postfix 16)
.postfix ALDI, BITSUM (semi-merged-postfix X)
.postfix CMP (semi-merged-postfix Y)
.postfix #LIT (semi-merged-integer-literal)
.postfix =I, ≠I, <I, <=I, <>I (semi-merged-dual)
#define integer #LIT
LBL "LADY"
WSIZE 16
integer 0xffe
<>I 01 M
STI IND 02
MASKL 4
LBL 00
VIEWI X
PSE
DSZI X
GTO 00
END
As can be seen, you still need to import the module to being in the
available instructions. The .postfix directive is used to dress
them up as instructions that actually take operands.
Note
The integer literal instruction (#LIT) starts with a hash
symbol which will cause an error from the C preprocessor if used
first on a line, as the preprocessor will try to interpret it as a
preprocessor directive.
To work around it, the #define defines a synonym integer
which expands to the #LIT instruction.
Following the postfix style, the default argument can optionally be
specified. If it is specified, the compiler can avoid generating an
explicit postfix instruction when the default will perform the same
operation. WSIZE uses an argument that is the same as the default
in this example program.
In the example VIEWI is a secondary function and gets an extra
byte that represents its function code due to this.
Dual argument functions as <>I are entered with the function name
first followed by its two operands. This is also how they are entered
from the keyboard on the HP-41.
The following list file shows the result:
###############################################################################
# #
# Calypsi RPN compiler for Hewlett-Packard Nut version 5.16 #
# 14/Apr/2026 16:42:38 #
# Command line: -l ladybug-postfix.rpn #
# #
###############################################################################
0000 .section FAT
0000 ...... .fatrpn LADY
0000 .section RPN
0000 007230 ;; COPY header
0002 1c0001 01 LBL "LADY"
0004 0f5000
0006 04c041
0008 044059
000a 1a4008 02 WSIZE
000c 1a4001 03 #LIT 0xffe
000e 1f200f 04 [15,254]
0010 0fe
0011 1a403a 05 <>I 01 M
0013 1f3004 06 [4,1,117]
0015 001075
0017 1a402c 07 STI IND 02
0019 1f1082 08 [130]
001b 1a4029 09 MASKL 04
001d 1f1004 10 [4]
001f 101 11 LBL 00
0020 1a403a 12 VIEWI X
0022 1f2005 13 [5,115]
0024 073
0025 189 14 PSE
0026 1a4031 15 DSZI X
0028 1f1073 16 "s"
002a 1b100d 17 GTO 00
002c 1c0006 18 END
002e 22f
##########################
# #
# Memory sizes (decimal) #
# #
##########################
Executable (Text): 49 words
In the generated listing, such XROM instructions are generated as two separate instructions. The postfix byte is held in alpha string literals following the actual instruction. To make it easier to read and save you from having to look it up from some table, instructions are shown together with their operands. However, close studying of the generated opcodes and line numbers reveals that they are actually two instructions from a program list point of view.