HGPPascal Pascal P-Code

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

Format

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.

Core Instructions

HGPPascal Extensions

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:

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.

Object Method Calls

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.

Functional Closures

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.

Runtime Block And Numeric Engine

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.

Example Coverage

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.