0Pricing
C Academy · レッスン

入力をトークン化する

テキストをトークンに変換します。

「入力をトークン化する」はCoddyKit上の無料C Academyレッスンです。 これはレッスン1/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはC Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 C Academyコースには全4レッスンが含まれています。

Tokenizerとは

インタープリタはまず、生のテキストをトークン、つまり意味を持つ最小単位に変換します。文字列 3 + 4 * 2 に対して、Tokenizer(またはlexer)は数値と演算子を生成します。

この段階で空白を取り除き、文字のまとまりを分類するため、パーサーは生のバイトを扱わずに済みます。

トークン型

各トークンをenumタグとペイロードで表現します。数値は整数値を持ち、演算子と括弧には種類だけが必要です。

値を構造体内に保持しておくと、後でソースを再スキャンせずに済みます。

typedef enum {
  TOK_NUM, TOK_PLUS, TOK_MINUS,
  TOK_STAR, TOK_SLASH,
  TOK_LPAREN, TOK_RPAREN, TOK_EOF
} TokKind;

typedef struct {
  TokKind kind;
  int value; /* used when kind == TOK_NUM */
} Token;

スキャン位置

lexerはカーソルポインタを使ってソースを走査します。小さなヘルパー関数で、現在の文字を消費せずに確認できます。末尾では '\0' を返します。

ポインタ演算によって、スキャナを高速かつ単純に保てます。

static const char *src;

static char peek(void) {
  return *src;
}

static char advance(void) {
  return *src++;
}

空白のスキップ

トークンを読み取る前に、スペースとタブを破棄します。<ctype.h> の標準関数 isspace が、あらゆる空白文字を処理します。

ここでは改行も空白として扱うため、式を複数行にまたがって記述できます。

#include <ctype.h>

static void skip_ws(void) {
  while (isspace((unsigned char)peek()))
    advance();
}

数値の字句解析

カーソルが数字の上にある場合、連続する数字を整数にまとめます。10倍して各数字を加えることで、左から右へ値を構築します。

ループは最初の数字以外の文字で停止し、カーソルは次のトークンを読み取れる位置に残ります。

static int lex_number(void) {
  int n = 0;
  while (isdigit((unsigned char)peek())) {
    n = n * 10 + (advance() - '0');
  }
  return n;
}

The next_token関数

中心となる処理では、空白をスキップしてから現在の文字に応じて処理を振り分けます。数字は TOK_NUM になり、各演算子はそれぞれの種類に対応します。

終端NULに到達すると、停止の合図である TOK_EOF が生成されます。

static Token next_token(void) {
  skip_ws();
  char c = peek();
  if (c == '\0') return (Token){TOK_EOF, 0};
  if (isdigit((unsigned char)c))
    return (Token){TOK_NUM, lex_number()};
  advance();
  switch (c) {
    case '+': return (Token){TOK_PLUS, 0};
    case '-': return (Token){TOK_MINUS, 0};
    case '*': return (Token){TOK_STAR, 0};
    case '/': return (Token){TOK_SLASH, 0};
    case '(': return (Token){TOK_LPAREN, 0};
    case ')': return (Token){TOK_RPAREN, 0};
  }
  return (Token){TOK_EOF, 0};
}

lexerを実行する

これは式をトークン化し、それぞれのトークンの種類を表示する完全なプログラムです。数値の場合は値も表示します。

TOK_EOF を読み取るとループが終了することに注目してください。

#include <stdio.h>
#include <ctype.h>

typedef enum { TOK_NUM, TOK_PLUS, TOK_STAR, TOK_EOF } TokKind;
typedef struct { TokKind kind; int value; } Token;

static const char *src;
static char peek(void){ return *src; }
static char advance(void){ return *src++; }

static Token next_token(void){
  while (isspace((unsigned char)peek())) advance();
  char c = peek();
  if (c=='\0') return (Token){TOK_EOF,0};
  if (isdigit((unsigned char)c)){
    int n=0; while(isdigit((unsigned char)peek())) n=n*10+(advance()-'0');
    return (Token){TOK_NUM,n};
  }
  advance();
  if (c=='+') return (Token){TOK_PLUS,0};
  return (Token){TOK_STAR,0};
}

int main(void){
  src = "12 + 3 * 4";
  Token t;
  do {
    t = next_token();
    if (t.kind==TOK_NUM) printf("NUM %d\n", t.value);
    else if (t.kind==TOK_PLUS) printf("PLUS\n");
    else if (t.kind==TOK_STAR) printf("STAR\n");
    else printf("EOF\n");
  } while (t.kind != TOK_EOF);
  return 0;
}

1トークン先読み

パーサーでは通常、次のトークンを消費する前に調べる必要があります。グローバルな current に1つのトークンを格納し、マッチするたびに補充します。

この1トークン先読みで、今回のLL(1)文法には十分対応できます。

static Token current;

static void init_lexer(const char *s) {
  src = s;
  current = next_token();
}

static Token cur(void) { return current; }

static void bump(void) { current = next_token(); }

字句エラーの報告

$ や @ などの未知の文字を、黙って消失させてはいけません。堅牢なlexerは問題のバイトを報告して中止します。

lexerで速やかに失敗させることで、後続の段階が壊れたトークンを受け取らずに済みます。

#include <stdio.h>
#include <stdlib.h>

static void lex_error(char c) {
  fprintf(stderr, "lex error: unexpected '%c'\n", c);
  exit(1);
}

複数文字の演算子

実際の言語には == や <= のようなトークンがあります。これらを字句解析するには、最初の文字の後にもう1文字先読みします。

次のバイトで演算子が完成する場合は両方を消費し、そうでなければ1文字形式を生成します。

/* fragment: distinguish '=' from '==' */
if (peek() == '=') {
  advance();
  if (peek() == '=') { advance(); /* TOK_EQ */ }
  else { /* TOK_ASSIGN */ }
}

トークンが重要な理由

文字をトークンにまとめることで、パーサーは整理された型付きのストリームを扱えるようになります。演算子の優先順位、グループ化、エラーの処理についても考えやすくなります。

次は、これらのトークンを再帰下降パーサーに渡します。

クイックチェック

グループ化記号に対してlexerが何を生成するか考えてみましょう。

まとめ

lexerを構築しました。トークン型、スキャン用カーソル、空白のスキップ、数値の字句解析、そして1トークン先読みを行う next_token ディスパッチャです。

これらのトークンがパーサーへの入力となり、パーサーはそこから構文木を構築します。

よくある質問

「入力をトークン化する」レッスンは無料ですか?

はい。「入力をトークン化する」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、C Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 C Academyコースには全4レッスンが含まれています。

「入力をトークン化する」で何を学びますか?

テキストをトークンに変換します。 ブラウザで直接実行するハンズオンコードでC Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

C Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのC Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/4です。

「入力をトークン化する」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このC Academyレッスンでコードを書いて実行できますか?

はい。すべてのC Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 入力をトークン化する
  2. 式を解析する
  3. 構文木を評価する
  4. 変数を追加する
← C Academyに戻る