Cコンパイラ・インタプリタ プログラム 写経編
環境 WINDOWS10/Cygwin SetUpVersion 2.897
を参考にした。
3章 |
字句解析 |
||
4章 |
逆ポーランド 再帰的下向き構文解析 (ミニ電卓) |
||
(20191102) Cインタプリタ骨組みv00 cci.c内にあるint main(int atg, char *argv[])の流れを確認する。 ---------------------------------------------- <確認> ※ STEP 1( コンパイルとリンク。) $ make ※ STEP 2( 用法( help? )を表示する。) $ ./cci Usage: cci prgfile [--code] [--code]->For
Generated Code ※ STEP3( cci_pars内complile()関数に飛ぶ。return 0の正常値を返す。) $ ./cci
foo Debug: in compile() Debug: foo ※ STEP4 ( cci_pars.c内complile()関数に飛ぶ。return 1のエラー値を返す。) (main関数内で --codeが入力されたかされていないか判断し、cci_code.c内のcode_dump()に飛ぶか、execute()に飛ぶか判断する。) $ ./cci
bar --code Debug: in compile() Debug: bar Debug: in code_dump() ※ STEP5 $ ./cci
bar Debug: in compile() Debug: bar Debug: in execute() ※ STEP6(掃除をしておきましょう。) $ make
clean rm cci.o
cci_pars.o cci_code.o |
cci_skeleton_v00 (ダウンロード) リンク・コンパイルルールについては makefileを 参考にしてください |
||
(20191103) Cインタプリタ骨組みv01 cci_pars.c内、int compile(char *fname)を実装してゆく。 「文字種表」をセットする。 initChTyp()を中心とする動きを確認する。 ---------------------------------------------- <確認> ※STEP1(コンパイルとリンク) $ make ※STEP2( int compile(char *fname)内のinitChTyp()を実行。cci_tkn.c内のvoid initChTyp(void)を呼び出す。) $./cci
cookie.c Debug: in initChTyp() Debug: TokenKind Value of Digit: '1': 182 Debug: TokenKind Value of Digit: '9': 182 Debug: TokenKind value of Letter:'A': 181 Debug: TokenKind value of Letter:'Z': 181 Debug: TokenKind value of Letter:'a': 181 Debug: TokenKind value of Letter:'z': 181 Debug: TokenKind value of Letter:'_': 181 Debug: ASCIICODE value
of
'(': 40 Debug: TokenKind value
of Lparen:'(': 40 Debug: ASCIICODE value of
'"': 34 Debug: TokenKind value of DblQ :'"': 34 Debug: in execute() 1〜9の文字はDigit(enum:182)として、'A〜Z'の文字並びに'a〜z'の文字、さらに'_'はLetter(enum:181)として設定される。 '('はLparen(enum:40)などとして設定されるが、ASCIIコードの数値40と合致するようになっている。 このあたりは、ちょっとトリッキー。 cci.h内TknKind列挙型を参照し読み解いてください。 ※STEP3 (掃除をしておきましょう。) $make
clean rm cci.o
cci_pars.o cci_code.o cci_tkn.o |
cci_skelton_v01 (ダウンロード) (今回追加した関数) cci_tkn.c内 --> void initChTyp(void) リンク・コンパイルルールについては makefileを 参考にしてください |
||
(20191104) Cインタプリタ骨組みv02 cci_pars.c内、int compile(char *fname)を実装してゆく。 コード生成とその初期化の一部を確認する ---------------------------------------------- cci.c内main()からスタート ↓ 実行ファイルを実行時、ターゲットファイル名がしてされていれば、cci_pars.c内compile()関数にジャンプ。 ↓ compile関数内でinitChTyp()関数を実行 (20191103)で実装済 ↓ gencode2(CALL,
-1)とgencode1(STOP)を実行 (今回はここを作る) ---------------------------------------------- (プロファイル) cci.hファイル内で、gencode3()をマスタとし、マクロ登録を以下の通り実行。エイリアス化? #define gencode1(op) gencode3(op,0,0) #define gencode2(op,dt) gencode3(op,0,dt) ↓ cci_pars.cファイル内compile()関数内で次のように処理している。 gencode2(CALL, -1); // main()関数を呼ぶ。実行コードを0番地に置く gencode1(STOP); // プログラムを終了 実行コード1番地に置く (落穂ひろい) スタートアップ処理というものを実行している。 C言語ではプログラムはmain()関数から実行される。(念のため。。。ここでいうmain関数とは、今回作成しているインタプリタのmain関数を自身をさしているのではない!) この命令や実行コードの0番地から格納されます。CALL命令のジャンプ先番地は現在-1を仮指定(仮置き?)しています。 このジャンプ先番地は、あとで実際にmain()関数の定義が行われたときに決定し、後埋め(バックパッチ)される。 ↓ CALL・STOP等のキーワードが気になるのだが。。。 cci.hファイル内
OpCode列挙型内にあります。 typedef enum { NOP, INC, … …, CALL, … …, STOP, … } OpCode; CALL等のキーワードは列挙型(enumerated type)に登録され序数化され整理される。 ↓ 一方、cci_code.c内gencode3()内でコード(アセンブラ?中間コード?機械語?)生成している。 ***** まず、「定数畳込」という最適化を実施している。 if
(const_fold(op)) return codeCt;内 後日考える。 ***** cci.h内Inst構造体を20000個準備し、各メンバーに値をセットしている。 ちなみに各メンバ(cci.h内Inst構造体から抜粋)は以下のように定義されている。 unsigned
char opcode : 命令コードCALLなど unsigned
char flag : フラグ
数値か番地かをスイッチ int
opdata : データ(フラグに依存) ***** 最後にgencode3()内return codeCt;でコードを生成したスタックのアドレスを返している ---------------------------------------------- <確認> ※STEP1(コンパイルしましょう) $ make ※STEP2(動きを確認しましょう) $ ./cci
hoge.c Debug: in gencode3() -> gencode2(CALL,
-1); 呼出 Debug: codeCt is 0 -> スタックのインデックス 0 Debug: opcode is 18 -> 18はCALLに対応 Debug: flag is 0 -> FLAGは0 Debug: opdata is -1 -> -1にセット/バックパッチ用 Debug: in gencode3() -> gencode1(STOP); 呼出 Debug: codeCt is 1 -> インクリメントされる Debug: opcode is 34 -> 34はSTOPに対応 Debug: flag is 0 -> FLAGは0 Debug: opdata is 0 -> 0にセット Debug: in execute() -> main()関数に戻り、次へ ※STEP3(掃除しておきましょう) $ make
clean |
cci_skelton_v02 (ダウンロード) (今回追加した関数) cci_tkn.c内 --> int get_lineNo(void) cci_code.c内 --> int gencode3(OpCode op, int fg, int dt) --> const_fold(OpCode op) 定数畳込用関数 --> is_binaryOP(OPCode op) --> binaryExpr(OpCode op, int d1, int d2) --> void err_s(char *s) --> void err_fi(char *fmt, int idt) --> void err_ss(char *s1, char *s2) リンク・コンパイルルールについては makefileを 参考にしてください |
||
(20191105) Cインタプリタ骨組みv03 cci_pars.c内、int compile(char *fname)を実装してゆく。 外部ファイルcp_add.cを開き、プログラムに取り込む。 今までは空のファイルをコンソールでの引数としていたが、今回のcp_add.cにはc言語で記述された簡単な足し算プログラムが記録されている。 ※STEP1(コンパイルしましょう) $ make ※STEP2 $ ./cci
hoge.c Unable
to open [hoge.c]. 注:hoge.cなどというファイルはない! 注:Debug: in execute()が表示されていないので、stderrがきちんと機能し、プログラムを停止している。 ※STEP3(cp_add.cは実際に存在している) $ ./cci
cp_add.c Debug: ***START*** int
main(void) { int n, sum; sum = 0; printf("Please enter
a number. Enter '0' to exit\n"); while ((n=input()) != 0) {
sum = sum + n; printf(" >%d\n", sum); } return 0; } Debug: *** END *** Debug: in execute() 注:外部ファイルから内容を取り込み、その内容を表示している。 ※STEP4(掃除しておきましょう) $ make
clean |
cci_skelton_v03 (ダウンロード) (今回追加した関数) cci_tkn.c内 --> void fileOpen(char *fname); リンク・コンパイルルールについては makefileを 参考にしてください |
||
(20191109) Cインタプリタ骨組みv04 cci_pars.c内、int compile(char *fname)を実装してゆく。 nextTkn()を端とするトークン取得(1つのみ)の流れを確認する。 (字句解析) ※トークンとは コンパイラはソースプログラムを読み込むと、まず構文として意味のある最小構成単位に分割する作業を行う。 この意味のある最小単位のことを「字句」または「トークン」という。 (例) キーワード int for whileなど 識別子 変数名、関数名など 定数 100 23.45 'a'など 文字列リテラル "abcde"など 演算子 + - < <= など 区切り子 [ ] { } ( )など 〇「 nextTkn() 」 式のトークンを取得する cci_tkn.c内のToken nextTkn(void)を実行 各種変数の定義と初期化からはじまる。 tkn Token型変数は時々のトークンを保存する一時的な変数? {NulKind, "", 0}で初期化している。 pポインタは識別子の文字列を保存する?31文字まで liteポインタは文字列定数を保存する?100文字まで int chはstaticをつけることで前回の文字を保存することができる。関数間を超えて情報を保存するようなスコープになる。 static
int ch = ' ';で初期値としてスペースをセット。 <参考: Cygwin x64 環境下> Debug: pointer p
Addr is 0xffffcb24 Debug: pointer p_end31 Addr is 0xffffcb43 Debug: pointer lite Addr is 0xffffcab0 Debug: pointer lite_end Addr is 0xffffcb14 0xcb43 - 0xcb24 = ( 0x001F )16 --> (
31 )10 0xcb14 - 0xcab0 = ( 0x0064 )16 --> (
100 )10 教科書的にint型の配列を指定したとき、添え字を一つ進めたときには、int型のバイト幅( =4←古い資料では )を掛けるらしいが、上記の<参考>から察するにx64環境下ではint型のバイト幅はどうも(=1)でいいらしい。。。 ※空白読み捨て while
(isspace(ch)) { ch = nextCh(); } chがスペースかどうか判定し、nextCh()を実行するかどうか決める。
※ Token
nextTkn(void)内でよく出てくるP_SET()について以下のようなマクロ定義がなされている。 #define P_SET() *p++=ch, ch=nextCh() |
cci_skelton_v04 (ダウンロード) (今回追加した関数) cci_tkn.c内 --> Token nextTkn(void); --> int nextCh(void); cci_code.c内 --> int mallocS(char *s); --> int mallocG(int n); --> char *mem_adrs(int indx) cci_misc.c --> int is_kanji(int ch); --> int get_kanji_mode(void) リンク・コンパイルルールについては makefileを 参考にしてください |
||
(20191125) (20191231) Cインタプリタ骨組みv05 実行 execute() 直前までの処理を実装。 ここで、「記号表」まわりが実装される。 サンプルとしてエラーの検出を実行する。 ※STEP1(コンパイルしましょう) $ make ※STEP2(流し込むソースファイルを確認する) $ cat
cp_add.c /*
Sample Add Program */ int
main(void) { int n, sum; sum = 0; printf("Please enter a
number. Enter '0' to exit\n"); while ((n=input()) != 0) {
sum = sum + n; printf(" >%d\n", sum); } return 0; } ※STEP3(エラーがないファイルを実行) $ ./cci
cp_add.c Debug: in
execute() ※STEP4(エラーを含むソースファイルを確認する) $ cat
cp_add_dammy.c /*
Sample Add Program */ int
main(void) { int n, sum; sum = 0; printf("Please enter
a number. Enter '0' to exit\n"); while ((n=input()) != 0) {
sum = sum + n; printf(" >%d\n", sum) /* ここの';'を忘れてみる */ } return 0; } ※STEP3(エラーを含むファイルを実行) $ ./cci
cp_add_dammy.c #11 error: Before }, There is not ; 1 error occurred. |
cci_skelton_v05 (ダウンロード) (サンプルコード) (makeファイル) |
||
(20200109) Cインタプリタ骨組みv06 実行 execute()処理を実装。(核です) cp_add.cが実行できるかチェックをする。 --codeの分岐ができるかチェックする。 ※STEP1(コンパイルしましょう) $ make ※STEP2(cp_add.cを実行する) $ ./cci cp_add.c Please enter a number. Enter '0' to exit 45
>45 56
>101 0 ※STEP3(cp_add.cを実行する) $ ./cci cp_add.c --code Debug: in
code_dump() ※流れをcode_dump()に引き込むこともできている。 |
cci_skelton_v06 (ダウンロード) (サンプルコード) (makeファイル) |
||
(20200202) Cインタプリタ骨組みv07 (校正前最終) これで、 【明解入門】 コンパイラ・ インタプリタ 開発 C処理系を作りながら学ぶ 林晴比古 著 のソースをコピーした。 --codeが実行できるようにする。 ※STEP1(コンパイルしましょう) $ make ※STEP2(cp_add.cを実行する) $ ./cci cp_add.c --code 0000: CALL 2 0001: STOP 0002: ADBR -12 0003: STO
0[b] 0004: LDA
8[b] 0005: LDI 0 0006: ASS 0007: LDI 4 0008: LIB 3 0009: LDA
4[b] 0010: LIB 2 0011: ASSV 0012: LDI 0 0013: NTEQ 0014: JPF 24 0015: LDA
8[b] 0016: LOD
8[b] 0017: LOD
4[b] 0018: ADD 0019: ASS 0020: LDI 48 0021: LOD
8[b] 0022: LIB 4 0023: JMP 9 0024: LDI 0 0025: LOD
0[b] 0026: ADBR 12 0027: RET |
cci_skelton_v07 (ダウンロード) (サンプルコード) (makeファイル) |