Getting Started

What You Need

One executable, lp9, and the directory of the standard library. The interpreter, the builder, the language server and the debugger are all that one executable.

lp9 run needs nothing else. On Windows the SDL3 DLL comes with lp9.

lp9 build needs the Zig toolchain. It turns an LP-9 program into Zig source and has Zig make a native executable of it. What it makes runs on its own; on Windows the SDL3 DLL goes beside it.

On Linux, SDL3 and a Japanese font come from the distribution:

sudo apt install libsdl3-dev fonts-noto-cjk

Only x86_64 is supported: the collector's register capture and the stack switch are written for it.

Getting LP-9

The binaries (lp9, the standard library and the VS Code extension) are provided free of charge and as-is, with no warranty. See "Licensing & Engagement" on the top page.

LP-9 is not open source. Source is disclosed only to partner companies under NDA, together with a paid engagement. If you are considering LP-9 for production, please contact us first.

Commands

lp9 run   hello.lp9                  run it by walking the tree
lp9 build hello.lp9                  emit Zig and build a native executable
lp9 build hello.lp9 --out hello.exe  the same, saying where to put it
lp9 bundle --out all.exe a.lp9 b.lp9 several programs in one executable
lp9 lsp                              the language server for an editor
lp9 dap                              the debugger for an editor
lp9 api                              write every class and selector
lp9 version                          which build this is (a date tag, e.g. v2026.10.14)

run while writing, build to ship. The two give the same answers; they differ only in speed and in how deep a recursion goes before it stops.

On Windows, lp9 build makes a program with a screen into a windowed application, so started from Explorer or the taskbar it opens no console window. A program without a screen stays a console program.

A First Program

Object subclass: #Point fields: (x, y).

Point class >> x: a y: b = Point fields: { x: a, y: b }.
Point >> x = x.
Point >> y = y.
Point >> + other = Point x: x + other x y: y + other y.
Point >> printString = '(', x asString, ', ', y asString, ')'.

p := Point x: 3 y: 4.
q := Point x: 10 y: 20.
(p + q) displayNl.                     "(13, 24)"

"copyWith: answers a new value with fields replaced. The original is unchanged."
(p copyWith: { x: 100 }) displayNl.    "(100, 4)"
p displayNl.                           "(3, 4)"

A class declares its fields, and a value is made with fields:, giving every one of them -- with no nil, none can be left out. A value never changes; copyWith: makes a new one.

lp9 run hello.lp9

A First Window

When an actor has class >> start and view, the runtime opens a window and runs that actor as the screen.

imports: (Ui, Theme).

Object subclass: #Bump fields: ().

Actor subclass: #Counter fields: (n).
Counter class >> start = Counter fields: { n: 0 }.
Counter class >> title = 'Counter'.
Counter class >> width = 320.
Counter class >> height = 160.

Counter >> theme = Theme day.
Counter >> Bump = self copyWith: { n: n + 1 }.

Counter >> view = Paint group: #[
        Paint rect: 0 y: 0 w: 320 h: 160 color: #paper,
        Paint move: (Paint column: #[
            W label: ('pressed ' , n asString , ' times'),
            W gap: 20,
            (W button: 'Press' wide: 120) on: #click send: Bump ] gap: 0) x: 24 y: 28 ].
The Counter window: pressed 1 times, and the Press button

After one press, drawn without opening a window

view draws nothing. It answers a description of what to draw (a Paint), and the runtime draws what differs from the frame before. Pressing the button sends a message of class Bump, and Counter >> Bump answers the next state. A colour is written as a name, #paper, and theme says what each name is.

Screens are covered in Screens, actors in Actors.

Checking Without a Window

A screen can draw one frame to a PNG without opening a window. Clicks, keys, typed text, even an input method halfway through a conversion, are given as arguments. Every picture on this site was drawn by LP-9 this way.

lp9 run counter.lp9 --click 70,105 --shot out.png
lp9 run entry.lp9 --click 120,40 --text 受注伝票 --key Return --shot out.png

The conformance run has 29 screens checked this way: given input, and what they drew compared.

Modules

One file is one module, and a file declares no name. The program's own file is Main; an imported file is named by its path: ui.lp9 is Ui, date_picker.lp9 is DatePicker, net/http_client.lp9 is Net::HttpClient.

The header says what the file imports and what it exports. Both are optional.

imports: (Ui, Theme).
exports: (Board, opaque Ticket).

Only the classes exports: lists are visible outside. A class marked opaque is exported as a type with its methods, while making one (fields:) and replacing its fields (copyWith:) stay inside its module.

An import is looked for in the importing file's directory, then in each directory of LP9_PATH, then in the standard library. When none has it, the error lists every path tried.

A class is identified by its module and its name. When two imported modules export a class of the same name, write Sales::Order. A program may declare a Dict of its own without meeting the language's.

The Standard Library

Written in LP-9.

Uithe widgets (W), a one-line field (Edit), a multi-line box (Memo), a tree view (Tree)
Themecolour names and their colours: Theme day and Theme night
Tablea table's columns, defined once (title, width, right-aligned)
Comparablewrite <, and get >, <=, max: and the rest
Enumerablewrite inject:into:, and get collect:, select:, detect:, sum and the rest
OrderedDicta dictionary walked in the order things were put in