本プログラムはLinux上で動作する独自仕様のコンパイラであり、asm_x64アセンブラソースコードを出力します。
以下のコマンドをタイプするとコンパイラの実行可能ファイルが作成されます。
gcc -o [コンパイラの実行可能ファイル名] compiler.c
以下のコマンドをタイプするとasm_x64形式のアセンブラソースファイルが作成されます。
./(コンパイラの実行可能ファイル名) stdio.rh ソースファイル
ファイル"stdio.rh"は付属のヘッダ・ライブラリファイルです。
また出力されるアセンブラソースファイル名は"asm.s"で固定されています。
出来上がったアセンブラソースファイルをアセンブルすると実行可能ファイルが作成されます。
java -jar asm_x64.jar asm.s [実行可能ファイル名]
現在のところエラー出力は未実装の状態です。エラーはアセンブルするとき出ます。
アセンブラソースのエラーの出る箇所で、ソースファイルの不具合を出る部分を推定して修正しています。
このコンパイラのソースファイルは数式をRPN(逆ポーランド記法)で記述します
RPNとは数式をあらわすのに使われる表記方の1つで 通常の数式とは逆の順序で表わされます。
例えば一般的なプログラムの数式の記述では、b とc を足してaに代入することを
a=b+c と書きますが
逆ポーランド記法では例えば
b, c, + a=
のように表記します。
以下にこのソースファイルの表記法を述べます。
(1) 宣言文
1. 64ビット整数の宣言
-2^63 から2^63-1までの整数を宣言します
例
long a#,b#
2. 32ビット整数の宣言
0から2^32-1までの整数を宣言します
例
int a!,b!
2. 16ビット整数の宣言
0から65535までの整数を宣言します
例
short a%,b%
3. 8ビット整数の宣言
0から255までの整数を宣言します
例
char a$,b$
1つの文中に最大8個までの整数の宣言ができます。
2.配列変数の宣言
例
long aa#(10),bb#(20)
int a!(10),b!(20)
short c%(30),d%(40)
char e$(50), f$(60)
配列変数は単純変数と違って1つの文中に最大4個までしか宣言できません。
配列変数は1次元配列だけです。
また、単純変数と配列変数を同じ文中で宣言することはできません。
3.カウンタ型変数の宣言
例
count i#,j#,k#
本コンパイラではfor-next文に対応するため特殊な64ビット型の整数を用意しています。 数値の範囲はlong型の整数と同じです。
この型の変数を宣言するとfor-next文のためのワークエリアが確保されます
上の例は次のようにかきなおすことができます。
long i#(4),j#(4),k#(4)
注… カウンタ型の配列変数は存在しません。
またCPUのレジスタ名と同じ名前の変数は使用しないで下さい。
4.定数の宣言
形式
const 定数1 値1
]定数1に値1を割り付けます
例
const XXX 12 /* xxxに12を割り付ける */
なお本コンパイラにはあらかじめ以下のような定数が組み込まれています
const EOF 255 /* ファイルの終わりをあらわす文字コード */
const ERROR -1 /* エラーが発生したことをあらわす */
const NULL 0 /* ヌルポインタ */
const SPACE 32 /* 空白文字 */
const CR 13 /* キャリッジリターンコード */
const LF 10 /* ラインフィードコード */
const PLUS 43 /* プラス記号の文字コード */
const MINUS 45 /* マイナス記号の文字コード */
const ESC 27 /* ESCキーの文字コード */
(2) if-then文
形式
if 値1(条件) 値2 then 文1
条件が成立したとき文1を実行します
例
if a%>=b% then .... /* 変数a$が変数b%以上のとき...
if a$=b$ then ... /* 変数a$が変数b$と等しいとき...
if a%<b$ then .... /* 変数a%が変数b$より小さいとき...
if a!<=1 then... /* 変数a!が1以下のとき...
if 2<>b$ then ... /* 2と変数b$が異なるとき...
上の例からも分かるように違う型の変数でも比較できます。
条件は次の6種類です
< …小さい
> …大きい
<= …以下
>= …以上
= …等しい
<> …等しくない
注意...このif-then文はネスティングができません。
また if-then-elseではないので注意してください。
(3) if-goto文
形式
if 値1 (条件) 値2 goto ラベル1
条件が成立したときにラベル1にジャンプします。
条件はif-then文の時と同じです。
(4) for-next文
形式
for 変数1=値1 to 値2 [ step 値3 ]
.
.
.
next 変数1
BASICのfor-next文と同様ですがnextの後の変数名は省略できません。
例1
for i#=1 to 10
.
.
next i#
for j#=i!% to k$ step n%
.
.
next j#
(5) goto文
形式
goto ラベル1
gotoラベル2
BASICのgoto文と同じです
例
goto label1
gotolabel2
goto label3
(6)data文
形式
data 値1[,値2,...値8]
BASICのdata文とほとんど同じものですが、1行に最大8個までの数値や文字列しか書く事ができないので注意して下さい。
(7)end文
形式
end
メインプログラムのなかではプログラムの実行を終了し、サブルーチンのなかでは呼び出したプログラムに帰ります。
(8)return文
形式
return
end文と同じ動作をします。
(9) 式の評価と代入
数値を格納するためにCPU上のレジスタが割り当てられています。
1.単純な値の代入
値1,
の形式の文があると、CPUは値1をスタックにpushします。
そして
変数1=
の形式の文があればCPUはスタックトップの値を変数1に代入します。
例
aa!, bb%= /* aa!の値がbb%に代入される */
1, aa%= /* aa%に1が代入される */
aa$(1), cc%= /* aa$(1)の値がcc%に代入される */
dd%, cc$(j!)= /* dd%の値がcc$(j!)に代入される */
XXX, dd$= /* 定数XXXの値が変数dd$に代入される */
以下のものは同じものとみなせます("≡"は同じものという意味)。
aa!≡aa!(0)
aa+0$≡aa$(0)≡aa$
aa+1$≡aa$(1)
aa+2$≡aa$(2)
aa+2%≡aa%(1) (オフセットがインデックスの2倍になっている)
aa+4%≡aa%(2) 〃
aa+8!≡aa!(2) (オフセットがインデックスの4倍になっている)
aa$≡( aa%の下位8ビット )
aa$(1)≡( aa%の上位8ビット )
aa≡( 変数aa%やaa$のアドレス(定数) )
XX$≡( XX番地のメモリの内容 )
1234$≡( 1234番地のメモリの内容 )
2.加減乗除算
次の記号がでてくるとマシンは以下の操作をします。
+ (スタックトップの値とその次の値を足した値をスタックトップに格納する)
ー (スタックトップの値をその次の値から引いた値をスタックトップに格納する)
* (スタックトップの値とその次の値に乗じてスタックトップに格納する)
/ (スタックトップの値でその次の値を割った値をスタックトップに格納する)
例
1, 2, + aa%= /* aa%に3が入る */
3, 4, - bb%= /* bb%に-1が入る */
bb%, cc%, * dd$= /* dd$にbb%とcc%をかけた値が入る */
aa%, dd, / aa%= /* aa%に変数dd%のアドレスで割った値が入る */
ss%, XX, + kk$= /* kk$にss%と定数XXを足した値が入る */
3.ポインタ
64ビット整数はポインタとして用いることができます。
例
int p!,q!,r!,j!
short aa%(10)
int bb!(10)
char cc$(10)
aa, p#=
bb, q#=
cc, r#=
とした時に以下のものは同じものとみなせます
short型の場合
(p)%≡aa%≡aa%(0)
(p)%(1)≡aa%(1)
(p)%(j#)≡aa%(j#)
この後、次の文を実行すると以下のものは同じものとみなせます
p!, 2, + p!=
(short型ではポインタのオフセットと配列のインデックスが2倍の関係になっていることに注意。)
(p)%≡(p)%(0)≡aa%(1)
(p)%(1)≡aa%(2)
int型の場合
(q)!≡bb!≡bb!(0)
(q)!(1)≡bb!(1)
(q)!(j!)≡bb!(j!)
この後、次の文を実行すると以下のものは同じものとみなせます
q!, 8, + q!=
(int型ではポインタのオフセットと配列インデックスが4倍の関係になっていることに注意。)
(q)!≡(q)!(0)≡bb!(2)
(q)!(1)≡bb!(3)
char型の場合
(r)$≡cc$≡cc$(0)
(r)$(1)≡cc$(1)
(r)$(j#)≡cc$(j#)
この後、次の文を実行すると以下のものは同じものとみなせます
r#, 3, + r#=
(char型ではポインタのオフセットと配列インデックスが等倍の関係になっていることに注意。)
(r)$≡(r)$(0)≡cc$(3)
(r)$(1)≡cc$(4)
(10)文字列
8ビット整数の集まりを文字列と呼びます。
本コンパイラでは引用符"で文字列を囲むとその文字列がメモリ上に確保され、その文字列のアドレスがスタックトップに格納されます。
例
"abcd", p#= を実行するとそれぞれ、
(p)$(0) = 'a'
(p)$(1) = 'b'
(p)$(2) = 'c'
(p)$(3) = 'd'
となっていることが確かめられます。
(11) 関数
数値を扱う関数はスタックの値を参照して結果をスタックトップに返します。
あるいは値を返さない関数(サブルーチン)もあります。
1.関数定義,呼び出し
/* メイン関数:プログラムはここから実行される(この関数は必須) */
main:
int i%,j%,k%
i%, j%, funct1 k%= /* 変数i%, j%の値をスタックにpushして関数func1を呼び出す */
/* そして戻り値を変数k%に格納する */
end
/* 関数名はラベルで定義する */
funct1:
int ii%, jj%, kk%
jj%= pop ii%= /* 結果的にi%の値がii%に,j%の値がkk%に入る(関数popについては後述) */
.
.
kk%, return /* 戻り値をスタックにpushしてリターン */
2.組込関数
本コンパイラでは以下の関数を用意しています。
abs...絶対値を返す
-1, abs k%= /* k%に1が入る */
pop...レジスタ・スタックの値をpopする
2, 3, 4, i%= pop j%= pop k%= /* i%に4、j%に3、k%に2が入る */
swap...スタックトップとその次の値を交換する
4, 5, j%= swap k%= /* j%に5, k%に4が入る */
restore...read関数の読み取り位置を決める(注意:最初のread関数を実行する前に必ず実行しなければならない)
read...data文から値を読み取る
data_start, restore
read j%=
read s!=
read a$=
read x$=
.
.
data_start:
data 1000,"abcdef"
data 10
putchar...1文字表示
'A', putchar
inputs...キーボードから文字列を入力
prints...文字列表示
nl...改行する
char ss$(64)
ss, inputs
ss, prints
"abcdefg", prints nl
printd...数値を表示する
1234, k!=
k!, printd
1234, printd
strcpy...文字列のコピー
strcat...文字列の追加
strlen...文字列の長さを返す
strstr...文字列のある場所(アドレス)を返す
char ss$(64)
"13456ABCDEF", ss, strcpy
ss, "ghi", strcat /* ssには"13456ABCDEFghi"が入る */
ss, strlen k#= /* k#には14が入る */
ss, "ABC", strstr k#= /* k#にはss+5が入る */
atoi ...文字列を数値に変換する(基数を指定すること)
"1010", 10, atoi k!= /* k!には1010が入る */
"1010", 16, atoi k!= /* k!には5112入る */
"1010",2, atoi k!= /* k!には10が入る */
hex ...数値を16進数の文字列に変換する
255, hex ss, strcpy /* ssには"ff"が入る */
dec ...数値を10進数の文字列に変換する
1234, dec prints /* "1234"と表示される */
oct ...数値を8進数の文字列に変換する
bin ...数値を2進数の文字列に変換する
ropen ....ファイルを読み出しモードでオープンする
wopen ....ファイルを書き込みモードでオープンする
getc ....ファイルから1文字入力する
putc ....ファイルへ1文字出力する
finputs ...ファイルから1行文字列を入力する
fprints ...ファイルへ文字列を出力する
fprintd ...ファイルへ数値を出力する
fnl .....ファイルへ改行コードを出力する
close ....ファイルのクローズ
long hrd#,hwr# /* ファイルハンドル */
char cc$
char ss$(64)
"input.txt", ropen hrd#=
"output.txt", wopen hwr#=
hrd#, getc cc$=
cc$, hwr#, putc
ss, hrd#, finputs
ss, hwr#, fprints
ss, strlen hwr#, fprintd
hwr#, fnl
hrd#, close
hwr#, close
(12) インラインアセンブラ
行の前後を "/"(スラッシュ)で囲むと囲まれた部分をアセンブラ命令として解釈します
(最初の"/"は行の1桁目でないと除算演算子として解釈されるので注意)
例
i%, func j%=
/* 渡された数値を2倍にする */
func:
/ rax=rdi/
/ rdi+=rax/
end
(13) プログラムの例
1. "hello world"を表示するプログラム
main:
"hello world", prints nl
end
2.1から10までを表示するプログラム
count i#
main:
for i#=1 to 10
i#, printd nl
next i#
end
3.ファイル"abc"の内容を表示するぷろぐらむ
long hf#
char cc$
main:
"abc", ropen hf#=
if hf#=ERROR then "ファイルがありません", prints end
main_loop:
hf#, getc cc$=
if cc$=EOF goto exit_loop
cc$, putchar
goto main_loop
exit_loop:
hf#, close
end
4.ファイル"abc"から指定された文字列を見つけるプログラム
long line#,p#,hf#
char buf$(80),ss$(16)
char f$
main:
1, line#=
"文字列? ", prints ss, inputs
"abc", ropen hf#=
if hf#=ERROR then "ファイルがありません", prints nl end
loop1:
buf, hf#, finputs f$=
if f$=EOF goto exit1
buf, ss, strstr p#=
if p#<>NULL then line#, printd "行目: ", prints buf, prints nl
line#, 1, + line#=
goto loop1
exit1:
hf#, close
end