RPL (プログラミング言語)
RPL (Reverse Polish Lisp)
RPLは、ヒューレット・パッカード社(HP社)が開発した小型電卓のオペレーティングシステム (OS) の一部であり、アプリケーションプログラミング言語である(初期開発者によるとReverse Polish Lispに由来する。一方、HP社のマーケティングは1987年の短い期間の間、RPLから逆にROM-based Procedural Languageという意味をこじつけで作り出そうとした)。
"Reverse Polish"は逆ポーランド記法(後置記法)を意味しており、直訳するなら『逆ポーランド式(後置記法式)LISP』となる。後置記法やスタック指向などという点ではForthとも類似している。
ヒューレット・パッカード社の科学技術計算グラフRPN(逆ポーランド記法)電卓のHP 28 シリーズ、HP 48 シリーズ、HP 49/50 シリーズで使われた。
HP 38G、HP 39/40 シリーズのような非RPN電卓でも特殊なツールを使って使用可能であるが、後述のSystemRPLだけに限られる。
RPLはRPNに基づいた構造化プログラミング言語であるが、数値だけでなく代数式や方程式も扱える。RPLはスレッド化されたインタプリタとして実装されている。RPLはForth言語と多くの類似点がある。両言語はスタック指向であり、Lispのようなリスト指向だけではない。以前のHP社のRPN電卓(4レベル固定スタック)に反して、RPLが使えるスタックは利用可能な電卓のRAMさえ余っていれば、スタックのレベル数を増やせる。
RPLは1984年にオレゴン州コーヴァリス市の開発拠点で誕生した。アセンブリ言語でOSを実装する以前の技術を置き換えるために開発された。RPLに対応した最後の電卓はHP 50gであり、2015年に製造終了となった。
RPLにはUser RPLとSystem RPLの2種類があり、一般的にRPLと言えば、User RPLを指す。
RPLの中でもシステムの低レベルから中レベルまでを扱うSystem RPL(もしくはSysRPL)言語は、OSを実装する言語の一部として前述の機種だけでなくいくつかの初期のHP社電卓で使われた。HP 48 シリーズの場合、外部ツールを使わないとユーザーはSystem RPLを使用できない。しかし、HP 49/50 シリーズの場合、SysRPLを使うための内蔵コンパイラが搭載されている。誤りのあるSysRPLコードを実行すると深刻なクラッシュを引き起こす可能性がある。
RPLの一種である高レベル言語のUser RPL(もしくはUserRPL)は、グラフィックスアプリケーションプログラムだけでなくテキスト型のプログラムを開発するために前述の電卓で利用できる。全てのUserRPLプログラムは内部的にはSysRPLで実装されている。しかし、利用可能なSysRPLコマンドの中の安全なサブセットだけを使う。UserRPLのコマンドはエラーチェックを行うので安全ではあるが、そのエラーチェックによってUserRPLプログラムはSysRPLプログラムよりも非常に動作が遅くなっている。
UserRPLコマンドのSYSEVALは、アドレスで指定された無名のオペレーティングシステムオブジェクトを評価・実行する。
newRPL は  が2014年から開発している User RPL を模倣した言語及び開発環境及びOSの総称である。HP社の正式なものではない。文法的には User RPL を拡張したものであるが、インタプリタ言語ではなくコンパイラ言語として動作するので、その実効速度は User RPL よりも圧倒的に高速である。
newRPL を使えるハードウェアは HP 48 シリーズ、HP 49/50 シリーズ ということになっていた。しかし、実際には HP 39gs/HP 40gs/HP 50g/PC(Windows) が対象になっている。
newRPL を使用するにはファームウェアの全面的な書換が必要である(データも全て消える)。OSも書き換わるので、GUIも変更される。なお、HPGCC3.org 側は、HP社のファームウェアに書き戻すことができると主張している。
2017年12月15日に Build 1001 がリリースされ、α版ではなくなった。2019年1月現在CAS(数式処理システム)は未対応である。
前述の対象ハードウェアが2015年に全て製造終了となっているので、別のハードウェアを開発対象に切替えるべきだという意見もある。
この節の内容は""に従っている。そのため、各項目ごとに脚注を明記しない。
ここではUser RPLのみ解説する。System RPLはスタックベースであることを除けば、User RPLと文法が大幅に異なることに注意する必要がある。
RPLは厳密な後置記法ではなく、中置記法も取り入れており、いくつかの重要な例外は存在するものの、制御構造は標準的な中置記法の言語と類似している。これらの制御構造を実装することによって、必要な時にプログラムの内部で条件分岐やループを実行できる。
RPLのスタックはインタプリタ上に実装されたソフトウェア的なものであり、CPUのスタックではない。スタックの底辺がスタックレベル1であり、電卓上では"1:"と表示される。スタックレベル番号が大きいほどスタックの上になる。スタックの出し入れ (POP/PUSH)は基本的にスタックの底辺から行われる。
RPLは文中でオブジェクト(数値、数式、文字列、条件式、プログラムブロックなど)に遭遇すると何も考えずにスタックにオブジェクトを入れることが動作の基本である。コマンドに遭遇したときはコマンドを実行する。条件式、数式、プログラムブロックのような実行できるオブジェクトは条件文などのコマンドが実行する場合と、EVALコマンドで意識的に実行することがある。
IF/THEN/ELSE/END構造は、基本的な条件分岐をサポートする。この制御構造の基本的な文法は以下のようになる。
<pre>
IF condition THEN if-true [ELSE if-false] END
</pre>
以下の例はスタックの底辺(スタックレベル1)の値が"1"であるかどうかをテストし、そうであれば、スタックの底辺を"Equal to one"に置き換える。
<pre>
« IF 1 == THEN "Equal to one" END »
</pre>
(説明)上のコードの条件式は '1 ==' である。IF文はこの条件式を実行する。1はオブジェクトなのでスタックに入れられる。==はコマンドである(一般的な言語の場合、==は演算子だが、RPLではコマンドである)。==コマンドはスタックから2つのオブジェクトを取り出すので、このプログラムを実行する前にユーザーが手動でスタックに値を入れておく必要がある。そうしないとスタックに1しかオブジェクトがないので、==コマンドが2つのオブジェクトを取り出せない。==コマンドは2つのオブジェクトが同じ時は真(1)、異なる時は偽(0)をスタックに入れる。IF文は条件式を実行した後、スタックの底辺から条件式の実行結果を取り出し、その数値が真(1)ならば、THEN文が実行される。上のコードではELSE文はないが、ELSE文がありその数値が偽(0)であれば、ELSE文が実行される。IF文はcondition(条件式)を評価するとその結果(0:偽あるいは1:真)をスタックの底辺に残す。そして、その条件式の結果を得るためにスタックの底辺を検証する。この動作を利用すると、RPLはForth形式のIF文をオプションでサポートすることができる。つまりIF文の前に評価される条件式を書くことができる。IF文の条件式を空にすることによって、IF文は条件式実行の間にスタックの変更を行わない。そして、THENを実行する前にスタックの底辺にある既存の結果(IF文の前に書いた条件の結果のこと。0:偽あるいは1:真)を取り出して使う。
IFT ("if-then")文とIFTE ("if-then-else")文を使うことによって後置条件式を実現できる。IFTとIFTEはそれぞれ2つまたは3つのオブジェクトをスタックから取り出す。取り出した2つまたは3つのオブジェクトの最上位のオブジェクトは真偽値として評価される。もしも真ならば、上から2番目のオブジェクト("then"に相当)はスタックに戻される。IFTEは上から3番目の"else"に相当するオブジェクトを必要とする。偽の場合、そのオブジェクトがスタックに戻される。
以下の例は、IFT文の例である。スタックの底辺から1つのオブジェクトを取り出し、それが1ならば、1を"One"に置き換える。
<pre>
« 1 == "One" IFT »
</pre>
(説明)上のコードの 1 はオブジェクトなので、すぐにスタックに入れられる。そして、==コマンドがスタックから2つのオブジェクトを取り出す。もちろん1を先にスタックに入れているので、1の上のスタックレベルにもオブジェクトがないとオブジェクトが不足する。そのオブジェクトはプログラム実行前にユーザーが自分でスタックに入れる必要がある。==コマンドは2つのオブジェクトを比較して、同じ場合は真(1)を、異なる場合は偽(0)をスタックに入れる。次に"One"もオブジェクトなので、スタックに入れられる。つまりスタックの状態は以下のようになる。
2: 0 or 1(条件式の結果)
1: "One"(thenに相当するオブジェクト)
そして、IFTコマンドが上の状態のスタックから2つのオブジェクトを取り出して、上のスタックレベル2が真(1)ならば、"One"を実行するが、"One"はただのオブジェクトなので、またスタックに入れられる。
以下の例はIFTE文の例である。スタックの底辺からオブジェクトを取り出し、それが1と等しいならば、1を"One"に置き換える。それが1でない場合は、それを"Not one"に置き換える。
<pre>
« 1 == "One" "Not one" IFTE »
</pre>
IFTとIFTEは引数として与えられたプログラムブロック(下のコードの最初の行と最後の行以外の« »で囲まれたところ)を条件によって評価・実行することがある。その動作を利用することによって、IF/THEN/ELSE/END構造よりもさらにコンパクトな条件文を作ることができる。
以下の例はスタックの底辺からオブジェクトを取り出して、それが1と等しいのか、1より小さいのか、1より大きいのかを判断してからそれを"One"、"Less"、"More"のどれかに置き換える。
<pre>
«
DUP 1 ==
« DROP "One" »
« 1 < "Less" "More" IFTE »
»
</pre>
(説明)RPLはオブジェクトと見なしたものはそれがプログラムブロックでも実行しないでスタックに入れる。そのため、プログラムブロックは評価(evaluate)されるまで実行されない。そのため、IFTEが実行される直前のスタックの状態は以下のようになる。3: 0 or 1(条件式の結果)2: « DROP "One" »1: « 1 < "Less" "More" IFTE »IFTE文はスタックから3つのオブジェクトを取り出す(つまり上の3つのオブジェクトはスタックから取り除かれる)。条件式の結果が真(1)のときは"then"に対応するプログラムブロック(« DROP "One" »)を評価する。その結果、オブジェクトはプログラムブロックなので実行される。結果が偽(0)の場合は、"else"に対応するプログラムブロック(« 1 < "Less" "More" IFTE »)を評価し、プログラムブロックなので実行する。
CASE/THEN/END構造を用いて、複数の条件から排他的に1つの条件を選ぶ、より複雑な条件論理式を実行できる。CASE文の中の1つだけの選択肢が実行される。この構造の基本文法は以下のようになる。
<pre>
condition_1 THEN if-condition_1 END
...
condition_n THEN if-condition_n END
if-none
</pre>
以下のコードはCASE/THEN/END構造の使用方法の例である。スタックの底辺にある1文字を取得して、それに対応する文字列をそれと置き換える。対応する文字がない場合は"Unknown letter"で置き換える。
<pre>
«
DUP "A" == THEN "Alpha" END
DUP "B" == THEN "Beta" END
DUP "G" == THEN "Gamma" END
"Unknown letter"
SWAP DROP  @ Get rid of the original letter
»
</pre>
上記のコードは以下の入れ子になったIF/THEN/ELSE/END構造と完全に等価である。
<pre>
«
IF DUP "A" ==
"Alpha"
IF DUP "B" == THEN
"Beta"
IF DUP "G" == THEN
"Gamma"
"Unknown letter"
SWAP DROP  @ Get rid of the original letter
»
</pre>
FOR/NEXT構造は、あるインデックス値から他のインデックス値までのループをサポートする。ループ用のインデックス値はループ内でプログラムからアクセス可能な一時的ローカル変数(インデックス変数)に格納される。FOR/NEXT構造の文法は以下のようになる。
<pre>
index_from index_to FOR variable_name loop_statement NEXT
</pre>
以下の例は1から10までの数値を合計するためにFORループを使っている。FORループのインデックス変数は"I"である。
<pre>
«
0       @ Start with zero on the stack
1 10    @ Loop from 1 to 10
FOR I   @ "I" is the local variable
I +  @ Add "I" to the running total
NEXT    @ Repeat...
»
</pre>
(説明)プログラムの最初の0は実行されて、スタックに入れられる。次の行の '1 10' も実行されて、2つの値がスタックに入る。FOR文はスタックから2つの値(1と10)を取り出して、インデックス変数Iに1を代入する。10は終了条件としてFOR文が保持する(スタックには返さない)。このとき、最初に入れた0がスタックの底辺に残ることになる。FOR文とNEXT文の間に 'I +' という実行される文がある。最初の I はインデックス変数であり、1から10の値をとる。Iはオブジェクトでもあるので、実行されるとスタックに入れられる。I=1のとき、スタックには0と1が入ることになる。次の + はコマンドである(多くの言語では + を演算子とするが、RPLではコマンド扱い)。+コマンドはスタックから2つのオブジェクトを取り出して加算して、その結果をスタックに入れる。I=1のとき、+コマンド実行前のスタックには0と1が入っているので、0 + 1 = 1となり、結果の1をスタックに入れる。I=2のとき、+コマンド実行前のスタックには1と2が入っているので、1 + 2 = 3となり、結果の3をスタックに入れる。この繰り返しで1から10までの数値を合計する。
START/NEXT構造は、開始インデックスから終了インデックスまでの単純なループをサポートする。FOR/NEXT構造と違って、インデックス変数(ループ変数)はプログラムから利用できない。しかし、インデックス変数は内部的に存在する。NEXTコマンドはインデックス変数を+1するだけである。START/NEXT構造の文法は以下のようになる。
<pre>
index_from index_to START loop_statement NEXT
</pre>
FOR/NEXTとSTART/NEXTは1回のループでインデックス変数を+1することしかできない。しかし、NEXTを増分値とSTEPで置き換えると、インデックス変数は+1以外の値で増やしたり減らしたりできる。例えば、以下のループはインデックス変数を1回のループ毎に-2加算することによって、10から2へ逆行するようにループする。
<pre>
« 10 2 START -2 STEP »
</pre>
WHILE/REPEAT/END構造は、ループの開始地点で条件テストをする回数未定のループをサポートする。WHILE/REPEAT/END構造の文法は以下のようになる。
<pre>
WHILE condition REPEAT loop_statement END
</pre>
DO/UNTIL/END構造は、ループの最後で条件テストをする回数未定のループをサポートする。DO/UNTIL/END構造の文法は以下のようになる。
<pre>
DO loop_statement UNTIL condition END
</pre>