Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PsySHから紐解くREPLの仕組み

muno92
March 22, 2025

 PsySHから紐解くREPLの仕組み

muno92

March 22, 2025
Tweet

More Decks by muno92

Other Decks in Programming

Transcript

  1. 自己紹介 X ( 旧Twitter): @muno_92 所属: スパイダープラス株式会社 Web エンジニア PHPerKaigi

    2025 コアスタッフ PHP カンファレンス小田原2025 コアスタッフ 来月4/12( 土) 開催。来てね! 2
  2. そんな場合にREPL はどうでしょう? REPL (Read Eval Print Loop の略) 入力の読み取り (Read)

    入力されたコードの評価 (Eval) 評価結果の出力 (Print) を繰り返し (Loop) ながらインタラ クティブにコードを実行できる環境 0:00/ 0:20 4
  3. REPL の例 php -a (Interactive shell) ※Print 機能は備えていない PsySH (PHP)

    IRB (Ruby) IPython (Python) Node.js REPL (JavaScript) 7
  4. PsySH とは PHP 製の開発者向けコンソール デバッガ機能やREPL 機能を備えている 「A runtime developer console,

    interactive debugger and REPL for PHP. 」 https://github.com/bobthecow/psysh PsySH をベースとしてlaravel/tinker やcakephp/repl などが作られて いる 13
  5. シェルの起動 (psysh コマンドの実態) 1. Phar アーカイブ リリースページなどからダウンロードした場合 https://www.php.net/manual/ja/intro.phar.php 2. Phar

    アーカイブにしていないPHP スクリプト composer (global) require でインストールした場合 Phar アーカイブを使用すれば、PHP のアプリケーションをひ とつのファイルとして配布できるようになります。 “ “ 24
  6. シェルの起動 (psysh コマンドの内部実装) call_user_func(function () { // オートロード // psyshコマンドを実行したディレクトリにpsyshがインストールされていたらローカルのpsyshを使う

    }); // オートロードできなかったらPsySH自身が保持している依存ライブラリを使う if (!class_exists('Psy\Shell')) { Phar::mapPhar('psysh.phar'); require 'phar://psysh.phar/.box/bin/check-requirements.php'; require 'phar://psysh.phar/vendor/autoload.php'; } call_user_func(Psy\bin()); 25
  7. Psy\bin() を深掘り (1/2) $input = new ArgvInput(); $input->bind(new InputDefinition(\array_merge(Configuration::getInputOptions(), [

    new InputOption('help', 'h', InputOption::VALUE_NONE), new InputOption('version', 'V', InputOption::VALUE_NONE), new InputOption('self-update', 'u', InputOption::VALUE_NONE), new InputArgument('include', InputArgument::IS_ARRAY), ]))); symfony/console を使ってコマンドラインオプションをパース 26
  8. Psy\bin() を深掘り (2/2) $config = Configuration::fromInput($input); $shell = new Shell($config);

    $shell->run(); コマンドラインオプションから読み取った設定をセットした状態で シェルを起動 27
  9. Shell クラスの実装 class Shell extends Application { private function doInteractiveRun():

    int { $this->initializeTabCompletion(); $this->readline->readHistory(); // 省略 $loop = new ExecutionLoopClosure($this); $loop->execute(); } } 30
  10. ExecutionLoopClosure ? while (true) { // Loop $__psysh__->getInput(); // Read

    $_ = eval($__psysh__->flushCode()); // Eval $__psysh__->writeReturnValue($_); // Print } シェルが起動した後、while ループでユーザーの入力を待ち受ける closure を宣言しているクラス 文字通りRead Eval Print Loop してる! 31
  11. GNU Readline / libedit ラインエディタ 対話型のツールを作るための様々な機能を提供している ユーザーの入力の読み取り tab 補完 history

    GNU Readline はGPL ライセンスのためMac では代替のlibedit を使用 PHP からはあまり違いを意識せずに使える https://www.php.net/manual/ja/ref.readline.php 34
  12. PHP でのreadline 利用サンプル while (true) { $line = readline(); echo

    "line: {$line}\n"; } 1 行ごとに入力を受け取るだけならこれだけでOK とはいえ、複数行入力したい場合もある 35
  13. つまり for ($i = 0; $i < 10; $i++) {

    echo $i . PHP_EOL; まで入力してから「$i < 100 に変更したいな」 ↓ 出来ない 37
  14. 文と式 文 (statement) if 文、switch 文など 式 (expression) https://www.php.net/manual/ja/language.expressions.php コードを評価(

    実行) した結果として値が返ってくるもの 今はこれだけ分かっていればOK 最も簡単で最も正確な式の定義は、" 値があるもの全て" “ “ 46
  15. 元々のノードをReturn ノードで包む // Psy\CodeCleaner\ImplicitReturnPass } elseif ($last instanceof Expression &&

    !($last->expr instanceof Exit_)) { $nodes[\count($nodes) - 1] = new Return_($last->expr, [ 'startLine' => $last->getStartLine(), 'endLine' => $last->getEndLine(), ]); } return $nodes; → ユーザーが入力したコードにreturn が付く! 51
  16. cakephp/repl class ConsoleCommand extends Command { public function execute(Arguments $args,

    ConsoleIo $io) { $psy = new Shell(); $psy->run(); } } ログの設定などは行っているが、ほとんどこれだけ シンプル。必要十分 61
  17. laravel/tinker class TinkerCommand extends Command { public function handle() {

    $config->getPresenter()->addCasters( $this->getCasters() ); $loader = ClassAliasAutoloader::register(引数は省略); $shell->execute($code); } } 62
  18. ClassAliasAutoloader > User::find(1) [!] Aliasing 'User' to 'App\Models\User' for this

    Tinker session. これ spl_autoload_register() にalias を解決するcallable を登録している spl_autoload_register([$loader, 'aliasClass']); . . . class_alias($fullName, $class); 66
  19. まとめ PsySH は文字通りRead Eval Print Loop を体現する実装になっていた 対話型ツールの実装はreadline / libedit

    が支えている PHP には文と式がある REPL に出力される実行結果は式を評価した値 PHP のソースコードを解析・操作したい場合はPHP Parser が便利 67
  20. 付録: PHP 標準関数だけでRead を再実装 $input = []; $editing = true;

    do { $input[] = readline(); try { eval(implode("\n", $input)); $editing = false; } catch (ParseError) { } } while ($editing); printf("input: %s\n", implode("\n", $input)); ポイント eval をパーサー代わ りに使っている 不正な構文の場合 にParse Error を投 げるのを利用 72