> For the complete documentation index, see [llms.txt](https://tenpoku1000.gitbook.io/tenpoku-book/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tenpoku1000.gitbook.io/tenpoku-book/chapter-2/4_token.md).

# 2.4 字句解析

ソースコードを読み込んで、後述のトークン列を出力する処理を字句解析と呼びます。 この章で作るコンパイラでは、 [int\_calc\_compiler/src/lib/tp\_compiler/tp\_make\_token.c](https://github.com/tenpoku1000/int_calc_compiler/blob/master/src/lib/tp_compiler/tp_make_token.c) ：tp\_make\_token 関数が該当します。

## 2.4.1 トークン列の出力

例として、以下のようなソースコードをコンパイルできることを前提としています。

```
int32_t value1 = (1 + 2) * 3;
int32_t value2 = 2 + (3 * value1);
value1 = value2 + 100;
```

ソースコード中の文字列を、以下のようなルールに従って、トークンという単位に切り出します。ルールの左辺は正規表現です。 正規表現を実装すると大がかりになってしまうため、手書きのコードで、ソースコードを読み込んだバッファの先頭から 1 バイトずつ文字の種類を判別することで 文字列をトークン化する処理を実装しています。空白・改行・ASCII コードの制御文字は読み捨てます。

```
'+' = TP_SYMBOL_PLUS
'-' = TP_SYMBOL_MINUS
'*' = TP_SYMBOL_MUL
'/' = TP_SYMBOL_DIV
'(' = TP_SYMBOL_LEFT_PAREN
')' = TP_SYMBOL_RIGHT_PAREN
'=' = TP_SYMBOL_EQUAL
';' = TP_SYMBOL_SEMICOLON
[0-9]+ = TP_SYMBOL_CONST_VALUE(数値)
[^0-9+-*/()=;][^+-*/()=;]* = TP_SYMBOL_ID(識別子)
```

上記のソースコードの 1 行目を字句解析すると、以下のようにトークン化されます。これはイメージで、実際には後述の TP\_TOKEN 構造体の配列で表現されます。

```
int32_t
value1
=
(
1
+
2
)
*
3
;
```

トークン列は、 [int\_calc\_compiler/src/lib/tp\_compiler/tp\_compiler.h](https://github.com/tenpoku1000/int_calc_compiler/blob/master/src/lib/tp_compiler/tp_compiler.h) の記号表(TP\_SYMBOL\_TABLE 構造体)の TP\_TOKEN\* member\_tp\_token; に格納されます。 トークン列の各要素には、ソースコード上の行と桁が記録されます。数値や識別子などの文字列が格納される member\_string メンバ変数は固定長の配列とし、 文字列の長さに制限を設けています。ソースコードを読み終わった時に、トークン列の末尾は TP\_SYMBOL\_NULL で終端されるようになっています。 トークンは、構文解析(tp\_make\_parse\_tree.c)と、意味解析(tp\_semantic\_analysis.c)で参照されます。トークンを表す TP\_TOKEN 構造体は、以下の通りです。

```
typedef enum TP_SYMBOL_
{
    TP_SYMBOL_NULL,
    TP_SYMBOL_ID,
    TP_SYMBOL_CONST_VALUE,
    TP_SYMBOL_PLUS,
    TP_SYMBOL_MINUS,
    TP_SYMBOL_MUL,
    TP_SYMBOL_DIV,
    TP_SYMBOL_LEFT_PAREN,
    TP_SYMBOL_RIGHT_PAREN,
    TP_SYMBOL_EQUAL,
    TP_SYMBOL_SEMICOLON
}TP_SYMBOL;

typedef enum TP_SYMBOL_TYPE_
{
    TP_SYMBOL_UNSPECIFIED_TYPE,
    TP_SYMBOL_ID_INT32,
    TP_SYMBOL_TYPE_INT32,
    TP_SYMBOL_CONST_VALUE_INT32
}TP_SYMBOL_TYPE;

#define TP_MAX_ID_BYTES 63
#define TP_ID_SIZE (TP_MAX_ID_BYTES + 1)

typedef struct tp_token_{
    TP_SYMBOL member_symbol;
    TP_SYMBOL_TYPE member_symbol_type;
    rsize_t member_line;
    rsize_t member_column;
    uint8_t member_string[TP_ID_SIZE];
    int32_t member_i32_value;
}TP_TOKEN;
```

コンパイル対象のソースコードの 1 行目を、もう一度見てみましょう。

```
int32_t value1 = (1 + 2) * 3;
```

このソースコードを字句解析して得られたトークン列は、デバッグ用途のために int\_calc\_token.log ファイルに出力できるようになっています。 以下は、上記の TP\_TOKEN 構造体をテキスト表現にしたものです。1 つのトークンが { } で囲まれていて、その中に TP\_TOKEN 構造体の内容が 入っていることがお分かりいただけるでしょうか。

```
   {
    TP_SYMBOL_ID
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(0)
     member_string(int32_t)
     member_i32_value(0)
   }

   {
    TP_SYMBOL_ID
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(8)
     member_string(value1)
     member_i32_value(0)
   }

   {
    TP_SYMBOL_EQUAL
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(15)
     member_string()
     member_i32_value(0)
   }

   {
    TP_SYMBOL_LEFT_PAREN
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(17)
     member_string()
     member_i32_value(0)
   }

   {
    TP_SYMBOL_CONST_VALUE
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(18)
     member_string(1)
     member_i32_value(0)
   }

   {
    TP_SYMBOL_PLUS
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(20)
     member_string()
     member_i32_value(0)
   }

   {
    TP_SYMBOL_CONST_VALUE
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(22)
     member_string(2)
     member_i32_value(0)
   }

   {
    TP_SYMBOL_RIGHT_PAREN
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(23)
     member_string()
     member_i32_value(0)
   }

   {
    TP_SYMBOL_MUL
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(25)
     member_string()
     member_i32_value(0)
   }

   {
    TP_SYMBOL_CONST_VALUE
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(27)
     member_string(3)
     member_i32_value(0)
   }

   {
    TP_SYMBOL_SEMICOLON
    TP_SYMBOL_UNSPECIFIED_TYPE
     member_line(0)
     member_column(28)
     member_string()
     member_i32_value(0)
   }
```

## 2.4.2 ソースコードの読み込み時の処理

自動生成された巨大なソースコードを受け付けないようにする目的で、TP\_MAX\_FILE\_BYTES を超えるサイズのファイルはコンパイル・エラーにしています。

```
#define TP_MAX_LINE_BYTES 4095
#define TP_BUFFER_SIZE (TP_MAX_LINE_BYTES + 1)
#define TP_MAX_FILE_BYTES (TP_MAX_LINE_BYTES * 4096)
```

改行は LF に統一します。改行が CR の場合に LF に置き換え、CR/LF の場合は CR を空白に置き換えます。

ファイルやソケットなどの信頼できない入力は、バッファの途中に NUL 文字が混入していることがあるため、予期せぬ不具合の原因になります。 バイナリモードでファイルを開き、固定長 TP\_MAX\_LINE\_BYTES のバッファに fread() でファイルを読み込むことで、バッファの途中に NUL 文字が混入していても、 NUL 文字を空白に置き換えることが可能になります。

入力するソースコードが UTF-8 であるか、バリデーション(妥当性確認)します。先頭に BOM(Byte Order Mark: 0xEF, 0xBB, 0xBF) が含まれていたら、空白で置き換えます。

1. ASCII コードの範囲内だったら、1 バイトのコード
2. 第 1 バイトが、0xC0 ～ 0xDF の範囲内だったら、2 バイトのコード
3. 第 1 バイトが、0xE0 ～ 0xEF の範囲内だったら、3 バイトのコード
4. 第 1 バイトが、0xF0 ～ 0xF7 の範囲内だったら、4 バイトのコード
5. 第 2 バイト以降が、0x80 ～ 0xBF の範囲外だったら、不正な UTF-8
6. 第 1 バイトが、0xC0・0xE0・0xF0 いずれかの場合に、第 2 バイト以降 & 0x3F の結果が、最終バイト以外が全部 0x00 だったら、冗長な ASCII コードで、不正な UTF-8
