Skip to content

πŸ•ŠοΈPax is a minimal systems programming language emphasizing simplicity and self-containment. Pascal/Oberon heritage with modern conveniences: managed strings, automatic memory management, and zero external dependencies.

License

Notifications You must be signed in to change notification settings

tinyBigGAMES/PaxLang

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

47 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Pax

Discord Follow on Bluesky

A minimal systems programming language that compiles to native executables via C99.

What is Pax?

The Pax Programming Language is a Pascal/Oberon-inspired systems programming language targeting Win64. It compiles to C via TinyCC with a completely self-contained toolchain - no external dependencies required. Memory is automatically managed via the Boehm-Demers-Weiser Garbage Collector. Configuration is handled through TOML files powered by DX.TOML, providing a clean, readable format for build settings, C header import definitions, and general-purpose application configuration.

module exe HelloWorld;

routine printf(const fmt: pointer to char; ...): int32; external 'msvcrt.dll';

var
  name: string;

begin
  name := 'Pax';
  printf('Hello from %s!\n', pointer to char(name));
  printf('GC heap: %lld bytes\n', gc_heapsize());
end.

✨ Key Features

  • 🎯 Minimal by design - Clean syntax, no redundancy, just what you need
  • πŸ“– Pascal heritage - Readable, structured code inspired by Pascal and Oberon
  • 🧹 Automatic memory - Boehm GC handles allocation and cleanup
  • πŸ“¦ Self-contained - Embedded TinyCC toolchain, single executable distribution
  • πŸͺŸ Windows native - Call any DLL directly, full Windows API access
  • πŸŽ›οΈ Multiple outputs - Build executables, DLLs, or static libraries
  • ⚑ JIT compilation - Compile and execute code in memory without generating files
  • 🧬 Type extension - Record inheritance without class complexity
  • πŸ›οΈ Classes - Methods, inheritance, and virtual dispatch
  • πŸ”€ Union types - C-compatible unions with anonymous nesting
  • πŸ“Š Dynamic arrays - setlength/len with automatic memory management
  • πŸ“ Managed strings - UTF-8 and UTF-16 string types with emoji support
  • πŸ”Œ Varargs support - Full C interop plus native Pax variadic routines
  • πŸ”„ Routine overloading - Multiple routines with same name, different signatures
  • πŸ“š Standard library - Console, Maths, Strings, Convert, and more
  • πŸ§ͺ Built-in testing - Integrated unit test framework
  • ⚠️ Exception handling - try/except/finally with OS exception support
  • 🏷️ Version info - Embed metadata and icons in executables
  • πŸ”„ C header import - Convert C headers to Pax modules automatically
  • βš™οΈ TOML configuration - General-purpose configuration file support

πŸ“˜ Language Overview

Built-in Types

Type Size Description
int8, int16, int32, int64 1-8 bytes Signed integers
uint8, uint16, uint32, uint64 1-8 bytes Unsigned integers
float32, float64 4-8 bytes Floating point
boolean 1 byte true / false
char, uchar 1 byte Signed/unsigned characters
wchar, uwchar 2 bytes Wide characters (UTF-16)
string, wstring 8 bytes Managed strings (UTF-8 / UTF-16)
pointer, pointer to T 8 bytes Untyped / typed pointers

Module Types

Type Description
module exe Name Executable program
module lib Name Static library (.a)
module dll Name Dynamic library (.dll)
module jit Name JIT compilation (compile and run in memory)

Module Imports

Use import to access routines and types from other modules:

module exe MyApp;

import Console;
import Maths, Strings;

begin
  Console.PrintLn('The square root of 2 is: %g', Maths.Sqrt(2.0));
  Console.PrintLn('Uppercase: %s', Strings.UpperCase('hello'));
end.

Imported symbols are accessed with qualified names (ModuleName.Symbol). The compiler automatically resolves module dependencies and links the required libraries.

Operators

Arithmetic and Comparison

+ - * /           // Arithmetic
div mod           // Integer division and modulo
= <> < > <= >=    // Comparison
and or not        // Logical
in                // Set membership

Compound Assignment

var
  n: int32;
  s: string;
  days: set of 0..6;

begin
  n := 10;
  n += 5;         // n = n + 5
  n -= 3;         // n = n - 3
  n *= 2;         // n = n * 2
  n /= 4;         // n = n / 4 (integer division)
  
  s := 'Hello';
  s += ' World';  // String concatenation
  
  days := {1, 2};
  days += {3};    // Set union
  days -= {1};    // Set difference
end.

Type Aliases

type
  THandle = uint64;
  TSize = int64;
  TFileHandle = THandle;  // Alias of alias
  
var
  h: THandle;
  fh: TFileHandle;

begin
  h := 12345;
  fh := h;        // Compatible assignment
end.

Records and Extension

type
  TPoint = record
    x: int32;
    y: int32;
  end;

  TColorPoint = record(TPoint)  // Inherits from TPoint
    color: uint32;
  end;

var
  p: TColorPoint;

begin
  p.x := 100;         // Inherited from TPoint
  p.y := 200;         // Inherited from TPoint
  p.color := $FF0000;
end.

Classes

Classes provide object-oriented programming with methods and virtual dispatch:

type
  TCounter = class
    Count: int32;
    Name: string;
    
    method Reset();
    begin
      Self.Count := 0;
    end;
    
    method Increment();
    begin
      Self.Count := Self.Count + 1;
    end;
    
    method GetCount(): int32;
    begin
      return Self.Count;
    end;
  end;

var
  counter: pointer to TCounter;

begin
  new(counter);
  counter.Reset();
  counter.Increment();
  printf('Count: %d\n', counter.GetCount());  // Count: 1
  dispose(counter);
end.

Inheritance and Parent Calls

Classes support single inheritance with method overriding:

type
  TAnimal = class
    method Speak();
    begin
      printf('Animal speaks\n');
    end;
  end;
  
  TDog = class(TAnimal)
    method Speak();
    begin
      parent.Speak();        // Call parent's implementation
      printf('Dog barks!\n');
    end;
  end;

var
  dog: pointer to TDog;

begin
  new(dog);
  dog.Speak();  // Prints: Animal speaks / Dog barks!
end.

Method Overloading

Class methods can be overloaded with different parameter types or counts:

