Top / Technical Document

Alan
Technical Document

Last updated: 2008-01-08
Created: 2004-08-01








by Fumisky Wells

目次

  1. 本書の目的
  2. 設計方針
    1. 層状の宇宙
    2. 複雑さの制御
    3. C++, Perl の置き換え
    4. 学習曲線がリニアな言語
    5. 開発者・保守者・実行効率、の優先順位で考える
    6. Simple C++
    7. Turing から学ぶ
    8. Eiffelから学ぶ
    9. Perlから学ぶ
    10. Cobolから学ぶ
    11. シンプルに
    12. 型について
      1. 潜水服と飛行機工場
      2. 思考を妨げる型、支援する型
    13. D&E
    14. 参照一様性
  3. クラス関係図
  4. Tiger, Oberon-0 について
    1. 比較
    2. Oberon-0 の Item と Obj についての考察
  5. 実行フロー
  6. ソース中の接頭辞(prefix)
  7. 組み込み型の追加
  8. 今後の計画
  9. リリース作業

本書の目的

本ドキュメントは、Alan の内部構造に関するもの。 一般向けではなく、どちらかと言うと自分のための備忘録を 整理したぐらいのもの。

設計方針

  1. 層状の宇宙
  2. 複雑さの制御
  3. C++, Perl の置き換え
  4. 学習曲線がリニアな言語
  5. 開発者・保守者・実行効率、の優先順位で考える
  6. Simple C++
  7. Turing から学ぶ
  8. Eiffelから学ぶ
  9. Perlから学ぶ
  10. Cobolから学ぶ
  11. シンプルに
  12. 型について
    1. 潜水服と飛行機工場
    2. 思考を妨げる型、支援する型
  13. D&E
  14. 参照一様性

設計方針

層状の宇宙

アーキテクチャが階層構造を持っていることは、エンジニアにとっては 既に常識のことと思う。Alan も同様で、階層構造の視点から 全体の方向性を考えてみたい。

Alan及び Alan を取り巻く外環境を層の視点から分類したのが次の図だ:
人間
アプリケーション GUI, CUI
ミドルウェア・ライブラリ
i/o, Math, CGI, SOAP, DB, ...
Alan VM, 仕様, parser, lexer, semantics, ...
OS
デバイスドライバ
ハードウェア
大枠として、なるべく「層全体でシンプルに」なるように 物事を決めていこうと考えている。

例えば、例外処理機構。VM の実装という点で見れば例外処理機構は 複雑になってしまうが、この機構のおかげで上位層の実装が簡単になり、 結果として「層全体でシンプルに」なると判断したため、例外処理は Alan に取り入れたのだった。

逆の例を。言語の肥大化かライブラリレベルでのサポートか、というジレンマがある。 Cobol は言語の肥大化に進んでしまった例だ。
これと対極にあると思われるのが Lisp だろう。Lisp の言語仕様は 恐ろしく小さく抽象度が高い。そして、できることはライブラリのレベルで 対応しようとしている。オブジェクト指向もライブラリレベルで いくつものオブジェクト指向ライブラリが提供されていると聞く (Shiroさん、参考にさせて頂きました)。

「シンプルに」という原則で見れば Lisp の辿った道の方が優れているようだ。

これは、下位層を単純にし・上位層でそのしわ寄せを吸収することで、 最上位層(アプリ・人間)に対しては影響を与えないようにした例だ。

「層状の宇宙」という言葉は、マイケルポランニー「暗黙知の次元」から借用した。

複雑さの制御

一番最初に来るのは、やはりこれではないだろうか。 何の複雑さか、で詳細は様々ではあるが。

"複雑さは、おそらくソフトウェアの品質の唯一の最も重要な敵ではないだろうか" -- B.Meyer, OOSC1, 邦訳版 p.161

以下、TBD.

C++, Perl の置き換え

仕事や趣味で使用している言語は主に C++ と Perl だ。 これらを仕事&趣味の使用範囲内で置き換えられることを目標としたい。

今の C++, Perl は覚えることが多く、今の私の小さい脳ではオーバフロー 気味となってしまった。 不満と見るか、修行不足と見るかは難しいが、 とりあえず、この不満を取り除き頭をすっきりさせたい、というところ。 文字通りの「俺言語」だ。でも、それでいいのだ。

学習曲線がリニアな言語

