はい! みなさん tukinyanJP こと つきにゃんじぇーぴーです。今回も久しぶりに記事を書きますね。
今回の記事は、こちらの BE-Lang を C で構築する試み2 という私の記事の続きとなっております。こちらの内容を前提として話しますので、まだ読んでいない方はぜひ読んでください。
導入
みなさん突然なのですが、 オブジェクト指向 プログラミングをご存じですか? 私は単一継承を持つ Ruby という言語を極めているのですが、その Ruby というやつはですね、すべての機能1がすべてクラス、モジュールとして提供されています。しかも、モンキーパッチと言って既に存在しているクラスを拡張してオリジナルの機能を作れるもあります。一言で表すと、 極めて柔軟性 が高いプログラミング言語です。今回はその柔軟性の高いメゾットの構文を私の自作言語である BE-Lang に組み込んでいこうと思います。
目標
「実装する」と言ってもどのような動作・文法になるか考えてみましょう。と言ってもこれだけなんですけどね。
値.メゾット名()
こういう形がデフォルトですね。
実装
ただ、メゾット呼び出し構文が関数の呼び出しとかぶってコンフリクトが発生してしまいました。
bison -o src/c/parse.c -d src/parser.y
src/parser.y: 警告: 2 個のシフト/還元競合 [-Wconflicts-sr]
src/parser.y: ノート: '-Wcounterexamples' オプションをつけて競合の反例を生成するために再実行
flex -o src/c/lexer.c src/lexer.l
gcc -Wall -g -o BE-Lang src/c/lexer.c src/c/parse.c -lgc
で、関数の新しい呼び出し方は、
-> 関数名.(引数)
これです。 Ruby の lambda からとってきました。
さて、これからどうメゾットの動作を定義しましょうか。どちらにせよ、この手順は踏みますよね。
- ノード (
AST)生成 -
ASTをたどり実行する
ノードを生成するのは前々回に話したのでガン無視するとして、実行部に目をつけて考えましょう。参考にしていたあの柔軟性が高い Ruby と言えば、確か ダックタイピング というそのクラスの型をどうこうと考えるのではなく、そのクラスが そのメゾット を持っているかを注目するので、
"Hello,World!".size # => 13
[1, 2, 3].size # => 3
# よちよち歩いて「クワックワッ」となくならばダック(アヒル)とする。
このような型が違っていても、 そのメゾット さえ持っていてたら動く仕様なのです。なんという柔軟性。2 この仕様を疑似的に BE-Lang に入れてみましょう。
Value eval(Node* n) {
// ※ eval はめちゃくちゃでかい一つの switch 文です。これだけで567行あります。
// ...略
case 256: {
char* m_name = n->name;
Node* m_left = n->left;
Node* m_right = n->right;
Value l = eval(m_left);
Value r = (m_right) ? eval(m_right) : (Value){TYPE_INT, false, {.i_val = 0}};
switch (l.type) {
case TYPE_ARRAY: {
// ここに値が配列の場合。要するに [1,2,3].メゾット名()の場合
switch (return_method(m_name)) {
case SIZE_: {
Value res;
res.type = TYPE_INT;
res.as.i_val = l.as.list_val->size;
return res;
break; // いろいろ出てきてないものがいっぱいありますが、配列の値を見て、配列のサイズを参照しそのまま返すというものです。
} // 比較的行数が少ないので書いておきます。
}
case TYPE_STR: {
// 文字列の場合
}
case TYPE_INT: {
// 整数の場合
}
default: {
break; // 未知のメゾットは無視
}
}
}
}
で、です。メゾットはこの中にさらにメゾット名の m_name の switch 文を書いていきます。そのー...全部この記事に書き残すことは難しいので、雰囲気だけつかんでくればうれしいのですが... まぁとにかくこれでメゾット風の構文は実装できました。
実際に使ってみる
追加しただけではだめですよね。動かしてみないと。この下のコードは、配列の [1] から始まって配列の要素数 + 1 を要素として追加するコードです。(?)
i = [1];
for (i -> i.push(i.size() + 1), i.size() <= 10) then {
puts(i);
};
結果はこれ:
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
いい感じですね。しっかり実行できてます。
謝るべきことがあります。実はこの記事投稿する時にはもう、ファイル書き込み(上書きと追記)とファイル読み込み、 CSV 形式の文字列を多次元配列で提供するメゾットはもう実装済みです。しかし、 現在 2026年3月時点で csv に規格はありません。 仕様を文書化してみます。 @SaitoAtsushi さんが指摘してくださりました。CSV と言ってちゃんと国際規格にあっているかどうかすらわかりませんが...
締めくくりに
今回は、 BE-Lang に革新をもたらした素晴らしい機能を追加してみました。 Github でもホストしようかなと思っていましたが、事情があって...とにかく楽しくできたのでよしとしましょう。プログラミングは楽しくするべきものなので。それではまた次の記事で。さようなら!