type
  TCalculator = class
    Value: int32;
    
    method SetVal(const n: int32);
    begin
      Self.Value := n;
    end;
    
    method SetVal(const f: float64);
    begin
      Self.Value := int32(f);
    end;
    
    method SetVal(const a: int32; const b: int32);
    begin
      Self.Value := a + b;
    end;
  end;

var
  calc: pointer to TCalculator;

begin
  new(calc);
  calc.SetVal(100);        // Calls SetVal(int32)
  calc.SetVal(3.14);       // Calls SetVal(float64)
  calc.SetVal(25, 75);     // Calls SetVal(int32, int32)
  dispose(calc);
end.

Key features:

  • Self references the current instance
  • parent calls the parent class's implementation (non-virtual dispatch)
  • Method calls on instances use virtual dispatch via automatic vtable
  • Fields are inherited from parent classes
  • Methods can be overloaded with different signatures

Routine Overloading

Multiple routines can share the same name with different parameter types or counts. The compiler selects the correct overload based on argument types:

// Overloaded Print routines - different parameter types
routine Print(const s: pointer to char);
begin
  printf('String: %s\n', s);
end;

routine Print(const n: int32);
begin
  printf('Int32: %d\n', n);
end;

routine Print(const n: int64);
begin
  printf('Int64: %lld\n', n);
end;

routine Print(const f: float64);
begin
  printf('Float: %f\n', f);
end;

routine Print(const b: boolean);
begin
  if b then
    printf('Boolean: true\n');
  else
    printf('Boolean: false\n');
  end;
end;

// Overloaded Add routines - different parameter counts
routine Add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

routine Add(const a: int32; const b: int32; const c: int32): int32;
begin
  return a + b + c;
end;

// Overloaded Add - different types, same count
routine Add(const a: float64; const b: float64): float64;
begin
  return a + b;
end;

begin
  Print('Hello');           // Calls Print(pointer to char)
  Print(42);                // Calls Print(int32)
  Print(int64(100));        // Calls Print(int64)
  Print(3.14);              // Calls Print(float64)
  Print(true);              // Calls Print(boolean)
  
  printf('%d\n', Add(1, 2));       // Calls Add(int32, int32) -> 3
  printf('%d\n', Add(1, 2, 3));    // Calls Add(int32, int32, int32) -> 6
  printf('%f\n', Add(1.5, 2.5));   // Calls Add(float64, float64) -> 4.0
end.

Overload resolution rules:

  • Exact type matches are preferred
  • Parameter count must match
  • Return types can differ between overloads

Packed Records and Alignment

type
  // Packed record - no padding between fields
  THeader = record packed
    magic: uint16;
    version: uint8;
    flags: uint8;
  end;

  // Explicit alignment for SIMD or cache optimization
  TAlignedData = record align(16)
    values: array[0..3] of float32;
  end;

  // Bit fields for compact storage
  TFlags = record packed
    enabled: uint8 : 1;   // 1 bit
    priority: uint8 : 3;  // 3 bits
    reserved: uint8 : 4;  // 4 bits
  end;

Union Types

type
  // All fields share the same memory location
  TValue = union
    asInt: int64;
    asFloat: float64;
    asPtr: pointer;
  end;

  // Record with anonymous union (C-style variant)
  TVariant = record
    kind: int32;
    union
      intVal: int64;
      floatVal: float64;
      strVal: pointer to char;
    end;
  end;

Dynamic Arrays

var
  numbers: array of int32;
  i: int32;

begin
  setlength(numbers, 10);
  
  for i := 0 to len(numbers) - 1 do
    numbers[i] := i * 2;
  end;
end.

Open Array Parameters

Routines can accept dynamic arrays as parameters:

// Const array parameter (read-only)
routine SumArray(const arr: array of int32): int32;
var
  i: int32;
  total: int32;
begin
  total := 0;
  for i := 0 to len(arr) - 1 do
    total := total + arr[i];
  end;
  return total;
end;

// Var array parameter (can modify)
routine FillArray(var arr: array of int32; const value: int32);
var
  i: int32;
begin
  for i := 0 to len(arr) - 1 do
    arr[i] := value;
  end;
end;

var
  nums: array of int32;
  sum: int32;

begin
  setlength(nums, 5);
  FillArray(nums, 10);      // Fill with 10s
  sum := SumArray(nums);    // sum = 50
end.

Sets

type
  TDays = set of 0..6;

var
  weekdays: TDays;
  weekend: TDays;
  alldays: TDays;
  today: int32;

begin
  weekdays := {1, 2, 3, 4, 5};  // Mon-Fri
  weekend := {0, 6};            // Sun, Sat
  
  alldays := weekdays + weekend; // Union
  weekdays := alldays - weekend; // Difference
  
  today := 3;
  if today in weekdays then
    // It's a weekday
  end;
end.

Enumerations

type
  TColor = (
    clRed,
    clGreen,
    clBlue
  );

  TErrorCode = (
    Success = 0,
    NotFound = 404,
    ServerError = 500
  );
  
  TPriority = (
    Low,           // 0 (auto)
    Medium = 5,    // 5 (explicit)
    High,          // 6 (auto continues)
    Critical = 10  // 10 (explicit)
  );

var
  color: TColor;
  err: TErrorCode;
  priority: TPriority;
  val: int32;

begin
  color := clGreen;
  err := NotFound;
  
  // Enum comparison
  if priority > Low then
    // Higher priority
  end;
  
  // Enum in case statement
  case err of
    Success:
      printf('OK\n');
    NotFound:
      printf('Not found\n');
    ServerError:
      printf('Server error\n');
  end;
  
  // Enum to integer cast
  val := int32(color);  // val = 1 (clGreen)
  
  // Integer to enum cast
  color := TColor(2);   // color = clBlue
end.

Control Flow

var
  i: int32;
  sum: int32;
  n: int32;
  ch: char;