「学習曲線がリニア」とは、以下の意味をまとめて表現している:
  1. 初心者にとっつきやすい
    -> 最初の敷居を低く
    -> 時間軸=0 の時、レベル=0から始まる、の意。
  2. 途中で急に難しくならない
    -> 反例:Cのポインタ
    -> レベルが急勾配になったり不連続にならない、の意。
  3. 途中から飽和しない
    -> 反例:実用以前で機能が飽和した、オリジナルPascal
    -> 反例:いわゆる 4GL 言語。初心者向け機能が豊富だけど、 すぐ使えなくなる傾向にある。
    -> ある時期・規模を超えると、とたんに使えなくなるようなことの ないようにする、の意。
  4. どこまでも上昇
    3. の裏返しだが。(しかし、そんな言語私に作れるのか?)
こう書くと、一見、Pascal や Turingなどの 「教育目的の言語」、という面もあるけれども、 そうでもない。私自身、認識の発展の過程的構造(つまり、ステップ ・バイ・ステップ)に大変恩恵をこうむった経緯があり、それを 大事にしたい、ということなのだ。

開発者・保守者・実行効率、の優先順位で考える

TBD

Simple C++

私と C++ は愛憎関係にある(笑)。 C や C++ の好きな部分もあるのだけれど、 今や手に負えない複雑言語になってしまった C++。 http://www.geocities.co.jp/SiliconValley-SantaClara/1294/parsingcxx.html によれば、素の C++ 文法は shift/reduce 百数十、reduce/reduce 十数、という 恐ろしい事態。 もはや私の小さな脳にはオーバフロー気味。 経験上有用と感じた部分だけにしようかと思い、Alan は以下のように Simplify したいと考えている:
キーワード、構文など 選択 説明
ポインタ × 参照のみに統一
struct × classのみに統一
static変数 × instanceで代用しよう
const ×' 関数引数はデフォルトで const reference 渡しとする。
テンプレート ? テンプレートがあまりに冗長で 読めない(理解できない)私。
例外処理 置換 try-throw-catch で著しく読みにくくなったと思っている。 Eiffel/Ruby 流の raise-rescue としたい。
ヘッダーファイル × Javaと同じだが。

Turingから学ぶ

今のコンパイラ系(Java, Eiffel)の Hello, World がかなり冗長なのに 比べ、スクリプト系(Perl, Ruby)の Hello, World はこれ以上ないほど シンプル。 両者は全く相容れない水と油なのだろうか?

Eiffel は、メイン関数の存在を批判していたが、私には いまだにその理由が分からない。オブジェクト指向と フリー関数は相反する存在なのだろうか?

個人的にはそうとは思えないのと、Turing の例もあることから、 Alan ではシンプルなケースはシンプルに、ということにしたいと 考えている。

Eiffelから学ぶ

Eiffel からは多くのことを学んだが、一番は DbC か。 DbC に基づいた例外機構は提供したいと考えている。

しかし、Eiffel の仕様には不満もある。

  1. rescue 句を設定できるのが関数スコープだけ (任意の end を rescue ... end に拡張できない)。
  2. quasi_inverse() (OOSC1 邦訳 p.210) はまともなフローとは思えない。
  3. rescue句の最後で上位に自動的に raise する仕様。 DbC に基づけばそうなるのだろう。しかし、このために ignore を 後付けで提供しているのなら、どちらが正しかったのか・・・。

    C の switch が 97%間違っている(Deep C Secrets だったか?)例は示唆的だ。 C の switch が fall-throw なのは、そちらの方がより汎用的だからだ。 しかし、そのために break 文を 97% の個所で書く必要が出てきた。 3%の利益のために、97%の個所でバグを誘発しやすくなったわけだ。

  4. Eiffel は普通のエラー処理と例外処理を分けて考えていること。 従来の方法(if文によるステータスチェック)が優れているケースが多い ことを OOSC1 では論じている。ここまでは私も同意する。 しかし、エラー処理と例外処理が別のものとなるなら、 かえって複雑さが増えはしないか。

Perlから学ぶ

Perl の言う斜め横断的というのは示唆的だ。 例えば
  1. 論理式の or や and が、数学的には可換が普通だが、 Perl や C では A and B では A を先に評価し、成り立たなければ B は 評価しない、という仕様になっていること。一見、論理的透明さ・単純さとは 別の複雑な仕様のように思えるが、
    if( a!=NULL && a->f() )
    のような例を考えると、人間的には確かに Perl/C 方式の方がしっくりくる。
  2. 他にもあったような・・・(今思い出せない)

