master
ブランチ master
LittleBeast / src / Std / Lib / Syntax / Language.php
5.2 KB、155行 生 Blame 履歴
1<?php
2namespace Std\Lib\Syntax;
3
4/**
5 * 言語定義の基本クラス
6 * C系の言語の既定値を持つので、各言語は違う所だけを上書きする
7 */
8abstract class Language implements SyntaxInterface {
9 /** @var string[] 一行コメントの開始 */
10 protected array $comment = ['//'];
11 /** @var array|null 複数行コメントの[開始, 終了] */
12 protected ?array $multiline = ['/*', '*/'];
13 /** 複数行コメントを入れ子に出来るか(Swift等) */
14 protected bool $nestedComments = false;
15 /** @var string[] 文字列の引用符(「\」でエスケープ) */
16 protected array $strings = ['"', "'"];
17 /** 行頭の「#」をプリプロセッサとして扱うか */
18 protected bool $preprocessor = false;
19 /** @var string[] 次の「<…>」をファイル名とするプリプロセッサ指令 */
20 protected array $headerDirectives = [];
21 /** 大文字と小文字を区別しないか */
22 protected bool $caseInsensitive = false;
23 /** 「名前(」を関数として扱うか */
24 protected bool $functionCalls = true;
25 /** 数字の区切り文字(C++の「'」、Go等の「_」) */
26 protected string $digitSep = "'";
27 /** 識別子の正規表現 */
28 protected string $identifier = '[\p{L}_][\p{L}\p{N}_]*';
29 /** @var array 追加の規則(既定の規則より先に確認する) */
30 protected array $rules = [];
31 /**
32 * @var array 種類 => 単語一覧
33 * 先に書いた種類が優先される
34 */
35 protected array $tokens = [];
36
37 /** @var array 単語 => 種類 */
38 protected array $words = [];
39
40 public function prepare(string $code): void {
41 $this->words = $this->buildWords($this->tokens);
42 }
43
44 public function getRules(): array {
45 $r = $this->rules;
46
47 if ($this->multiline && !$this->nestedComments) {
48 [$s, $e] = $this->multiline;
49 $r[] = ['re' => preg_quote($s, '~').'.*?(?:'.preg_quote($e, '~').'|\z)', 'class' => 'comment'];
50 }
51
52 if (!empty($this->comment)) {
53 $alts = implode('|', array_map(fn($c) => preg_quote($c, '~'), $this->comment));
54 $r[] = ['re' => "(?:{$alts})[^\\n]*", 'class' => 'comment'];
55 }
56
57 foreach ($this->strings as $q) {
58 $q = preg_quote($q, '~');
59 $r[] = ['re' => "{$q}(?:[^{$q}\\\\\\n]|\\\\.)*{$q}?", 'class' => 'string'];
60 }
61
62 if ($this->preprocessor) {
63 $r[] = ['re' => '#[ \t]*[A-Za-z_]\w*', 'class' => 'preprocessor', 'bol' => true];
64 }
65
66 $r[] = ['re' => $this->numberPattern(), 'class' => 'number'];
67 $r[] = ['re' => $this->identifier, 'class' => 'ident'];
68
69 return $r;
70 }
71
72 public function classify(string $word, array $ctx): ?string {
73 $key = $this->caseInsensitive ? strtolower($word) : $word;
74 if (isset($this->words[$key])) return $this->words[$key];
75 if ($this->functionCalls && $ctx['next'] === '(') return 'function';
76 return null;
77 }
78
79 public function getNestedComment(): ?array {
80 return $this->nestedComments ? $this->multiline : null;
81 }
82
83 public function getHeaderDirectives(): array {
84 return $this->headerDirectives;
85 }
86
87 /**
88 * 種類 => 単語一覧から、単語 => 種類の表を作る
89 * 一覧が「ヘッダー名 => 単語一覧」の場合はそのまま全部を使う(C::prepare()で絞る)
90 *
91 * @param array $tokens 種類 => 単語一覧
92 * @return array 単語 => 種類
93 */
94 protected function buildWords(array $tokens): array {
95 $words = [];
96 foreach ($tokens as $class => $list) {
97 foreach ($list as $k => $w) {
98 foreach (is_array($w) ? $w : [$w] as $word) {
99 $key = $this->caseInsensitive ? strtolower($word) : $word;
100 if (!isset($words[$key])) $words[$key] = $class;
101 }
102 }
103 }
104 return $words;
105 }
106
107 /**
108 * C系の数字(16進数・2進数・8進数・浮動小数点・接尾辞)
109 *
110 * @return string 正規表現
111 */
112 protected function numberPattern(): string {
113 $s = $this->digitSep === '' ? '' : preg_quote($this->digitSep, '~').'?';
114 $d = "\\d(?:{$s}\\d)*";
115 $x = "[0-9A-Fa-f](?:{$s}[0-9A-Fa-f])*";
116 return "(?:0[xX]{$x}(?:\\.(?:{$x})?)?(?:[pP][+-]?\\d+)?"
117 ."|0[bB][01](?:{$s}[01])*"
118 ."|0[oO][0-7](?:{$s}[0-7])*"
119 ."|(?:{$d}(?:\\.(?!\\.)(?:{$d})?)?|\\.\\d(?:{$s}\\d)*)(?:[eE][+-]?\\d+)?)"
120 ."(?:[A-Za-z_]\\w*)?";
121 }
122
123 /**
124 * ベクトル・行列型を作る(float → float2…float4, float2x2…float4x4)
125 *
126 * @param array $bases 基本の型
127 * @param bool $matrix 行列型も作るか
128 * @return array 型の一覧
129 */
130 protected static function vectorTypes(array $bases, bool $matrix = true): array {
131 $out = [];
132 foreach ($bases as $b) {
133 $out[] = $b;
134 for ($i = 1; $i <= 4; $i++) {
135 $out[] = "{$b}{$i}";
136 if ($matrix) for ($j = 1; $j <= 4; $j++) $out[] = "{$b}{$i}x{$j}";
137 }
138 }
139 return $out;
140 }
141
142 /**
143 * 「名前0」…「名前N」の一覧を作る(レジスター用)
144 *
145 * @param string|array $prefix 接頭辞
146 * @param int $from 最初の番号
147 * @param int $to 最後の番号
148 * @return array
149 */
150 protected static function numbered(string|array $prefix, int $from, int $to): array {
151 $out = [];
152 foreach ((array)$prefix as $p) for ($i = $from; $i <= $to; $i++) $out[] = "{$p}{$i}";
153 return $out;
154 }
155}