begin
  // If-then-else
  if n > 10 then
    sum := 1;
  else
    sum := 0;
  end;
  
  // While loop
  while i < 10 do
    i := i + 1;
  end;
  
  // For loop (ascending)
  for i := 1 to 10 do
    sum := sum + i;
  end;
  
  // For loop (descending)
  for i := 10 downto 1 do
    sum := sum + i;
  end;
  
  // Repeat-until
  repeat
    i := i + 1;
  until i >= 10;
  
  // Case statement with ranges
  case n of
    0: sum := 0;
    1..10: sum := 1;
    11..100: sum := 2;
  else
    sum := -1;
  end;
  
  // Case with character ranges
  case ch of
    'a'..'z': sum := 1;
    'A'..'Z': sum := 2;
    '0'..'9': sum := 3;
  else
    sum := 0;
  end;
end.

Exception Handling

Pax provides structured exception handling using try/except/finally blocks. Both software exceptions (raised by your code) and OS exceptions (access violations, divide by zero) are caught.

var
  result: int32;

begin
  // Basic try-except
  try
    raiseexception('Something went wrong');
    result := 1;  // Never reached
  except
    result := 0;  // Exception caught
  end;
  
  // Try-finally (cleanup always runs)
  try
    result := DoSomething();
  finally
    Cleanup();  // Always executes
  end;
  
  // Try-except-finally combined
  try
    riskyOperation();
  except
    handleError();
  finally
    cleanup();  // Runs whether exception occurred or not
  end;
end.

Raising Exceptions

begin
  // Raise with message (code defaults to 1)
  raiseexception('File not found');
  
  // Raise with custom code and message
  raiseexceptioncode(404, 'Resource not found');
end.

Exception Intrinsics

begin
  try
    raiseexceptioncode(42, 'Custom error');
  except
    printf('Code: %d\n', getexceptioncode());     // 42
    printf('Message: %s\n', pointer to char(getexceptionmessage()));  // Custom error
  end;
end.

Catching OS Exceptions

OS-level exceptions are automatically caught via Windows Vectored Exception Handling:

var
  p: pointer to int32;
  a, b, c: int32;

begin
  // Null pointer dereference
  try
    p := nil;
    p^ := 42;  // Access violation!
  except
    printf('Caught: 0x%08X\n', getexceptioncode());  // 0xC0000005
  end;
  
  // Integer divide by zero
  try
    a := 10;
    b := 0;
    c := a div b;  // Divide by zero!
  except
    printf('Caught: 0x%08X\n', getexceptioncode());  // 0xC0000094
  end;
end.

Common Exception Codes

Code Description
1 Default software exception
0xC0000005 Access violation (null pointer, invalid memory)
0xC0000094 Integer divide by zero
0xC0000095 Integer overflow
0xC00000FD Stack overflow

Routine Types (Function Pointers)

type
  TIntFunc = routine(const a: int32; const b: int32): int32;
  TCallback = routine(const x: int32): int32;

routine Add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

routine Multiply(const a: int32; const b: int32): int32;
begin
  return a * b;
end;

// Higher-order function
routine Apply(const fn: TIntFunc; const x: int32; const y: int32): int32;
begin
  return fn(x, y);
end;

var
  mathOp: TIntFunc;
  result: int32;

begin
  mathOp := Add;
  result := mathOp(10, 5);     // 15
  
  mathOp := Multiply;
  result := mathOp(10, 5);     // 50
  
  result := Apply(Add, 3, 4);  // 7
end.

Strings and Raw Strings

var
  path: string;
  msg: wstring;

begin
  // Normal strings - backslashes are escape characters
  path := 'C:\\Users\\Name\\file.txt';
  
  // Raw strings - no escape processing (great for paths)
  path := @'C:\Users\Name\file.txt';
  
  // Wide strings for Windows API (UTF-16)
  msg := L'Hello World!';
  
  // Raw wide strings
  msg := @L'C:\Path\To\File';
end.

String Literal Concatenation

String literals can be concatenated with the + operator:

var
  path: string;
  wide: wstring;

begin
  // Raw string + regular string
  path := @'C:\Users\' + 'Admin';
  
  // Wide string concatenation
  wide := L'Hello' + L'World';
  
  // Raw wide + regular wide
  wide := @L'C:\Path\' + L'File';
end.

String Comparison

Strings can be compared for equality using = and <>. These operators compare string contents, not pointers:

var
  s1: string;
  s2: string;

begin
  s1 := 'Hello';
  s2 := 'Hello';
  
  if s1 = s2 then
    printf('Strings are equal\n');  // This prints
  end;
  
  s2 := 'World';
  if s1 <> s2 then
    printf('Strings are different\n');  // This prints
  end;
end.

Pointers and Const Pointers

routine wprintf(const fmt: pointer to const wchar; ...): int32; external 'msvcrt.dll';

var
  value: int32;
  ptr: pointer to int32;
  constPtr: pointer to const char;

begin
  value := 42;
  ptr := address of value;  // Get pointer to value
  ptr^ := 100;              // Dereference and assign
  // value is now 100
end.

Pointer Arithmetic

Pointers support addition and subtraction for traversing memory:

var
  data: pointer to char;
  buffer: array of char;
  i: int32;
  diff: int64;
  start: pointer to char;
  end_ptr: pointer to char;

begin
  setlength(buffer, 100);
  data := pointer to char(buffer);
  
  // Pointer + integer: advance by N elements
  data := data + 10;      // Skip 10 bytes
  
  // Pointer - integer: move back by N elements
  data := data - 5;       // Move back 5 bytes
  
  // Pointer - pointer: get distance between pointers
  start := pointer to char(buffer);
  end_ptr := start + 50;
  diff := end_ptr - start;  // diff = 50
  
  // Common use: iterate through buffer
  data := pointer to char(buffer);
  for i := 0 to 99 do
    data^ := char(i);
    data := data + 1;
  end;
end.

External Routines

Call Windows DLL functions with simple declarations - no binding libraries required:

module exe WinAPI;

routine MessageBoxW(hwnd: pointer; text: pointer to wchar; 
  caption: pointer to wchar; utype: uint32): int32; external 'user32.dll';

begin
  MessageBoxW(nil, L'Hello from Pax!', L'Pax', 0);
end.

External Name Aliasing

Use the name clause to map a Pax routine name to a different DLL function name:

// Map Pax name to C runtime function name
routine c_exit(const status: int32); external 'msvcrt.dll' name 'exit';
routine c_sqrt(const x: float64): float64; external 'msvcrt.dll' name 'sqrt';
routine c_printf(const fmt: pointer to char; ...): int32; external 'msvcrt.dll' name 'printf';

