【御注意】本言語は2013年10月現在、C++/CLIにて、コンパイラをコーディング中です。
0.「CΞ言語」:「極限まで手を抜くが、それなりに楽しめる言語」
CΞ言語は、TTLのみで作成した16bitコンピュータRETROF-16(画像)専用のコンパイル言語です。CΞは「Cくさい」と読みます。その名の通りC言語風の言語ですが、限界まで簡易化しています。実際にはコンパイラと言うよりも「構造化アセンブラ」と呼んだ方が正しいのかも知れません。
「限界まで簡易化する」といっても「Hello,World」とか「1から10までの足し算」しかできない様な「つまらない言語」ではなく、1978年に大ヒットした「インベーダーゲーム」程度を記述できる実用性を有します。
画像はTTLコンピュータ、RETROF-16で表示したインベーダー。
画像の表示は静止画、プログラムは機械語で直接入力した。
今回の独自コンパイラ作成はこのインベーダー達を実際に動かす事が目標。
1.識別子
予約語、変数、定数、関数の区別
英数字(アンダーバー含む)からなる英数字トークン(単語)は、予約語、変数名、関数名、もしくは定数です。
CΞ言語の予約語はvar if else loop escape の4つしかありません。予約語以外の英数字トークンは全て関数か変数か定数です。
C言語は先頭文字で変数か定数かを区別しますが、CΞ言語は定数として解釈できるものは定数、それ以外は変数もしくは関数と解釈します。
16進定数は末尾にHをつける事とし、評価結果が65535を超える定数はコンパイルエラーとなります。
変数と関数の区別は前後の文脈で判断します。
コメントとしてC言語同様の//が使えます。/* … */形式のコメントは実装予定はありません。
0 // 定数0 100 // 定数100 100H // 定数100(16進) 99999 // コンパイルエラー 100X // 変数(もしくは関数)
変数の宣言
CΞ言語の変数には型も符号もありません。宣言は全て予約語「var」の後ろに変数名(最大8文字長さ無制限)を記述するのみです。
verの後ろに複数の変数名を記述する事により、複数の変数を一度に宣言できます。末端の「;」は不要です。
変数名の後ろに数字トークンが続くと配列宣言になります。さらに数字が続くとそれは「初期値」と見做されます。
var aaa // 変数 aaa を宣言 var x y z // 変数x y z を宣言 var a 20 // aと言う名の要素数20の配列宣言 var data 3 0 0 0 // dataと言う名の3要素の配列を全て0で初期化する var aaa x y z a 20 data 3 0 0 0 // 上記4行を一括宣言
アドレス定数
変数名の先頭に@を付けると、その変数の格納アドレスを表す定数となります。C言語における「単項演算子&」の働きと同じですが、CΞ言語の@は演算子ではなく、定数を表す識別子の一部と見做します。
x = y + 1 // xにはyの値に1加えた値が格納される x = @y + 1 // xにはyの格納アドレスに1加えた値が格納される
宣言不要な特殊変数
(1)レジスタ変数
RETROF-16には16bit長のレジスタが256本あります。これらのレジスタはCΞ言語では、宣言せずとも変数として使えます。
このレジスタ変数の名はR0~R255です。数字部分は16進での記述も可能で、この場合R0H~RFFHとなります。
但し0番レジスタは実行時のエントリーアドレスを決める分岐命令を書く必要がある為、汎用レジスタとしては使えません。
(2)メモリ変数
レジスタの値をアドレスと見做し、そのアドレスが指定する主メモリを変数とします。C言語におけるポインタの考え方です。
このメモリ変数の名はM0~M255です。数字部分がレジスタ番号に対応します。数字部分は16進での記述も可能です。
R10 = 100H //10番レジスタに256が格納される。 M10 = 1 //10番レジスタで示される主メモリ(=256番地)に1が格納される。
(3)画像メモリ変数
RETROF-16には32KBのビデオメモリがあります。このメモリをアクセスするための変数が画像メモリ変数です。
この画像メモリ変数の名はV0~V255です。数字部分がレジスタ番号に対応します。数字部分は16進での記述も可能です。
例えば、V10=0 は、10番レジスタの値で示される画像メモリの内容を0(黒)にする画像メモリ代入文となります。
RETROF-16は256画素×256画素のLCDコントローラーを有しています。1画素は4ビット(16色)です。画像メモリは1アドレスあたり8bitです。すなわち画像メモリ1アドレスあたり2画素分の情報を保持します。LCDの左上が0000H番地、右下が7FFF番地に対応します。
2.演算子
演算子は全て2項
演算子は全て2項演算子です。単項演算子や多項(3項以上)演算子はありません。
各演算子の働きはC言語に準拠しますが、「++」と「--」はC言語と意味が異なり、それぞれキャリー付加算とボロー付減算を意味します。
+ - ++ -- & | ^ // 算術論理演算子 = // 代入演算子 == != > < >= <= // 比較演算子
アドレス・データ変換演算
単純な構文で多彩なメモリアクセス機能を実現するには、アドレス値とデータ値との間での相互変換の仕組みが必須です。C言語ではこの変換を&と*の二つの演算子によりプログラマが明示的にその変換をできるようにしていますが、 CΞ言語はこの方法は用いません。
下記はC言語の場合
// yにデータが格納されている場合 x = y; // xにはyの値が格納される x = &y; // xにはyのアドレスが格納される (明示的なデータ→アドレス変換) // yにアドレスが格納されている場合 x = y; // xにはy(アドレス値)が格納される x = *y; // xにはyが示すメモリの内容が格納される (明示的なアドレス→データ変換)
CΞ言語では&や*を用いずに、アドレス定数とメモリ変数で、アドレス・データ変換を実現します。
下記はCΞ言語の場合
// yにデータが格納されている場合 x = y; // xにはyの値が格納される x = @y; // xにはyのアドレスが格納される (明示的なデータ→アドレス変換) // yにアドレスが格納されている場合 x = y; // xにはy(アドレス値)が格納される Rn = y // n番レジスタにアドレス値がコピーされる x = Mn // xにはn番レジスタが示すメモリの内容が格納される (明示的なアドレス→データ変換)
3.式とシフト
一般に「式」には長さ(項の数)に制限が無く、演算子の優先順位なども絡むためコンパイル処理は面倒なものとなります。
勿論、式の解釈には「定番アルゴリズム」がありますが、CΞ言語ではアセンブリ言語並みに式の表現を限定しました。
これにより、コンパイラは極めて単純なアルゴリズムでオブジェクト生成が可能になります
代入式
代入式はそれ自体が文(代入文)でもあります。
//■=□◎◇ (■は変数、□と◇は変数または定数、◎は算術論理演算子)、以下は記述例 foo = x +1 foo = @ARRAY + R0 R10 = R11 + R12 //■◎□ (左辺と右辺の第一項が等しい場合はこの様に略記できる)、以下は記述例 RF0H+1 i+1
左項は1つ、右項は2つまです。つまり、x=y+1 は書けますが、x=y+z+1 の様に右辺の項が3つ以上になるとエラーとなります。
これはかなり厳しい制限の様に思えますが、実用上はさほど問題にならないと予測しています。実際にCΞ言語でいくつかアプリを組んでみて苦しいようであれば制限を緩める予定です。(Jun.2013)
また、代入式には省略形があり、x=x+1 は x+1 と略記できます。
比較式
比較式は、if文もしくはfor文の条件部分にしか記述できません。
//□★◇ (□と◇は変数または定数、★は比較演算子)、以下は記述例 x>0 R20 == R21
比較式は代入式よりも更に制限が厳しく、右辺と左辺は変数もしくは定数が一つだけです。
この仕様も実際にCΞ言語でいくつかアプリを組んでみて苦しいようであれば制限を緩める予定です。(Jun.2013)
論理シフト
シフトは文です。代入式や比較式の中には記述できません。
>>Rn //n番レジスタの右シフト <<Rn //n番レジスタの左シフト
シフト命令上記の様に単独の文として記述します。乗除算サブルーチンや、擬似乱数発生サブルーチンの中でしか使わないと判断し、独立文としました。またシフトの対象もレジスタ変数に限定しました。
4.配列と構造体
配列
配列とは明示しない(暗示的な)アドレス→データ変換です。C言語はポインタを用いて、配列と同様の処理が可能ですが、連続するメモリ領域のアクセスである事を直感的に視認できる様、[]演算子(配列演算子)を用意しています。
しかし、CΞ言語では思い切って配列演算子は割愛しました。但し配列処理自体はアドレス・データ変換を用いて実現可能です。
下記はC言語の場合
// x[a]=y[b]; を配列演算子を使わずに表現する。 *(&x+a)=*(&y+b) ;
CΞ言語においても、C言語よりかなり煩雑になりますがC言語と同様に[]等の配列演算子を用いずに配列相当の処理がは可能です。
下記はCΞ言語の場合。煩雑にはなるが配列相当の処理は可能
// C言語の x[a]=y[b]; をCΞ言語で表現する。 Rn = @x + a Rm = @y + b Mn = Mm
構造体
CΞ言語には構造体はありません。共用体や列挙体もありません。もちろんクラスもありません。
これは、インベーダーゲームの実現や、BASICインタプリタ等のアプリケーション記述には必須の機能ではないと判断したためです。
5.制御文
判断と繰り返し
C言語と同じですが、繰り返しはloop(while相当)のみです。
C言語では条件式を()で括りますが、CΞ言語では括りません。また文末のセミコロンもありません。
loop文は式を省略すると無限ループとなります。
if 式 {文} if 式 {文}else {文} loop 式 {文} loop {文} //無限ループ
escape 文
C言語におけるreturnとbreakとcontinueとexitを全てこの一つで賄います。escape文の後には数字が続き、その値で分岐先が決まります。
数字は省略すると1となります。数字が0の場合はC言語でいうcontinue相当になりますが、関数を先頭からやり直す目的にも使えます。
下記のfuncは任意の関数名を表します
func{ … escape } //関数終了 (C言語のreturn相当。main関数なら機械語のHALT命令を生成する) func{ … escape 0 } //関数再実行(関数レベルのcontinue) func{ … escape 2 } //コンパイルエラー func{ loop{ … escape }} //ループ脱出 (C言語のbreak) func{ loop{ … escape 0 }} //ループ再実行(C言語のcontinue) func{ loop{ … escape 2 }} //関数終了 func{ loop{ … escape 3 }} //コンパイルエラー func{ loop{ loop{ … escape }}} //内側のループ脱出 func{ loop{ loop{ … escape 0 }}} //内側のループ再実行 func{ loop{ loop{ … escape 2 }}} //外側のループ脱出 func{ loop{ loop{ … escape 3 }}} //関数終了 func{ loop{ loop{ … escape 4 }}} //コンパイルエラー
このescape文が上手く働けば、goto文は不要と考えていますが、goto文の実装に関しては保留中です。(Jun.2013)
6.関数
関数定義
xxxxxx{ … }
上記の形をし、xxxxxxxの部分が予約語、変数、定数のいずれでもなければ関数と見做します。(プロトタイプ宣言は不要です)
CΞ言語には「引数」と言う概念はありません。これは対象となるCPU、RETROF-16が256本のレジスタを有する為、関数への値の受け渡しは全てコーダーの責任で全て「レジスタ渡し」とする事が可能だからです。
C言語では最初に実行する関数の名前はmainですが、CΞ言語にはそのような規則は無く、常にソース上で最初に定義された関数が実行されます。
関数呼出し
xxxxxx
CΞ言語では上記xxxxxxが、関数として定義されていれば、それを関数呼出しと見做し、その先頭アドレスに分岐します。
ソースプログラム上における関数定義と関数呼出しの順序は任意です。
戻りアドレス
CΞ言語の対象になるCPU(RETROF-16)にはスタックという概念がありません。このため、関数呼び出し(サブルーチンコール)の戻りアドレスは関数自身のオブジェクト領域内部に格納されます。具体的には関数に対応するオブジェクト領域の先頭1ワードに戻り番地が格納されます。関数自体のロジック(機械語命令)は対応するオブジェクト領域の先頭2ワード目から始まります。
この方法はサブルーチンコールの仕組みを極力単純化する反面、ROMが使えない、あるいは再帰呼び出しができない等の副作用も持ちます。しかし、「実用性を保ちつつ、最大限の手抜きをする」というCΞ言語コンパイラの思想に従い、あえてこの方法をとることにしました。
御意見歓迎します。
本言語は100%個人の趣味で作っている言語です。作ること自体が目的です。勿論ターゲットとなるCPUも手作り、手ハンダです。
このあたりの「特殊事情」を御理解頂けるかたがおられましたら、お声掛け頂ければ幸いに存じます。(2013年6月)
御質問等ありましたらTwitterでお気軽にどうぞ。
くだらない質問は大歓迎しますが、
不真面目な質問は御遠慮願います。
id=duo6750
ダイレクトメッセージには一切応対しておりません。