認識の過程的構造というやつだろうか。

Cobolから学ぶ

過去、最もバカにされてきた言語のナンバー1ではないだろうか。 でも、未だに現役だ。 そのことを考えると、Cobol からも何か学ぶべきものがあるのではないだろうか? もちろん、初学者の慣性(一度学んだものはずっと使い続ける傾向)が 今も存続している一番の大きい理由だろう。

でも、Cobol のプログラムはあとから読みやすい、というのが定評だ (もちろん異論もあるだろうけど)。私も、C や Perl と比べて、 Cobol の 単語指向(word-oriented)な言語の方が読みやすいと感じている。 C や Perl の記号指向(TBD-oriented)の言語は、一旦頭の中に入ってしまうと、 多くの機能を圧縮して記述できる。 1行のプログラムがそれなりの処理をしてくれるので、「俺ってスゲー」感を 満たしてくれる麻薬的な(大げさ^^;)側面があり、かなりハッカー(主に開発者)に 熱狂的に受け入れられた、と私は見る。でも、C や Perl の使用者自身が 認めているように、1年後にそのコードを見ても何をやっているのか 書いた本人にも解読不可能になってしまうのが記号指向言語の副作用か。

私は C/C++/Perl を使うが、気持ち的には「後から読みやすい単語指向言語」 の方が段々好きになってきている。

シンプルに

C++, Perl の置き換えと言っても、全機能を包含する気はない。 そうではなく、自分の仕事や趣味の分野に限って使えるぐらいであればいい、 というぐらいのところ。

型について

  1. 潜水服と飛行機工場
    コンパイル時に型チェックを行い、実行時に行わないことを
    潜水服を着て練習し、実際に海に潜るときに 潜水服なしで潜るようなもの
    という例え話で、型あり言語を批判した例がどこかにあった記憶がある。

    私は、これは違うと思っている。 むしろ、型チェックとは、工場で飛行機を整備するようなもの、 あるいは、オリンピックなどの試合に望む前に練習場で訓練したり何回も 失敗を繰り返したりしておくものではないか、と。

    で、いざ飛ぶときには、一緒に工場をつれて飛ぶは必要ない、 ・・・そういうものではないか、と思うのだが。

  2. 思考を妨げる型、支援する型
    C++ と Perl を長年使用してきて思ったことは、 型があった方が仕事が捗ったということ。 むしろ、Perl の方が注意深く「えー、この時に使う演算子は・・・」 といった感じで余分に神経を消費してきた *。

    C や Pascal などのデータ型は、 関数やブロックの最初で宣言しないといけない。 そのため、文を書き下している最中に追加で変数が必要になるとソーステキスト の上の方に戻って型を宣言して、それから戻って・・・といった感じになり、 思考を中断してしまう。

    でも、(CLUを先祖とする?)C++ などのように必要なときになって宣言できる 構文では、思考の妨げにはならない。int を使うのか str を使うのか、 file を使うのかは既に決まっているわけだし、型があるため、演算子が "+" なのか "." なのか、eq なのか == なのか、ということにも 悩まなくて済む。

    そういうわけで、C++ の型は、思考のコストを下げてくれる、と思っている。

    Perlだって、int か string かの区別はないけど、スカラーか($x)、 配列か(@x)、連想配列か(%x)、の区別がある。型の区別を 変数の表記法で行っているわけで、型があると言えないこともない。

    交通ルールは、車のじゃまをしているのではなく、スムーズな交通を促すための もの(原則は)。

    いきなり金銭感覚の話になるけど、 節約のための色々なガイドラインは自由を束縛するものではなく、 むしろ無駄な出費を抑えることで、使いたいことにお金を使えるようにする ためのもの、つまり、より自由度を増してくれるものだ。

    データ型も、CLU / C++ に至って、思考を支援してくれるもの、 とは考えられないだろうか?

    C や Pascal がなぜ関数やブロックの最初に宣言しておかないといけないかと いうと、1パスでコンパイルさせるための単純化だったと予想する。 1パスでコードを生成するためには、関数の最初に、コードを生成する前に その関数で使う全てのローカル変数の領域を確保する必要があるから。 (バックパッチすれば1パスでもできるのかな?)

    * この点について、まつもとさんから " 動的言語の代表としてPerlを選ぶのはやめてほしい " とのコメントを頂きました。 まあ、私は Perl しか知らず、代表という意味で書いたわけでは ありませんので、ここはそういうことでご了承下さい。 型なし言語として Perl しかしらなかった男の不幸と思って頂ければ・・・ (^^;)。 Ruby か Python を知っていれば Alan を作ろうなどという無謀なことも 考えなかったかもしれません(自爆)。 ・・・というのは冗談 50% で、言語処理系の深奥を知るために 作っているので、まあこれはこれで良いのか、と。
    ここで突然 Perl思い出話になってしまうのだが、 Perl を使って10年近くになるのだが、特に不満というのはこれまで なかった。CPAN は素晴らしいし、ライブラリはそろっていたので。 ところが、ふと仕事で SOAP 辺りを扱う必要が出て CPAN から SOAPLite をダウンロードして使おうとしたが、分け分からなくて逆ギレしたのが、 Perl に見切りをつけたくなったきっかけだった。 ここで Python か Ruby に転向するパスもあったはずだが、 Alan という泥沼にはまってしまったのが Alan の発端であった・・・ というのは本気 50% である。