begin
  c_printf('Square root of 2: %f\n', c_sqrt(2.0));
  c_exit(0);
end.

This is useful when:

  • The C function name conflicts with a Pax reserved word
  • You want a more descriptive name in your Pax code
  • You need to follow naming conventions (e.g., c_ prefix for C runtime functions)

Linking Libraries

For custom DLLs or when linking multiple functions from the same library, use #library:

module exe CustomDLL;

#library 'mylib'

// Routines declared without DLL name - linked via #library
routine MyFunction(const value: int32): int32; external;
routine MyOtherFunction(const msg: pointer to char); external;

begin
  MyOtherFunction('Hello');
end.

Variadic Routines

External Varargs (C Interop)

Call C functions that accept variable arguments:

routine printf(const fmt: pointer to char; ...): int32; external 'msvcrt.dll';

begin
  printf('Hello %s, you are %d years old\n', 'World', 42);
end.

Native Pax Varargs

Pax routines can also accept variable arguments using the ... syntax. Inside the routine, use the built-in VarArgs variable to access arguments:

// Variadic routine with no fixed parameters
routine SumAll(...): int64;
var
  i: int32;
  total: int64;
begin
  total := 0;
  for i := 0 to len(VarArgs) - 1 do
    total := total + VarArgs[i].vint;
  end;
  return total;
end;

// Variadic routine with fixed parameters
routine PrintValues(const prefix: string; ...);
var
  i: int32;
  arg: TVarArg;
begin
  printf('%s:', pointer to char(prefix));
  for i := 0 to len(VarArgs) - 1 do
    arg := VarArgs[i];
    case arg.vtype of
      vtInt64: printf(' int=%lld', arg.vint);
      vtFloat64: printf(' float=%g', arg.vfloat);
      vtString: printf(' str=%s', pointer to char(string(arg.vptr)));
    else
      printf(' ?');
    end;
  end;
  printf('\n');
end;

begin
  printf('Sum: %lld\n', SumAll(10, 20, 30, 40));  // Sum: 100
  PrintValues('Mixed', int64(42), 3.14, 'hello');  // Mixed: int=42 float=3.14 str=hello
end.

TVarArg Record

Each variadic argument is a TVarArg record with these fields:

Field Type Description
vtype int32 Type tag indicating the argument type
vint int64 Value for integer types
vfloat float64 Value for floating point types
vptr pointer Value for pointers, strings, arrays, records

Type Tag Constants

Constant Value Types
vtInt8 0 int8
vtInt16 1 int16
vtInt32 2 int32
vtInt64 3 int64
vtUInt8 4 uint8
vtUInt16 5 uint16
vtUInt32 6 uint32
vtUInt64 7 uint64
vtFloat32 8 float32
vtFloat64 9 float64
vtBool 10 boolean
vtChar 11 char
vtUChar 12 uchar
vtWChar 13 wchar
vtUWChar 14 uwchar
vtString 15 string
vtWString 16 wstring
vtPointer 17 pointer
vtArray 18 arrays
vtRecord 19 records
vtClass 20 classes
vtUnion 21 unions
vtSet 22 sets
vtEnum 23 enums
vtRoutine 24 routine types

GC Intrinsics

begin
  // Force garbage collection
  gc_collect();
  
  // Query heap statistics
  printf('Heap size: %lld bytes\n', gc_heapsize());
  printf('Used: %lld bytes\n', gc_usedsize());
  printf('GC cycles: %lld\n', gc_collectcount());
  
  // Dump detailed GC stats (debug builds)
  gc_dump();
end.

Command Line Arguments

var
  i: int32;
  arg: string;

begin
  printf('Arguments: %d\n', paramcount());
  
  for i := 0 to paramcount() do
    arg := paramstr(i);
    printf('  [%d] %s\n', i, pointer to char(arg));
  end;
end.

Conditional Compilation

#define DEBUG
#define VERSION 100

begin
  #ifdef DEBUG
  printf('Debug mode enabled\n');
  #endif
  
  #ifndef RELEASE
  printf('Not a release build\n');
  #endif
  
  #if VERSION >= 100
  printf('Version 100 or higher\n');
  #elif VERSION >= 50
  printf('Version 50-99\n');
  #else
  printf('Old version\n');
  #endif
  
  #undef DEBUG
end.

PAX Compiler Constants

begin
  // Available as both preprocessor macros and runtime constants
  printf('Pax version: %s\n', PAX_VERSION_STR);
  printf('Major: %d\n', PAX_MAJOR_VERSION);
  printf('Minor: %d\n', PAX_MINOR_VERSION);
  printf('Patch: %d\n', PAX_PATCH_VERSION);
  printf('Combined: %d\n', PAX_VERSION);
  
  // Preprocessor detection
  #ifdef PAX
  printf('Compiled with Pax\n');
  #endif
end.

Unit Testing

module exe MyTests;

#unittestmode on

routine Add(const a: int32; const b: int32): int32;
begin
  return a + b;
end;

routine IsPositive(const a: int32): boolean;
begin
  return a > 0;
end;

begin
  // Normal entry point (skipped in test mode)
end.

test 'Addition works correctly'
begin
  TestAssertEqualInt(5, Add(2, 3));
  TestAssertEqualInt(0, Add(-1, 1));
end;

test 'Boolean assertions'
begin
  TestAssertTrue(IsPositive(5));
  TestAssertFalse(IsPositive(-5));
end;

test 'Pointer and allocation assertions'
var
  p: pointer to int32;
begin
  p := nil;
  TestAssertNil(p);
  
  new(p);
  TestAssertNotNil(p);
  p^ := 42;
  TestAssertEqualInt(42, p^);
end;

Version Info and Icons

module exe MyApp;

#subsystem gui
#addverinfo yes
#vimajor 1
#viminor 0
#vipatch 0
#viproductname 'My Application'
#videscription 'A sample Pax application'
#vicompanyname 'My Company'
#vicopyright 'Copyright 2025'
#exeicon @'assets\app.ico'

begin
  // Application code
end.

JIT Compilation

JIT modules compile and execute directly in memory without generating executable files. This is useful for scripting, rapid prototyping, or embedding Pax as a scripting language in applications.

module jit Calculator;

routine printf(const fmt: pointer to char; ...): int32; external 'msvcrt.dll';

