PL1CompInPy is a starter Python project for a PL/1 compiler.
The initial implementation provides a small compiler pipeline:
CALL statementsClassic PL/I does not reserve keywords globally. A word such as IF, CALL, or DECLARE
can still be used as an identifier when the surrounding syntax makes that role clear. For
that reason, PL1CompInPy keeps words as identifier tokens and attaches optional keyword
metadata from pl1compinpy.keywords.KEYWORD_CATALOG.
The current catalog covers the main language-reference families:
PROCEDURE, PROC, ENTRY, BEGIN, DO, ENDDECLARE, DCL, DEFAULT, DFT, FORMATCALL, IF, THEN, ELSE, SELECT, GO, GOTO, RETURN, STOPON, SIGNAL, REVERT, ERROR, FINISH, ENDFILE, ZERODIVIDE, and related conditionsALLOCATE, ALLOC, FREE, AUTOMATIC, STATIC, BASED, CONTROLLEDFIXED, FLOAT, BINARY, DECIMAL, CHARACTER, BIT, POINTER, PICTURE, and related aliasesOPEN, CLOSE, GET, PUT, READ, WRITE, REWRITE, LOCATE, DELETE, LIST, SKIP, KEYINCLUDE, XINCLUDE, ACTIVATE, DEACTIVATE, REPLACE, PAGE, PRINT, PUSH, POPpython -m venv .venv
source .venv/bin/activate
python -m pip install -e .
python -m pl1compinpy --help
Compile a file:
python -m pl1compinpy examples/hello.pl1
Emit assembly instead of the default Python-like output:
python -m pl1compinpy examples/hello.pl1 --target python-source
python -m pl1compinpy examples/hello.pl1 --target jvm-bytecode
python -m pl1compinpy examples/hello.pl1 --target dotnet-il
python -m pl1compinpy examples/hello.pl1 --target x586-windows
python -m pl1compinpy examples/hello.pl1 --target x86_64-windows
python -m pl1compinpy examples/hello.pl1 --target x586-macos
python -m pl1compinpy examples/hello.pl1 --target arm64-macos
python -m pl1compinpy examples/hello.pl1 --target arm64-windows
Include packaged PL/I builtin source before compiling:
python -m pl1compinpy examples/hello.pl1 --builtin SUBSTR
Create a binary executable/container artifact:
python -m pl1compinpy examples/hello.pl1 --emit binary --binary-format pe32-x586-windows -o hello.exe
python -m pl1compinpy examples/hello.pl1 --emit binary --binary-format pe64-x86_64-windows -o hello-x64.exe
python -m pl1compinpy examples/hello.pl1 --emit binary --binary-format elf64-x86_64 -o hello-x86_64.elf
python -m pl1compinpy examples/hello.pl1 --emit binary --binary-format elf64-aarch64 -o hello-aarch64.elf
python -m pl1compinpy examples/hello.pl1 --emit binary --binary-format macho64-x86_64-macos -o hello-intel-macos
python -m pl1compinpy examples/hello.pl1 --emit binary --binary-format macho64-arm64-macos -o hello-m2-macos
Create a JVM .class file using Java 17 classfile version 61:
python -m pl1compinpy examples/backend/jvm_bytecode.pl1 --emit class -o PL1Program.class
Create a .NET executable using Microsoft ILAsm:
python -m pl1compinpy examples/backend/dotnet_il.pl1 --emit dotnet-exe -o PL1Program.exe
Run tests:
python -m unittest discover -s tests
The project includes backend emitters for:
python-source: Python source codejvm-bytecode: JVM bytecode-style textual output--emit class: binary JVM .class output targeting JDK 17 classfile version 61dotnet-il: .NET Common Intermediate Language / ILAsm textual output--emit dotnet-exe: invokes ILAsm to turn generated .NET IL into a PE .exex586-windows: 32-bit x86-style assembly for Windows toolchains using C printfx86_64-windows: 64-bit x86 assembly for Windows x64 toolchains using C printfx586-macos: 32-bit x86-style assembly with macOS symbol namingarm64-macos: Apple Silicon/M2-style ARM64 assembly using macOS symbol namingarm64-windows: ARM64-style assembly with Windows symbol namingCurrently supported compiler features:
+, -, *, and /**, unary operators, *//, +/-, ||, comparisons, &, and |IF/THEN/ELSE comparisons with =, ^=, <>, <, <=, >, and >=DO WHILE pre-test loops and post-test DO ... UNTIL loopsSELECT/WHEN/OTHERWISE conditional groupsPROC OPTIONS(MAIN) as a program entry pointPROC RECURSIVE metadata, with recursive calls lowered as ordinary calls that continue at the next statement after returnPROC RETURNS(...) metadata for function return typeCALL DISPLAY(...), CALL PRINT(...), and basic PUT LIST(...)The emitters generate readable assembler text. They are intentionally small and direct so the runtime calling conventions and target-specific prologues can be refined as the compiler grows.
The binary writer now follows the compiler pipeline:
PL/1 source -> lexer -> parser -> AST -> executable mnemonics -> machine code -> binary container
For example, a PL/1 assignment such as TOTAL = 40 + 2; is lowered into mnemonic operations
such as MOV_EAX_IMM, PUSH_EAX, POP_EBX, ADD_EAX_EBX, and STORE_EAX_VAR, then encoded
as machine-code bytes before being placed in the executable file.
The binary writer currently creates minimal executable/container files with correct platform signatures and source-derived starter code:
pe32-x586-windows: Windows PE32 .exe format for 32-bit x86/x586pe64-x86_64-windows: Windows PE32+ .exe format for x86_64/AMD64elf64-x86_64: ELF64 executable container for Intel/AMD 64-bit Unix-style systemself64-aarch64: ELF64 executable container for ARM64/AArch64 Unix-style systemsmacho64-x86_64-macos: Mach-O 64-bit executable container for Intel macOSmacho64-arm64-macos: Mach-O 64-bit executable container for Apple Silicon/M2 macOSmacOS uses Mach-O, not ELF. ELF is provided for Unix-style targets; Apple Intel and M2 targets use Mach-O containers.
The binary layer exposes explicit PE, ELF, and Mach-O linker classes in pl1compinpy.codegen.linkers.
The pe32-x586-windows and pe64-x86_64-windows paths include source-driven instruction encoding
for starter arithmetic and exit code. The ELF and Mach-O paths use the same mnemonic pipeline and
have starter encoders for Intel and ARM64 machine code, ready to be expanded with full runtime I/O
and platform linker details.
The executable pipeline includes a first runtime calling convention:
CALL P(B,A) BY NAME; is normalized by matching argument names to P's parameter names, sorting them into parameter order, and lowering the result as a by-reference callDCL name BUILTIN; before calls are acceptedCALL validation uses the merged runtime and dynamic tables before loweringpl1rt_init/pl1rt_shutdown, then the final executable is resolved against a PL/I runtime object/archive or shared/import library plus the platform C runtimepl1compinpy/runtime/PL1Runtime class on the classpathPL1CompInPy.Runtime managed assemblyThe runtime also includes starter storage and I/O services:
DCL A(10) FIXED BIN(31);FLOAT declarations initialized as floating-point values in Python outputPICTURE/PIC decimal display patterns using digit positions such as 9, zero-suppressed Z, stored decimal ., and implied decimal VPOINTER locator variables and BASED(pointer) record storage bound to heap blocks through pointer valuesSUBSTR(string, start[, length])DCL SUBSTR BUILTIN;DCL F FILE RECORD OUTPUT ENVIRONMENT(RECFM(V), LRECL(80), PATH('out.dat')) BINARY;OPEN FILE(F);, READ FILE(F) INTO(BUF);, WRITE FILE(F) FROM(BUF);, and CLOSE FILE(F);RECFM(F) and LRECL(n)RECFM(V), represented here with a two-byte big-endian length prefix followed by record dataREAD/WRITE, with Unix-style, fixed, and variable record framingALLOC, FREE, file I/O, VSAM I/O, TCP/IP, SSL, and TLS helpersOPEN, WRITE FROM, KSDS keyed READ, ESDS RBA READ, RRDS RRN READ/WRITE, LDS RBA/LENGTH READ, and CLOSEPL1CompInPy/
pyproject.toml
docs/
API.md
scripts/
generate_api_docs.py
src/pl1compinpy/
builtins/
loader.py
pl1/
substr.pl1
cli.py
codegen/
backends.py
binary_formats.py
dotnet_executable.py
dotnet_il.py
executable_pipeline.py
jvm_bytecode.py
python_source.py
compiler.py
core/
ast.py
compiler.py
frontend/
keywords.py
lexer.py
parser.py
runtime/
arrays.py
based.py
calculation.py
calling.py
function_table.py
heap.py
io.py
picture.py
socket_io.py
strings.py
vsam/
catalog.py
io.py
tests/