D&E

D&E 4章「C++言語の設計ルール」が大変参考になる。 その中から、 ・・・について述べておきたい。
優先度 項目 コメント
>シンタックス タイプシステム
>シンタックス セマンティックス
TBD シンタックス "シンタックスは言語の一番重要な U/I だ"(D&E-J p.150)が、 タイプシステムやセマンティックスに比べると二義的とした Stroustrup の優先順位付けは私にとっては大変教訓的だ。
TBD 直交性 "直交性は原則的には良いことだが、しかしコストを伴う。 ...C++では、効用と効率が最優先され、直交性は二義的な関心になった。" (D&E-J p.129)
TBD a/b(Alan Beautifier) 簡単に実装できそう
TBD ドキュメント
生成
統合環境よりも容易に実装できるのと、実用度という点で統合環境に近い ものが可能という意味で、ドキュメント生成ツールの実装は あって欲しい。
TBD 統合環境

C++ D&E を参考にした、言語開発の難易度:
相対
難易度
項目 コメント
x100 テンプレート、例外処理
x10 多重継承 "...[多重継承反対論者は]多重継承を深刻に受け取りすぎている...。 多重継承はプログラミングの万能薬ではない。 安物の薬だから万能でなくても良い。" (D&E-J p.342)
(TBD) 名前空間 まだ難易度が良く分かってないもの
1 RTTI(RunTime Type Information) "...RTTIは例外処理やテンプレートにに比べて2桁以上簡単...であり、 継承と比べて1桁以上簡単...なのだ。" (D&E-J p.390)

参照一様性

Eiffel にて言及されている(また Ruby においても導入されている)参照一様性を Alan でも取り入れた。 Ruby を使ってみて、やはり便利だと感じたからである。

参照一様性とは、オブジェクト x のメンバ a が関数であるか変数であるか に関わらず、同じ表記をすることを言う。例えば、Eiffel や Ruby では、 引数無し関数と変数はどちらも x.a と表す。C++/Java ではメンバ変数だと x.a で、メンバ関数だと x.a() となり、一様ではない。

Alan の参照一様性は配列にもあてはめることとする:

変数と関数の参照一様性

戻値の型 T の引数のない関数 a と、型 T の変数 a は外見上どちらも同じ
a
である。

配列と関数の参照一様性

整数添字 i の配列 a と整数引数 i の関数 a は外見上どちらも同じ
a(i)
である。

配列を丸括弧で表現するのは Fortran の影響である (というより、配列と関数は参照時、意味論的に同一なので 同一の表現であるべき、というポリシーが最初に頭の中にあった。 そこから振り返ってみると、Fortran がその祖先であったことを改めて 再発見した、というのが真相である)。

比較

Alan Eiffel Ruby C/C++ Perl
変数 a a a a $a
引数無し関数 a a a a() a()
引数あり関数 a(i) a(i) a(i) a(i) a($i)
配列 a(i) a.entry(i) a[i] a[i] $a[$i]
連想配列 a(i) ? a[i] ? $a{$i}
あまりに単純化すると、最後は Lisp になる(?)。 Alan の参照一様性は、Lisp の一つ手前、と言ったところだろうか。

クラス関係図

