[LSP42] Oberon-2

(この記事はLISP Implementation Advent Calendar 21日目のためのエントリです。)

Oberon-2でLISPを作りました。
https://github.com/zick/Oberon2Lisp

動機

今年の春、訳あって42個のプログラミング言語でLISP処理系を実装することになりました。これはその35個目です。
使った言語が30を超えてからは基本的に「名前を知ってる言語は使ってしまえ」戦略をとっていて、「次は本で見たことがあるModula-3を使おう」と思っていました。しかし、Modula-3の処理系のインストールが上手く行かず失敗。Wikipediaを読むとOberon-2という似たような言語があると書いてあり、こっちは処理系のインストールがうまくいったので使うことにしました。

外観

Oberon-2のプログラムはなかなかインパクトがあります。

PROCEDURE Nreverse(lst: LObj): LObj;
VAR
  ret: LObj;
  tmp: LObj;
BEGIN
  ret := kNil;
  LOOP
    WITH
      lst: Cons DO
        tmp := lst.cdr;
        lst.cdr := ret;
        ret := lst;
    ELSE
      EXIT
    END;
    lst := tmp  (* This statement can't be in WITH due to type checking. *)
  END;
  RETURN ret;
END Nreverse;

で、出た〜wwww大文字奴〜wwwwwww
教科書などに載っている古いFORTRANのプログラムみたいです。まあ、FORTRANを元にした言語なので間違ってはいないのですが、90年代に作られた言語なのにキーワードがすべて大文字なのはどうかと思います。でも、シンタックスハイライトがなくてもキーワードがすぐに分かるのはある意味いいかもしれませんね。見るぶんには。タイプするときにはたまったものじゃありません。

オブジェクト

Oberon-2ではレコード型があるので、これでLISPのオブジェクトを表すことにしました。

TYPE
  LObjDesc = RECORD END;
  LObj = POINTER TO LObjDesc;
  NilDesc = RECORD (LObjDesc) END;
  Nil = POINTER TO NilDesc;
  ConsDesc = RECORD (LObjDesc) car, cdr: LObj END;
  Cons = POINTER TO ConsDesc;

実際に使うときはレコードそのものより、そのポインタの方が便利なのでレコードとそのポインタにそれぞれ名前をつけます。レコードは継承関係を作ることが出来ます。上記NreverseのようにWITHを使ってキャストをすることもできます。
レコードの実体をつくるにはNEWを使います。

PROCEDURE MakeCons(a, d: LObj): LObj;
VAR
  cons: Cons;
BEGIN
  NEW(cons);
  cons.car := a;
  cons.cdr := d;
  RETURN cons
END MakeCons;

未初期化の変数に対してNEWを呼ぶのがなんだか違和感があります。ちなみに、NEWで作ったレコードはごみ集めによって自動的に回収されます。昔のFORTRANみたいな文法の言語とは思えないくらい気が利いてます。

メソッド

レコードに対してはメソッド(のようなもの)を定義することも出来ます。

TYPE
  SubrFnDesc = RECORD END;
  SubrFn = POINTER TO SubrFnDesc;
  SubrCarDesc = RECORD (SubrFnDesc) END;
  SubrCar = POINTER TO SubrCarDesc;
  ...
  SubrDesc = RECORD (LObjDesc) fn: SubrFn END;
  Subr = POINTER TO SubrDesc;
...
PROCEDURE (f: SubrFn) Call(args: LObj): LObj;
BEGIN
  RETURN MakeError("unknown subr")
END Call;

PROCEDURE (f: SubrCar) Call(args: LObj): LObj;
BEGIN
  RETURN SafeCar(SafeCar(args))
END Call;

関数Callは subr.fn.Call(args) のように呼び出します。 fn の型に応じてどの Call が呼ばれるかが決まるという仕組みです。 Call に現れる f はいわゆる this の役割で、好きな名前をつけることが出来ます。

論理積は & で、論理否定は ~ です。しかし論理和はなぜか OR です。意味が分かりません。
無条件ループをつくる LOOP では、それを抜け出す EXIT が使えるのに、条件付きループをつくる WHILE では EXIT が使えません。意味が分かりません。
なんか、色々と古臭いのに、処理系のヘルプを読む限りJITコンパイルをしてるみたいです。逆に意味が分かりません。

小学生並みの感想

大文字が許されるのはFORTRAN 77までだよねー

Leave a Reply