Cコンパイラ・インタプリタ プログラム 写経編

環境 WINDOWS10/Cygwin SetUpVersion 2.897

を参考にした。

 

 3

 

字句解析

chap3_token_p.c

chap3_token_txt.txt

 

4

 

逆ポーランド

chap4_polish_p.c

 

再帰的下向き構文解析

(ミニ電卓)

chap4_minicalc.c

 

 

(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_parscomplile()関数に飛ぶ。return 0の正常値を返す。)

$ ./cci foo

Debug:  in compile()

Debug:  foo

 

STEP4 cci_pars.ccomplile()関数に飛ぶ。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

(ダウンロード)

cci.h

cci_prot.h

cci.c

cci_pars.c

cci_code.c

 

makefile

 

リンク・コンパイルルールについては

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()

 

 19の文字はDigit(enum:182)として、'AZ'の文字並びに'az'の文字、さらに'_'Letter(enum:181)として設定される。

 '('Lparen(enum:40)などとして設定されるが、ASCIIコードの数値40と合致するようになっている。

 このあたりは、ちょっとトリッキー。

 cci.hTknKind列挙型を参照し読み解いてください。

 

STEP3 (掃除をしておきましょう。)

$make clean

rm cci.o cci_pars.o cci_code.o cci_tkn.o

 

 

cci_skelton_v01

(ダウンロード)

cci.h

cci_prot.h

cci.c

cci_code.c

cci_pars.c

cci_tkn.c

makefile

 

(今回追加した関数)

cci_tkn.c

--> void initChTyp(void)

 

リンク・コンパイルルールについては

makefile

参考にしてください

 

(20191104)

Cインタプリタ骨組みv02

 

 cci_pars.c内、int compile(char *fname)を実装してゆく。

 コード生成とその初期化の一部を確認する

 

----------------------------------------------

 

cci.cmain()からスタート

実行ファイルを実行時、ターゲットファイル名がしてされていれば、cci_pars.ccompile()関数にジャンプ。

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()関数の定義が行われたときに決定し、後埋め(バックパッチ)される。

  CALLSTOP等のキーワードが気になるのだが。。。

  cci.hファイル内 OpCode列挙型内にあります。

 

  typedef enum {

    NOP,  INC, 

    ,  CALL, 

    ,  STOP, 

  } OpCode;

 

  CALL等のキーワードは列挙型(enumerated type)に登録され序数化され整理される。

  一方、cci_code.cgencode3()内でコード(アセンブラ?中間コード?機械語?)生成している。

*****

まず、「定数畳込」という最適化を実施している。

if (const_fold(op)) return codeCt;

後日考える。

*****

  cci.hInst構造体を20000個準備し、各メンバーに値をセットしている。

 ちなみに各メンバ(cci.hInst構造体から抜粋)は以下のように定義されている。

 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  -> 18CALLに対応

Debug:  flag   is 0   -> FLAG0

Debug:  opdata is -1  -> -1にセット/バックパッチ用

Debug:  in gencode3() -> gencode1(STOP); 呼出

Debug:  codeCt is 1   -> インクリメントされる

Debug:  opcode is 34  -> 34STOPに対応

Debug:  flag   is 0   -> FLAG0

Debug:  opdata is 0   -> 0にセット

Debug:  in execute()  -> main()関数に戻り、次へ

 

STEP3(掃除しておきましょう)

$ make clean

 

 

cci_skelton_v02

(ダウンロード)

cci.h

cci_prot.h

cci.c

cci_code.c

cci_misc.c

cci_pars.c

cci_tkn.c

makefile

 

(今回追加した関数)

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.h

cci_prot.h

 cci.c

cci_code.c

cci_misc.c

cci_pars.c

cci_tkn.c

cp_add.c

makefile

 

(今回追加した関数)

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 chstaticをつけることで前回の文字を保存することができる。関数間を超えて情報を保存するようなスコープになる。

 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()を実行するかどうか決める。

 

 

〇「 nextCh() 」  次の1文字をファイルから取得する。

 

 static int c = 1 <-- には c = fgetc(fin)で一文字セットされる。

 static int quot_ch <-- には 文字(列)リテラル内部にあるときの文字保存に利用される。(入れ子?)

 

 前回読込文字で、\nを検出したら ++srcLineno(行カウント)を更新。

 

  int c EOFのとき

 int c が文字(列)リテラルのとき

 /* */コメントを排除する

 

 

/* */コメントを排除過程のエラー処理!

 

cci_misc.c内の

void err_s(char *s)

