Chapter 07

ブロックパーサーの実装

shunsock
shunsock
2025.03.04に更新

概要

本章ではScannerで作成したトークン列を評価しやすいように構造化します。完全な実装は次のURLにあります。

https://github.com/shunsock/alocasia/tree/main/app/src/Interpreter/Parser

実装

実のところ、単なるStack型の言語の実装をしたいのであれば、この章は不要です。今回は説明を簡単にするためにBlockをあらかじめ作成します。

raw text: { 1 }
token: BraceLeft LiteralInteger(1) BraceRight
token with block: Block(LiteralInteger(1))

tokenを保持できればいいので次のようなClassがあれば良いです。

readonly class Block extends Token
{
    /** @var Token[]  */
    public array $tokens;

    /**
     * @param Token[] $tokens
     */
    public function __construct(int $line, int $position, array $tokens) {
        parent::__construct(line: $line, position: $position);
        $this->tokens = $tokens;
    }
}

Block ParserはScannerと比べればよっぽど簡単なプログラムになっています。再帰で可変参照を渡して、それを編集するコードです。

参照と再帰の肩ならし

再帰と可変参照になれるためにちょっと肩慣らしをしましょう。PHPの場合、暗黙的に書くので先にRustで明示的に書くことでイメージを膨らませます。Rustの場合、可変にするには mut で宣言する必要があります。また、参照渡しをするには & をつけるので、 &mut で可変参照渡しになります、

struct MyStruct {
    value: i32,
}

// 可変参照を引数に取る関数
fn modify_struct(s: &mut MyStruct) {
    s.value += 1;
}

fn main() {
    let mut s = MyStruct { value: 10 };
    println!("Before: {}", s.value);
    
    // 可変参照渡し
    modify_struct(&mut s);
    
    println!("After: {}", s.value);
}

PHPの場合 classはデフォルトで可変で、functionには参照渡しされます。つまり下記のコードは先程のコードと等価です。

class MyClass {
    public $value;

    public function __construct($value) {
        $this->value = $value;
    }
}

function modifyClass($obj) {
    // オブジェクトのプロパティを変更
    $obj->value += 1;
}

$s = new MyClass(10);
echo "Before: " . $s->value . "\n";

modifyClass($s);

echo "After: " . $s->value . "\n";

再帰も少し練習します。空配列でないときに先頭の文字を出力する関数を作ります。

<?php
function print_character(array $arr) {
    if (empty($arr)) {
        return;
    } else {
        $firstCharacter = array_shift($arr); // $arrの最初の要素が消える
        echo $firstCharacter;
        print_character($arr);
    }
}

print_character(["h", "e", "l", "l", "o"]);
// hello

ブロックをパースする

ここまでくればパースは簡単です。 array_shift でtokenからとりだしつづけるコードを書き、LeftBraceをみたらBlockを探索する関数を起動します。

class Parser
{
    /** @var Token[]  */
    private array $tokens;

    /**
     * @param list<Token> $tokens
     */
    public function __construct(array $tokens) {
        $this->tokens = $tokens;
    }

    /**
     * @return Token[]
     * @throws ParserException
     */
    public function parse(): array {
        $parsed_tokens = [];
        while ($token = array_shift($this->tokens)) {
            if ($token instanceof LeftBrace) {
                $parsed_tokens[] = $this->parseBlock($this->tokens, $token->line, $token->position);
                continue;
            }
            $parsed_tokens[] = $token;
        }
        return $parsed_tokens;
    }
}

Blockを探索する関数は再帰をします。

  1. ネストが深くなるときに再帰呼び出し
  2. ブロックの終了条件のときにreturn

するところだけが異なります。

/**
 * @param Token[]& $tokens
 * @param int $line
 * @param int $position
 * @return Block
 * @throws ParserException
 */
private function parseBlock(array &$tokens, int $line, int $position): Block {
    $tokens_in_block = [];
    while ($token = array_shift($tokens)) {
        if ($token instanceof LeftBrace) {
            $tokens_in_block[] = $this->parseBlock($tokens, $token->line, $token->position);
            continue;
        }
        if ($token instanceof RightBrace) {
            return new Block(
                line: $line,
                position: $position,
                tokens: $tokens_in_block
            );
        }
        $tokens_in_block[] = $token;
    }
    throw new ParserException(
        source_code_line: $line,
        source_code_position: $position,
        message: "{ (line: " . $line . ", position: " . $position . ") に対応する } が存在しません"
    );
}

まとめ

この章では、Block構造を作成しました。もちろん、この機能をStackMachineに入れてもいいですが、次の章のコードがかなり楽になります。このように評価器の前にデータ構造を扱いやすくすることは大切です。

補足: PHPで不変参照は使えるの?

readonlyを付けると不変になります。

<?php
readonly class MyClass {
    public $value;

    public function __construct($value) {
        $this->value = $value;
    }
}

function modifyClass($obj) {
    // オブジェクトのプロパティを変更
    $obj->value += 1;
}

$s = new MyClass(10);
echo "Before: " . $s->value . "\n";

modifyClass($s);

echo "After: " . $s->value . "\n";

// <br />
// <b>Fatal error</b>:  Readonly property MyClass::$value must have type in <b>php-wasm run script</b> on line <b>2</b><br />