だんだん自分でも分からなくなってきたので、一旦まとめる。 Doxygen を使ってもいいのだが、ここではもう少し概要的なものを。
フェーズ
分類
構文解析時 意味解析・コード生成時 実行時
抽象クラス A_stmt
  kind
  pos
A_exp
  pos
Obj
  kind
  type
  id
Type
  kind
  size
  id
 
データ型(*2) A_type Obj_type Type_int
Type_bool
Type_str
Type_rex
Type_file
Type_array
変数(*1) A_def_var(定義時)
  id
A_exp_id(使用時)
  id
Obj_var
  level
  adrs

V_str
V_rex
V_file
V_array
メンバ A_exp_member
  A_exp exp
  Atom id
Obj_member
  offset
runtime変数(V_*),
runtime関数
関数
A_def_func
  id
  type
  fparms
  stmts
Obj_func
  result
  adrs
  local_size
  parm_size
prolog ... ret
組込関数
Obj_cfunc
  vmcfunc
vm_put() など
(*1)
変数の各フェーズを追っていくと、変数というものが各フェーズで どのように変換されていき、最終的に実行対象となるかが見えて 分かりやすい。
構文解析時は、単なる文字列 id であり、ソース中の出現位置 pos を保持している(定義文 (例: int i;)は A_stmt の派生クラス、 式中の使用(例: ... 3 + i * j ...) は A_exp の派生クラス)。
意味解析時、アドレスを保持しており、型情報(=サイズ情報)を 参照している。
そして、実行時、int のような簡単なケースは、指定された アドレスを直接 C++ の int にキャストして実行している。また、 Alan 独自のデータ型 (str, file, rex, arrayなど)の場合は 実行時データを表現する構造体 V_type にキャストして 実行させている。実行時は変数名(id)もソース上の位置(pos)も 必要ないのだ。
(*2)
まだユーザ定義型をサポートしていないので A_type がそっけないのは これでいいのだけれども、Obj_type と Type の間の関係が イマイチはっきりしない。本来、実行時に型名など必要ないのだが、 debug 目的に追加した結果、Obj_type と重複してしまっている。 まあ、こういった未整理部分を発見するためにこうやって クラス関係図を作ったのだから目的は達せられたのだが。 さて、どう解決しよう。

Tiger, Oberon-0 について

  1. 比較
  2. Oberon-0 の Item と Obj についての考察

比較

Alan は Tiger と Oberon-0 を参考にして作成している。 比較図を書き、どう違っているか、何を採用したかを書く。
比較項目 Alan Tiger Oberon-0 Hoc C-- コメント
識別子文字列 Atom S_symbol Ident Tigerを参考にしている。
構文木 A_exp, A_stmt
A_def_var
A_def_func
A_exp
A_vardec
A_fundec
n/a n/a n/a Tigerを参考にしている。
識
別
子
の
内
部
表
現
抽象クラス Obj
  Type type
  Atom id
E_enventry Object Oberon-0を参考にしている。
変数 Obj_var
  int adrs
E_enventry.var
  Ty_ty
?
関数 Obj_func
  V_code *adrs
E_enventry.fun
  Ty_ty result
  Ty_tyList formals
Object
  Object *dsc(引数list)
  int val(アドレス)
Tiger と Oberon-0 には微妙な違いがある。 引数のリストを、Oberon-0 では Obj のリストとしている のに対し、Tiger では一旦型だけのリスト(Ty_tyList) に変換して保持している点だ。
レコード Ty_fieldList = list[Ty_field] = list[S_symbol, Ty_ty] Object Oberon-0 では Object 1つでスコープやレコードの表現両方を実現している。
記号表(*) Sym_tbl S_table Object の linked-list Oberon-0を参考にしている。
データ型 Type
Type_int
  :
Ty_ty Type Oberon-0を参考にしている。
文法属性 Obj expty
  Tr_exp(中間コード)
  Ty_ty
Item
  mode(*1)
  Type
  a,b,c,r
構文木を意味解析している間、構文木に沿って やりとりしているデータ。Alan では式の中間結果も 一時変数に保持するため、Obj を定義体(変数等) と中間結果の保持の両方に使用している。Oberon-0 と違う点。

(*1) mode は CPU のアドレスモードに対応しているそうだ。

(*) 検索キーとなる名前を内部表現(Alan/Oberon の Obj, Tiger の E_enventry) の中の1フィールドとしているか、STL の map のように外だししているか、 という違いがある。Alan/Oberonは1フィールドとして、Tiger は外だしと している。両者の違いは些細なものなのでどちらでもいいのだが、 個人的には1フィールドとする方が RDBMS との類似で理解しやすい。

