RETROF-16M用システム記述言語「BAC+

はじめに

BAC+とはTTL(74シリーズ標準ロジック)のみで構築したコンピュータ「RETROF-16M」のために開発した言語の名前です。命令の種類が極めて限られているCPUでも効率のよいオブジェクトを生成できることを最優先として設計しました。
名前の由来はアセンブラとC言語の中間に位置する言語(Between Assembly language and C language)であり、その名前の通り、C言語的な構造化書式を有してはおりますが、実質的にはアセンブラに近い言語です。

コンパイラ

R16M

BAC+のコンパイラはWindows上で動くクロスコンパイラです。このクロスコンパイラはVisual studio 2015 expressを用いてC++/CLI言語で記述しています。

実機(右の画像)にはオブジェクトコードのみを転送し実行させる方式です。

実機自身でのコンパイル機能(セルフコンパイラ)は2016年3月現在、開発の構想はありますが、開発自体には着手しておりません


記述例

BACテストプロ

BACコンパイラ自体の動作試験を兼ねて作成したプログラムの全ソースコードがこちらのページにあります。

http://diode.matrix.jp/R16M/TESTPRO.htm

某有名ゲームを模したアプリケーションです。フォントデータは搭載しませんでしたので「文字」の表示は省略しましたが、その他の部分は命中しなかったビームの自爆やUFOの出現等も含め「本物」を極力忠実に再現しています。


文法説明

型と数

BAC+で扱う数値は文字変数を含め、全ての定数/変数は符号なし16bitです。
C言語のunsigned short intに相当しますが、全て同じ型であるため、型を区別するための予約語や文法等は存在しません。

定数

10進表記と16進表記があります。先頭に#を付加すると16進表記と見做します。16進表記の英字は大文字・小文字の混在が可能です。また10進表記、16進表記共に先行するゼロは無視され、値が65535を超す記述はコンパイルエラーとなります。

変数

変数名は英数字とアンダーバーからなる任意文字数が可能です。
但し、以下の文字列は変数名として使用できません。
・数字から始まる変数名
・英字1文字のみの変数名
・VARで始まる変数名

変数は使用する前に宣言をする必要があります。
 終端記号(C言語でいうセミコロン)は不要であることに留意してください。

VAR 変数名

配列変数

一次元配列のみ可能です。変数宣言の後ろに要素数をカッコで囲んで指定します。(要素数の書式は定数の書式と同じです)

VAR 変数名 (要素数)

配列変数の変数名はC言語と同様、その配列の先頭アドレスを保持する定数扱いとなり、代入はできません。

初期化子付配列変数

