Perlテックブログ

ITエンジニアの成長意欲を刺激する技術考察、モジュール開発の日記。Perlイベントや国内や海外のPerlの記事の紹介。

予約語を変数名と関数名で使えるようにするコンパイラの実装について

SPVMを開発しているときに、予約語を変数名と関数名で使えるようにするコンパイラの実装についての考察が深まったのでここに記す。

変数名が予約語とぶつからないようにする方法

変数名は短い。完全修飾名というものを持たない。だから、予約語や標準関数名と非常に衝突しやすい。その解決方法のひとつは、シジルを使うことだ。 知らない人のために書くと、シジルというのは「$」のことね。

my $if;

シェルスクリプトPerlPHPという言語群は、変数名にシジルを採用している。シジルの発明者は、シェルスクリプトで、Perlは、それをシェルスクリプトから受け継いで、PHPは、それをPerlから受け継いでいる。

シジルの明らかな利点は、プログラムをコーディングするときに、予約語のことをまったく意識しないでよいことだ。

my $next;
my $begin;
my $end;
my $sub;
my $class;

予約語のことをまったく忘れて、プログラムに集中できる。

シジルのもう一つの利点は、エディタに色付け機能があるときに、はっきりと変数名として色がつくことだ。

これは、とても目に優しく、可読性に貢献する。

関数名と予約語がぶつからないようにする方法

関数名について考えてみよう、たとえば、int関数なんてどうだろうか。intという予約語といかにも衝突しそうじゃないか。

そして、ソースコードの上では、見た目だけでは、予約語と関数名の区別はつかない。

見た目で判断できないのであれば、どうするか。そのひとつの解決方法のひとつは文脈を見ることだ。

関数名は、どんな場所にあるだろうか。それは、定義の場合は「sub」の後ろ、メソッド呼び出しの場合は、「->」の後ろだ。

sub foo (...) { }
Point->foo;
$point->foo;

fooというだけでは見分けがつかないから、この場合は、あきらめるしかないんだけど、文脈で判定できる場所であれば、予約語か関数名かを判定できる。

ここで、SPVMの実装を少し見せよう。まずコンパイラで「expect_sub_name」というフィールドを定義している。これは、「サブルーチンを期待する」という意味を持つフィールドだ。

struct SPVM_compiler {
  // Expect subroutine name
  _Bool expect_sub_name;

プログラミングをする場合は、どんなデータが必要になるかということをまず考える。

そして、この変数に、「sub」の後ろと「->」の後ろで1を設定する。以下は、トークンを処理する、トーカナイザーのコードだ。

// ->の後ろ
      case '-':
        compiler->bufptr++;
        
        // 10 digit literal or floating point literal allow minus
        if (
          isdigit(*compiler->bufptr)
          &&
          (
            (*compiler->bufptr != '0')
            || ((*compiler->bufptr == '0') && (*(compiler->bufptr + 1) == '.')))
          )
        {
          minus = 1;
          continue;
        }
        else if (*compiler->bufptr == '>') {
          compiler->bufptr++;
          yylvalp->opval = SPVM_TOKE_newOP(compiler, SPVM_OP_C_ID_NULL);
          compiler->expect_sub_name = 1;
          
          return ARROW;
        }

// subの後ろ
              case 's' :
                if (strcmp(keyword, "self") == 0) {
                  yylvalp->opval = SPVM_TOKE_newOP(compiler, SPVM_OP_C_ID_SELF);
                  return SELF;
                }
                else if (strcmp(keyword, "switch") == 0) {
                  yylvalp->opval = SPVM_TOKE_newOP(compiler, SPVM_OP_C_ID_SWITCH);
                  return SWITCH;
                }
                else if (strcmp(keyword, "sub") == 0) {
                  yylvalp->opval = SPVM_TOKE_newOP(compiler, SPVM_OP_C_ID_SUB);
                  compiler->expect_sub_name = 1;
                  
                  return SUB;
                }

このように判定することで、次に来るのが関数名を期待しているのか、そうでないのかを、知らせることができる。

見た目の判定と文脈での判定の利点と欠点

ここで、見た目で判定することと、文脈で判定することの利点と欠点を記しておく。

見た目判定 文脈判定
利点 シンプル 文脈で判定できる
欠点 見た目でしか判断できない 複雑

「そのままやんか! 」

プログラムは必要がなければシンプルに構成するのが、美しい。だから、95%は、見た目判定で言って、こだわりの5%について、文脈判定を使うみたいなのがきっと理想だろうと思う。