指導書

情報工学実験 山田俊行
コンパイラの設計と製作
目的
1
簡単なプログラミング言語のコンパイラの試作を通して,コンパイラ作成技術を学び,プログラミン
グ言語についての理解を深め,プログラミング言語設計の基礎を習得する.
概要
2
2.1
コンパイラの構成
本実験で作るコンパイラは,再帰下降型の構文解析と構文主導型のコード生成に基づく 1 パスのコン
パイラである.高水準言語のコンパイラは,通常,図 1 に示す構造を持つ.本実験のコンパイラは,字
句解析,構文解析,意味解析,コード生成を順に行なわず,これらの処理をすべて,原始プログラムを
先頭から順に 1 度読む間に並行させる.また,解析木も実際には作らず,コンパイラ中の手続き呼び出
しに解析木と同じ構造を持たせる.
原始プログラム
?
字句解析
字句列
?
構文解析
解析木
?
意味解析
@
@
@
@
P
iP
PP
@
PP
PP
R
P@
q
P
1 記号表
)
中間コード
?
コード生成
?
目的コード
図 1: コンパイラの構造
コンパイラを作るために,まず,次のことがらを決める.
1. 原始プログラム
2. 目的コード
3. コンパイラ記述言語
1
2.2
原始プログラム
本実験で作るコンパイラの原始プログラム言語は,以下の特徴を持つ C 風の言語である.
• 基本データ型は整数型だけを扱う.
• 制御文は if,while,return だけを扱う.
• 関数が使え,再帰呼び出しできる.
コンパイラの簡素化のため,通常の C 言語とは次の点で異なる.
• 入出力関数 input() と output() が使える.
• 関数定義の前に fun を,変数宣言の前に var を書く.
• 変数や仮引き数の宣言で型を省く.
• 代入は文として扱う (式として扱わないので値をもたない).
• 主関数 main() は値を返さない.
以後,この言語を C′ (C-Prime) と呼ぶ.C′ のプログラム例を資料 A に,構文を資料 B に示す.
本実験では,まず基本的な言語機能だけを実現するコンパイラを作り,機能を順次加えて C′ コンパ
イラを完成させる.
2.3
目的コード
本実験では,Pascal コンパイラの中間コードとして設計された P コード(に似た命令コード)を,目
的コードとして使う.その命令セットの概要を表 1 に,詳細を資料 C に示す.
LDC
load constant
STP
stop
LOD
load
UJP
unconditional jump
STR
AOP
store
arithmatic operation
FJP
XST
jump on false
expand stack
COP
RLN
comparison operation
read line
MST
CAL
mark stack
call
WLN
write line
RET
return
表 1: 目的コードの命令セット
P コードは,仮想的なスタック計算機の機械語と見なせる.この計算機は 4 個のレジスタ p, i, b, t と,
プログラムを格納する読み出し専用メモリと,実行用のスタックを持つ(図 2 参照).
スタック計算機のプログラムカウンタ p は,次に実行する命令の番地を格納する.スタック計算機は
p が示す命令 (instruction) を命令レジスタ i に格納し,p の値を 1 増やした後,格納した命令を解釈し
実行する.例えば,命令が算術演算ならば,スタックの先頭の 2 つの値が被演算子であり,その 2 つを
計算結果に置き換える.また,命令が呼び出しならば,呼び出し後に使うデータ領域をスタックの先頭
に置いてから手続き本体を実行し,実行を終えるとそのデータ領域をスタックから取り除く.スタック
の先頭データ領域の基点 (base) とスタックの先頭番地は,それぞれベースポインタ b とスタックトッ
プポインタ t に格納される.
この実験で作るコンパイラは,C′ 言語で書かれたプログラムを P コードへと変換する.変換後の P
コードは,スタック計算機を模倣するプログラム(仮想スタック計算機)で解釈・実行する.スタック
計算機では,式の値を逆ポーランド記法 (演算子を後置する記法) の順に計算する.つまり,被演算子の
値を先に計算し,被演算子がそろってから演算を実行する.例として,式 7 + 8 − 9 の値を出力するプ
2
-
p
プログラムカウンタ
P コード
プログラム
実行用
スタック
-
t
スタックトップポインタ
-
b
ベースポインタ
i
命令レジスタ
図 2: スタック計算機
ログラム,それをスタック計算機上で実行するための目的コード,実行時のスタックの変化を図 3 に示
す.ここで,LDC はスタック上に値を積む命令,AOP は算術演算命令,WLN は出力命令,STP は停止命
令である.
LDC
LDC
0
0
7
8
fun void main() {
output(7+8-9);
AOP
LDC
0
0
0
9
}
AOP
WLN
0
0
1
0
STP
0
0
(a) 原始プログラム
8
LDC
LDC
9
AOP
LDC
AOP
WLN
=⇒ 7 =⇒ 7 =⇒ 15 =⇒ 15 =⇒ 6 =⇒
(b) 目的コード
(c) 実行時のスタックの変化
図 3: プログラムの翻訳と実行
2.4
コンパイラ記述言語
C′ 言語のコンパイラは,手続きを再帰的に呼び出せる言語を使うと,簡潔に書ける.本実験では,コ
ンパイラの記述に C 言語を使う.
コンパイラの製作
3
3.1
字句解析
コンパイラの字句解析器 (scanner) は,原始プログラムを入力として受け取り,文字列を文法上の基
本単位である字句 (token) に分ける.字句は,識別子,予約語,特殊記号,数,などからなる.識別
子 (identifier) は英数字 (下線
を含む) の列で表された名前である.識別子のうち,予約語 (reserved
word) に指定されていないものは,使用者が定義して使える.字句解析器は,初めて現れた識別子を記
号表に登録する(詳細は 3.4 節を参照).
C′ の予約語と特殊記号を表 2 に,原始プログラムの文字列を字句へ分割する例を図 4 に示す.
3
予約語
特殊記号
if
else
while
return
+
-
*
/
==
!=
=
(
var
fun
void
int
%
>
>=
<
<=
)
{
}
,
;
表 2: 予約語と特殊記号
var x;
fun void main() {
x = 5;
VAR IDENT(x) SEMICOLON
=⇒
FUN VOID IDENT(main) LPAREN RPAREN LBRACE
IDENT(x) BECOMES NUMBER(5) SEMICOLON
RBRACE
END OF INPUT
}
(a) 原始プログラム
(b) 字句列
図 4: 原始プログラムから字句列への変換
3.2
構文解析
コンパイラの構文解析器 (parser) は,プログラムの構文を検査する.文法的に正しいプログラムに
対しては,字句解析器が出力した字句列を構文規則に従ってまとめ,解析木を生成する.構文に誤りの
あるプログラムに対しては,適切な誤り処理をする.本実験で採用する再帰下降構文解析器 (recursive
descend parser) では,解析木を直接作らず,解析手続きの呼び出しの構造に,解析木を作るのと同じ役
割りを持たせる.再帰下降構文解析器の特徴は,各非終端記号に 1 つの手続きが対応し,これらの手続
きが互いに呼び合う形で構成されることである.C′ の構文の非終端記号はプログラム (program), 変数
宣言 (declaration), 関数定義 (definition), 文 (statement), 式 (expression), 比較式 (comparison), 算術
式 (arithmetic), 項 (term), 因子 (factor), 原始式 (atom) であり (資料 B を参照),それぞれに対応する
構文解析の手続きを用意する.
例として,非終端記号 program に対応する構文解析の手続きを図 5 に示す.C′ の構文規則 (資料 B)
によると,非終端記号 program に対する構文は,字句 var と非終端記号 declaration の列か空列,字句
fun と非終端記号 definition の列の 0 回以上の繰り返し,入力末端 (end of input ),の並びである.手
続き program は,この順序で字句列を認識する.図 5 の token_type は直前に切り出した字句の種類
を返す関数,read_token は原始プログラムから字句を 1 つ切り出す手続きである.また,VAR と FUN
はそれぞれ,原始プログラムの予約語 var と fun に対応する.手続き error は,構文の誤りを見つけ
たときに呼び出す誤り処理用の手続きである.
void program() {
if (token_type() == VAR) { read_token(); declaration(); }
while (token_type() == FUN) { read_token(); definition(); }
if (token_type() != END_OF_INPUT) { error(); }
}
図 5: 構文解析手続き
構文規則は再帰的に定義されるため,再帰下降構文解析器の手続きの多くは再帰的に呼び出される.
例えば,手続き statement は,その中で自分自身を呼び出す.このため,手続きを再帰呼出しできる
コンパイラ記述言語を使うことが望ましい.
字句列から解析木への変換の例を図 6 に示す.再帰下降構文解析器では,解析木の根を出発点として
深さ優先探索の順序で木の各節をたどりながら,節の字句と入力字句とを照合し,節の非終端記号に対
4
応する構文解析手続きを呼び出すことで処理を進める.各構文解析手続きの呼び出しから終了までの処
理で,対応する非終端記号を根とする部分木を認識する.
program
.
end of input
var declaration fun definition
VAR IDENT(x) SEMICOLON
FUN VOID IDENT(main) LPAREN RPAREN LBRACE
IDENT(x) BECOMES NUMBER(5) SEMICOLON
RBRACE
END OF INPUT
ident ;
=⇒
void ident ( ) { statement }
ident = expression ;
.
.
.
number
(a) 字句列
(b) 解析木
図 6: 字句列から解析木への変換
3.3
コード生成
本実験のコード生成 (code generation) には,構文主導型変換 (syntax-directed translation) という方
式を使う.この方式は,構文の各生成規則に対してコード生成手続きを割り当て,非終端記号を認識す
るたびに,対応する手続きを呼び出して目的コードを生成するものである.これは,コード生成手続き
に構文と同じ構造を持たせる自然な方法である.再帰下降構文解析器では,各非終端記号に対応する手
続きの実行がその非終端記号に関する構文の認識に対応することから,コード生成手続きを構文解析手
続きに組み込むと,構文主導型変換を実現できる.
算術式 (arithmetic expression) の構文解析手続き arithmetic にコード生成の機能を加えたものを
図 7 に示す.この手続きは,算術式の構文に従って字句列を認識しながら,現れる演算子の種類に応じ
た算術演算命令 AOP を生成する.図 7 の instruction は命令を生成する手続きである.また,図 7 の
PLUS, MINUS は,原始プログラム中の字句 +, - に対応し,手続き term は,加減算の被演算子である項
(term) を認識するための構文解析手続きである.このように,コード生成の機能を加えると,例えば,
図 3(a) の原始プログラムから図 3(b) の目的コードを生成できる.
void arithmetic() {
int op;
term();
op = token_type();
while (op == PLUS || op == MINUS) {
read_token();
term();
if (op == PLUS) instruction(AOP, 0, 0);
else
instruction(AOP, 0, 1);
op = token_type();
}
}
図 7: 構文解析へのコード生成の付加
次に,プログラム全体のコンパイルと実行の過程を,図 8(a) の例で確かめる.コンパイラは 1 行目か
ら順に各行を左から右へと読みながら,目的コードをこの順に生成する.つまり,コードは,関数 sub(),
関数 main() の順に生成され,図 8(b) の目的コードを得る.出力された目的コードの実行は,main() の
コードの先頭から始まる.main() のコード中で sub() の呼び出し (call) 命令が実行されると,sub()
5
に制御が移り,呼び出された関数のコードが先頭から実行される.関数の実行が終わると,復帰 (return)
命令の実行により,main() に制御が戻り,main() の残りのコードが実行される.
var x, y;
fun int sub(a) {
var x, z;
sub() のコード
sub() の本体
}
fun void main() {
var y;
main() のコード
main() の本体
}
(a) 原始プログラム
(b) 目的コード
図 8: コード生成の順序
C′ プログラムの各構成要素に対するコード生成の方法を資料 D に示す.また,C′ コンパイラに資料
A の例題プログラムを入力して得られる目的コードの例を資料 E に示す.
3.4
記号表
コンパイラは原始プログラムを読む際に,初めて現れた識別子を付随する情報と共に記号表 (symbol
table) に登録する.記号表に格納した情報は,意味解析やコード生成で使う.
原始プログラムの各変数には,記憶領域が割り当てられる.この割り当てを記憶することが,記号表
の最も基本的な役目である.コード生成時の記号表の基本的な使い方を,図 9(a) のプログラムと,登録
する情報を簡素化した記号表を使って説明する.コンパイラはプログラム 1 行目の変数宣言を読み込む
と,変数 x, y とその記憶領域の番地との対応を定め,その対応を記号表に登録する.変数 x, y を登録
した記号表を図 9(b) に示す.コンパイラが生成する目的コードを 図 9(c) に示す.命令 XST は変数用に
記憶領域を 確保・解放 するために使う.変数を扱うためには,記憶領域の値を受け渡す転送命令 STR,
LOD を使う.コンパイラが 3 行目と 4 行目を読むとき,これらの命令の生成に必要な変数 x, y の番地
は,記号表を検索して得る.
var x, y;
fun void main() {
x = 5;
y = x;
0
2
0
0
5
0
0
1
名前
番地
LDC
STR
x
y
0
1
LOD
STR
0
0
XST
0 -2
}
(a) 原始プログラム
XST
(b) 記号表
STP 0 0
(c) 目的コード
図 9: 記号表とコード生成
原始プログラムが複雑になると,記号表にはより多くの情報が必要になる.まず,識別子の種類(変
数,関数,引き数)を記号表に記す.変数名の場合は,その変数の有効範囲(大域変数か局所変数か)
の区別と,その変数に割り当てる基点からの相対番地を記号表に書く.仮引き数の場合にも同様にこれ
6
らの情報を記号表に書く.識別子が関数名の場合は,目的コードの開始番地を記号表に記す.また,型
情報など,意味解析やコード生成に必要になる他の情報も記号表に登録する.
図 8(a) のプログラムを使い,本格的な記号表について説明する.プログラムの先頭で宣言された大域
変数名 x, y を記号表に登録した後,2 行目の関数名 sub,引き数名 a,3 行目の変数名 x, z を順に登録
する.この時点での記号表を図 10(a) に示す.関数 sub() の本体では,記号表に登録された名前がすべ
て有効である.ただし,変数 x は後で登録した,つまり,関数 sub() 内で宣言された局所変数が有効
である.このように,記号表の検索では,大域変数よりも局所変数を優先する.なお,関数を実行する
場合,各関数のデータ領域の基点付近(相対番地 0∼2)に制御情報を格納する必要がある (3.5 節を参
照).そのため,制御情報に続く領域 (相対番地 3∼) を変数等の格納に使う.関数 sub() で宣言された
仮引き数 a や局所変数 x, z は,関数の外側では無効になるので,sub() の本体を読み終えた時点で記
号表から検索できないようにする.これにより,main() 中で局所変数 z を参照する誤りを検出できる.
なお,関数 sub() は main() 中で呼べるので,無効にしてはいけない.続いて,6 行目の関数名 main,
7 行目の変数名 y を順に登録する.この時点での記号表を図 10(b) に示す.main() の本体では,この表
に登録されている名前が有効である.ただし,変数 y は後から登録した局所変数が有効である.main()
の本体を読み終えた時点で,main() で新たに登録した変数名 y を無効にする.
名前
種類
範囲
番地
x
変数
大域
y
変数
大域
sub
a
関数
大域
引き数
局所
3
変数
局所
変数
局所
4
5
x
z
型
名前
種類
範囲
番地
0
x
変数
大域
0
1
y
変数
大域
1
sub
main
関数
大域
関数
大域
y
変数
局所
int→int
(a) 関数 sub() で有効な記号表の情報
型
int→int
void→void
3
(b) 関数 main() で有効な記号表の情報
図 10: 記号表の変化
3.5
記憶領域の管理
各関数の実行に使うデータ領域は,呼び出しごとにスタックに確保し,関数の実行終了時に解放する.
このデータ領域は,駆動レコード (activation record) やスタックフレーム (stack frame) などと呼ばれ,
作業領域,変数・引き数領域,制御領域で構成される(図 11)
:
1. 作業領域 … 関数の実行に使う作業領域.
2. 変数・引き数領域 … 関数内で宣言された局所変数と関数の実引き数の領域.
3. 制御領域 … 戻り番地 RA (return address),旧基点 OB (old base),戻り値 RV (return value)
からなる:
• RA … この関数の終了後に実行される,呼び出し元の目的コードの番地.
• OB … この関数の呼び出し元の駆動レコードの基点番地(つまり,現在の駆動レコードのす
ぐ下の駆動レコードの基点番地).
• RV … この関数の終了後に呼び出し元に返される値.
図 8 のプログラムで,主関数 main() の実行時のスタックを図 12(a) に示す.関数 sub() を呼ぶと,
スタックは図 12(b) のように変わる.さらに sub() の中で関数 sub() を再帰的に呼ぶと,スタックは
図 12(c) のように変わる.
7
t
-
作業領域
変数
引き数・変数 領域
引き数
b
-
戻り番地 RA
旧基点
OB
戻り値
RV
制御領域
図 11: 駆動レコードの構成
sub() の領域
sub() の領域
main() の領域
t -
t -
t -
作業領域
z
x
a
RA
OB
RV
b
作業領域
作業領域
z
x
a
z
x
a
RA
OB
- RV
b
RA
OB
RV
作業領域
作業領域
作業領域
y
y
y
RA
OB
RV
y(大域)
x(大域)
RA
OB
RV
y(大域)
x(大域)
RA
OB
RV
b
y(大域)
x(大域)
(a) main() 実行中
(b) sub() 実行中
(c) sub() 実行中
図 12: 関数呼び出しによるスタックの変化
8
4
実験課題
課題の概要は以下の通りである.
基本課題 1
整数と算術演算子からなる式の値を出力できるようにする.
基本課題 2
変数と入力の機能を使えるようにする.
基本課題 3
比較演算子を含む式や制御文 (if, while) を使えるようにする.
基本課題 4
定義した関数を呼び出せるようにする.
基本課題 5
関数の引き数と局所変数を使えるようにする.
基本課題 6
関数が値を返せるようにし,誤り処理を強化する.
発展課題
言語機能を拡張する.
コンパイラ実験のホームページに公開される:
http://www.cs.info.mie-u.ac.jp/~toshi/lectures/compiler-ex/
実験を円滑に進めるには,十分な予習が必須である.毎回,ウェブページ上にある予習問題を解き,答
案を紙に書いてくること.また,ウェブページの課題とその解説をよく読み,必要な作業を把握してか
ら実験に臨むこと.
参考文献
[1] A. V. エイホ,M. S. ラム,R. セシィ,J. D. ウルマン,
『コンパイラ —原理・技法・ツール—』,
第 2 版,サイエンス社,2009.
[2] A. W. エイペル,
『最新コンパイラ構成技法』,翔泳社,2009.
[3] 佐々政孝,
『プログラミング言語処理系』,岩波書店,1989.
[4] T. パー,
『言語実装パターン —コンパイラ技術によるテキスト処理から言語実装まで—』,オライ
リー・ジャパン,2011.
[5] B. W. カーニハン,D. M. リッチー,
『プログラミング言語 C』,第 2 版,共立出版,1989.
[6] S. P. ハービソン 3 世,G. L. スティール・ジュニア,
『C リファレンスマニュアル』,エスアイビー
アクセス,2008.
9
原始プログラムの例
A
(1) 算術式・出力
(5) 引き数・局所変数
fun void main() {
var x, pow;
output(7+8-9);
}
fun void powself(x) {
var i;
i = 0;
(2) 変数・入力
while (i < x) {
pow = pow*x;
var in, out;
fun void main() {
in = input();
out = -in;
output(out);
i = i+1;
}
}
fun void main() {
x = input();
pow = 1;
}
powself(x);
output(pow);
(3) 比較式・制御文
var i;
fun void main() {
i = 1;
while (i < 10) {
if (i%2 != 0)
output(i);
i = i+1;
}
}
(6) 戻り値
var num;
fun int gcd(m, n) {
if (n == 0)
return m;
else
}
return gcd(n, m%n);
(4) 関数
}
var x;
fun void main() {
var m, n;
num = 3;
fun void double() {
while (num > 0) {
m = input();
x = x*2;
}
n = input();
output(gcd(m, n));
fun void main() {
x = 100;
num = num-1;
double();
output(x);
}
}
}
10
原始プログラムの構文
B
?
→
[ var 変数宣言 ]
変数宣言
→
ident , · · · ;
関数定義
→
void ident ( [ ident , · · · ]? ) { [ var 変数宣言 ] 文 }
|
?
文
→
[ fun 関数定義 ]
∗
プログラム
program
end of input
declaration
?
∗
?
∗
definition
int ident ( [ ident , · · · ] ) { [ var 変数宣言 ] 文 }
?
ident ( [ 式 , · · · ] ) ;
|
ident = 式 ;
|
|
if ( 式 ) 文 [ else 文 ]
while ( 式 ) 文
|
|
{文 }
?
return 式 ;
statement
?
∗
式
→
比較式 [ == | != ] · · ·
expression
比較式
→
算術式 [ > | >= | < | <= ] · · ·
comparison
算術式
→
項 [ + | - ] ···
arithmetic
項
→
因子 [ * | / | % ] · · ·
term
因子
→
[+|-]
原子式
→
|
number
(式)
|
|
?
原子式
factor
atom
ident
?
ident ( [ 式 , · · · ] )
この定義では,文法記号をまとめるために括弧 [ ] を使うことに注意.
記法
意味
別の記法
α|β
α?
αかβ
α か空列
α|ε
α の 0 個以上の列
β で区切られた α の 1 個以上の列
α[βα]
∗
α
α β ···
11
∗
C
目的コードの命令セット
命令
名称
機能
LDC 0 n
load constant
t←t+1 ;
s[t] ← n
定数 n をスタックに積む.
LOD r a
load
t←t+1 ;
領域 r (大域は 0,局所は 1) の a 番地の値を読み,
s[t] ← s[base(r) + a]
その値をスタックに積む.
†
LDA r a
load address
t←t+1 ;
s[t] ← base(r) + a
領域 r の a という番地をスタックに積む.
†
ILD 0 0
indirect load
s[t] ← s[s[t]]
スタック上の番地を取り出し,
その番地の値を読み,
その値をスタックに積む.
STR r a
†
ISR 0 0
AOP 0 n
store
indirect store
arithmetic
operation
s[base(r) + a] ← s[t] ;
スタック上の値を取り出し,
t←t−1
その値を領域 r の a 番地に書く
s[s[t − 1]] ← s[t] ;
スタック上の値と番地を順に取り出し,
t←t−2
その値をその番地に書く.
s[t − 1] ← s[t − 1] op s[t] ;
t←t−1
スタック上の値を 2 個取り出し,
算術演算をし,その結果をスタックに積む.
n = 0, 1, 2, 3, 4 のとき op は +, −, ×, div, mod.
COP 0 n
comparison
operation
s[t − 1] ← s[t − 1] op s[t] ;
t←t−1
スタック上の値を 2 個取り出し,
比較演算をし,その結果 (0 か 1) をスタックに積む.
n = 0, 1, 2, 3, 4, 5 のとき op は >, ≥, <, ≤, =, 6=.
RLN 0 0
read line
WLN 0 0
write line
STP 0 0
stop
UJP 0 a
unconditional
t←t+1 ;
readline(s[t])
標準入力を 1 行読み,
writeline(s[t]) ;
スタック上の値を取り出し,
その値をスタックに積む.
その値を標準出力に 1 行書く.
t←t−1
実行を停止する.
p←a
制御を a 番地に移す.
スタックの値を取り出し,その値が 0 なら,
jump
FJP 0 a
jump on false
p ← a if s[t] = 0 ;
t←t−1
制御を a 番地に移す.
XST 0 n
expand stack
t←t+n
n 個の変数用領域をスタック上に確保する.
MST 0 0
mark stack
s[t + 1] ← 0 ;
s[t + 2] ← b ;
戻り値 (RV) の初期値をスタックに積み,
呼び出し前の基点 (OB) をスタックに積み,
t←t+3
戻り番地 (RA) 用の領域をスタック上に確保する.
s[t − n] ← p ;
b ← t − (3 + n) + 1 ;
戻り番地 (RA) を定め,
p←a
制御を a 番地に移す (n は引き数の個数).
t←b+n−1 ;
p ← s[b + 2] ;
スタックの戻り値以外の領域を解放し,
制御を RA の値の番地に戻し,
b ← s[b + 1]
基点を OB の値に戻す (n は戻り値の数の 0 か 1).
CAL n a
RET 0 n
call
return
基点を変え,
† LDA, ILD, ISR の 3 つの命令は,ポインタや配列などを扱えるように言語を拡張するときに使う.
12
D
コード生成
(1) 式・変数・入出力
整数には LDC 命令,算術演算には AOP 命令,比較演算には COP 命令を生成
変数の値には LOD 命令,変数への代入には STR 命令を生成
入力には RLN 命令,出力には WLN 命令を生成
(2) 条件文・反復文
構文
コード
if ( 式 ) 文 1 else 文 2
while ( 式 ) 文
式のコード
式のコード
FJP
FJP
文 1 のコード
文のコード
UJP
UJP
文 2 のコード
(3) 関数定義
構文
コード
[ void | int ] 名前 ( 引き数 , · · · ) { [ var 変数宣言 ]
?
本体 }
XST
本体のコード
RET
(4) 呼び出し
構文
コード
(プログラム全体)
名前 ( 引き数 1 , · · · , 引き数 n )
XST 確保
MST
UJP
引き数 1 のコード
関数定義のコード
MST
CAL main() の先頭へ
XST 解放
..
.
引き数 n のコード
CAL 関数の先頭へ
STP
(5) 復帰文
構文
コード
?
return 式 ;
式のコード (必要なら)
STR
(
〃
)
RET
13
E
目的コードの例
var num;
fun int gcd(m, n) {
if (n == 0)
return m;
else
return gcd(n, m%n);
}
fun void main() {
var m, n;
num = 3;
while (num > 0) {
m = input();
n = input();
output(gcd(m, n));
num = num-1;
}
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
0:
1:
XST
UJP
0
0
1
42
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
XST
LOD
LDC
COP
FJP
LOD
STR
RET
UJP
MST
LOD
LOD
LOD
AOP
CAL
STR
RET
RET
0
1
0
0
0
1
1
0
0
0
1
1
1
0
2
1
0
0
0
4
0
4
11
3
0
1
19
0
4
3
4
4
2
0
1
1
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
XST
LDC
STR
LOD
LDC
COP
FJP
RLN
STR
RLN
STR
MST
LOD
LOD
CAL
WLN
LOD
LDC
AOP
STR
UJP
RET
MST
CAL
XST
STP
0
0
0
0
0
0
0
0
1
0
1
0
1
1
2
0
0
0
0
0
0
0
0
0
0
0
2
3
0
0
0
0
41
0
3
0
4
0
3
4
2
0
0
1
1
0
23
0
0
20
-1
0
14