0Pricing
C Academy · レッスン

変数を追加する

値を保存して取り出します。

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

算術演算の先へ

x = 5 や、その後の x + 1 をサポートするには、インタープリターに識別子と、その値を保存する場所が必要です。その保存場所が環境です。

名前と代入を扱えるように、字句解析器、パーサー、評価器を拡張します。

識別子の字句解析

識別子は英字またはアンダースコアで始まり、英字、数字、アンダースコアが続きます。名前をトークンにコピーします。

固定サイズのバッファーを使って例を簡単にしています。実際の字句解析器では名前を intern します。

#include <ctype.h>
#include <string.h>

typedef struct { TokKind kind; int value; char name[32]; } Token;

static Token lex_ident(void) {
  Token t; t.kind = TOK_IDENT; int i = 0;
  while (isalnum((unsigned char)peek()) || peek() == '_')
    if (i < 31) t.name[i++] = advance(); else advance();
  t.name[i] = '\0';
  return t;
}

変数ノード

AST に2種類のノードが加わります。変数参照と代入です。どちらも変数名を保持します。

代入ノードは、保存する値を計算する式も保持します。

typedef struct Node {
  enum { N_NUM, N_BINOP, N_VAR, N_ASSIGN } kind;
  int value; char op; char name[32];
  struct Node *left, *right;  /* assign uses right as value expr */
} Node;

環境

環境は名前と値を対応付けます。小規模なインタープリターなら、名前と値のペアを格納した単純な線形配列で十分です。

規模の大きな言語では、O(1) の検索と入れ子になったスコープのためにハッシュテーブルを使います。

#define MAX_VARS 64

typedef struct {
  char names[MAX_VARS][32];
  int  values[MAX_VARS];
  int  count;
} Env;

static Env env;  /* global for this tiny example */

変数への代入

代入では、既存のスロットを更新するか、新しいスロットを追加します。線形検索によりコードを短く保てます。

保存した値を返すことで、y = (x = 3) のように代入を式として使えます。

#include <string.h>

static int env_set(const char *name, int v) {
  for (int i = 0; i < env.count; i++)
    if (strcmp(env.names[i], name) == 0) {
      env.values[i] = v; return v;
    }
  strcpy(env.names[env.count], name);
  env.values[env.count] = v;
  env.count++;
  return v;
}

変数の読み取り

検索では名前を走査して値を返します。未定義の変数を参照すると実行時エラーになります。

ここで検出すれば、意味のない値を返す代わりに、明確なメッセージを表示できます。

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

static int env_get(const char *name) {
  for (int i = 0; i < env.count; i++)
    if (strcmp(env.names[i], name) == 0)
      return env.values[i];
  fprintf(stderr, "undefined variable: %s\n", name);
  exit(1);
}

代入の解析

代入は最も優先順位が低く、右結合です。まずファクターを解析し、それが単独の識別子で、続けて = がある場合は assign ノードを構築します。

それ以外の場合、識別子は単なる変数の読み取りです。

static Node *parse_assign(void) {
  if (cur().kind == TOK_IDENT) {
    char saved[32]; strcpy(saved, cur().name);
    bump();
    if (cur().kind == TOK_ASSIGN) {
      bump();
      Node *val = parse_assign();
      return assign_node(saved, val);
    }
    return var_node(saved);  /* not an assignment */
  }
  return parse_expr();
}

変数の評価

評価器に2つのケースを追加します。変数ノードでは環境から名前を検索し、assign ノードでは値の式を評価して保存します。

どちらも env_get と env_set のヘルパーを再利用します。

int eval(Node *n) {
  switch (n->kind) {
    case N_NUM:    return n->value;
    case N_VAR:    return env_get(n->name);
    case N_ASSIGN: return env_set(n->name, eval(n->right));
    case N_BINOP: {
      int l = eval(n->left), r = eval(n->right);
      switch (n->op) {
        case '+': return l + r; case '-': return l - r;
        case '*': return l * r; case '/': return l / r;
      }
    }
  }
  return 0;
}

動作するREPLのステップ

このプログラムは環境を通じて変数を保存し、その後、式の中で読み取ります。実際のインタープリターにおける1行分の処理をモデル化したものです。

実行して、代入と検索が連携する様子を確認してください。

#include <stdio.h>
#include <string.h>

#define MAX_VARS 64
static char names[MAX_VARS][32];
static int  values[MAX_VARS];
static int  count;

static int env_set(const char *n,int v){
  for(int i=0;i<count;i++) if(!strcmp(names[i],n)){values[i]=v;return v;}
  strcpy(names[count],n); values[count]=v; count++; return v;
}
static int env_get(const char *n){
  for(int i=0;i<count;i++) if(!strcmp(names[i],n)) return values[i];
  return 0;
}

int main(void){
  env_set("x", 5);            /* x = 5 */
  int r = env_get("x") + 1;   /* x + 1 */
  printf("x = %d\n", env_get("x"));
  printf("x + 1 = %d\n", r);
  return 0;
}

スコープとシャドーイング

単一の平坦な環境はグローバル環境です。実際の言語では、関数のローカル変数が外側の名前を上書きしないように、入れ子になったスコープを追加します。

これを実装するには環境を連結し、最も内側のスコープから外側に向かって検索します。

typedef struct Env {
  char names[MAX_VARS][32];
  int  values[MAX_VARS];
  int  count;
  struct Env *parent;  /* enclosing scope */
} Env;

次のステップ

変数が使えるようになったので、文、条件分岐、関数を追加できます。それぞれ、新しいノード種別と eval のケースを追加します。

ここから、同じ字句解析器・パーサー・評価器のパイプラインを拡張して、実用的な小さな言語へ発展させられます。

理解度チェック

環境がどのような役割を担っているか考えてみましょう。

まとめ

字句解析器に識別子を追加し、AST に var/assign ノードを追加しました。また、set と get を備えた環境と、読み取りおよび代入の eval ケースも実装しました。

これでインタープリターは状態を記憶できるようになりました。これは文や関数の基盤となります。

よくある質問

「変数を追加する」レッスンは無料ですか?

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

「変数を追加する」で何を学びますか?

値を保存して取り出します。 ブラウザで直接実行するハンズオンコードでC Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

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

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

「変数を追加する」レッスンにはどのくらい時間がかかりますか?

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

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

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

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

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