var
  x: int32;
  y: int32;

begin
  x := 10;
  y := 20;
  printf('=== JIT Module ===\n');
  printf('x = %d, y = %d\n', x, y);
  printf('x + y = %d\n', x + y);
  printf('x * y = %d\n', x * y);
end.

JIT modules use the same language features as regular executables but run immediately after compilation. The paxrtl.dll runtime must be available in the host application's working directory.

πŸ“š Standard Library

Pax includes a standard library of modules for common tasks. Import them with import ModuleName; and access symbols with qualified names.

Console Module

Console I/O with ANSI color support and cursor control.

import Console;

begin
  // Basic output
  Console.Print('Hello ');           // No newline
  Console.PrintLn('World!');         // With newline
  
  // Colored output
  Console.Print('%s%sError:%s ', Console.clRed, Console.clBold, Console.clReset);
  Console.PrintLn('Something went wrong');
  
  // Cursor control
  Console.ClearScreen();
  Console.SetCursorPos(10, 5);       // Move to column 10, row 5
  Console.HideCursor();
  Console.ShowCursor();
  
  // Input
  Console.Pause();                   // Wait for Enter
  Console.Pause('Press Enter...');   // Custom message
end.

Color Constants:

Constant Description
clReset Reset all attributes
clBold Bold text
clDim Dim text
clItalic Italic text
clUnderline Underlined text
clBlack, clRed, clGreen, clYellow, clBlue, clMagenta, clCyan, clWhite Foreground colors
clBrightBlack, clBrightRed, ... Bright foreground colors
clBgBlack, clBgRed, ... Background colors

Routines:

Routine Description
Print(fmt, ...) Print formatted string
PrintLn(fmt, ...) Print with newline
ReadChar(): char Read single character
ReadLn() Wait for Enter
Pause() Press Enter to continue
ClearScreen() Clear terminal
ClearLine() Clear current line
SetCursorPos(x, y) Move cursor (1-based)
HideCursor() / ShowCursor() Toggle cursor visibility
SetFgColor(n) / SetBgColor(n) Set 256-color palette
SetFgRGB(r, g, b) / SetBgRGB(r, g, b) Set RGB color
ResetColors() Reset to defaults

Maths Module

Mathematical functions and utilities.

import Maths;

var
  angle: float64;
  x: float64;

begin
  // Constants
  x := Maths.PI;         // 3.14159...
  x := Maths.E;          // 2.71828...
  
  // Basic math
  x := Maths.Abs(-5);           // 5
  x := Maths.Sqr(4.0);          // 16.0
  x := Maths.Sqrt(16.0);        // 4.0
  x := Maths.Power(2.0, 10.0);  // 1024.0
  
  // Trigonometry
  angle := Maths.DegToRad(45.0);
  x := Maths.Sin(angle);
  x := Maths.Cos(angle);
  x := Maths.Tan(angle);
  
  // Rounding
  x := Maths.Round(3.7);        // 4
  x := Maths.Trunc(3.7);        // 3
  x := Maths.Ceil(3.2);         // 4
  x := Maths.Floor(3.8);        // 3
  
  // Min/Max/Clamp
  x := Maths.Min(5, 10);        // 5
  x := Maths.Max(5, 10);        // 10
  x := Maths.Clamp(15, 0, 10);  // 10
  
  // Random numbers
  Maths.Randomize();
  x := Maths.Random(100);       // 0..99
  x := Maths.RandomF();         // 0.0..1.0
  
  // Interpolation
  x := Maths.Lerp(0.0, 100.0, 0.5);  // 50.0
end.

Key Routines:

Category Routines
Basic Abs, Sqr, Sqrt, Power, Sign
Trigonometric Sin, Cos, Tan, ArcSin, ArcCos, ArcTan, ArcTan2
Hyperbolic Sinh, Cosh, Tanh, ArcSinh, ArcCosh, ArcTanh
Logarithmic Ln, Exp, Log10, Log2, LogN
Rounding Round, Trunc, Ceil, Floor, Int, Frac
Comparison Min, Max, Clamp
Random Random, RandomF, Randomize
Conversion DegToRad, RadToDeg
Utility Swap, Lerp

Strings Module

String manipulation routines.

import Strings;

var
  s: string;
  pos: int32;

begin
  // Case conversion
  s := Strings.UpperCase('hello');     // 'HELLO'
  s := Strings.LowerCase('HELLO');     // 'hello'
  
  // Trimming
  s := Strings.Trim('  hello  ');      // 'hello'
  s := Strings.TrimLeft('  hello');    // 'hello'
  s := Strings.TrimRight('hello  ');   // 'hello'
  
  // Searching
  pos := Strings.Pos('world', 'hello world');  // 7 (1-based)
  if Strings.ContainsStr('hello world', 'world') then
    // Found (case-sensitive)
  end;
  if Strings.StartsStr('hello', 'hello world') then
    // Starts with 'hello'
  end;
  if Strings.EndsStr('world', 'hello world') then
    // Ends with 'world'
  end;
  
  // Substring extraction
  s := Strings.LeftStr('hello', 3);           // 'hel'
  s := Strings.RightStr('hello', 3);          // 'llo'
  s := Strings.MidStr('hello world', 7, 5);   // 'world'
  s := Strings.Copy('hello', 0, 3);           // 'hel' (0-based)
  
  // Building
  s := Strings.StringOfChar('*', 10);         // '**********'
  s := Strings.DupeString('ab', 3);           // 'ababab'
  
  // Padding
  s := Strings.PadLeft('42', 5);              // '   42'
  s := Strings.PadRight('42', 5);             // '42   '
  s := Strings.PadLeft('42', 5, '0');         // '00042'
  
  // Comparison
  if Strings.SameText('Hello', 'HELLO') then
    // Case-insensitive equal
  end;
  
  // Misc
  s := Strings.ReverseString('hello');        // 'olleh'
end.

Convert Module

Type conversion routines.

import Convert;

var
  n: int64;
  f: float64;
  s: string;
  b: boolean;

