HGPPascal can generate either C99 or a textual Pascal P-Code IR. The P-Code target is intended for a future VM/interpreter backend and for debugging the compiler pipeline.
Generate P-Code:
clojure -M:run examples/hello.pas --target pcode -o out/hello.pcode
clojure -M:run examples/pointer_new_dispose.pas --target pcode -o out/pointer_new_dispose.pcode
clojure -M:run examples/object_methods.pas --target pcode -o out/object_methods.pcode
clojure -M:run examples/higher_order_lambda.pas --target pcode -o out/higher_order_lambda.pcode
The output is a stack-machine text format:
PROGRAM hello
GLOBAL i : integer
PROC main 0
PUSHI 1
STORE i
SYS WRITELN 0
SYS HALT 0
ENDPROC
ENDPROGRAM
Expressions push values to the stack. Statements consume values from the stack.
Labels are local textual symbols generated by the backend.
Generated P-Code also includes debug symbol-table comments near the top of the
file so backend tests and future VM tools can inspect types, variables, and
call-table entries. For object types those comments include object-space,
object-self, object-field, object-method, and object-call rows so a
future optimizer or VM can use scoped metadata instead of reconstructing
method dispatch from flattened names.
PUSHI, PUSHR, PUSHB, PUSHS push integer, real, boolean, and string values.LOAD name and STORE name read/write scalar variables.ADD, SUB, MUL, DIVR, DIVI, MOD, NEG implement arithmetic.AND, OR, NOT implement boolean operations.CEQ, CNE, CLT, CLE, CGT, CGE implement comparisons.JMP label and JZ label implement control flow.CALL name argc, RET, and DROP implement routines.PROC name argc RETURNS type: marks a function procedure with a return type.function(arg:type):return: represents function-valued type metadata.These instructions are explicit extensions so the compiler can represent Turbo Pascal-style source without pretending the historical Borland P-Code format is being cloned:
SYS name argc: runtime service call for write, writeln, readln, and halt.SYS NEW argc and SYS DISPOSE argc: pointer allocation and disposal.ADDR name: pushes a scalar variable address for input/runtime calls.LOAD_INDEX_VALUE: loads an array element using an address/index pair.ADDR_INDEX_VALUE: pushes an array element address using adjusted indexes.STORE_AT: stores through an address produced by selector code.LOAD_FIELD, ADDR_FIELD, DEREF, and DEREF_ADDR: record and pointer operations.STORE_ENV object_name and RESTORE_ENV object_name: object-method environment frame tags.MAKE_CLOSURE name capturec and CAPTURE name: create a function value with an explicit capture list.SSTD_STORE_ENV name, APPLY argc, and SSTD_RESTORE_ENV name: invoke a function value through a Stack/Environment/Call/Dump-style frame.SSTD_ENTER name and SSTD_LEAVE name: mark the body context of a generated lambda procedure.SSTD_CALL name argc: marks a procedure/function call through the SSTD call boundary.SSTD_CALL_LAMBDA name argc and SSTD_CALL_LAMBDA_ONE name n: mark lambda invocation and one-argument curry/apply steps.SSTD_RETURN_VALUE name: marks restoration of the caller frame with the returned stack value.NUMERIC name argc: invokes the numeric compute engine for runtime math functions such as SQRT, POW, and ROUND.HEAP_ALLOC argc and HEAP_FREE argc: allocate and release runtime heap objects for pointer targets such as records.HEAP_LOAD and HEAP_STORE: reserved heap value operations for future P-Code hosts.RUNTIME_BLOCK hgppascal_runtime: attaches SECD, SSTD, NUMERIC, and HEAP runtime exports to the P-Code artifact.Array index code subtracts the Pascal lower bound before indexed access, so
array[1..3] of integer keeps Pascal source semantics while still mapping to
zero-based runtime storage.
Turbo Pascal 5.5-style object methods are represented with normal P-Code naming conventions: type and method names are normalized with underscores, and tags remain uppercase.
TYPE counter = object{value:integer; procedure init/1; function current/0:integer}
PROC counter_init 2
PARAM self : ^counter value
PARAM start : integer value
LOAD start
ADDR self
DEREF_ADDR
ADDR_FIELD value
STORE_AT
RET
ENDPROC
PROC main 0
ADDR c
PUSHI 10
STORE_ENV counter
CALL counter_init 2
RESTORE_ENV counter
ENDPROC
The environment tags intentionally bracket the CALL instruction. Argument and
receiver setup stays regular stack-machine code, while the actual cross-object
method dispatch is explicit and easy for a future VM to validate.
Function-valued type aliases and lambda expressions use the same textual naming
style as routines, with generated names such as hgp_lambda_1.
TYPE intunary = function(x:integer):integer
PROC hgp_lambda_1 1 RETURNS integer
PARAM x : integer value
SSTD_ENTER hgp_lambda_1
LOAD_ENV base
LOAD x
ADD
SSTD_LEAVE hgp_lambda_1
RET
ENDPROC
PROC main 0
MAKE_CLOSURE hgp_lambda_1 1
CAPTURE base
STORE incby
LOAD incby
PUSHI 5
SSTD_CALL_LAMBDA_ONE incby 1
SSTD_CALL_LAMBDA incby 1
SSTD_STORE_ENV incby
APPLY 1
SSTD_RESTORE_ENV incby
SSTD_RETURN_VALUE incby
ENDPROC
The SSTD_* tags keep the future VM boundary explicit: stack, captured
environment, call target, and dump/return context can be optimized or validated
without changing the Pascal front-end syntax.
Every P-Code program declares the runtime engines it expects:
RUNTIME_BLOCK hgppascal_runtime
RUNTIME_ENGINE SECD
RUNTIME_ENGINE SSTD
RUNTIME_ENGINE NUMERIC
RUNTIME_ENGINE HEAP
RUNTIME_EXPORT SSTD_CALL
RUNTIME_EXPORT SSTD_CALL_LAMBDA
RUNTIME_EXPORT SSTD_RETURN_VALUE
RUNTIME_EXPORT NUMERIC
RUNTIME_EXPORT HEAP_ALLOC
RUNTIME_EXPORT HEAP_FREE
RUNTIME_EXPORT HEAP_LOAD
RUNTIME_EXPORT HEAP_STORE
ENDRUNTIME
Numeric calls lower to stack-machine arguments followed by a NUMERIC tag:
PUSHI 81
NUMERIC SQRT 1
PUSHI 2
PUSHI 5
NUMERIC POW 2
A P-Code executable host can attach this block to an executable and use it to route runtime calls into the SECD/SSTD/Numeric runtime.
Heap allocation is explicit around new/dispose:
ADDR cp
HEAP_ALLOC 1
SYS NEW 1
ADDR cp
HEAP_FREE 1
SYS DISPOSE 1
The heap engine is intended to retain allocated records and future aggregate descriptors as inspectable runtime objects.
The P-Code backend is covered by examples for arrays, records, objects,
pointers, runtime calls, user routines, higher-order lambdas, numeric promotion,
and control flow. See docs/EXAMPLES.md for the full list.
Harald Glab-Plhak
Computer Science since 1992
© Harald Glab-Plhak (2026)