日本語 | English
いにしえの言語 PL/I のプログラムをブラウザだけで、あるいはVSCode の中で書いて動かせます。 コンパイラのインストールは要りません。処理系(インタプリタ)を TypeScript で 自作して同梱しているためです。
hello: proc options(main);
put list('HELLO, PL/I');
end hello;
メインフレームの環境が無くても、手続き型言語 PL/I と階層型データベース IMS を 手を動かして体感できるようにすること。
PL/I と IMS/DB は 1960 年代に生まれた技術です(PL/I は 1964 年、IMS は 1968 年)。それなのに、個人が 触る手段がほとんどありません。z/OS は個人で借りられる値段ではなく、 PL/I コンパイラも IMS も商用製品です。結果として「名前は聞くが、 書いたことも動かしたこともない」技術になっています。
このツール一式は、その 2 つをブラウザ 1 枚で動かせるところまで持ってきます。 ファイルをダブルクリックすれば、その場で PL/I を書いて、階層型データベースを 読み書きできます。インストールもサーバも要りません。
| 意義 | 中身 |
|---|---|
| 教育 | 「COBOL でも SQL でもない書き方」を実際に動かして覚えられる。10 進固定小数点、PICTURE による桁編集、ON 条件、GU / GN による位置づけ — どれも説明を読むより動かした方が早い |
| 歴史 | 動かない技術は読み物でしか残らない。動く形で残しておけば、なぜその設計になったのか(なぜ予約語が無いのか、なぜ JOIN が無いのか)を手で確かめられる |
| 保守の入口 | PL/I や IMS のコードを読むことになった人が、手元で文法と挙動を試せる。本番に触る前に、落とし穴(ステータスコードを見ない、基数を混ぜる)を安全に踏める |
あくまで擬似的な実行環境であり、すべての動作を保証するものではなく、 性能や互換性で商用製品の代わりになるものではありません。サブセットです。 できないことを黙って動かすことはせず、何が未実装かを名指しで断ります。
IBM が 1964 年に発表した汎用プログラミング言語(Programming Language One)。 科学技術計算の FORTRAN と事務処理の COBOL を1 つの言語に統合することを 狙って作られました。
特徴的なところ:
-
予約語が無い。
IFもTHENも変数名に使えます。文脈だけで決まるので、 次のプログラムは合法です(この処理系でも動きます)dcl (if, then, else) fixed bin(31); if = 1; then = 2; else = 3; if if = then then then = else; -
10 進固定小数点が言語の型。 金額計算で誤差が出ません。
123.45 * 6.7は827.115ちょうどになります(2 進浮動小数点だとずれる) -
PICTUREで桁を編集する。dcl amt pic'$$$,$$9V.99';に1234.5を 入れると$1,234.50になります(先頭の空白まで規定どおり)。 帳票を書くための機能が言語側にあります -
ON条件で異常を受ける。on zerodivide .../on endfile(f) ...のように、 例外処理を宣言的に書きます(例外機構としては現代のtryより古い世代) -
構造体・配列を当初から持ち、後の版でポインタと
BASED記憶域が入りました
抽象度は COBOL より高い一方で、C が広まるより前からポインタや記憶域の
重ね合わせといった低水準の操作も備えていました。ただしこの処理系は、
そのうち UNION による重ね合わせを実装していません(処理系の限界)。
IBM が 1968 年に製品化した階層型データベース(IMS = Information Management System)。元はアポロ計画の部品表を管理するために開発されたもので、 リレーショナルデータベースより前の世代にあたります。
データを表ではなく木で持ちます。
STUDENT(学生)
└ COURSE(履修科目)
└ GRADE(成績)
RDB との違いが、そのままプログラムの書き方の違いになります。
| リレーショナル(SQL) | 階層型(IMS/DB) | |
|---|---|---|
| データの形 | 表と外部キー | 親子の木 |
| 関連の取り出し | JOIN |
木をたどる(親から子へ) |
| 問い合わせ | 「何が欲しいか」を書く(宣言的) | 「どう進むか」を書く(手続き的) |
| 現在位置 | カーソルを使わない限り無い | 常にある。直前にどこにいたかで次の呼び出しの結果が変わる |
| 失敗の知らせ方 | 例外・エラー | ステータスコード(例外は出ない) |
PL/I からは CALL PLITDLI で呼びます。DL/I(Data Language/I)が
そのインタフェースです。
/* S0002 の学生を 1 件取る */
dcl ssa char(25) init('STUDENT (STUDNO =S0002)');
call plitdli(four, func_gu, db_pcb, seg_io, ssa);
if db_pcb.stat_code = ' ' then put list(seg_io);
GU(Get Unique)で 1 件取り、GN(Get Next)で木を階層順にたどります- 何がどこにあるかは DBD(データベースの定義)が、プログラムが何を 見てよいかは PSB が決めます。どちらもテキストファイルです
- 失敗しても例外は出ません。
PCBのステータスコードを見ないと、 取れなかったセグメントを取れたものとして処理してしまいます (IMS のプログラムで典型的な誤り。この処理系の Linter がdli-status-uncheckedとして指摘します)
使い方の詳細は docs/dli.md。
| できること | 入口 |
|---|---|
| PL/I を書いて即実行する | ブラウザ版(HTML 1 枚) / VSCode Extension |
| 書いたコードをテストする(PL/I で書くテストフレームワーク) | 両方 + CLI npm run plitest |
| **階層型データベース(IMS/DB)**を読み書きする | 両方 + CLI npm run pli -- x.pli --psb NAME |
| **3270 の画面(MFS)**を読み書きする | ブラウザ版の「端末」 + CLI npm run pli -- x.pli --psb NAME --keys x.keys + VSCode |
| 怪しい書き方を検査する(Linter 16 規則) | 両方 + CLI npm run plilint |
| よく書く形を Snippet から入れる(43 本) | 両方 |
対応している PL/I の範囲(内部の作りは engine/README.md):
- 宣言・式・代入・
IF/DO(DO;/DO WHILE/DO UNTIL/ 反復、および複数指定)/SELECT/GOTO/LEAVE/ITERATE - 手続き(入れ子・再帰・戻り値)、
BEGINブロック、構造体、配列、DEFINED+ iSUB。 引数は PL/I の規定どおり参照渡しで、宣言した属性が渡した値と違うときだけ 一時変数(ダミー引数)になります。構造体もまるごと渡せます(形が違えば断ります) PUT LIST/PUT EDIT/GET LIST/GET EDIT(規定どおりの桁揃え)- 10 進固定小数点の正確な算術(
123.45 * 6.7 = 827.115が誤差なく出る) ON単位と条件。発火するのはERROR/ZERODIVIDE/FIXEDOVERFLOW/SIZE/SUBSCRIPTRANGE/CONVERSION/ENDFILE(f)/UNDEFINEDFILE(f)の 8 つ (SIGNALは任意の名前で起こせます)。ON ... SYSTEM;で既定動作に戻せます。 これ以外の条件名にON単位を置いても構文は通りますが、処理系がその条件を 起こさないため実行されません- ストリーム入出力(
OPEN/CLOSE/GET FILE/PUT FILE/LINESIZE) %INCLUDE(取り込んだ先の誤りも、ファイル名と元の行を添えて報告)PICTURE($$$,$$9V.99のような数値編集)BASED記憶域とポインタ(ALLOCATE/FREE/->/ADDR/NULL)- レコード入出力(
READ/WRITE/REWRITE、ENVIRONMENT(F RECSIZE(n))) - IMS/DB(DL/I)(
CALL PLITDLIで階層型 DB を読み書き。DBD / PSB / SSA / PCB のステータスコード。詳細はdocs/dli.md) - 画面入出力(MFS)と IMS TM(
FMT/MSGの書式定義から MID / MOD / DIF / DOF を作り、3270 の画面を組む。入出力 PCB へのGU/GN/ISRT/PURG、会話型の SPA、/FORMAT。詳細はdocs/mfs.md) - 組込関数 21 種、
%REPLACE
出力書式と精度規則は、実在の PL/I 処理系の出力と突き合わせて決めています。
条件の扱いも突き合わせましたが、計算条件の再試行だけは意図的に規定から
外しました(止まらないプログラムを作らないため。理由は
engine/README.md の「ON 条件の扱いを規定からずらした」)。
書式や精度を推測で埋めた箇所はありません。保留していた 3 点(基数混在の端数、
F(w,d) の丸め、F 書式の桁あふれ)も実機で確かめ、2 つは実装を
直しました(詳細は engine/README.md の
「実機と突き合わせて決めたこと」)。
実機と意図的に違えているところは 5 つで、理由を同じ README の
「実機と違えている点」に書いてあります(10 進の最大精度を IBM PL/I for
MVS and VM 1.1 の 15 桁に合わせていること、レコードの区切りを行にしていること、
GET LIST がレコード境界を区切りと見ること、%INCLUDE 'ファイル名' を
受け付けること、ROUND(x,n) の n が負のときに負の尺度を持たないこと)。
ただし IMS 関連(DL/I と MFS)だけは突き合わせる相手がありません(IMS は
z/OS 専用で、手元で動かせる実装も入手できません)。この 2 つは IBM の仕様文書を
正とし、各規則の根拠をテストに引用して担保しています。画面像のゴールデンは
実機由来のものと混ざらないよう別の場所(engine/test/screen/)に置き、
出処の 1 行目を「IBM 仕様に基づく(実機の出力ではない)」で始めることを
テストで機械的に確かめています。
メッセージ・診断・Linter の指摘・テストの報告・画面の文字は、日本語と英語の
どちらでも出せます。既定は日本語で、明示して切り替えます。
環境変数 LANG やブラウザの言語設定には追従しません(同じコマンド・同じ URL が
機械の設定で違う言葉を出すと、出力を固定したテストや説明が成り立たなくなるため)。
| 入口 | 切り替え方 |
|---|---|
| ブラウザ版 | 画面上部の言語の選択(選ぶと ?lang=en を付けて読み直します。書いた内容は残ります) |
| VSCode | 設定 pli.language(auto / ja / en。既定の auto は VSCode の表示言語に合わせます) |
| CLI | `--lang ja |
| ライブラリとして | setLocale("en") / withLocale("en", fn) / runProgram(src, { locale: "en" }) |
npm run pli -- examples/tests/arith_test.pli --lang en
PLI_LANG=en npm run plitest -- examples/testsVSCode のコマンド名と設定の説明だけは VSCode 自身の仕組み(package.nls.*.json)で
決まるため、VSCode の表示言語に従います。pli.language で食い違わせることも
できますが、既定の auto なら揃います。
訳は engine/src/i18n/en.ts に置いてあり、日本語の文そのものが鍵です。
抜けは engine/test/i18n.test.ts が構文木から鍵を集めて落とします。
| 版 | 用途 | |
|---|---|---|
| Node.js | 20 以上 | ビルドと CLI。nodejs.org |
| git | 任意 | 取得 |
| VSCode | 1.90 以上 | Extension を使う場合のみ |
ブラウザで動かすだけなら、ビルドの後は Node.js も不要です (生成物は HTML ファイル 1 つ)。
以下のコマンドは、共通手順を終えた直後(pli-engine/engine の中)
からの相対パスで書いています。
git clone https://github.com/Mt-riv/pli-engine.git
cd pli-engine/engine
npm ci
npm run web:build # dist-web/index.html ができる(1 ファイル)open dist-web/index.htmlNode.js を Homebrew で入れる場合は brew install node。
PowerShell で:
git clone https://github.com/Mt-riv/pli-engine.git
cd pli-engine\engine
npm ci
npm run web:build
start dist-web\index.htmlNode.js は nodejs.org の LTS 版インストーラ、または
winget install OpenJS.NodeJS.LTS で入ります。
PowerShell の実行ポリシーで npm が止まる場合は
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned を一度実行してください。
xdg-open dist-web/index.htmlディストリビューションの Node.js は古いことがあります。20 未満なら nodesource か nvm を使ってください。
Releases から
pli-lang-0.5.1.vsix をダウンロードします。ビルドは要りません。
gh が使えるなら次でも取れます。
gh release download vscode-v0.5.1 --repo Mt-riv/pli-enginecd ../vscode-pli # pli-engine/engine から
npm ci
npm run build
npx @vscode/vsce package # pli-lang-0.5.1.vsix ができるVSCode の拡張ビュー(Ctrl+Shift+X / Cmd+Shift+X)→ 右上の … →
「VSIX からのインストール」 で pli-lang-0.5.1.vsix を選びます。
code コマンドが使えるなら code --install-extension pli-lang-0.5.1.vsix でも入ります。
使い方は docs/vscode-manual.md。
cd ../engine # VSCode の手順を飛ばした場合はそのまま engine にいます
npm run pli -- examples/dli/stuprt.pli --psb STUPSB # プログラムを 1 本走らせる
npm run plitest -- examples/tests # PL/I で書いたテストを走らせる(PSB は自動検出)
npm run pli -- examples/screen/dbinq.pli --psb INVPSB --keys examples/screen/dbinq.keys # 画面(MFS)
npm run plilint -- examples # 怪しい書き方を検査する
npm run plilint -- --list-rules # 規則の一覧と、その理由
npm run pli -- --help # オプションの一覧Windows でも同じコマンドが動きます(npm run 経由なのでパス区切りを問いません)。
--help でオプションの一覧が出ます。おもなもの:
| オプション | 対象 | 内容 |
|---|---|---|
--psb <名前> |
pli / plitest |
IMS/DB(DL/I)を使う。plitest は省略すると隣の *.psb を自動で使う |
--keys <ファイル> |
pli |
端末の台本を流して画面を出す(MFS)。隣の *.mfs を書式定義として読む |
--stdin <ファイル> |
pli |
SYSIN に流し込む |
--max-steps N / --max-output N |
pli |
実行の上限(1 以上の整数。不正な値は終了コード 2) |
-I <ディレクトリ> |
pli |
%INCLUDE とファイルの探索先を足す |
--allow-outside |
pli |
ソースのディレクトリの外も読み書きできるようにする |
--write |
plitest |
テストの書き出しを実ファイルへ反映する(既定は反映しない) |
--all |
plitest |
名前が規約に合わないファイルも中身で判定する |
--pass-with-no-tests |
plitest |
テストファイルが 1 つも無くても終了コード 0 にする(既定は失敗) |
--strict |
plilint |
warning も失敗として扱う |
--lang ja|en |
3 本とも | メッセージの言語(既定 ja。環境変数 PLI_LANG でも指定できる) |
ファイル入出力の範囲。 pli は既定で、ソースのあるディレクトリと -I で
足した場所だけを読み書きします。../ や絶対パスで外へ出ることはできず、
シンボリックリンクも実体の位置で判定します。外を触るときは --allow-outside
を明示してください。
| 文書 | 内容 |
|---|---|
docs/browser-manual.md |
ブラウザ版の使い方。実行・テスト・検査・Snippet・ファイル・共有 |
docs/vscode-manual.md |
VSCode Extension の使い方。コマンド・設定・トラブルシュート |
docs/test.md |
テストフレームワークの設計と書き方 |
docs/lint.md |
Linter の規則一覧と、各規則の理由 |
docs/dli.md |
IMS/DB(DL/I)の使い方。DBD / PSB / データの書き方、SSA、ステータスコード |
docs/mfs.md |
画面入出力(MFS)の使い方。書式定義、入出力 PCB、台本、会話型 |
engine/README.md |
処理系の内部。字句・構文・評価・書式の作り |
英語版は docs/en/ にあります(browser /
vscode / test / lint /
dli / mfs / engine)。
engine/ 処理系(TypeScript、Node 非依存)
src/ 字句・構文・評価・Linter・テストフレームワーク・PICTURE・入出力
src/i18n/ 表示する言語(日本語が鍵、英語の訳の表)
src/dli/ IMS/DB(DL/I)
src/mfs/ 画面入出力(MFS)
src/tm/ IMS TM(メッセージキュー・画面との往復)
web/ ブラウザ版(HTML 1 枚にビルドされる)
examples/ PL/I で書いたテストの例(tests/)と IMS/DB の動く例(dli/)
scripts/ CLI(pli / plitest / plilint / Snippet 生成)と実ファイル用のホスト
test/golden/ 実機の出力を固定した期待値
test/screen/ 画面像の期待値(出処は IBM 仕様)
vscode-pli/ VSCode Extension(処理系を同梱)
docs/ 文書(en/ に英語版)
browser-manual.md ブラウザ版の使い方
vscode-manual.md VSCode Extension の使い方
test.md テストフレームワークの設計と書き方
lint.md Linter の規則一覧と理由
dli.md IMS/DB(DL/I)の使い方
mfs.md 画面入出力(MFS)の使い方
PL/I のサブセットです。学習と検証には充分ですが、次は実装していません。
- 索引・直接編成ファイル(
KEYED/REGIONAL) - IMS の物理層(HDAM / HIDAM などの違い)、二次索引、論理関係、
EXEC DLI、AIB インタフェース、同期点(CHKP/ROLB) - 本物の 3270 データストリーム(
SBA/SF/ICの並び)、tn3270 との接続、 DBCS / EGCS、論理・物理ページング、分割画面、MSG OPT=2/OPT=3、PASSWORD、代替 PCB(CHNG)、MFS の EXIT ルーチン (詳細はdocs/mfs.mdの「何を再現し、何を再現しないか」) AREA/OFFSET、自己定義構造体(REFER)、UNION、LABEL変数- 構造体の配列(
dcl 1 tbl(3), 2 nm char(4);)。次元は葉に付けてください (dcl 1 rec, 2 nm(3) char(4);) INITIALの繰り返し係数(init((5) 0))、可変の配列境界(dcl a(n))- 多重処理(
TASK/WAIT/EVENT) - 浮動小数点は JavaScript の数値(表示は
FLOAT DEC(6)相当の桁) - ポインタはアドレス値を持ちません。確保した記憶域への参照なので、 ポインタ算術や型の違う再解釈はできません(代わりに NULL・解放済み・ 二重解放を必ず検出します)
- 組込関数は 21 種だけです。
CHAR/SQRT/DATE/ONCODEなど 実務で使う 99 語は名前は知っていて、使うと「未実装」と断ります (名前を知らない語は「未知の関数」なので、綴り間違いと区別できます)
未実装の機能に当たると、何が未実装かを名指しで報告します。
属性(KEYED / REGIONAL / AREA / UNION / LIKE / EVENT など)、
文(WAIT / DISPLAY / REVERT / DELETE など)、
書き方(INITIAL の繰り返し係数、可変の配列境界、構造体の配列)、
組込関数、IMS の呼び出し(CHKP / ROLB / CHNG など)のいずれも、
名前で区別して「未実装」と答えます。知らない語は「解釈できない」と
答えるので、綴り間違いと未実装は見分けられます。
- 字句解析はキーワードを判定しない。 PL/I には予約語が無く、
IF IF = THEN THEN THEN = ELSE;が合法です。語がキーワードか識別子かは 文脈でしか決まらないため、判断は構文解析に委ねています - FIXED DECIMAL は尺度付きの BigInt。 JavaScript の数値で代用すると 10 進の計算が合いません
- ブラウザには別プロセスが無い。 暴走は処理系側の上限で止めます。
文の数(既定 500 万)と出力(既定 100 万文字)だけでは足りず、
dcl a(200000000)のような1 文でメモリを使い切れるので、 配列の要素数・文字列の長さ・ALLOCATEの回数にも上限があります - 外界に触れる処理は 1 箇所にまとめる。
%INCLUDEとファイル入出力はPliHostという差し込み口を通します。src/にnode:fsを持ち込むと 「HTML 1 枚で配れる」形が壊れるためです
cd engine && npm ci && npm test # TypeScript 側のテスト
cd ../vscode-pli && npm ci && npm test # VSCode を起動せずに動く出力書式と精度規則は engine/test/golden/ にバイト単位で固定しています。
期待値を取り直す道具は別リポジトリ(pli-oracle。非公開)にあり、
このリポジトリのテストはそれが無くても動きます
(詳細は engine/test/golden/README.md)。
main への push と Pull Request ごとに GitHub Actions が同じものを走らせます
(.github/workflows/ci.yml)。
型検査・テスト・PL/I で書いたテスト・Linter・ブラウザ版のビルド・
vsix のパッケージまでを通し、出荷物に入る依存の脆弱性も見ています。