begin
  // Integer conversions
  s := Convert.IntToStr(12345);              // '12345'
  s := Convert.IntToHex(255);                // 'FF'
  s := Convert.IntToHex(255, 4);             // '00FF'
  n := Convert.StrToInt('12345');            // 12345
  n := Convert.StrToIntDef('abc', -1);       // -1 (default on error)
  n := Convert.HexToInt('$FF');              // 255
  n := Convert.HexToInt('0xFF');             // 255
  
  // Float conversions
  s := Convert.FloatToStr(3.14159);          // '3.14159'
  s := Convert.FloatToStr(3.14159, 2);       // '3.14'
  f := Convert.StrToFloat('3.14');           // 3.14
  f := Convert.StrToFloatDef('abc', 0.0);    // 0.0
  
  // Boolean conversions
  s := Convert.BoolToStr(true);              // 'True'
  s := Convert.BoolToStr(true, false);       // '-1'
  b := Convert.StrToBool('true');            // true
  b := Convert.StrToBool('yes');             // true
  b := Convert.StrToBool('1');               // true
  
  // Character conversions
  n := Convert.Ord('A');                     // 65
  s := Convert.Chr(65);                      // 'A'
  
  // Formatting
  s := Convert.FormatNumber(1234567);        // '1,234,567'
end.

Assertions Module

Testing and assertion utilities with colored output.

import Assertions;

begin
  // Basic assertions
  Assertions.Assert(1 + 1 = 2, 'Math works');
  Assertions.AssertTrue(true, 'Should be true');
  Assertions.AssertFalse(false, 'Should be false');
  
  // Equality (overloaded for int64, float64, string, boolean)
  Assertions.AssertEqual(5, 2 + 3, 'Addition');
  Assertions.AssertEqual(3.14, 3.14, 'Float equality');
  Assertions.AssertEqual('hello', 'hello', 'String equality');
  
  // Inequality
  Assertions.AssertNotEqual(5, 10, 'Should differ');
  
  // Pointer assertions
  Assertions.AssertNil(nil, 'Should be nil');
  Assertions.AssertNotNil(pointer(1), 'Should not be nil');
  
  // Range assertions
  Assertions.AssertInRange(5, 1, 10, 'In range');
  Assertions.AssertInRange(5.5, 1.0, 10.0, 'Float in range');
  
  // Comparison assertions
  Assertions.AssertGreater(10, 5, 'Greater');
  Assertions.AssertGreaterOrEqual(10, 10, 'Greater or equal');
  Assertions.AssertLess(5, 10, 'Less');
  Assertions.AssertLessOrEqual(5, 5, 'Less or equal');
  
  // Control flow
  Assertions.Pass('Test passed');  // 🟒 PASS: Test passed
  Assertions.Fail('Test failed');  // πŸ”΄ FAIL: Test failed (exits with code 1)
end.

Geometry Module

Geometric types and operations.

import Geometry;

var
  p1: Geometry.TPoint;
  p2: Geometry.TPointF;
  r: Geometry.TRect;
  s: Geometry.TSize;

begin
  // Integer types
  p1 := Geometry.Point(10, 20);
  r := Geometry.Rect(0, 0, 100, 50);
  r := Geometry.Bounds(0, 0, 100, 50);  // From position + size
  s := Geometry.Size(800, 600);
  
  // Float types (overloaded)
  p2 := Geometry.Point(10.5, 20.5);
end.

Types:

Type Fields Description
TPoint X, Y: int32 Integer point
TPointF X, Y: float64 Float point
TRect Left, Top, Right, Bottom: int32 Integer rectangle
TRectF Left, Top, Right, Bottom: float64 Float rectangle
TSize CX, CY: int32 Integer size
TSizeF CX, CY: float64 Float size

Other Standard Library Modules

Module Description
DateTime Date and time operations
Files File I/O operations
Paths Path manipulation utilities
Memory Memory operations

πŸ“‹ Directives Reference

Build Directives

Directive Description
#subsystem console/gui PE subsystem (default: console)
#library 'name' Link a library
#librarypath 'path' Add library search path
#includepath 'path' Add C include path
#addfile 'file' Add .c, .obj, .lib, .dll to link
#modulepath 'path' Add module search path
#outputpath 'path' Set output directory
#generatedpath 'path' Set generated C files directory

Preprocessor Directives

Directive Description
#define SYM [value] Define preprocessor symbol
#undef SYM Undefine symbol
#ifdef SYM Conditional if defined
#ifndef SYM Conditional if not defined
#if expr Conditional expression
#elif expr Else if
#else Else branch
#endif End conditional

Compiler Directives

Directive Description
#debug Enable debug info (STABS format)
#unittestmode on/off Enable unit test mode
#maxerrors N Max errors before stopping
#option 'flag' Pass raw TCC option

Version Info Directives

Directive Description
#addverinfo yes/no Enable version info embedding
#vimajor N Major version number
#viminor N Minor version number
#vipatch N Patch version number
#viproductname 'name' Product name
#videscription 'desc' File description
#vifilename 'name' Original filename
#vicompanyname 'name' Company name
#vicopyright 'text' Copyright notice
#exeicon 'path' Executable icon

πŸ”§ C Header Importer

Pax includes a built-in C header importer that converts C headers into Pax module source code. It uses TCC for preprocessing (expanding macros, includes) then parses the result to generate Pax declarations.

Basic Usage

LImporter := TPaxCImporter.Create();
try
  LImporter.SetModuleName('sdl3');           // Output module name (sdl3.pax)
  LImporter.SetDllName('sdl3.dll');          // DLL to link against
  LImporter.SetOutputPath('libs/sdl3/src');  // Where to write output
  LImporter.AddIncludePath('libs/sdl3/include');
  LImporter.AddExcludedType('va_list');
  LImporter.SetHeader('libs/sdl3/include/sdl3/sdl.h');
  LImporter.Process();
finally
  LImporter.Free();
end;

Configuration Files

The importer supports TOML configuration files for repeatable builds:

// Save current settings to config file
LImporter.SaveConfig('libs/sdl3/sdl3.toml');

// Load settings from config file
LImporter.LoadConfig('libs/sdl3/sdl3.toml');
LImporter.Process();

TOML Config Format

[cimporter]
header = "libs/sdl3/include/sdl3/sdl.h"
module_name = "sdl3"
dll_name = "sdl3.dll"
output_path = "libs/sdl3/src"
include_paths = ["libs/sdl3/include"]
library_paths = ["libs/sdl3/bin"]
copy_dlls = ["libs/sdl3/bin/sdl3.dll"]
excluded_types = ["va_list", "__builtin_va_list", "MSG", "XEvent"]
excluded_functions = ["alloca"]

