以下は多分まだ嘘がたくさん書いてあります。推敲中です。噂を信じちゃいけないよ。間違いに気づいたらぜひ教えてくださいませ。
以下は途中で挫折しています。これを再利用してバージョンアップ版を公開するのは自由です(出典だけ明記してね、はあと)
まえがき
元のフォーマットに完全に準拠することもできはするはずですが、私の能力の範囲で書いてしまいますので突っ込み歓迎です。
本記事は主に PL/I の文法面について解説します。基本的に Enterprise PL/I for z/OS and OS/390 バージョン3リリース2.0 の範囲内の文法について取り扱いますが,そこからはみ出す部分については適宜注釈をつけて書けたらとってもうれしいな。
PL/IにはISO標準は存在します。IBMが主に汎用機向けに作った言語で、今でも最も多く用いられるのはIBMメインフレームです。COBOLとFORTRANとALGOLから当時としてはいいとこどりをして作られました。手続き的なプログラミングをするのであれば、ある意味高機能です。ただ、外部データの処理(ファイル入出力やデータベースアクセス)としては、取扱データフォーマットは「固定長データフォーマット」を得意としています。
汎用機で動かす限りにおいては、データソート等をPL/Iのコード内でやるよりは、JCL(Job Control Language)側でファイルソートを行う方が一般的のようです。入門としては「(Hello Worldであろうと)JCLから実行する」と覚えてしまって差し支えないと思います。
現在となってはPL/Iの最も一般的な実行環境はIBM汎用機であるため、PCで実行したければエミュレータになると思いますが、案外エミュレータはどこにでもあるわけではないようで…。
最小限のHello Worldのセットは
HELLO:PROC OPTIONS(MAIN);
DISPLAY('HELLO WORLD!');
END HELLO;
で、これをJCL
//*************************************************
//* STEP010 HELLO *
//*************************************************
//STEPHELO EXEC PGM=HELLO
で呼び出せば多分実行できると思いますがどうなんでしょう。あれ?
基礎
コメント
コメントは C を想定すれOKです。
/* */
文法チェック
この辺のことはよくわかりませんが、Strictモードみたいなものはなさそうです。
文の最後にはセミコロン ( ; ) を付けます。というか他の言語じゃ文とは呼びそうにないものにも;つけることもあって…。以下参照のこと
変数の宣言
DECLARE で宣言します。変数にはいろいろな型があります。
DECLARE HOGE CHAR(1);
DCL FUBA CHAR(1); /* DCLはDECLAREの省略表記 */
HOGE = '1';
DCL (UGA,BOGA) CHAR(1) INIT('1');
型にはいろいろな種類がありますし、文字なら文字で何桁目には数字のみ等幅広い定義が可能です。特に汎用機のデータ構造としてよく用いられる固定長ファイルを処理するのに便利なデータ構造を多様に再現できます。といっても、当時としては「COBOLと比べてPL/Iは可変長文字列に強い」のだそうです。
数値演算
DCL HOGE DECIMAL(3,1) = INIT(1 + 2); /* => 3 INITは初期値代入を示す */
HOGE = 3 - 2; /* => 1 */
HOGE = 1 * 5; /* => 5 */
HOGE = 3 / 2; /* => 1.5 (…多分) */
HOGE = 2 ** 3; /* => 8(累乗の演算子) */
演算を示す組み込み関数ADD、SUBTRACT、DIVIDE、MULTIPLYなどでも数値計算可能です。FORTLAN並の算術演算はひと通りあるらしい。
演算時には数値の型の自動変換が定義されていますし、代入先が文字であれば文字型に変換されます。変換ルールは割愛。
文字列
文字列はシングルクオート( ' )で囲みます。タブや改行等を扱う特殊文字…ってあるんですか?
文字列操作
結合
JOIN1 = 'aaa' || 'bbb';
長さ
NAGASA = LENGTH(MOJIRETSU);
固定長配列の長さは、宣言の時に長さの値に変数を使ってしまえばループの際に自分で数えなくても済むよ!
切り出し
SUBSTR = SUBSTR('abcd', 1, 2); /* => bc */
SUBSTR(HENTAI, 5, 10) = SUBSTR('abcd', 1, 2); /* => bcをなんとHENTAIの5桁目から10桁目に突っ込める */
検索
SAGASU = INDEX('UHIHI', 'H'); /* 2 */
POSIT = INDEX('UHIHI', 'H', 1); /* 2 */
POSIT = INDEX('UHIHI', 'H', POSIT); /* 4 */
INDEXの逆に「特定の文字以外が何文字目かを探すVERIFYというのもあります。
配列
DCL LIST1 FIXED DECIMAL(5) DIMENSION(10); /* 固定小数点表示DECIMAL(5)の要素を持つ要素10の配列 */
DCL LIST2(10) FIXED DECIMAL(5) ; /* 上に同じ */
DCL LIST3(4,2) FIXED DECIMAL(5) ; /* 要素8の二次元配列 */
LIST1(1) = 3; /* 要素の1番目のエレメントに3を代入。見たままです */
配列の操作
・・・ってあるんでしたっけ?
連想配列
あったら、それはとってもうれしいな
文字列操作その他
文字列操作で「何文字目はどの意味の値」等(たとえば8桁の数字で「YYYYMMDD」みたいに、8桁そのままでも扱うけど、MMの部分は月を示す、みたいなデータの関係が成り立っている時)は、以下のようにします。
DCL NENGAPPI CHAR(8);
DCL 1 BARA BASED(ADDR(NENGAPPI)),
2 NEN CHAR(4),
2 TSUKI CHAR(2),
2 HI CHAR(2);
BARAはメンバーにNEN,TSUKI,HIを持つCでいうところの構造体みたいなものです。しかし、BARAの定義はベース付き変数(ポインタ的な構造。ポインタというとBASEDではなくPTR/POINTERのことを言うのでここでは使わない)を使っている。この構造体は何階層でも自由にできるので自由なデータ構造ですが、データ構造だけを定義して使い回すことはできないのかな?(←よくわかってない)。ポインタを使えばリスト構造も使えるらしいぞ。
NENGAPPI = '2010102'; /* NENGAPPI自体に8桁の文字列を入れることができる
NEN = '2009'; /*上に続いてこれをやると、'20090102'になる。BARAの名前は書かなくてもいい
上記の範囲であればSUBSTRでも実現できますが、さらに次のようになると便利さがわかるかも?
DCL NENGAPPI CHAR(8);
DCL 1 BARA BASED(ADDR(NENGAPPI)),
2 NEN CHAR(4),
2 TSUKI CHAR(2),
2 HI CHAR(2);
DCL 1 BARA2 BASED(ADDR(NENGAPPI)),
2 NEN2 CHAR(4),
2 GPPI CHAR(4);
ある意味で言えば、TSUKIとHIの両方に値を入れることと、GAPPIに値を入れることは同じことです。似たようなことが最近オープン系で使われている言語でできないってわけじゃないんですけどね…。
制御文
IFステートメント
IF 条件 THEN DO;
CALL HOGE;
CALL FUGA;
END;
IF-ELSE文
IF 条件 THEN
CALL HOGE;
ELSE
CALL FUGA;
THENとELSEの後が1文ならそのままで、複数ならDO ENDで囲む。Cのぶら下がりと似たイメージでいいのかな?あれ?(←わかってない)
ELSE以下に更に条件分岐を重ねる時は以下のようにも書いたりもします。DOで囲まずIFが階層になってる場合ELSEは最も近い内側のものとみなされるので、飛ばしたいときはELSE;として空のELSEを入れるとよいです。
IF 条件 THEN
/* 条件1がtrue */
ELSE DO;
IF 条件2 THEN
/* 条件1がfalseで条件2がtrue */
ELSE DO;
ELSE IF 条件3 THEN
/* 条件1, 2がfalseで条件3がtrue */
ELSE
/* 条件1, 2, 3 がfalse */
END;
END;
END;
WHILE的なもの
DO WHILE (FLAG = '0'); /* ここにもセミコロン */
CALL HOGE;
END;
DO UNTIL (FLAG = '0'); /* 書く場所はループの先頭UNTILの場合は評価は当然ループの後。WHILEとUNTILを同時に使うことも出来る */
CALL HOGE;
END;
FOR的なもの
DO I = 1 TO 30;
CALL HOGE;
END;
FOR的なものとWHILE的なものをまとめて!
DO I = 1 TO 30 WHILE(FLAG1 = ' '); /* カウンタとカウンタのループ脱出条件とカウンタ以外のループ条件を同時に指定 */
CALL HOGE;
END;
ちょっと便利かもー
SWITCH的なもの→SELCT
DCL A CHAR(1);
SELECT(A);
WHEN('AB')
CALL HOGE;
WHEN('BC', 'BF') /* 条件に複数入れられる */
CALL FUGA;
OTHER /* OTHERはOTHERWISEと書いてもいい、というかOTHERは省略形
CALL NYAA;
END;
という以外にもなんと
DCL A CHAR(1);
DCL B CHAR(1);
A = 'AA';
B = 'BB';
SELECT;
WHEN(A = 'AB')
CALL HOGE;
WHEN(B = 'BC')
CALL FUGA;
OTHER /* OTHERはOTHERWISEと書いてもいい、というかOTHERは省略形
CALL NYAA;
END;
WHENの中身がフリーダムだと感じる人もいるかも。
Cとは違ってフォールスルーしない。
比較演算子
IFとかに使う比較演算子には、
= 等しい
¬= 等しくない /* 汎用機でないと美しくない… */
<
>
<=
>=
¬< /* <の否定。>=と何が違うのか誰か私に教えてください。まぁ、慣れるとこれを使う方が読みやすい場面というのはありうる気がする */
¬> /* 上に同じ */
論理演算子
& /* 論理積 */
| /* 論理和 */
¬ /* 論理否定 */
プロシージャ
プロシージャの一例である関数の場合
FCTN:PROC OPTIONS(MAIN);
Z = SUM3A(1, 2, 3);
END FCTN;
SUM3A :PROC(A, B, C); /* PROCはPROCEDUREと書いてもよい */
RETURN = A + B + C;
END SUM3A;
プロシージャの一例であるサブルーチンの場合
SBRN:PROC OPTIONS(MAIN);
CALL HELO;
END SBRN;
HELO :PROC; /* PROCはPROCEDUREと書いてもよい */
DISPLAY('HELLO WORLD!');
END HELO;
ファイル入出力
OPEN FILE(INFILEA);
READ FILE(INFILEA) INTO(RDATA);
WRITE FILE(INFILEA) FROM(WDATE);
CLOSE FILE(INFILEA);
ファイルの読み込みの排他制御等はPL/I呼び出し時にJCLの起動パラメータで制御するのが普通?
正規表現
そんなのあるわけない?
例外処理
まったくないわけではありません。
ON SUBSCRIPTRANGE GO TO ERRORS; /* 配列の添え字が範囲を超えた状態SUBSCRIPTRANGE発生時のアクションとしてGOTOすることを決める */
DO I=X TO X+5;
B = B || C(I); /* 今回のコードでのSUBSCRIPTRANGEの発生しうるポイント */
END;
CALL HOGE;
RETURN;
ERRORS: CALL FUGA; /* ONの行き先 */
言語仕様でカバーできない分はGO TOでも使ってください。<strike>ループからのbreak的なのも同じ。GO TOではDOループの外から中へは飛べないようです。</strike>古いPL/I解説本には見かけないものもおおいのですが、少なくとも最新のIBMのPL/Iでは「LEAVE」がbreakにあたる「ループ脱出」を示します。ループの識別子を使うことでネストしたループから任意の階層のループの脱出も可能です(2011/7/14追記)
PL/Iのオブジェクト指向
考えない方がいいと思う
というわけで、本家のフォーマットであれこれ書いたので、おさまりづらい部分は大量にごそっと抜けてます。