」をファイル名とするプリプロセッサ指令 */ protected array $headerDirectives = []; /** 大文字と小文字を区別しないか */ protected bool $caseInsensitive = false; /** 「名前(」を関数として扱うか */ protected bool $functionCalls = true; /** 数字の区切り文字(C++の「'」、Go等の「_」) */ protected string $digitSep = "'"; /** 識別子の正規表現 */ protected string $identifier = '[\p{L}_][\p{L}\p{N}_]*'; /** @var array 追加の規則(既定の規則より先に確認する) */ protected array $rules = []; /** * @var array 種類 => 単語一覧 * 先に書いた種類が優先される */ protected array $tokens = []; /** @var array 単語 => 種類 */ protected array $words = []; public function prepare(string $code): void { $this->words = $this->buildWords($this->tokens); } public function getRules(): array { $r = $this->rules; if ($this->multiline && !$this->nestedComments) { [$s, $e] = $this->multiline; $r[] = ['re' => preg_quote($s, '~').'.*?(?:'.preg_quote($e, '~').'|\z)', 'class' => 'comment']; } if (!empty($this->comment)) { $alts = implode('|', array_map(fn($c) => preg_quote($c, '~'), $this->comment)); $r[] = ['re' => "(?:{$alts})[^\\n]*", 'class' => 'comment']; } foreach ($this->strings as $q) { $q = preg_quote($q, '~'); $r[] = ['re' => "{$q}(?:[^{$q}\\\\\\n]|\\\\.)*{$q}?", 'class' => 'string']; } if ($this->preprocessor) { $r[] = ['re' => '#[ \t]*[A-Za-z_]\w*', 'class' => 'preprocessor', 'bol' => true]; } $r[] = ['re' => $this->numberPattern(), 'class' => 'number']; $r[] = ['re' => $this->identifier, 'class' => 'ident']; return $r; } public function classify(string $word, array $ctx): ?string { $key = $this->caseInsensitive ? strtolower($word) : $word; if (isset($this->words[$key])) return $this->words[$key]; if ($this->functionCalls && $ctx['next'] === '(') return 'function'; return null; } public function getNestedComment(): ?array { return $this->nestedComments ? $this->multiline : null; } public function getHeaderDirectives(): array { return $this->headerDirectives; } /** * 種類 => 単語一覧から、単語 => 種類の表を作る * 一覧が「ヘッダー名 => 単語一覧」の場合はそのまま全部を使う(C::prepare()で絞る) * * @param array $tokens 種類 => 単語一覧 * @return array 単語 => 種類 */ protected function buildWords(array $tokens): array { $words = []; foreach ($tokens as $class => $list) { foreach ($list as $k => $w) { foreach (is_array($w) ? $w : [$w] as $word) { $key = $this->caseInsensitive ? strtolower($word) : $word; if (!isset($words[$key])) $words[$key] = $class; } } } return $words; } /** * C系の数字(16進数・2進数・8進数・浮動小数点・接尾辞) * * @return string 正規表現 */ protected function numberPattern(): string { $s = $this->digitSep === '' ? '' : preg_quote($this->digitSep, '~').'?'; $d = "\\d(?:{$s}\\d)*"; $x = "[0-9A-Fa-f](?:{$s}[0-9A-Fa-f])*"; return "(?:0[xX]{$x}(?:\\.(?:{$x})?)?(?:[pP][+-]?\\d+)?" ."|0[bB][01](?:{$s}[01])*" ."|0[oO][0-7](?:{$s}[0-7])*" ."|(?:{$d}(?:\\.(?!\\.)(?:{$d})?)?|\\.\\d(?:{$s}\\d)*)(?:[eE][+-]?\\d+)?)" ."(?:[A-Za-z_]\\w*)?"; } /** * ベクトル・行列型を作る(float → float2…float4, float2x2…float4x4) * * @param array $bases 基本の型 * @param bool $matrix 行列型も作るか * @return array 型の一覧 */ protected static function vectorTypes(array $bases, bool $matrix = true): array { $out = []; foreach ($bases as $b) { $out[] = $b; for ($i = 1; $i <= 4; $i++) { $out[] = "{$b}{$i}"; if ($matrix) for ($j = 1; $j <= 4; $j++) $out[] = "{$b}{$i}x{$j}"; } } return $out; } /** * 「名前0」…「名前N」の一覧を作る(レジスター用) * * @param string|array $prefix 接頭辞 * @param int $from 最初の番号 * @param int $to 最後の番号 * @return array */ protected static function numbered(string|array $prefix, int $from, int $to): array { $out = []; foreach ((array)$prefix as $p) for ($i = $from; $i <= $to; $i++) $out[] = "{$p}{$i}"; return $out; } }