変数を追加する
値を保存して取り出します。
「変数を追加する」は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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- 入力をトークン化する
- 式を解析する
- 構文木を評価する
- 変数を追加する