[[cimporter.insertions]]
target = "(* Forward declarations (opaque types) *)"
file = "libs/sdl3/src/sdl3_constants.txt"
position = "before"
occurrence = 1

Importer Methods

Method Description
SetHeader(filename) Set the C header file to import
SetModuleName(name) Set output module name (defaults to header name)
SetDllName(name) Set DLL name for external declarations
SetOutputPath(path) Set output directory for generated .pax file
AddIncludePath(path) Add C include search path
AddLibraryPath(path) Add library search path
AddCopyDLL(path) Add DLL to copy to output
AddExcludedType(name) Skip type during import
AddExcludedFunction(name) Skip function during import
InsertTextBefore(target, text) Insert text before target line
InsertTextAfter(target, text) Insert text after target line
InsertFileBefore(target, file) Insert file content before target line
InsertFileAfter(target, file) Insert file content after target line
LoadConfig(filename) Load settings from TOML file
SaveConfig(filename) Save settings to TOML file
Process() Run the import process
GetLastError() Get error message if Process() failed
Clear() Reset all settings

What Gets Imported

  • Structs, unions, and typedefs
  • Function declarations with varargs
  • Enumerations with explicit values
  • Pointer types and arrays
  • Forward declarations for opaque types
  • Packed records, alignment, and bit fields
  • Constants from #define (integers, floats, strings)
  • Typed constants from compound literals (e.g., raylib Color constants)

Importing Headers with Dependencies

When a C library depends on another (e.g., SDL3_image uses SDL3 types), the importer can generate proper module imports and qualified type references.

Example: Importing SDL3_image (depends on SDL3)

LImporter := TPaxCImporter.Create();
try
  // Basic settings
  LImporter.SetModuleName('sdl3_image');
  LImporter.SetDllName('sdl3_image.dll');
  LImporter.SetOutputPath('libs/sdl3_image/src');
  
  // Generate "import sdl3;" in output
  LImporter.AddModuleImport('sdl3');
  
  // Generate "#modulepath 'libs/sdl3/src'" so compiler can find sdl3.pax
  LImporter.AddModulePath('libs/sdl3/src');
  
  // Include paths - TCC needs both for preprocessing
  LImporter.AddIncludePath('libs/sdl3_image/include');
  LImporter.AddIncludePath('libs/sdl3/include', 'sdl3');  // Associate with sdl3 module
  
  // Source path - only generate output from SDL3_image headers
  LImporter.AddSourcePath('libs/sdl3_image/include');
  
  LImporter.SetHeader('libs/sdl3_image/include/sdl3_image/sdl_image.h');
  LImporter.Process();
finally
  LImporter.Free();
end;

How It Works:

  1. AddModuleImport('sdl3') - Generates import sdl3; at the top of the output module
  2. AddModulePath('libs/sdl3/src') - Generates #modulepath directive so the Pax compiler can locate sdl3.pax
  3. AddIncludePath(path, 'sdl3') - Associates the SDL3 include path with the sdl3 module name. Types from this path are automatically qualified (e.g., SDL_Surface becomes sdl3.SDL_Surface)
  4. AddSourcePath - Only headers in this path produce output. SDL3 headers are parsed for type information but not re-exported

Generated Output:

module lib sdl3_image;

import sdl3;

#modulepath 'libs/sdl3/src'

public type
  IMG_Animation = record
    frames: pointer to pointer to sdl3.SDL_Surface;  // Qualified!
    // ...
  end;

public routine IMG_LoadTexture(const Arenderer: pointer to sdl3.SDL_Renderer;
  const Afile: pointer to const char): pointer to sdl3.SDL_Texture; external 'sdl3_image.dll';

Current Limitations

Limitation Workaround
Function-like macros Manually add equivalent Pax routines
Expression macros (arithmetic/bitwise) Use InsertFileBefore() to inject constants from a text file
va_list types cause errors Use AddExcludedType('va_list') and similar
Platform-specific types (MSG, XEvent) Use AddExcludedType() to skip them
Inline functions Manually translate to Pax routines
Complex preprocessor conditionals Import may include/exclude based on TCC's platform detection

βš™οΈ TOML Configuration

Pax includes a general-purpose TOML configuration class that can be used for any configuration needs. It wraps the DX.TOML library with a simple, type-safe API.

Basic Usage

LConfig := TPaxConfig.Create();
try
  if LConfig.LoadFromFile('settings.toml') then
  begin
    // Read values with defaults
    LHost := LConfig.GetString('database.host', 'localhost');
    LPort := LConfig.GetInteger('database.port', 5432);
    LEnabled := LConfig.GetBoolean('database.enabled', True);
    
    // Read arrays
    LPaths := LConfig.GetStringArray('search.paths');
    
    // Check if key exists
    if LConfig.HasKey('database.timeout') then
      LTimeout := LConfig.GetInteger('database.timeout');
    
    // Modify and save
    LConfig.SetString('database.host', 'newhost');
    LConfig.SetInteger('database.port', 3306);
    LConfig.SaveToFile('settings.toml');
  end;
finally
  LConfig.Free();
end;

Supported Types

Method Description
GetString / SetString String values
GetInteger / SetInteger Int64 values
GetFloat / SetFloat Double values
GetBoolean / SetBoolean Boolean values
GetDateTime / SetDateTime TDateTime values
GetStringArray / SetStringArray Array of strings
GetIntegerArray / SetIntegerArray Array of Int64
GetFloatArray / SetFloatArray Array of Double

Arrays of Tables

For TOML arrays of tables (e.g., [[items]]):

// Get count of table entries
LCount := LConfig.GetTableCount('items');

// Read fields from each table
for LI := 0 to LCount - 1 do
begin
  LName := LConfig.GetTableString('items', LI, 'name', '');
  LValue := LConfig.GetTableInteger('items', LI, 'value', 0);
  LWeight := LConfig.GetTableFloat('items', LI, 'weight', 0.0);
  LActive := LConfig.GetTableBoolean('items', LI, 'active', False);
end;

Key Path Notation

Keys use dot notation to access nested tables:

[database]
host = "localhost"
port = 5432

[database.connection]
timeout = 30
retries = 3
LHost := LConfig.GetString('database.host');
LTimeout := LConfig.GetInteger('database.connection.timeout');

πŸ—οΈ Architecture

The compiler is built in Delphi with a clean pipeline architecture:

Unit Purpose
Pax.Lexer Tokenization with full Unicode support
Pax.AST Abstract syntax tree node definitions
Pax.Parser Recursive descent parser with error recovery
Pax.Types Type registry and type system management
Pax.Symbols Symbol table with scope management
Pax.Checker Semantic analysis and type checking
Pax.CodeGen C99 code generation
Pax.Compiler Build orchestration and TinyCC integration

Additional Components

Unit Purpose
Pax.ArArchive Native AR archive creation for static libraries
Pax.ZipVFS Virtual file system for embedded toolchain
Pax.IATHook IAT hooking for transparent file redirection
Pax.LibTCC TinyCC (libtcc) integration
Pax.ModuleLoader Module dependency resolution
Pax.Errors Error types, codes, and diagnostic formatting
Pax.CImporter C header to Pax module converter
Pax.Config General-purpose TOML configuration management
Pax.Utils Utility functions and console helpers
Pax.Resources Localized error messages and resource strings

πŸ“Š Status

Under active development.

The core compiler is functional and can produce working executables, DLLs, and static libraries. All major language features are implemented:

  • Records with inheritance
  • Classes with methods and virtual dispatch
  • Method overloading
  • Routine overloading
  • Unions (named and anonymous)
  • Packed records and alignment
  • Bit fields
  • Dynamic arrays
  • Sets with ranges and operations
  • Managed strings (UTF-8/UTF-16)
  • String content comparison
  • External DLL calls with varargs
  • Native Pax variadic routines
  • External name aliasing
  • Module system with imports
  • Pointer arithmetic
  • Unit testing framework
  • GC intrinsics
  • Version info embedding
  • Icon embedding
  • Type aliases
  • Routine types (function pointers)
  • Compound assignment operators
  • Case statement ranges
  • Conditional compilation
  • PAX compiler constants
  • Enumeration types
  • Exception handling (try/except/finally)
  • JIT compilation (compile and run in memory)
  • C header importer with TOML config
  • General-purpose TOML configuration
  • Standard library (Console, Maths, Strings, Convert, Geometry, Assertions, etc.)

πŸ”¨ Building

Get the Source

Option 1: Download ZIP

Option 2: Git Clone

git clone https://github.com/tinyBigGAMES/PaxLang.git

Compile

  1. Open src\Pax Programming Language.groupproj in Delphi
  2. Build the project

That's it! Everything is included - TinyCC, Boehm GC, and runtime resources are already bundled in the repo. The compiled executable will be output to the bin folder.

πŸ’» Command Line Interface

The pax command line tool provides everything you need to create, build, and run Pax projects.

Usage

pax <COMMAND> [OPTIONS]

Commands

Command Description
init <n> [type] Create a new Pax project
build Compile the project
run Build and execute the program
clean Remove all generated files
version Display version information
help Display help message

Creating a Project

# Create an executable project (default)
pax init MyGame

# Create a static library
pax init MyLib lib

# Create a shared library (DLL)
pax init MyPlugin dll

This creates a project folder with the following structure:

MyGame/
β”œβ”€β”€ src/
β”‚   └── MyGame.pax    # Main source file
└── pax.toml          # Project configuration

Project Types

Type Description Output
exe Executable program (default) .exe
lib Static library .a
dll Shared library .dll

Building and Running

# Navigate to project folder
cd MyGame

# Build the project
pax build

# Build and run
pax run

# Clean generated files
pax clean

Project Configuration (pax.toml)

The pax.toml file stores all project settings:

[pax]
signature = "PAX-7F3A9B2C-E1D5-4A8F-B6C2-9D0E3F7A1B5C"
version = "1"
compiler = "0.1.0"
last_build = "2025-01-01T12:00:00.000Z"

[build]
mainsourcefile = "src\\MyGame.pax"
outputpath = "."
modulename = "MyGame"
buildtype = "exe"
subsystem = "console"
unittestmode = false
debugmode = false
verbose = false
maxerrors = 100

[paths]
modulepaths = []
includepaths = []
librarypaths = []

[tccoptions]
options = []

[versioninfo]
enabled = false
major = 0
minor = 0
patch = 0
productname = ""
description = ""
filename = ""
companyname = ""
copyright = ""

[resources]
exeicon = ""

Build Output Structure

After building, the project folder contains:

MyGame/
β”œβ”€β”€ src/
β”‚   └── MyGame.pax
β”œβ”€β”€ generated/           # Generated C code
β”‚   β”œβ”€β”€ MyGame.c
β”‚   └── MyGame.h
β”œβ”€β”€ out/
β”‚   └── bin/             # Compiled output
β”‚       β”œβ”€β”€ MyGame.exe
β”‚       └── paxrtl.dll   # Runtime library
└── pax.toml

Example Workflow

# Create a new project
pax init HelloWorld

# Edit the source (src/HelloWorld.pax)
cd HelloWorld

# Build and run
pax run

# Output:
# Hello from Pax!

Generated Template (exe)

(*
  HelloWorld - Pax EXE
*)

module exe HelloWorld;

routine printf(const fmt: pointer to const char; ...): int32; external 'msvcrt.dll';

begin
  printf('Hello from Pax!\n');
end.

πŸ“‹ Requirements

Minimum Tested
Platform Windows 10 x64 Windows 11 25H2 x64
Build Delphi 11 (Alexandria) Delphi 12 (Athens)

Dependencies: None for end users - TinyCC and Boehm GC are embedded.

🀝 Contributing

Contributions are welcome! Join our Discord to discuss development.

πŸ“„ License

Pax is licensed under the Apache License 2.0. See LICENSE for details.

πŸ”— Links


Paxβ„’ Programming Language.

Copyright Β© 2025-present tinyBigGAMESβ„’ LLC
All Rights Reserved.

About

πŸ•ŠοΈPax is a minimal systems programming language emphasizing simplicity and self-containment. Pascal/Oberon heritage with modern conveniences: managed strings, automatic memory management, and zero external dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project