以下、補足。

  1. 関数は引数がない場合は括弧不要にしたい 理由:下記のようなシンプルな記述ができる:
    window.position.x
    プロパティのようなものだ。 Eiffel の参照一様性(だったか?)にも通ずるか? 確か Ruby もここら辺り注意深く設計していた記憶がある。

  2. 上と同様に、手続きも括弧不要にしようか、と思ったが、v0.03 辺り?で Cライクな変数定義文法と衝突することが判明。
    a b;
    とあった場合、これが変数定義
    int i;
    なのか手続き呼出し
    put i;
    なのかは文脈自由文法の範囲では判別できない。

Oberon-0 の Item と Obj についての考察

Oberon-0 の処理系では、Obj とそれによく似た Item という2つの データ構造が出てくる。両者の違いはテキストに簡潔に述べられているが、 あまりに簡潔なため、ここでもう少し理解を深めておきたい。
比較項目 Obj Item Comment
名前 name - Obj はソース中の宣言(=Alanで言う定義)されたもの(変数、型、関数)を 表現している。 だから、ソース中の識別子 id を内部表現である Obj にマップする ための検索システムが必須だ。
これに対し、Item は式の構文木の属性だ。だから、名前は不要。
kind class mode Obj.class は、派生クラスの識別子だ。Objの実装は union なので、実際は何を表現しているか(変数なのか、関数なのか、メンバなのか) を識別させる役目を担っている。
これに対し、Item.mode は、Obj.class と密接に関係があるとは言え、 こちらは何と「CPU のアドレッシングモード」を意味しているという。 単に Item.mode = Obj.class と考えていた私にとっては、この飛躍 (派生クラスの識別子が Item となっては CPU のアドレッシングモード) がまだよく理解できていない。
しかし、class との関係はさておき、Item = 式の中間結果を表現するもの、 と考えれば、Item.mode はアドレッシングモードであると言うのは よくわかる。もっとも、mode = cond の場合はもはやアドレッシングモード とは言えない。そう考えると、「式の構文木の属性」という辺りが無難か。
Obj.class Item.mode Comment
Var Var 構文木の末端でClass->Itemにコピーされるもの
Const Const
- Reg Item独自。 古典的な例(Hoc, PL/0 など?)では式の中間結果は スタックに保存していたが、Oberon-0 では何と スタックマシンは放棄して RISCの流行りを意識してかレジスタに保存しているのだ。 これには2点問題があると思う:
  1. レジスタが無くなった場合どうなるかと言えば、 そこで abort するか、 あるいは他のレジスタ値を上書きしてエラーを無視して 処理を続けるかだ。いずれにしても、OSG.GetReg() の INCL(regs, i) の動作次第だ。この振る舞いは、 スタックの時よりも後退してないか? 複雑な式では 28個のレジスタは簡単に消費してしまう ような気がするのだが。
  2. レジスタは 32bit 固定なので、中間結果の保持 として 1byte, 2byte の時は非効率だし、32bit を 超えるデータ型には対応できない。 Oberon-0 のデータ型は全て 32bit という前提なので 問題はないのだが、この前提を超えようとすると、 途端にこの中間結果をレジスタに置くという アプローチは使えなくなるし、Item の設計も ゼロからやり直しだ。教育用言語から実用言語への 大きな jump が必要となる部分か。
