X680x0 の C Compiler PRO-68K (XC) には BAStoC (BC.X) というコンバータが付属していて、X-BASIC のソースコードを C 言語に変換してコンパイルすることで高速化できたのですが、 残念ながら 無償公開されている XC からは削除されています。
このプログラムは BC.X の代替を目指して、X-BASIC のソースコードを C 言語に変換する Python スクリプト、および C++ プログラムです。
Python 版はクロス開発環境 elf2x68k と組み合わせて利用することを想定していますが、MicroPython for X68000 上でも動作するので、実機上での変換もできないことはないです (ただしめちゃくちゃ遅いです…)。
C++ 版は、X680x0 実機環境での動作に対応するために Python 版のコードを元に開発しました。オリジナルの BC.X と比べるとかなり遅いですが、無償公開版 XC での BC.X の補完のために公開します。
以下の説明は Python 版についてのものです。 X680x0 実機、エミュレータで動作する C++ 版については、README-bas2c.txt を参照してください。
Python 版は Linux (WSL) や MSYS2 等、Python3 が動作する環境で使用します。
bas2c.py と bas2c.def をパスの通った同じディレクトリに置いて、以下のように実行します。
bas2c.py [<オプション>] [<BASICソースコード>.bas] [-o <Cソースコード>.c]
<Cソースコード> の指定を省略すると、BASICソースコードの拡張子を .c に変更したファイル名を使用します。 <BASICソースコード> の指定を省略すると、標準入力から読み込んだソースコードを変換して標準出力に出力します。
以下の <オプション> が指定できます。
-u
- ソースコード中に X-BASIC の標準関数や外部関数にない関数呼び出しがあった場合、通常はそれをそのまま出力しますが (BC.X と同じ動作)、このオプションを指定するとエラーにします。
-n
- 通常、変換後の C ソースコードはプログラムの開始時に
b_init()
、終了時にb_exit()
を呼び出して X-BASIC としての初期化/終了処理を行いますが、このオプションを指定するとb_init()
は呼ばず、b_exit()
の代わりにexit()
を呼び出します。 b_init()
は X-BASIC プログラムの初期状態に合わせるため以下のような処理を行い、b_exit()
はここで変更した内容を変更前の状態に戻す処理を行っているようです。- 画面モードの 512×512 への切り替え
- ファンクションキー表示モード設定
- ファンクションキーや特殊キーの定義内容初期化
- CTRL+C 押下時のベクタ設定
- 乱数系列の初期化
- カーソル表示の消去
-n
を指定して出力したソースコードではこれらの処理を行わないため、例えば変換後のプログラムの終了時に画面が消去されて表示結果が見えなくなるのを防ぐことができる一方で、初期化が行われていることが前提の処理が正常に動作しなくなる場合があります。- 具体的には、以下のような現象が発生します。
- キー入力でカーソルキーなどの特殊キーを認識しない (X-BASIC ではこれらのキーに特別なコントロールコードが割り当てられるため)
- BASIC プログラム中、KEY コマンドでファンクションキーの内容を再定義すると、Human68k に戻ってもその内容が残ったままになる
- 具体的には、以下のような現象が発生します。
- 通常、変換後の C ソースコードはプログラムの開始時に
-s
- 変換後の C ソースコードの文字コードを Shift_JIS にします。デフォルトは UTF-8 です。
- (変換前の X-BASIC ソースコードは、Shift_JIS か UTF-8 かを自動判別します)
- MicroPython 上で実行する場合はこのオプション指定は意味を持ちません。入力ソースコードの文字コードがそのまま出力に使われます。
-b
- 変換時に、式における X-BASIC と C 言語の仕様の違いに対する補正を行いません。 X-BASIC と BC.X の出力とで結果が異なるようなソースコードを変換する場合、通常は X-BASIC の仕様に合わせるような変換を行いますが、このオプションを指定すると BC.X の出力に近い結果が得られるようになります。
-v
- 変換中の BASIC ソースコードを標準出力に表示します。
-c[TAB数]
- 変換後の C ソースコードに BASIC の各行をコメントとして出力します。
TAB数
はコメントのインデントに使うタブの数です。デフォルトは 7 です。
- 変換後の C ソースコードに BASIC の各行をコメントとして出力します。
bas2c.py で変換したソースコードは、elf2x68k で以下のようにしてコンパイルできます。
m68k-xelf-gcc -o <実行ファイル名> <Cソースコード>.c -specs=xc.specs -lbas
変換後のソースコードのコンパイルを X68k 実機やエミュレータ上の XC で行う場合は、
CC /W <Cソースコード>
のように /W
オプションを付けて、BASIC ライブラリをリンクするようにしてください。
X-BASIC の str 型(文字列型)変数を関数の引数や戻り値として使う場合、X-BASIC インタプリタではその変数の値が引数や戻り値として渡される(値渡し)一方、bas2c や BC.X で変換した C ソースコードではその変数への参照が渡される(参照渡し)という違いがあります。
この違いは、X-BASIC インタプリタと変換後の C ソースコードでの挙動の違いだけでなく、アプリケーションのクラッシュを含めた様々な問題を起こす可能性があります。
bas2c.py では、こうした問題の原因となる操作をエラーとして扱います。-b
オプションを指定すると、エラーチェックを行わず BC.X と同様の出力を得ることができます。
str 型変数を引数に取る関数は、BC.X/bas2c の出力ではその変数への参照が引数として渡されます。 このため、関数内で引数変数への代入を行うと、X-BASIC インタプリタでは呼び出し元の変数には影響を与えない一方で、BC.X の出力では呼び出し元の変数の値も変わってしまいます。
それだけではなく、X-BASIC インタプリタと BC.X の出力のそれぞれで、以下のような一見奇妙な挙動を示します。
- X-BASIC インタプリタ
- 関数の str 型引数への代入を行うと、その関数が呼び出されたときに渡された引数の文字数+1 文字までが格納できるようです。
- BC.X の出力
- 関数の str 型引数への代入を行うと、最大3文字までしか格納できません。更に、参照渡しのため関数呼び出し元の変数の値も変化します。
10 str s="abcdef"
20 sfunc(s)
30 print "s=";s
40 end
100 func str sfunc(a;str)
110 a="1234567890"
120 print "a=";a
130 endfunc
X-BASIC インタプリタでの実行結果:
a=1234567
s=abcdef
BC.X 出力の実行結果:
a=123
s=123
bas2c では、関数の str 型引数への代入をエラーとして扱います。
str 型の値を返す関数は、BC.X/bas2c の出力では戻り値を格納した変数への参照を返します。 関数内で定義されるローカル変数は関数から戻ると解放されてしまうため、このような変数への参照を返すと、関数から戻った後に存在しない変数への参照となるため、アプリケーションがクラッシュする可能性があります。
10 print sfunc()
20 end
100 func str sfunc()
110 str s="abcdef"
120 return(s) /* return後に変数 s は解放されるため正しくない操作 */
130 endfunc
bas2c では、str 型を返す関数でローカル変数を返すとエラーとして扱います。
BC.X には、X-BASIC プログラム中の #c
から #endc
で囲まれた行をそのまま C ソースコードとして出力する、という未公開機能があります。
bas2c.py でも同等の機能をサポートしています。
X-BASIC では実現できない処理を C のコードとして記述するのに利用できますが、この機能で出力されるコードは main 関数または func 命令で定義中の関数の中になるため、グローバル変数の定義や #include ディレクティブなど、関数定義の外でないと書けない処理には使用できないことに注意が必要です。
X-BASIC プログラム (テキスト VRAM の プレーン0 への書き込み):
10 #c
20 {
30 void B_SUPER(int); /* #includeが使えないので個別にプロトタイプ宣言が必要 */
40 B_SUPER(0); /* スーパーバイザモードへ */
50 }
60 #endc
70 for i=&HE00000 to &HE0FFFF
80 poke(i,&HFF)
90 next
100 end
1000 func poke(a,v) /* アドレスaに値vを書き込む関数 */
1010 #c
1020 *(char *)a = v;
1030 #endc
1040 endfunc
変換後の C ソースコード
/******** program start ********/
void main(int b_argc, char *b_argv[])
{
b_init();
{ // #c - #endc の中がそのまま出力される
void B_SUPER(int); //
B_SUPER(0); //
} //
for (i = 0xE00000; i <= 0xE0FFFF; i++) {
poke(i, 0xFF);
}
b_exit(0);
}
/***************************/
int poke(int a, int v)
{
*(char *)a = v; // #c - #endc の中がそのまま出力される
}
※ インライン C 言語で DOS コールや IOCS コールを使用した場合は、elf2x68k でのコンパイル時には -ldos -liocs
、XC でのコンパイルの際には /Y
オプション (DOS/IOCSライブラリの使用) を指定してください。
bas2c.py は、X-BASIC の標準関数や外部関数を C 言語での関数名を変換するために、bas2c.def というファイルを使用します。 BC.X で使われていた *.DEF ファイル (BASIC.DEF, GRAPH.DEF,...) を 1 ファイルにまとめた形になっています。
ファイルは以下のようなフォーマットで記述されています。
[<グループ名>]
<BASIC関数の戻り値型> <BASIC関数名>(<BASIC関数引数型...>) : <C関数名>(<C関数引数型...>)
:
<グループ名> は、関数呼び出しが使われた際に必要なヘッダファイルを include するために使われます。 例えば、[GRAPH] グループにある関数 (fill(), line()など) がプログラム中で使われると、変換後の C ソースコードの冒頭に
#include <graph.h>
が追加されます。 適切な C 関数用ヘッダファイルを用意して bas2c.def に関数定義を追加することで、ユーザーが独自の外部関数を追加することもできます。
(bas2.def の冒頭には最初のグループとして、特殊な変換規則を必要とする関数、ステートメントが定義されています。これらの変換規則は bas2c.py 内の変換ルーチンの記述と連携しているので変更しないようにしてください)
- BC.X は XC ver.1 の頃から使われていることもあり、出力される C ソースコードの関数定義が (いわゆる) K&R スタイルで書かれていました。 bas2c.py は XC ver.2 や gcc での利用を前提として関数定義を ANSI スタイルで出力します。 その他、配列の初期化などでも BC.X と異なるコードを出力します。
- X-BASIC と C 言語とでは演算子の優先順位や論理演算の値が異なります(例えば、真を表す値は X-BASIC では -1 ですが C 言語では 1 です)。
BC.X はこの違いを考慮せずそのまま C 言語に変換していたため、式の計算結果が X-BASIC と異なることがありました。
bas2c.py では、変換時に必要に応じてカッコを補うことなどにより計算結果が変わらないようにしています。
この挙動は
-b
オプションによって変更できます。
bas2c.py は MIT ライセンスとします。