はじめに

必要なもの

lp9 の実行ファイル一つと、標準ライブラリのディレクトリです。解釈器、ビルド、言語サーバ、デバッガは全部この一つが受け持ちます。

lp9 run には、ほかに何も要りません。 Windows では SDL3 の DLL が lp9 と一緒に入っています。

lp9 build には Zig のツールチェインが要ります。 LP-9 のプログラムを Zig のソースにして、Zig にネイティブの実行ファイルを作らせるからです。できた実行ファイルは単独で動きます。Windows では SDL3 の DLL を横に置きます。

Linux では SDL3 と日本語のフォントをディストリビューションから入れます。

sudo apt install libsdl3-dev fonts-noto-cjk

動くのは x86_64 だけです。回収がレジスタを読むところと、スタックを切り替えるところが x86_64 向けに書かれています。

入手について

バイナリ(lp9、標準ライブラリ、VS Code の拡張)は無償・現状有姿で提供します。保証はありません。詳しくはトップページの「ライセンスとお問い合わせ」を参照してください。

LP-9 はオープンソースではありません。ソースの開示は NDA を締結したパートナー企業に限られ、有償案件とセットです。本番評価をご検討の際は、事前に お問い合わせ ください。

コマンド

lp9 run   hello.lp9                  構文木をたどって実行する
lp9 build hello.lp9                  Zig を吐いてネイティブの実行ファイルを作る
lp9 build hello.lp9 --out hello.exe  同上。置く場所を指定する
lp9 bundle --out all.exe a.lp9 b.lp9 いくつかのプログラムを一つの実行ファイルにまとめる
lp9 lsp                              エディタに答える言語サーバ
lp9 dap                              エディタから使うデバッガ
lp9 api                              全部のクラスとセレクタの一覧を書く
lp9 version                          この lp9 の版(日付の tag。v2026.10.14 など)

開発中は run、配るときは build です。二つは同じ答えを出します。違うのは速さと、深い再帰がどこで止まるかだけです。

lp9 build は、画面を持つプログラムを Windows ではウィンドウのアプリとして作ります。エクスプローラやタスクバーから起動してもコンソールの窓は開きません。画面を持たないプログラムはコンソールのプログラムのままです。

最初のプログラム

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: は一部のフィールドを替えた新しい値を返す。元は変わらない。"
(p copyWith: { x: 100 }) displayNl.    "(100, 4)"
p displayNl.                           "(3, 4)"

クラスはフィールドを宣言し、値は fields: で全部のフィールドを与えて作ります。nil が無いので、一つも省けません。値は変わりません。変えたいときは copyWith: で新しい値を作ります。

lp9 run hello.lp9

最初のウィンドウ

class >> start と view を持つアクタがあると、ランタイムがウィンドウを開いて、そのアクタを画面として動かします。

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 ].
Counter のウィンドウ。pressed 1 times と Press ボタン

ボタンを一度押したところ。ウィンドウを開かずに描いたもの

view は描きません。「何を描くか」の記述(Paint)を返すだけで、前のコマと違うところをランタイムが描きます。ボタンを押すと Bump クラスのメッセージが届き、Counter >> Bump が次の状態を返します。色は #paper のように名前で書き、名前が何色かは theme が答えます。

画面のことは画面に、アクタのことはアクタにあります。

ウィンドウを開かずに確かめる

画面のプログラムは、ウィンドウを開かずに一コマを PNG に描けます。クリック、キー、打った文字、日本語入力の途中の状態も、引数で渡せます。このサイトの画像は全部この方法で LP-9 自身が描いたものです。

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

同梱の検査は、画面に操作を与えて描いたものを比べる形で 29 本あります。

モジュール

一つのファイルが一つのモジュールで、ファイルの中に名前は書きません。プログラムの入口のファイルは Main、取り込まれるファイルはパスで名前が決まります。ui.lp9 は Ui、date_picker.lp9 は DatePicker、net/http_client.lp9 は Net::HttpClient です。

ヘッダに書くのは、取り込むものと公開するものだけです。どちらも省けます。

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

exports: に書いたクラスだけが外から見えます。opaque を付けたクラスは、型とメソッドは公開しますが、fields: で作ることと copyWith: で替えることはそのモジュールの中に残します。

取り込みは、取り込む側のファイルのディレクトリ、LP9_PATH に並べたディレクトリ、標準ライブラリの順に探します。見つからなければ、探した場所を全部並べたエラーになります。

クラスはモジュールと名前の組で区別されます。二つのモジュールが同じ名前のクラスを公開していれば、Sales::Order と修飾します。プログラムが自分の Dict を宣言しても、言語の Dict とはぶつかりません。

標準ライブラリ

LP-9 で書かれています。

Uiウィジェット(W)、一行の入力欄(Edit)、複数行の入力欄(Memo)、ツリー(Tree)
Theme色の名前と色の組。昼の Theme day と夜の Theme night
Table一度だけ定義する表の列(見出し、幅、右寄せ)
Comparable< だけ書けば >、<=、max: などが付くプロトコル
Enumerableinject:into: だけ書けば collect:、select:、detect:、sum などが付くプロトコル
OrderedDict入れた順に歩く辞書