Nil Python on the desktop

Nil Python is Python compiled ahead of time to machine code. The same compiler that builds Pascal and C builds it, and there is no interpreter at run time: ./pxx prog.npy prog writes a static Linux executable, as it does for a .pas file. This page runs a few ordinary Python programs on a PC. For the ESP32, where Nil Python gets MicroPython's module names, start with Coming from MicroPython instead.

What was checked. Every program on this page was cut from this page and built with pin v450 (compiler sha256 c19cc2d531e4) from the v0.1.0-beta.1 release tarball, after ./install.sh --yes, on x86-64 Linux on 2026-09-29. Each output block is what the program printed. For the first three programs, CPython 3.14.4 printed the same bytes.

Hello

hello.npy (hello.py works too; the extension picks the frontend):

print("Hello, world!")
./pxx hello.npy hello
./hello
Hello, world!

Functions, a class, a list and a dict

shop.npy:

class Item:
    def __init__(self, name, price, qty):
        self.name = name
        self.price = price
        self.qty = qty

    def total(self):
        return self.price * self.qty


def totals_by_name(items):
    totals = {}
    for it in items:
        totals[it.name] = it.total()
    return totals


items = [Item("apple", 0.5, 6), Item("pear", 0.75, 4), Item("fig", 2.0, 1)]
totals = totals_by_name(items)
for name in sorted(totals):
    print(f"{name:6s} {totals[name]:5.2f}")
print("sum", sum(totals.values()))
print("most", max(totals, key=totals.get))
./pxx shop.npy shop
./shop
apple   3.00
fig     2.00
pear    3.00
sum 8.0
most apple

Files and the standard library

json and os.path import as in CPython. config.npy writes a JSON file, checks it, and reads it back:

import json
import os.path

data = {"name": "pxx", "targets": ["x86-64", "aarch64"], "beta": 1}
with open("config.json", "w") as f:
    json.dump(data, f)

print(os.path.exists("config.json"), os.path.getsize("config.json"))
with open("config.json") as f:
    back = json.load(f)
print(back["name"], len(back["targets"]), back == data)
True 60
pxx 2 True

The config.json it writes is byte for byte the one CPython writes.

Calling Pascal from Python

A Pascal unit is imported by quoting its file name and giving it an alias. With a path (./mathx.pas) the file is found next to the program; with a bare name (sysutils.pas) it is found on the unit search path, which is how the Pascal runtime's own units are reached. mathx.pas:

unit mathx;
{$mode objfpc}{$H+}

interface

function Gcd(A, B: Integer): Integer;
function Shout(const S: string): string;

implementation

uses SysUtils;

function Gcd(A, B: Integer): Integer;
var T: Integer;
begin
  while B <> 0 do
  begin
    T := A mod B;
    A := B;
    B := T;
  end;
  Result := A;
end;

function Shout(const S: string): string;
begin
  Result := UpperCase(S) + '!';
end;

end.

usepas.npy, in the same directory:

import './mathx.pas' as mx
import 'sysutils.pas' as su

print(mx.Gcd(84, 36))
print(mx.Shout("hello from pascal"))
print(su.Trim("   trimmed   ") + "|")
./pxx usepas.npy usepas
./usepas
12
HELLO FROM PASCAL!
trimmed|

There is no binding code to write: the Python call reaches the Pascal function directly. CPython does not accept the quoted import, which is Nil Python's own. More on imports, including C headers, is in Nil Python: Imports.

Another CPU

--target= builds for i386, aarch64 or arm32 as it does for Pascal, and tools/run_target.sh runs the result under QEMU user mode:

./pxx --target=aarch64 shop.npy shop.a64
tools/run_target.sh aarch64 ./shop.a64

This printed the same five lines as on x86-64, and so did --target=riscv32 under tools/run_target.sh riscv32: riscv32 Linux takes Nil Python from v450 (d07cbfbb40), and refused it before.

With v450 itself, os.path.getsize and os.stat(...).st_size read 0 on i386, aarch64 and arm32, so config.npy prints True 0 there. That is fixed after v450 (822df4c6ad), where the same program prints True 60 on aarch64 and arm32.

What is different from CPython

Nil Python follows CPython's syntax and semantics, but it is compiled, and some things differ on purpose:

The full list is in Nil Python, under Where it differs on purpose and Known limits. Problems that are not deliberate are on Known issues. Nil Python is best effort in this beta: a program that uses the shapes above is on well-tested ground.