変数宣言の後に初期化子(定数)を空白区切りで任意個カッコで囲んで記述します。
(記述した初期化子の数が配列の要素数になります。

VAR 変数名 (定数 定数 定数。。。)

初期化子付配列変数の記憶領域はロジック領域に確保されます。従ってプログラムをROM等に書き込む場合は、この「初期化子付配列変数」は書き換え不可の「初期化子付配列定数」となります。この場合はアプリケーション側の責任(プログラマの責任)で、この「初期化付配列定数」を通常の配列に事前にコピーして用いる等の配慮を要します。

文字列型初期化子付配列変数

前述の初期化子付配列変数を文字列で初期化するものです。文字の種類によらず文字数が配列の要素数になります。「"」は「\"」、「\」は「\\」と記述します。漢字混在も可能です。

VAR 変数名 "任意の文字列”

レジスタ変数

BAC+言語は汎用レジスタが大量にあるCPU(具体的には汎用レジスタを256本有するRETROF-16M)を想定して設計されています。
そのため、普通の変数よりもレジスタ変数を用いた方が高速かつ効率の良いオブジェクトを生成できます。

VAR 変数名 ($レジスタ番号)

使用できるレジスタ番号はRETROF-16Mの場合、0〜255(#0〜#FF)です。
但し、0番レジスタと200番以降のレジスタは特別な意味を持ちますので、実質的に指定可能なのは1〜199となります。
また、複数のレジスタ変数に同じレジスタ番号を割り当て、レジスタの使用本数を節約することが可能です(C言語の共用体に似た考え方)。

1文字変数(宣言不要なレジスタ変数)

名前が1文字の変数A〜Zとa〜zの計52種は変数宣言をせずに使えます。
変数A〜Zは200番レジスタ〜225番レジスタに、同じく変数a〜zは226番〜251番レジスタに自動的に割り当てられるレジスタ変数として機能します。

入力ポート変数 ? ?? 

変数名が「?」の変数はは0番入力ポートのを、同じく変数「??」は1番入力ポートの値を保持する変数です。
これら変数は読み込み専用であり、代入を行うとコンパイルエラーとなります。

出力ポート変数 !!

変数名が「!!」の変数は出力ポートへ値を書き込むための変数です。
この変数は書き込み専用であり、参照を行うとコンパイルエラーとなります。

プログラムカウンタ変数 %%

変数名が「%%」の変数はプログラムカウンタの値を保持する変数です。この変数は読み込み専用であり、代入を行うとコンパイルエラーとなります。この変数はコンパイラ自体のデバッグ用でアプリケーションの記述に用いることを想定していません。
 

アドレッシングモード指定先行子

アドレッシングモード指定先行子とは

アドレッシングモード指定先行子とは定数名や変数名の直前に配置する記号のことで、その定数や変数の値や格納領域を変化させる役割を持ちます。C言語のポインタアクセスで用いる、単項演算子&(変数名をポインタに変更する演算子)や単項演算子*(ポインタをそれが示す値に変更する演算子)の考え方をBAC+言語が独自に拡張したものです。

$ (レジスタ先行子)

定数をレジスタに変化させる先行子です。

イ $10
ロ $#A

これはイ、ロともに10番レジスタを意味します。
通常、レジスタを使用する場合は、レジスタ変数を宣言して使いますので、この書式は実際には使用することはありません(コンパイラ自体の試験用の書式です)

% (メモリ先行子)

定数・単純変数・レジスタ変数をその値が示すメモリの内容の値に変化させる先行子です。考え方はC言語における単項演算子*とほぼ同じです。 

  VAR foo($100)  VAR hoge(100)
イ %100      
ロ %$100    
ハ %foo
ニ %hoge   

イは主メモリの100番地の内容を意味します。
ロとハは共に100番レジスタの内容が示す主メモリの内容を意味します。
ニは配列hogeの先頭要素の内容を意味します

@ (VRAM先行子)

レジスタ変数をその値が示すVRAMの番地の内容の値に変化させる先行子です。
VRAMは256画素×256画素の画面に対応し、左上が0番地、右上が255番地、右下が65535番地となります。VRAMの各番地は8ビットで「未未赤赤緑緑青青」に対応します(未はメモリは存在するが色には影響を与えないビット)。
例えば値3は明るい青、値1は暗い青、値63は白、値0は黒を意味します。

  VAR foo($100)  VAR hoge
イ @$100      
ロ @foo
ニ @1000
ホ @hoge   

VRAM先行子が許されるのはレジスタ変数のみです
イとロは共に100番レジスタの内容をVRAMのアドレスとした画素の値を意味します。
ニは定数に対するVRAM先行子、ホは通常変数に対するVRAM先行子でコンパイルエラーとなります。

ニ演算子と優先順位 

2項演算子は「+」「-」「&」「|」「^」「=」の6種類で、その意味はC言語と同じです。但し「^」はExclusiveORではなくExclusiveNORの意味にしています。
演算子の優先順位はなく、常に左から右へ評価します。代入記号「=」も左から右へ評価しますので、代入文は右辺から左辺ではなく左辺から右辺への代入となります

記述例

イ a+b-c=x  
ロ 0=a=b=c    
ハ a=100 @a & #F =@a   

イはa+b-cの結果をxに代入します。0以下になる減算や65536以上になる加算は数学的には正しい値となりませんが、エラーにはならずに実行できます。
ロはa、b、cに0を代入します。(C言語の「a=b=c=0;」に相当します)
ハは、少々複雑になりますが、VRAMの100番地に対応する画素から赤色成分を「抜く」処理を行う演算となります。

配列アクセス

BAC+言語は配列アクセスのための演算子(C言語の[]相当)はありません。配列の個々の要素にアクセスするにはメモリ先行子%と演算子式を組み合わせて行う必要があります。

  VAR foo(10)
イ foo+2=w %w=x      
ロ foo+4=w 0=%w   

イは、レジスタ変数wを媒介として、配列fooの3番目の要素の値をxに代入。
ロは、同様に配列fooの5番目の要素を0にする処理を意味します。
配列要素番号は0から始まるので、3番目が「+2」、5番目が「+4」となります

単項演算子

前置演算子として「++」(インクリメント)と「−−」デクリメントがありますが、二項演算子と組み合わせたり、単項演算子を重ねることはできません。

イ ++b--C   

イは一つの式に見えますが、「bをインクリメントする文」と「Cをデクリメントする文」の2つの文を空白をあけずに記述したものです。

シフト演算子

正確にいうと演算子ではなく演算補助記号ですが、右シフトを行う「>>」が用意されています。
この記号は演算の途中結果を右に1bitシフトしますが、その結果はどこにも格納されません。シフト結果は続く2項演算子の左辺値として解釈されます。
この記号は任意個重ねて記述できます。重ねる際に空白を挟むか否かは任意です。 

イ a>>      
ロ a+b >> = c
ハ %a >>>>    >> =x   

イはaを右シフトしますが結果はどこにも格納されませんし、a自身も変化しません。意味のない構文ですがコンパイルエラーにはなりません
ロはa+bの結果を右シフトしてからcに代入することを意味します。
ハはaが示すメモリ内容を3bitシフトした値をXに代入します。

制御構文

BACにおける条件判断の仕組み

プログラミング言語には条件を判断する仕組みが必須です。しかし、BACにはC言語における比較演算子の様な物は一切ありません。条件判断は全て「最後に行った演算の結果」で行います。これはアセンブリ言語における「フラグ判断」と全く同じ考え方です。

if文相当の構文

イ +[ 文 ]      
ロ -[ 文 ]
ハ *[ 文 ]

ニ /[ 文 ]
ホ '[ 文 ]
  
ヘ ,[ 文 ]     

イ:最後に行った演算の結果の最上位ビットが0ならば、文を実行します。
ロ:最後に行った演算の結果の最上位ビットが1ならば、文を実行します。 
ハ:最後に行った演算の結果が#FFFFならば、文を実行します。 
ニ:最後に行った演算の結果が#FFFF以外ならば文を実行します。 
ホ:最後に行った加算の結果がオーバーフローした場合、もしくは最後に行った減算の結果がアンダーフローしなかった場合に文を実行します。最後に行った演算が加減算以外の場合は動作は不定になります。 
ヘ:最後に行った加算の結果がオーバーフローしなかった場合、もしくは最後に行った減算の結果がアンダーフローした場合に文を実行します。最後に行った演算が加減算以外の場合は動作は不定になります 

if〜else文相当の構文

if文相当の構文に更に[]を重ねるとelse節相当になります。

イ +[ 条件が一致した時に実行する文 ][ 条件が一致しなかった時に実行する文 ]      
ロ -[ 条件が一致した時に実行する文 ][ 条件が一致しなかった時に実行する文 ]   
ハ *[ 条件が一致した時に実行する文 ][ 条件が一致しなかった時に実行する文 ]   
ニ /[ 条件が一致した時に実行する文 ][ 条件が一致しなかった時に実行する文 ]   
ホ '[ 条件が一致した時に実行する文 ][ 条件が一致しなかった時に実行する文 ]   

ヘ ,[ 条件が一致した時に実行する文 ][ 条件が一致しなかった時に実行する文 ]   

繰り返し構文

繰り返し構文はif文相当の構文の6種の条件に加え、無条件ループ(下記のト)が加わります。条件に一致する間、処理を繰り返します。
(条件の意味の説明はif文相当の構文と同じですので省略します)

イ [ 文 +]      
ロ [ 文 -]
ハ [ 文 *]

ニ [ 文 /]
ホ [ 文 ']
  
ヘ [ 文 ,]
ト [ 文 !]
    

break文相当

条件に一致した場合にループを抜け出すため構文です。
(条件の意味の説明はif文相当の構文と同じですので省略します)

イ +[>]      
ロ -[>]
ハ *[>]

ニ /[>]
ホ '[>]
  
ヘ ,[>]     

continue文相当

条件に一致した場合にループの先頭に戻るための構文です。
(条件の意味の説明はif文相当の構文と同じですので省略します)

イ +[<]      
ロ -[<]
ハ *[<]

ニ /[<]
ホ '[<]
  
ヘ ,[<]     

一致比較の代替書式

2つの値の一致判断は両者の値のExclusiveNORを取り、結果が#FFFFならば一致とするのが最も簡単です。
XとYが同じ値の時に「文」を実行するならば以下の構文となります。

X^Y*[文]

しかし、値の比較は頻繁に出現する処理でありながら、上記の構文ではその意味が直感的ではありません。そこで、演算記号「^」の代りに「:」を、フラグ種別記号「*」の代りに「==」を用いる代替書式を許しています。この代替書式を用いると上記の文を以下の様に書くことができ、より直感的になります。

X:Y==[文]

同様に不一致の場合はフラグ種別記号「*」の代りに「!=」を用いることができます。

X:Y!=[文]

この代替書式は繰り返し構文や、break文相当、continue文相当でも同様に使えます

 

関数宣言と関数呼び出し

関数宣言

関数宣言は下記の書式です。<>で囲まれた関数名(命名規則は変数名と同じ)から「;」までが関数本体と見做します。
BAC+における変数は全てグローバル変数相当となるので、引数の概念はありません。

<関数名> 関数本体 ;

関数呼出

関数呼出は、文中に関数名を記述するだけです。但しソースリスト上で関数呼出は対応する関数宣言の後にしか記述できません。関数宣言の前にその関数を呼び出すとコンパイルエラーとなります。

関数呼び出しの入れ子に関する補足

関数呼び出しの入れ子は文法上は無制限ですが、戻り番地を格納するメモリは有限ですので、実質的な限界があり、それを超すと動作は保証されません(i8080と同じ)。
BAC+コンパイラは主メモリの末尾から変数領域を確保し、残りを関数の戻り番地の格納エリアとします。主メモリが64KWで、ロジックが32KW、変数領域が16KWの場合、残りの16KWが戻り番地の格納エリアとなり、これは約1万6千重の入れ子が可能である事を意見します。

エントリーポイント

エントリーポイントとは

エントリーポイントとは、ソースコード中で一番最初に実行される記述です。BAC+はでは以下の規則に従います。

関数<MAIN>が宣言されている場合

関数<MAIN>から実行されます。これはC言語におけるmain()の考え方と同じです。
<MAIN>を終了するとプログラム自体が終了します(HALT命令を実行する)。
大文字小文字は区別されますので、<main>はこの対象とはなりません。

関数<MAIN>が宣言されていない場合

ソースコードの先頭から実行されます。ソースコードの先頭が関数記述の場合はその関数を実行しますが、その関数を終了した際の動作は保証されません。


その他の構文

コメント記号 //

記号「//」から、その行の終わりまではコメントとなります(C言語と同じ)。

コメント記号 ####

記号「####」を記述すると、ソースの末尾までの全てがコメントになります。これはコンパイラ自身のデバッグに用いる記号で、一般のアプリケーション記述で用いる事は想定していません。

終了記号 ...

記号「...」(ピリオドの3つ重ね)を記述するとそこでプログラムが停止します。エミュレータの場合は「継続指示」をすることにより、プログラムを続行することができますので、終了記号というよりも一時停止記号(デバッグ時のブレークポイント)相当の記号となります。
一方、実機での動作の場合はそのアドレスからの再開手段は無く、リセット信号による先頭番地からの再スタート扱いになります。

終わりに

BAC+言語は、今現在も成長している言語です。ここで紹介した構文だけでは、記述が困難なアプリケーションに遭遇すると、その場で新たな構文を追加しています。
事実、break文とcontinue文はこの文章を書く数日前に追加しました。
今後どう成長するかはわかりませんが、上手に育てて行きたく思います。

(2016年3月8日)