void err_ss(char *s1, char *s2)

に飛ぶ。

 

$ cat cp_v04_01

/*abc

 

$ ./cci cp_v04_01

...

#2 error: There is no [*/] corresponding to [/*]

 

-->#に続く数字はエラーが発生した付近の行数を表示する

 

$ cat cp_v04_02

abc*/

 

$ ./cci cp_v04_02

...

#1 error: [*/] was detected when [/*] was not present

Debug:  ch is [a]

 

cp_v04_01を流したときは#2だったのが、cp_v04_02を流したときには#1になった。

 これは、cp_v04_01では"\n"を処理したのちにエラー判定されたから。

 また cp_v04_02ch is [a]となっていたのは、前出の"a"が記憶されたままになっていたから?

 

 最後に

 return c; <-- 文字を返す

 

 

 switch (ctyp[ch]) で列挙型によるスイッチ文を実現する。

 

 "Letter"では31文字まで有効で、それ以上は切り詰められる。エラーは吐かない。

 

$ cat cp_v04_04

abcdefghijklmnopqrstuvwxyz1234567890

 

$ ./cci cp_v04_04

Debug:  ch is [a]

Debug:  val of tkn.text [abcdefghijklmnopqrstuvwxyz12345]

Debug:  in execute()

 

 "Digit"では、整数をtkn構造体変数tkn.intValの中に格納する。

 

$ cat cp_v04_05

123456

 

$ ./cci cp_v04_05

Debug:  ch is [1]

Debug:  val of tkn.intVal [123456]

 

 "SngQ"では、'シングルクオーテーションでかこまれた1文字をtkn.intValの中に保存する。

 文字列が含まれる場合、現状ではエラーに分岐する。

$ cat cp_v04_06

'1'

 

$ ./cci cp_v04_06

Debug:  ch is [']

Debug:  val of tkn.intVal SngQ[1]

 

 "DblQ"では、"ダブルクオーテーションでかこまれた文字列をliteポインタで始まる文字列として格納する。

$ cat cp_v04_07

"abc"

 

$ ./cci cp_v04_07

Debug:  ch is ["]

Debug:  val of tkn.intVal(lite) DblQ[abc]

 

 

Token nextTkn(void)内でよく出てくるP_SET()について以下のようなマクロ定義がなされている。

 

#define P_SET() *p++=ch, ch=nextCh()

 

 

cci_skelton_v04

(ダウンロード)

 

cci.h

cci_prot.h

cci.c

  cci_tkn.c

cci_code.c

cci_pars.c

cci_misc.c

 

cp_add.c

cp_v04_01

cp_v04_02

cp_v04_03

cp_v04_04

cp_v04_05

cp_v04_06

cp_v04_07

 

makefile

 

(今回追加した関数)

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

(ダウンロード)

 

cci.h

cci_prot.h

cci.c

  cci_tkn.c

cci_code.c

cci_pars.c

cci_misc.c

cci_tbl.c(今回新規)

 

(サンプルコード)

cp_add.c

cp_add_dammy.c

 

makeファイル)

makefile

 

 

(20200109)

Cインタプリタ骨組みv06

 

実行 execute()処理を実装。(核です)

cp_add.cが実行できるかチェックをする。

--codeの分岐ができるかチェックする。

 

STEP1(コンパイルしましょう)

$ make

 

STEP2cp_add.cを実行する)

$ ./cci cp_add.c

Please enter a number. Enter '0' to exit

45

    >45

56

    >101

0

 

STEP3cp_add.cを実行する)

$ ./cci cp_add.c --code

Debug:  in code_dump()

 

※流れをcode_dump()に引き込むこともできている。

 

 

cci_skelton_v06

(ダウンロード)

 

cci.h

cci_prot.h

cci.c

  cci_tkn.c

cci_code.c

cci_pars.c

cci_misc.c

cci_tbl.c

 

(サンプルコード)

cp_add.c

 

makeファイル)

makefile

 

 

(20200202)

Cインタプリタ骨組みv07

(校正前最終)

 

これで、

【明解入門】

コンパイラ・

インタプリタ

開発

C処理系を作りながら学ぶ

林晴比古 著

のソースをコピーした。

 

--codeが実行できるようにする。

 

STEP1(コンパイルしましょう)

$ make

 

STEP2cp_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

(ダウンロード)

 

cci.h

cci_prot.h

cci.c

  cci_tkn.c

cci_code.c

cci_pars.c

cci_misc.c

cci_tbl.c

 

(サンプルコード)

cp_add.c

 

makeファイル)

makefile