SkillAgentSearch skills...

PaxLang

Pax is a Pascal/Oberon-inspired systems language that compiles to modern C++23 using Zig’s Clang toolchain, with a pass-through design that lets you mix raw C++ and Pax code freely in the same source file.

Install / Use

/learn @tinyBigGAMES/PaxLang

README

<div align="center">

Pax

Discord Follow on Bluesky

A modern systems programming language with seamless C++ interoperability.

</div>

What is Pax?

The Pax Programming Language is a Pascal/Oberon-inspired systems programming language that compiles to C++ 23 via Zig's bundled Clang compiler. Pax delivers on a core philosophy: "If it's not Pax, it's C++ — pass through verbatim."

This means you can freely mix Pax and C++ code in the same source file. Use #include to bring in C++ headers, declare variables with C++ types like std::string or std::vector, call C++ functions directly, and let the compiler figure out the boundaries. No FFI bindings. No wrapper code. Just write.

module exe HelloWorld;

#include <string>
#include <iostream>

var
  name: std::string;

begin
  name := "Pax";
  std::cout << "Hello from " << name << "!" << std::endl;
end.

✨ Key Features

  • 🔀 Seamless C++ interop - Mix Pax and C++ freely in the same source file
  • 📖 Pascal heritage - Readable, structured code inspired by Pascal and Oberon
  • Modern C++23 - Compiles to C++23 via Zig's bundled Clang compiler
  • 🌍 Cross-platform - Target Windows, Linux, and macOS from a single codebase
  • 📦 Self-contained - Single executable with embedded Zig toolchain
  • 🪟 Native libraries - Link any C or C++ library directly
  • 🎛️ Multiple outputs - Build executables, DLLs, or static libraries
  • 🧬 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 tracking
  • 📝 Managed strings - UTF-8 and UTF-16 string types
  • 🔌 Varargs support - Full C interop plus native Pax variadic routines
  • 🔄 Routine overloading - Multiple routines with same name, different signatures
  • 🧪 Built-in testing - Integrated unit test framework
  • ⚠️ Exception handling - try/except/finally with hardware exception support
  • 🏷️ Version info - Embed metadata and icons in executables
  • 📋 Conditional compilation - $define, $ifdef, $ifndef, $else, $endif
  • 🧠 LSP tooling - Full Language Server Protocol with completions, hover, go-to-definition, rename, and more
  • 🐛 Source-level debugging - LLDB-DAP debugger with breakpoints, stepping, variable inspection, and interactive REPL

📘 Language Overview

Built-in Types

Pax uses explicit, fixed-size types with no implicit conversions. Every type has a known size, making memory layout predictable and interoperability with C/C++ straightforward.

| 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 (use 3.14f suffix for float32) | | 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

Every Pax source file is a module. The module declaration on the first line determines what the compiler produces — an executable, a static library, or a dynamic library (DLL/shared object).

| Type | Description | |------|-------------| | module exe Name | Executable program | | module lib Name | Static library | | module dll Name | Dynamic library |

Module Imports

Use import to access routines and types from other Pax modules:

module exe MyApp;

import Maths, Strings, Console;

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.

🔀 Mixed Mode: Seamless C++ Interoperability

Mixed mode is Pax's superpower. Write Pax code that seamlessly integrates with C++ — no bindings, no wrappers, no ceremony.

Including C++ Headers

Use standard #include directives to bring in any C++ header:

module exe MixedExample;

#include <cmath>
#include <string>
#include <vector>
#include <algorithm>

begin
  // C++ functions are immediately available
  writeln("sqrt(16) = {}", std::sqrt(16.0));
  writeln("abs(-42) = {}", std::abs(-42));
end.

C++ Types as Pax Variables

Declare Pax variables with C++ types:

var
  s: std::string;
  vec: std::vector<int32_t>;
  pos: size_t;

begin
  s := "hello world";
  writeln("Length: {}", s.length());
  
  if not s.empty() then
    writeln("First char: {}", s[0]);
  end;
  
  pos := s.find("world");
  if pos <> std::string::npos then
    writeln("Found 'world' at position {}", pos);
  end;
end.

C++ Method Calls in Pax Expressions

Call C++ methods naturally within Pax control flow:

var
  s: std::string;

begin
  s := "hello";
  
  // C++ methods in Pax if-statements
  if s.length() > 0 then
    writeln("String is not empty");
  end;
  
  // C++ in loops
  while not s.empty() do
    s.pop_back();
  end;
  
  // Mixed arithmetic
  writeln("Result: {}", std::abs(-5) + std::max(10, 20));
end.

Raw C++ Injection: cppstart/cppend Blocks

