#import "@preview/touying:0.6.3": * #import "@preview/codly:1.3.0": * #import "@preview/cjk-unbreak:0.2.0": remove-cjk-break-space, transform-childs #import "@preview/fletcher:0.5.8" as fletcher: diagram, node, edge #import "setoka.typ": * #show: codly-init.with() #show: remove-cjk-break-space #show: setoka-theme.with( aspect-ratio: "16-9", config-info( title: [ PHP を魔改造して学ぶ \ 言語処理系 ], subtitle: [PHPerKaigi 2026], author: [nsfisis (いまむら)], date: datetime(year: 2026, month: 3, day: 22), ), config-common(preamble: { codly( fill: rgb("#eee"), lang-format: none, number-format: none, zebra-fill: none, ) }) ) #set text(font: "BIZ UDPGothic", lang: "ja") #show raw: set text(font: "UDEV Gothic 35") #let process-color = rgb("#ffa500").lighten(60%) #let data-color = rgb("#e0e0e0") #let overview-diagram() = { set text(size: 18pt) diagram( node-stroke: 0.5pt, spacing: (6mm, 12mm), node((0, 0), [ソースコード], fill: data-color, corner-radius: 2pt), edge("-"), node((1, 0), [字句解析], fill: process-color, corner-radius: 2pt), edge("-|>"), node((2, 0), [トークン列], fill: data-color, corner-radius: 2pt), edge("-"), node((3, 0), [構文解析], fill: process-color, corner-radius: 2pt), edge((3, 0), (3, 0.5), (-0.75, 0.5), (-0.75, 1), (0, 1), "-|>"), node((0, 1), [AST], fill: data-color, corner-radius: 2pt), edge("-"), node((1, 1), [コンパイル], fill: process-color, corner-radius: 2pt), edge("-|>"), node((2, 1), [opcode], fill: data-color, corner-radius: 2pt), edge("-"), node((3, 1), [実行], fill: process-color, corner-radius: 2pt), edge("-|>"), node((4, 1), [結果], fill: data-color, corner-radius: 2pt), ) } #title-slide() #about-slide() #[ #set align(center + horizon) #set text(size: 40pt) PHP のソースコードを魔改造して\ 独自の演算子を追加する\ \ #pause PHP 処理系の内部実装を理解する ] --- #[ #set align(center + horizon) 追加する演算子 ] --- #[ #set text(size: 30pt) 関数合成演算子「`∘`」 #codly-range(2) ```php $x * 2; $add_one = fn($x) => $x + 1; $f = $add_one ∘ $double; echo $f(3); // → $add_one($double(3)) → 7 ``` ] --- #[ #set text(size: 22pt) #codly-range(2) ```php $x * 2; $add_one = fn($x) => $x + 1; $negate = fn($x) => -$x; $g = $negate ∘ $add_one ∘ $double; echo $g(3); // → $negate($add_one($double(3))) // → -7 ``` ] --- #[ #set align(center + horizon) 実行の流れ #overview-diagram() ] --- #[ #set align(center + horizon) 字句解析 #set text(size: 24pt) ソースコードの文字列を\ トークンの列に分割する処理 #set text(size: 18pt) #diagram( node-stroke: 0.5pt, spacing: (5mm, 10mm), node-shape: rect, node((3.5, 0), [`$g = $negate ∘ $add_one ∘ $double;`], fill: data-color, corner-radius: 2pt), edge((3.5, 0), (3.5, 1), "-|>"), ) #diagram( node-stroke: 0.5pt, spacing: (5mm, 10mm), node-shape: rect, node((0, 0), [`$g`], fill: process-color, corner-radius: 2pt), node((1, 0), [`=`], fill: process-color, corner-radius: 2pt), node((2, 0), [`$negate`], fill: process-color, corner-radius: 2pt), node((3, 0), [`∘`], fill: process-color, corner-radius: 2pt), node((4, 0), [`$add_one`], fill: process-color, corner-radius: 2pt), node((5, 0), [`∘`], fill: process-color, corner-radius: 2pt), node((6, 0), [`$double`], fill: process-color, corner-radius: 2pt), node((7, 0), [`;`], fill: process-color, corner-radius: 2pt), ) ] --- #[ #set align(center + horizon) re2c #set text(size: 36pt) 正規表現からコードを生成 ] --- #[ #set text(size: 24pt) `zend_language_scanner.l` ```re2c "\xe2\x88\x98" { RETURN_TOKEN(T_COMPOSE); } ``` - `\xe2\x88\x98` は `∘` (U+2218) の UTF-8 バイト列 - `T_COMPOSE` を返す - `re2c` が `zend_language_scanner.c` を生成 ] --- #[ #set align(center + horizon) 実行の流れ #overview-diagram() ] --- #[ #set align(center + horizon) 構文解析 #set text(size: 24pt) トークン列を\ 抽象構文木 (AST) に変換する処理 ] --- #[ #set align(center + horizon) #set text(size: 18pt) #diagram( node-stroke: 0.5pt, spacing: (5mm, 10mm), node-shape: rect, node((0, 0), [`$g`], fill: data-color, corner-radius: 2pt), node((1, 0), [`=`], fill: data-color, corner-radius: 2pt), node((2, 0), [`$negate`], fill: data-color, corner-radius: 2pt), node((3, 0), [`∘`], fill: data-color, corner-radius: 2pt), node((4, 0), [`$add_one`], fill: data-color, corner-radius: 2pt), node((5, 0), [`∘`], fill: data-color, corner-radius: 2pt), node((6, 0), [`$double`], fill: data-color, corner-radius: 2pt), node((7, 0), [`;`], fill: data-color, corner-radius: 2pt), edge((3.5, 0), (3.5, 1), "-|>"), ) #diagram( node-stroke: 0.5pt, spacing: (8mm, 8mm), node-shape: rect, node((2, 0), [`ZEND_AST_ASSIGN`], fill: process-color, corner-radius: 2pt), edge((2, 0), (0.5, 1), "-|>"), edge((2, 0), (3.5, 1), "-|>"), node((0.5, 1), [`$g`], fill: process-color, corner-radius: 2pt), node((3.5, 1), [`ZEND_AST_COMPOSE`], fill: process-color, corner-radius: 2pt), edge((3.5, 1), (2, 2), "-|>"), edge((3.5, 1), (5, 2), "-|>"), node((2, 2), [`$negate`], fill: process-color, corner-radius: 2pt), node((5, 2), [`ZEND_AST_COMPOSE`], fill: process-color, corner-radius: 2pt), edge((5, 2), (4, 3), "-|>"), edge((5, 2), (6, 3), "-|>"), node((4, 3), [`$add_one`], fill: process-color, corner-radius: 2pt), node((6, 3), [`$double`], fill: process-color, corner-radius: 2pt), ) ] --- #[ #set align(center + horizon) bison #set text(size: 36pt) 文法規則からコードを生成 ] --- #[ #set text(size: 20pt) `zend_language_parser.y` ```c /* 優先順位の設定 */ %left T_PIPE %right T_COMPOSE /* 追加 */ %left '.' ``` - `|>` より強い - `$f ∘ $g |> $h` → `($f ∘ $g) |> $h` - 右結合 - `$f ∘ $g ∘ $h` → `$f ∘ ($g ∘ $h)` ] --- #[ #set text(size: 20pt) `zend_language_parser.y` ```c /* 文法定義 */ expr : expr T_COMPOSE expr { /* f ∘ g の並びを見つけたら AST を構築 */ $$ = zend_ast_create(ZEND_AST_COMPOSE, $1, $3); } ``` ] --- #[ #set align(center + horizon) 実行の流れ #overview-diagram() ] --- #[ #set align(center + horizon) コンパイル #set text(size: 24pt) AST を PHP VM の\ opcode列 (命令列) に変換する処理 ] --- #[ #set align(center + horizon) #set text(size: 16pt) #diagram( node-stroke: 0.5pt, spacing: (8mm, 8mm), node-shape: rect, node((2, 0), [`ZEND_AST_ASSIGN`], fill: data-color, corner-radius: 2pt), edge((2, 0), (0.5, 1), "-|>"), edge((2, 0), (3.5, 1), "-|>"), node((0.5, 1), [`$g`], fill: data-color, corner-radius: 2pt), node((3.5, 1), [`ZEND_AST_COMPOSE`], fill: data-color, corner-radius: 2pt), edge((3.5, 1), (2, 2), "-|>"), edge((3.5, 1), (5, 2), "-|>"), node((2, 2), [`$negate`], fill: data-color, corner-radius: 2pt), node((5, 2), [`ZEND_AST_COMPOSE`], fill: data-color, corner-radius: 2pt), edge((5, 2), (4, 3), "-|>"), edge((5, 2), (6, 3), "-|>"), node((4, 3), [`$add_one`], fill: data-color, corner-radius: 2pt), node((6, 3), [`$double`], fill: data-color, corner-radius: 2pt), edge((4, 3), (4, 4), "-|>"), ) #diagram( node-stroke: 0.5pt, spacing: (5mm, 5mm), node-shape: rect, node((0, 0), [`ZEND_COMPOSE` \ `$add_one, $double → $0`], fill: process-color, corner-radius: 2pt), node((1, 0), [`ZEND_COMPOSE` \ `$negate, $0 → $1`], fill: process-color, corner-radius: 2pt), node((2, 0), [`ZEND_ASSIGN` \ `$g, $1`], fill: process-color, corner-radius: 2pt), ) ] --- #[ #set text(size: 18pt) `zend_compile.c` ```c static void zend_compile_compose(znode *result, zend_ast *ast) { zend_ast *lhs_ast = ast->child[0]; zend_ast *rhs_ast = ast->child[1]; znode lhs_result, rhs_result; zend_compile_expr(&lhs_result, lhs_ast); // 左辺をコンパイル zend_compile_expr(&rhs_result, rhs_ast); // 右辺をコンパイル // ZEND_COMPOSE opcode を出力 // 実際の関数合成は実行時に zend_emit_op_tmp(result, ZEND_COMPOSE, &lhs_result, &rhs_result); } ``` ] --- #[ #set align(center + horizon) 実行の流れ #overview-diagram() ] --- #[ #set align(center + horizon) `zend_vm_gen.php` #set text(size: 36pt) テンプレートから VM コードを生成 #set text(size: 18pt) #diagram( node-stroke: 0.5pt, spacing: (10mm, 5mm), node-shape: rect, node((0, 1.5), [`zend_vm_def.h`], fill: data-color, corner-radius: 2pt), edge((0, 1.5), (1, 1.5), "-|>"), node((1, 1.5), [`zend_vm_gen.php`], fill: process-color, corner-radius: 2pt, name: ), node((2, 0), [`zend_vm_execute.h`], fill: data-color, corner-radius: 2pt, name: ), node((2, 1), [`zend_vm_opcodes.h`], fill: data-color, corner-radius: 2pt, name: ), node((2, 2), [`zend_vm_opcodes.c`], fill: data-color, corner-radius: 2pt, name: ), node((2, 3), [`zend_vm_handlers.h`], fill: data-color, corner-radius: 2pt, name: ), edge(, , "-|>"), edge(, , "-|>"), edge(, , "-|>"), edge(, , "-|>"), ) ] --- #[ #set text(size: 18pt) `zend_vm_def.h` ```c ZEND_VM_HANDLER( 211, // opcode の番号 ZEND_COMPOSE, // opcode の名前 CONST|TMPVAR|CV, // operand 1 の種類 CONST|TMPVAR|CV) // operand 2 の種類 { /* (略) */ zend_compose_callables(EX_VAR(opline->result.var), op1, op2); /* (略) */ } ``` ] --- #[ #set text(size: 18pt) ```c ZEND_API void zend_compose_callables(zval *result, zval *lhs, zval *rhs) { // (略; いい感じに合成後のクロージャのメタデータを初期化) // (略; 左辺と右辺の情報をクロージャの $this に埋め込む) // (略; いい感じに結果データを生成) } ``` ] --- #[ #set align(center + horizon) 実行の流れ #overview-diagram() ] --- #[ #set align(center + horizon) #set text(size: 32pt) PHPerKaigi 2025 PHPで作るPHP\ ~セルフホストできる\ 言語処理系を作ろう~ ] --- #[ #set align(center + horizon) #set text(size: 32pt) PHPerKaigi 2026 PHP を魔改造して学ぶ \ 言語処理系 ] --- #[ #set text(size: 32pt) - PHP サブセット実装 - 複雑なものを単純化して\ 全体像を理解 - PHP 魔改造 - 複雑なものを複雑なまま\ 部分的に理解 ] --- #[ #set align(center + horizon) 言語処理系を読もう\ 言語処理系を書こう ] --- #[ #set align(center + horizon) ご静聴 \ ありがとうございました ] --- #set text(size: 20pt) 参考文献: - https://docs.raku.org/routine/o,%20infix%20%E2%88%98 - https://docs.julialang.org/en/v1/manual/functions/#Function-composition-and-piping - https://www.php.net/manual/ja/language.operators.precedence.php - https://www.npopov.com/2017/04/14/PHP-7-Virtual-machine.html - https://www.phpinternalsbook.com/