ゼロからはじめるスクリプト言語製作: エレガントにさばく(3日目)
前回製作した読み込み処理では、ユーザー入力に複数の丸カッコがあったときに、正しいところでコードを切り出すことができない問題があった。
今日はこの部分の改善を進めていこう。
入れ子構造の丸カッコに対応するには
ここは再帰呼び出しを使っていくべきだろう。
丸カッコの対応を見つけるだけなら他の方法でも良いのだが、このあと構文解析も必要になるし、前回までで1対の丸カッコを切り出す処理はそろっているので、これを活用できる。
Program.cs の ReadLine() の前半ブロックは、開き丸カッコの位置を探す処理になってて、ここの変更は必要ない。
後半ブロックは閉じ丸カッコの位置を探す処理になってて、ここに if 文を追加する。閉じ丸カッコよりも先に開き丸カッコを見つけたならば、ReadLine() を呼び直すことにしよう。
string ReadLine(ref string line)
{
string expression = "";
{ // skip chars to open paren.
int openedAt = line.IndexOf('(');
while (openedAt < 0)
{
Console.Write("> ");
line += Console.ReadLine();
openedAt = line.IndexOf('(');
}
expression += line.Substring(openedAt, 1);
line = line.Substring(openedAt + 1);
}
{ // find nearest paren.
char[] parens = { '(', ')', };
int parenAt = line.IndexOfAny(parens);
while (parenAt < 0 || line[parenAt].Equals(parens[0]))
{
if (parenAt < 0)
{ // read new from input stream.
Console.Write(">>> ");
line += ' ' + Console.ReadLine();
}
else
{ // read out before open paren.
expression += line.Substring(0, parenAt);
line = line.Substring(parenAt);
// read out from open paren to close paren.
expression += ReadLine(ref line);
}
parenAt = line.IndexOfAny(parens);
}
// read out to close paren.
expression += line.Substring(0, parenAt + 1);
line = line.Substring(parenAt + 1);
}
return expression;
}
if 文の else 節にご注目。これで入れ子にも対応できた。
プログラムを実行して確認してみよう。
よしよし、順調だ。どんな入力に対しても、開き丸カッコと閉じ丸カッコの数は一致している。
今日はここまで、おつかれさま。
内部表現について想いを巡らせる
Lisp といえば S 式が有名だ。
でもそれが何なのかを説明できなかったので、少し調べてみた。たどり着いた Clojure 公式ページの説明が比較的腹落ちできたので、自分の言葉でまとめ直してみた。
S 式は、シンボルやリテラル(数値など)の列を空白で区切り、丸カッコで括ることで、先頭から末尾までの直列構造を表すものである。そして S 式の要素に S 式を含めることもできる。
それと同時に、リスト先頭に位置する要素を「呼び出したいサブルーチン」、先頭に続くすべての要素を「サブルーチンに与える引数」という風にみなし、スクリプト言語の文脈や評価手順を表すものでもある。
これを扱うには S 式を表現するための構造を考えないといけない。
次回からはこの辺りを取り組んでいこうと思う。
この記事が気に入ったらサポートをしてみませんか?