While Pax's automatic passthrough handles most C++ seamlessly, some scenarios require explicit control over where C++ code appears in the generated output. The cppstart/cppend blocks provide this control.

Why this is needed:

  1. Header vs Source placement — C++ has strict rules about what goes where. Inline functions, macros, and templates must be in headers to be usable across translation units. Static helper functions should stay in source files to avoid symbol conflicts.

  2. Preprocessor macros — Pax doesn't have its own macro system. When you need #define constants or function-like macros, they must be injected into the header.

  3. C++ templates — Template definitions must be in headers. There's no Pax syntax for defining C++ templates, so direct injection is required.

  4. Integration with existing C++ code — When wrapping or extending existing C++ libraries, you may need to add compatibility shims, forward declarations, or adapter code.

module exe CppBlocks;

// Inject into the HEADER file (.h)
// - Inline functions (available to other modules that include this header)
// - Macros (must be in header to be visible)
// - Templates (must be in header for instantiation)
cppstart header
inline int32_t cpp_add(int32_t a, int32_t b) { return a + b; }
#define CPP_CONSTANT 42
#define CPP_MAX(a, b) ((a) > (b) ? (a) : (b))

template<typename T>
T cpp_clamp(T value, T min, T max) {
    return (value < min) ? min : (value > max) ? max : value;
}
cppend

// Inject into the SOURCE file (.cpp)
// - Static functions (internal linkage, no symbol export)
// - Implementation details not exposed in the API
// - Helper code that shouldn't pollute the header
cppstart source
static int32_t cpp_multiply(int32_t a, int32_t b) { return a * b; }

static void internal_helper() {
    // This function is only visible within this source file
}
cppend

begin
  writeln("cpp_add(10, 20) = {}", cpp_add(10, 20));
  writeln("CPP_CONSTANT = {}", CPP_CONSTANT);
  writeln("CPP_MAX(5, 3) = {}", CPP_MAX(5, 3));
  writeln("cpp_multiply(6, 7) = {}", cpp_multiply(6, 7));
  writeln("cpp_clamp(150, 0, 100) = {}", cpp_clamp(150, 0, 100));
end.

Inline C++ Expressions: cpp()

Pax's automatic passthrough works by recognizing Pax syntax boundaries — when something isn't valid Pax, it passes through as C++. However, some C++ constructs are ambiguous or conflict with Pax syntax. The cpp() function provides an explicit escape hatch.

Why this is needed:

  1. Pax new vs C++ new — Pax uses new(ptr) syntax for allocation, while C++ uses new Type. Without cpp(), there's no way to write C++ allocation:

    new(pPaxPtr);              // Pax new — calls pax runtime allocator
    cpp("pCppPtr = new int");  // C++ new — calls C++ allocator
    
  2. C++ cast operators — Expressions like reinterpret_cast<T*>(x) or static_cast<int>(x) use angle brackets that could conflict with Pax's comparison operators.

  3. Complex template expressions — Nested templates like std::vector<std::pair<int, int>> may need explicit escaping.

  4. Guaranteed verbatim passthrough — When automatic passthrough produces unexpected results, cpp() guarantees the exact string appears in output.

  5. Inline in Pax expressionscpp() returns a value, so it can be used within Pax assignments, function arguments, and conditions.

var
  pCppInt: int32_t*;
  pCppArr: int32_t*;
  pCppVec: std::vector<int32_t>*;
  pVoid: pointer;
  x: int32;
  d: float64;

begin
  // C++ new/delete — MUST use cpp() because Pax has its own 'new' syntax
  cpp("pCppInt = new int32_t(42)");     // C++ new with initialization
  writeln("Value: {}", cpp("*pCppInt"));
  cpp("delete pCppInt");
  
  // C++ array new/delete
  cpp("pCppArr = new int32_t[10]");
  cpp("pCppArr[0] = 100");
  cpp("delete[] pCppArr");
  
  // C++ container allocation
  cpp("pCppVec = new std::vector<int32_t>()");
  cpp("pCppVec->push_back(1)");
  cpp("pCppVec->push_back(2)");
  writeln("Size: {}", cpp("pCppVec->size()"));
  cpp("delete pCppVec");
  
  // C++ cast operators — angle brackets need explicit escaping
  pVoid := &x;
  pCppInt := cpp("reinterpret_cast<int32_t*>(pVoid)");
  cpp("*pCppInt = 99");
  writeln("x is now: {}", x);  // 
View on GitHub
GitHub Stars16
CategoryDesign
Updated26d ago
Forks2

Languages

Pascal

Security Score

80/100

Audited on Mar 6, 2026

No findings