- Cond Item独自。bool式を jmp に置き換えるために必要な 属性。もはや CPU の addressing mode とは言えない。
Par -
Fld -
Typ -
Proc -
Sproc -
データ型 type type class/mode の次に重要な属性。ここは両者共通。
ネストレベル lev lev global/local を表現。ここも両者共通。
リンク情報 next, dsc - スコープやデータベース(記号表)を表現するための属性が Obj には必要となっている(もっとも、これは実装の仕方次第だが)。 これに対し、Item は記号表へのエントリーは不要だし、 スコープとも無縁だ(式中の中間結果を表現するだけなので)。 従って、対応するものは存在しない。
値 val a class によって、val は アドレスなのか(Var, Par, Proc, Sprocの場合)、 即値なのか(Constの場合), オフセットなのか(Fldの場合), を表現している。 それぞれ全く意味が異なるので、Obj から派生させて別の サブクラスとして表現させたほうがよいと思うのだが、 Wirth によれば:
"個々のタイプの代わりに数値で表現された判別子(Obj.class) を使用することで多数の冗長なタイプ防護が不要となり、 よって効率が向上することになるからである。 結局のところ、データタイプを過度に増やしたくは ないのである。" ([Oberon-0] p.62 より)
とのことだ。 構文木の末端で val は a にコピーされる。
- r mode=Reg の時に使用。 Oberon-0 では式評価中の中間結果は 必要に応じてレジスタに保持している。 レジスタに保存された場合、そのレジスタ番号(r=1..28)を表現する
- c mode=Cond の時に、a とペアで使用。 ただし、この時の a は上の a とは全く異なる用途だ。 c = 比較演算子、a = fixup 用 jump アドレス。
- b まだよく調査してないのだが、not 演算子をコード生成するさいに 必要な、a とはまた別のアドレスを保持するためのもののようだ。

実行フロー

ソース
↓
字句解析
↓
構文解析
↓
意味解析
├┬─┐
│ObjObj_var
││├──┬・・・
││Type_intType_str ...
├┴┴┴・・・
↓
コード生成
├─┬──┐
│ ObjObj_var
│ │ ├───┬・・・
│ │ Type_intType_str ...
├─┴──┴──┴・・・
↓
実行

ソース中の接頭辞(prefix)

Prefix 説明 主なファイル
A_ Abstract Syntax Tree(抽象構文木)。 構文解析時, 意味解析時に使用。 absyn.h
e_ エラーモジュール。 構文解析時, 意味解析時、コード生成時に使用。 lib/err.h
g_ グローバル変数
あるいはコード生成時メンバ
lib/global.h
あるいは type/*, semant/gen.cpp
R_, r_ runtime 関連。 実行時クラス、関数。 type/*, type/runtime/*
s_ 意味解析(semantic analysis)フェーズ関連。 semant/semant.cpp
T_ Token。字句解析時&構文解析時に使用。 alany.y
V_ Virtual Machine 関連。 仮想マシン上のコードなどに使用。 vm.h

組み込み型の追加

(rex型を追加したときの経験を元に書いている 2004-09-26)
  1. 必要に応じて字句解析ルーチンを alanl.l に追加。
    例: regex
  2. 必要に応じて構文解析ルーチンを alany.y に追加。
    例: T_REX_LIT
  3. 必要に応じて構文木を absyn.h, absyn.cpp に追加。
    例: A_exp_rex_lit()
  4. データ型の実装を type/ に作成。
    ここがデータ型追加の核となる。 例: rex.cpp, rex.h
  5. データ型を type/types.h, types.cpp に追加。
  6. 必要に応じて意味解析ルーチン semant.cpp を変更。 例: rex.cpp, rex.h
  7. 必要に応じてコード生成部 gen.cpp を変更。
  8. 必要に応じて VM vm.cpp を変更。
    (rex の場合、変更部分はなかった(!))

今後の計画

今後実装したいと思っているのは以下だ(優先順位の 高いものから低いものへの順): これら言語の「ビルディングブロック」は既に他の言語でも馴染の ものだ。同じことを繰り返しても意味がないので、 なるべく Alan が特徴を出せるような形で実装していきたいとは考えているが、 「俺言語」なので車輪の再発明でもいいだろう。

制約事項 の項参照)も参照。

リリース作業

  1. ドキュメント整備
    1. ChangeLog 作成 devlocal/svn_diff.sh の出力を参考に。
    2. README の version を書き換え and/or 作成
    3. devlocal/version を書き換え
    4. install.d/install の PREFIX を書き換え
    5. doc/version, doc/footer.html を書き換え。
    6. SourceForge 向け document 作成
    7. langsmith, NetNews へのアナウンス文書も作成
  2. バージョン管理
    SubVersion リポジトリに保存
  3. make dist
  4. Installテスト
    Install が問題なく行えるかテスト。 問題が発生すれば、前に戻ってやり直しだ。
  5. パッケージの up SourceForge に up
  6. Homepage への document の up
    1. cd doc; make doc_up
  7. SubVersion にタグ付け
    この段階になってタグ付をしよう。タグ付はリリース直前の作業だ。 devlocal/svn_tag.sh 参考に。
  8. NetNews, langsmith, などでアナウンス

Top / Technical Document





Alan ver0.31