コードの色

守矢諏訪子 <suwako@076.moe>、2026-11-10 18:21
コミット 71875eee4fa2932b2347d600f08c31bb1c8e7fc0 ・親 9128438 ・ファイルを見る

27個のファイル、+1646 -734

@@ -1,6 +1,6 @@
11 pre {
22 background: #232023;
3- color: fcfcfc;
3+ color: #fcfcfc;
44 padding: 1rem;
55 border-radius: 6px;
66 overflow: auto;
@@ -9,11 +9,28 @@
99 overflow-x: auto;
1010 }
1111
12-/*pre > code {
13- color: #fcfcfc;
14-}*/
12+pre > code.hl {
13+ color: #fcfcfc;
14+}
1515
1616 code {
1717 color: #b421f8;
1818 line-height: 1.45;
1919 }
20+
21+.hl-comment { color: #2c980c; font-style: italic; } /* dark.green */
22+.hl-keyword { color: #2687f7; } /* medium.blue */
23+.hl-type { color: #10c074; } /* dark.lime */
24+.hl-builtin { color: #ecea71; } /* light.yellow */
25+.hl-function { color: #ecea71; } /* light.yellow */
26+.hl-string { color: #f7a717; } /* medium.orange */
27+.hl-number { color: #6cf344; } /* light.green */
28+.hl-constant { color: #ea79d8; } /* light.pink */
29+.hl-preprocessor { color: #ae6bdb; } /* light.purple */
30+.hl-attribute { color: #f8c56a; } /* light.orange */
31+.hl-variable { color: #29d3ff; } /* medium.cyan */
32+/* アセンブリ */
33+.hl-instruction { color: #2687f7; } /* medium.blue */
34+.hl-register { color: #1cbcd0; } /* dark.cyan */
35+.hl-directive { color: #f545f5; } /* medium.pink */
36+.hl-label { color: #ecea71; } /* light.yellow */
@@ -179,6 +179,7 @@
179179 $tmpl->addCss('news-article');
180180 $tmpl->addCss('search');
181181 $tmpl->addCss('blogtype');
182+ $tmpl->addCss('code');
182183 $tmpl->render('article');
183184 exit();
184185
@@ -121,6 +121,8 @@
121121 foreach ($lines as $line) {
122122 $i++;
123123 $line = rtrim($line, "\r\n");
124+ // コードの行末の「\」(Cのマクロの継続等)は残す
125+ $rawLine = $line;
124126 $hasBR = str_ends_with($line, '\\');
125127 $line = rtrim($line, "\\");
126128
@@ -133,7 +135,8 @@
133135 // }
134136
135137 // コードブロック
136- if (preg_match('/^```(\w*)$/', $line, $matches)) {
138+ // 言語名に「+」「#」「-」「.」も使える(```c++、```c#、```asm-x8664)
139+ if (preg_match('/^```([\w+#.-]*)\s*$/', $rawLine, $matches)) {
137140 if (!$this->inCodeBlock) {
138141 if (!empty($currentParagraph)) {
139142 $this->html[] = " <p>\n ".implode("", $currentParagraph)."\n </p>";
@@ -152,7 +155,7 @@
152155 }
153156
154157 if ($this->inCodeBlock) {
155- $this->codeBlockContent[] = $line;
158+ $this->codeBlockContent[] = $rawLine;
156159 continue;
157160 }
158161
@@ -517,14 +520,19 @@
517520 * @return string HTMLのコードブロック
518521 */
519522 private function createCodeBlock(): string {
520- $code = htmlspecialchars(implode("\n", $this->codeBlockContent));
521- $class = $this->codeBlockLanguage ? " class=\"language-{$this->codeBlockLanguage}\"" : '';
523+ $raw = implode("\n", $this->codeBlockContent);
524+ $lang = strtolower($this->codeBlockLanguage);
525+ $attr = htmlspecialchars($lang, ENT_QUOTES, 'UTF-8');
526+
527+ // 対応している言語はハイライトする(Syntax::colorize()はエスケープ済みのHTMLを返す)
528+ if ($lang !== '' && Syntax::isSupported($lang)) {
529+ $code = (new Syntax($lang))->colorize($raw);
530+ return " <p>\n <pre><code class=\"language-{$attr} hl\">{$code}</code></pre>";
531+ }
532+
533+ $code = htmlspecialchars($raw);
534+ $class = $lang !== '' ? " class=\"language-{$attr}\"" : '';
522535 return " <p>\n <pre><code{$class}>{$code}</code></pre>";
523- // $raw = implode("\n", $this->codeBlockContent);
524- // $lang = $this->codeBlockLanguage ?: 'txt';
525- // $class = $lang === 'txt' ? '' : " class=\"language-{$lang}\"";
526- // $highlighted = $this->highlightCode($raw, $lang);
527- // return "<pre><code{$class}>{$highlighted}</code></pre>";
528536 }
529537
530538 private function createBlockquote(array $content): string {
@@ -2,279 +2,332 @@
22 namespace Std\Lib;
33
44 use Std\Lib\Syntax\SyntaxInterface;
5+use Std\Lib\Syntax\Asm;
6+use Std\Lib\Syntax\Asm6502;
7+use Std\Lib\Syntax\Asm68000;
58 use Std\Lib\Syntax\AsmAarch32;
69 use Std\Lib\Syntax\AsmAarch64;
10+use Std\Lib\Syntax\AsmMips;
11+use Std\Lib\Syntax\AsmPower;
12+use Std\Lib\Syntax\AsmRiscv;
713 use Std\Lib\Syntax\AsmX8664;
14+use Std\Lib\Syntax\AsmZ80;
815 use Std\Lib\Syntax\C;
916 use Std\Lib\Syntax\Cpp;
17+use Std\Lib\Syntax\CSharp;
1018 use Std\Lib\Syntax\Glsl;
1119 use Std\Lib\Syntax\Go;
20+use Std\Lib\Syntax\Hlsl;
21+use Std\Lib\Syntax\ObjC;
22+use Std\Lib\Syntax\Slang;
23+use Std\Lib\Syntax\Swift;
1224
25+/**
26+ * コードブロックのシンタックスハイライト
27+ *
28+ * コードを先頭から1回だけ走査し、トークン毎に<span class="hl-種類">で囲む。
29+ * 色はstyle-code.cssで定義する。
30+ *
31+ * 使い方:
32+ * if (Syntax::isSupported($lang)) $html = (new Syntax($lang))->colorize($code);
33+ */
1334 class Syntax {
14- private array $alt = [
15- 'asm-aarch32' => ['asm-arm'],
16- 'asm-x8664' => ['asm', 'asm-x86', 'asm-x64'],
17- 'cpp' => ['cc', 'c++'],
18- 'css' => ['css3'],
19- 'go' => ['golang'],
20- ];
21-
22- private array $include = [];
23- private string $lang;
24- private SyntaxInterface $handler;
25-
26- private array $supported = [
35+ private const SUPPORTED = [
36+ 'asm' => Asm::class,
37+ 'asm-6502' => Asm6502::class,
38+ 'asm-68000' => Asm68000::class,
2739 'asm-aarch32' => AsmAarch32::class,
2840 'asm-aarch64' => AsmAarch64::class,
41+ 'asm-mips' => AsmMips::class,
42+ 'asm-power' => AsmPower::class,
43+ 'asm-riscv' => AsmRiscv::class,
2944 'asm-x8664' => AsmX8664::class,
45+ 'asm-z80' => AsmZ80::class,
3046 'c' => C::class,
3147 'cpp' => Cpp::class,
48+ 'csharp' => CSharp::class,
3249 'glsl' => Glsl::class,
3350 'go' => Go::class,
51+ 'hlsl' => Hlsl::class,
52+ 'objc' => ObjC::class,
53+ 'slang' => Slang::class,
54+ 'swift' => Swift::class,
3455 ];
3556
57+ private const ALIASES = [
58+ 'asm-6502' => ['6502', 'asm6502', 'asm-65c02'],
59+ 'asm-68000' => ['68000', '68k', 'm68k', 'asm-68k', 'asm-m68k'],
60+ 'asm-aarch32' => ['asm-arm', 'asm-arm32', 'arm', 'arm32', 'aarch32', 'asm-thumb'],
61+ 'asm-aarch64' => ['asm-arm64', 'arm64', 'aarch64'],
62+ 'asm-mips' => ['mips', 'asm-mips64'],
63+ 'asm-power' => ['asm-ppc', 'asm-powerpc', 'asm-ppc64', 'ppc', 'powerpc', 'power'],
64+ 'asm-riscv' => ['riscv', 'asm-riscv64', 'asm-rv64', 'rv64', 'risc-v'],
65+ 'asm-x8664' => ['asm-x86', 'asm-x64', 'asm-x86-64', 'asm-amd64', 'x86', 'x64', 'x86-64', 'nasm'],
66+ 'asm-z80' => ['z80', 'asm-gbz80', 'gbz80'],
67+ 'c' => ['h', 'c99', 'c11', 'c23'],
68+ 'cpp' => ['c++', 'cc', 'cxx', 'hpp', 'hh', 'hxx', 'h++'],
69+ 'csharp' => ['c#', 'cs'],
70+ 'glsl' => ['vert', 'frag', 'geom', 'comp', 'tesc', 'tese'],
71+ 'go' => ['golang'],
72+ 'hlsl' => ['fx', 'fxh', 'hlsli'],
73+ 'objc' => ['objective-c', 'obj-c', 'objectivec', 'm', 'mm', 'objc++'],
74+ 'slang' => [],
75+ 'swift' => [],
76+ ];
77+
78+ private string $lang;
79+ private SyntaxInterface $handler;
80+
81+ /** @var array 規則の正規表現をキャッシュ(言語 => [行頭用, 通常用, 種類一覧]) */
82+ private static array $compiled = [];
83+
3684 public function __construct(string $lang) {
37- $this->lang = $this->resolveLanguage($lang);
38- $this->handler = $this->createHandler($this->lang);
85+ $resolved = self::resolve($lang);
86+ if ($resolved === null) throw new \InvalidArgumentException("未対応の言語: {$lang}");
87+ $this->lang = $resolved;
88+ $class = self::SUPPORTED[$resolved];
89+ $this->handler = new $class();
3990 }
4091
41- public function colorize(string $code): string {
42- $lines = explode("\n", $code);
43- foreach ($lines as $c) {
44- $c = str_replace('<', '&lt;', $c);
45- $c = str_replace('>', '&gt;', $c);
46- }
47-
48- switch ($this->lang) {
49- case 'asm-aarch':
50- break;
51- case 'asm-x8664':
52- $lines = $this->addString($lines);
53- $lines = $this->addNumber($lines);
54- $lines = $this->addComment($lines);
55- $lines = $this->add($lines, 'register');
56- $lines = $this->add($lines, 'operator');
57- $lines = $this->addLabel($lines);
58- $lines = $this->addSection($lines);
59- $lines = $this->addDirective($lines);
60- break;
61- case 'c':
62- $this->include = $this->handler->getInclude($lines);
63- $lines = $this->addString($lines);
64- $lines = $this->addNumber($lines);
65- $lines = $this->addComment($lines);
66- $lines = $this->add($lines, 'preprocessor');
67- $lines = $this->add($lines, 'keyword');
68- foreach ($this->include as $inc) {
69- $lines = $this->add($lines, 'typedef', $inc);
70- $lines = $this->add($lines, 'libc', $inc);
71- }
72- $lines = $this->addFunction($lines);
73- // kys($lines);
74- break;
75- case 'cpp':
76- break;
77- case 'glsl':
78- break;
79- case 'go':
80- break;
81- default:
82- break;
83- }
84-
85- return implode("\n", $lines);
92+ /**
93+ * 言語が対応しているか
94+ *
95+ * @param string $lang 言語名又は別名
96+ * @return bool
97+ */
98+ public static function isSupported(string $lang): bool {
99+ return self::resolve($lang) !== null;
86100 }
87101
88- ///////////////
89-
90- private function resolveLanguage(string $lang): string {
91- if (isset($this->supported[$lang])) return $lang;
92- foreach ($this->alt as $k => $v) if (in_array($lang, $v, true)) return $k;
93- throw new \InvalidArgumentException("未対応の言語: {$lang}");
102+ /**
103+ * 別名を正式な言語名にする
104+ *
105+ * @param string $lang 言語名又は別名
106+ * @return string|null 正式な言語名、未対応ならnull
107+ */
108+ public static function resolve(string $lang): ?string {
109+ $lang = strtolower(trim($lang));
110+ if (isset(self::SUPPORTED[$lang])) return $lang;
111+ foreach (self::ALIASES as $k => $v) if (in_array($lang, $v, true)) return $k;
112+ return null;
94113 }
95114
96- private function createHandler(string $lang): SyntaxInterface {
97- $class = $this->supported[$lang] ?? null;
98- if (!$class) throw new \RuntimeException("言語のハンドルが存在しない: {$lang}");
99- return new $class();
115+ public function getLanguage(): string {
116+ return $this->lang;
100117 }
101118
102- private function addComment(array $code): array {
103- global $colorPalette;
119+ /**
120+ * コードをハイライトしたHTMLにする(HTMLエスケープ済み)
121+ * 失敗した場合はエスケープしただけのコードを返す
122+ *
123+ * @param string $code 生のコード
124+ * @return string HTML
125+ */
126+ public function colorize(string $code): string {
127+ $code = str_replace(["\r\n", "\r"], "\n", $code);
128+ if ($code === '' || preg_match('//u', $code) !== 1) return self::escape($code);
104129
105- $sc = $this->handler->getComment() ?? null;
106- $mc = $this->handler->getMultiline() ?? [null, null];
107- $mcs = $mc[0];
108- $mce = $mc[1];
130+ try {
131+ return $this->tokenize($code);
132+ } catch (\Throwable $e) {
133+ return self::escape($code);
134+ }
135+ }
109136
110- $color = $this->handler->getColors()['comment'] ?? $colorPalette['dark']['green'];
137+ ///////////////
111138
112- $isMulti = false;
113- $out = [];
139+ private function tokenize(string $code): string {
140+ $h = $this->handler;
141+ $h->prepare($code);
142+ [$reBol, $reAny, $classes] = $this->compile();
143+ $nested = $h->getNestedComment();
144+ $headers = $h->getHeaderDirectives();
114145
115- foreach ($code as $c) {
116- $cur = $c;
146+ $out = '';
147+ $plain = '';
148+ $pos = 0;
149+ $len = strlen($code);
150+ $bol = true; // 行頭から空白しか無い
151+ $first = true; // この行でまだ単語が無い(アセンブリの命令判定用)
152+ $prev = ''; // 直前の空白以外の文字
153+ $prevWord = ''; // この行の直前の単語
154+ $lineWord = ''; // この行の最初の単語
155+ $expectHeader = false;
117156
118- if ($isMulti || strpos($cur, $sc) !== false || ($mcs && strpos($cur, $mcs) !== false)) {
119- $cur = preg_replace('/<span[^>]*>(.*?)<\/span>/s', '$1', $cur);
157+ $emit = function(string $text, ?string $class) use (&$out, &$plain): void {
158+ if ($class === null) {
159+ $plain .= $text;
160+ return;
120161 }
162+ if ($plain !== '') {
163+ $out .= self::escape($plain);
164+ $plain = '';
165+ }
166+ $out .= '<span class="hl-'.$class.'">'.self::escape($text).'</span>';
167+ };
121168
122- if ($isMulti) {
123- $endPos = $mcs ? strpos($cur, $mce) : false;
169+ while ($pos < $len) {
170+ $ch = $code[$pos];
124171
125- if ($endPos !== false) {
126- $before = substr($cur, 0, $endPos + strlen($mce));
127- $after = substr($cur, $endPos + strlen($mce));
172+ if ($ch === "\n") {
173+ $plain .= "\n";
174+ $pos++;
175+ $bol = true;
176+ $first = true;
177+ $prevWord = '';
178+ $lineWord = '';
179+ $expectHeader = false;
180+ continue;
181+ }
128182
129- $cur = '<span style="color: '.$color.'; font-style: italics;">'.$before.'</span>'.$after;
130- $isMulti = false;
131- } else {
132- $cur = '<span style="color: '.$color.'; font-style: italics;">'.$cur.'</span>';
133- }
134- } else {
135- if ($sc && ($pos = strpos($cur, $sc, 1)) !== false) {
136- $before = substr($cur, 0, $pos);
137- $rest = trim(substr($cur, $pos));
138- $cur = $before.'<span style="color: '.$color.'; font-style: italics;">'.$rest.'</span>';
139- }
183+ if ($ch === ' ' || $ch === "\t") {
184+ $plain .= $ch;
185+ $pos++;
186+ continue;
187+ }
140188
141- if (!$isMulti && $mcs && $mce && str_contains($cur, $mcs)) {
142- $startPos = strpos($cur, $mcs);
143- $endPos = $mce ? strpos($cur, $mce) : false;
189+ // 入れ子のコメント(/* /* */ */)
190+ if ($nested && substr_compare($code, $nested[0], $pos, strlen($nested[0])) === 0) {
191+ $end = $this->scanNested($code, $pos, $nested[0], $nested[1]);
192+ $text = substr($code, $pos, $end - $pos);
193+ $emit($text, 'comment');
194+ $pos = $end;
195+ $bol = false;
196+ if (str_contains($text, "\n")) $first = false;
197+ continue;
198+ }
144199
145- if ($endPos !== false && $startPos < $endPos) {
146- $before = substr($cur, 0, $startPos);
147- $part = substr($cur, $startPos, $endPos - $startPos + strlen($mce));
148- $after = substr($cur, $endPos + strlen($mce));
200+ // #include <stdio.h> のファイル名
201+ if ($expectHeader && $ch === '<' && preg_match('~\G<[^>\n]*>~', $code, $m, 0, $pos)) {
202+ $emit($m[0], 'string');
203+ $pos += strlen($m[0]);
204+ $bol = false;
205+ $expectHeader = false;
206+ continue;
207+ }
149208
150- $cur = $before.'<span style="color: '.$color.'; font-style: italics;">'.$part.'</span>'.$after;
151- } else {
152- $before = substr($cur, 0, $startPos);
153- $rest = substr($cur, $startPos);
209+ if (preg_match($bol ? $reBol : $reAny, $code, $m, PREG_UNMATCHED_AS_NULL, $pos) === 1 && $m[0] !== '') {
210+ $text = $m[0];
211+ $class = null;
212+ foreach ($classes as $i => $c) {
213+ if (isset($m["r{$i}"])) {
214+ $class = $c;
215+ break;
216+ }
217+ }
154218
155- $cur = $before.'<span style="color: '.$color.'; font-style: italics;">'.$rest;
156- $isMulti = true;
219+ $after = $pos + strlen($text);
220+ if ($class === 'ident') {
221+ $class = $h->classify($text, $this->context($code, $after, $bol, $first, $prev, $prevWord, $lineWord));
222+ if ($class !== 'label') {
223+ if ($first) $lineWord = $text;
224+ $first = false;
157225 }
226+ $prevWord = $text;
227+ } else if ($class === 'preprocessor') {
228+ $name = strtolower(ltrim(substr($text, 1), " \t"));
229+ if (in_array($name, $headers, true)) $expectHeader = true;
158230 }
231+
232+ $emit($text, $class);
233+ $pos = $after;
234+ $bol = false;
235+ $prev = substr($text, -1);
236+ if (str_contains($text, "\n")) $first = false;
237+ continue;
159238 }
160239
161- $out[] = $cur;
240+ // どの規則にも合わない文字(演算子等)
241+ preg_match('~\G.~su', $code, $m, 0, $pos);
242+ $c = $m[0] ?? $ch;
243+ $plain .= $c;
244+ $pos += strlen($c);
245+ $bol = false;
246+ $prev = $c;
162247 }
163248
249+ if ($plain !== '') $out .= self::escape($plain);
164250 return $out;
165251 }
166252
167- private function addNumber(array $code): array {
168- $color = $this->handler->getColors()['number'];
169- $pattern = '/(?<![\w$@.])(?: # 関係ない
170- (?:0[xX][0-9a-fA-F]+) # 0xFF, 0XFF
171- |(?:0[bB][01]+) # 0b1010
172- |(?:[0-9]+[hH]) # FFh, 0FFh
173- |(?:\$[0-9a-fA-F]+) # $FF
174- |(?:%[01]+) # %1010
175- |(?:\d+(?:\.\d+)?(?:[eE][+-]?\d+)?) # 42, 3.14, 1e-5
176- |(?:\d+) # 普通数字
177- )(?![\w$@.])(?![a-zA-Z])/ix';
253+ /**
254+ * 規則を2つの正規表現にまとめる(行頭用と通常用)
255+ *
256+ * @return array [行頭用, 通常用, 番号 => 種類]
257+ */
258+ private function compile(): array {
259+ if (isset(self::$compiled[$this->lang])) return self::$compiled[$this->lang];
178260
179- return array_map(fn($c) => preg_replace_callback($pattern, fn($m) => '<span style="color:'.$color.'">'.$m[0].'</span>', $c), $code);
180- }
181-
182- private function addString(array $code): array {
183- $color = $this->handler->getColors()['string'];
184- $pattern = '/(?:"(?:[^"\\\\]|\\\\.)*"|\'(?:[^\'\\\\]|\\\\.)*\')/s';
185-
186- return array_map(
187- function ($c) use ($pattern, $color) {
188- return preg_replace_callback($pattern, fn($m) => '<span style="color:'.$color.'">'.$m[0].'</span>', $c);
189- },
190- $code
191- );
192- }
193-
194- private function addLabel(array $code): array {
195- $color = $this->handler->getColors()['label'];
196- $out = [];
197- foreach ($code as $c) $out[] = preg_replace('/^\s*([\.a-zA-Z_][a-zA-Z0-9_]*)\s*:/m', '<span style="color: '.$color.';">$1</span>:', $c);
198- return $out;
199- }
200-
201- private function addSection(array $code): array {
202- $color1 = $this->handler->getColors()['label'];
203- $color2 = $this->handler->getColors()['register'];
204- $color3 = $this->handler->getColors()['number'];
205- $out = [];
206-
207- foreach ($code as $c) {
208- $cur = $c;
209-
210- $cur = preg_replace(
211- '/^(?:(section|default|segment|group)\s+)([\w.][\w.\-]*)(?:\s*(?:;.*)?)?$/im',
212- '<span style="color: '.$color1.';">$1</span> <span style="color: '.$color2.';">$2</span>',
213- $cur);
214- $cur = preg_replace(
215- '/^(bits)\s+(16|32|64)(?:\s*(?:;.*)?)?$/im',
216- '<span style="color: '.$color1.';">$1</span> <span style="color: '.$color3.';">$2</span>',
217- $cur);
218-
219- $out[] = $cur;
261+ $rules = $this->handler->getRules();
262+ $bol = [];
263+ $any = [];
264+ $classes = [];
265+ foreach ($rules as $i => $r) {
266+ $classes[$i] = $r['class'];
267+ $part = "(?<r{$i}>{$r['re']})";
268+ $bol[] = $part;
269+ if (empty($r['bol'])) $any[] = $part;
220270 }
221271
222- return $out;
272+ $mk = fn(array $parts) => '~\G(?:'.implode('|', $parts).')~su';
273+ return self::$compiled[$this->lang] = [$mk($bol), $mk($any), $classes];
223274 }
224275
225- private function add(array $code, string $type, ?string $header = null): array {
226- $tok = null !== $header ? $this->handler->getTokens()[$type][$header] : $this->handler->getTokens()[$type];
227- $color = $this->handler->getColors()[$type];
228- $pattern = '/\b('.implode('|', array_map(fn($r) => preg_quote($r, '/'), $tok)).')\b/i';
229- $so = '<span style="color: '.$color.';">';
230- $sc = '</span>';
231- $out = [];
232- foreach ($code as $c) {
233- $out[] = preg_replace_callback($pattern, function ($m) use ($so, $sc) { return $so.$m[0].$sc; }, $c);
234- }
235- return $out;
276+ /**
277+ * 識別子の分類に使う文脈を作る
278+ *
279+ * @param string $code コード全体
280+ * @param int $after 識別子の直後の位置
281+ * @param bool $bol 識別子が行頭か
282+ * @param bool $first 行の最初の単語か
283+ * @param string $prev 直前の空白以外の文字
284+ * @param string $prevWord この行の直前の単語
285+ * @param string $lineWord この行の最初の単語(ラベルを除く)
286+ * @return array
287+ */
288+ private function context(string $code, int $after, bool $bol, bool $first, string $prev, string $prevWord, string $lineWord): array {
289+ preg_match('~\G[ \t]*([^ \t\n]?)~u', $code, $m, 0, $after);
290+ $next = $m[1] ?? '';
291+ preg_match('~\G.~su', $code, $r, 0, $after);
292+ preg_match('~\G[ \t]*:?[ \t]*([^\s,;:()]*)~u', $code, $w, 0, $after);
293+ return [
294+ 'bol' => $bol,
295+ 'first' => $first,
296+ 'prev' => $prev,
297+ 'prevWord' => $prevWord,
298+ 'lineWord' => $lineWord,
299+ 'next' => $next,
300+ 'nextRaw' => $r[0] ?? '',
301+ 'nextWord' => $w[1] ?? '',
302+ ];
236303 }
237304
238- private function addFunction(array $code): array {
239- $color = $this->handler->getColors()['function'];
240- $out = [];
241-
242- foreach ($code as $c) {
243- $cur = $c;
244- $cur = preg_replace_callback("/\b(.*)\(\b/i", fn($m) => '<span style="color: '.$color.';">'.$m[0].'</span>', $cur);
245- $out[] = $cur;
305+ /**
306+ * 入れ子のコメントの終わりを探す
307+ *
308+ * @return int コメントの直後の位置(閉じていなければコードの最後)
309+ */
310+ private function scanNested(string $code, int $pos, string $start, string $end): int {
311+ $depth = 0;
312+ $len = strlen($code);
313+ $ls = strlen($start);
314+ $le = strlen($end);
315+ while ($pos < $len) {
316+ if (substr_compare($code, $start, $pos, $ls) === 0) {
317+ $depth++;
318+ $pos += $ls;
319+ } else if (substr_compare($code, $end, $pos, $le) === 0) {
320+ $depth--;
321+ $pos += $le;
322+ if ($depth === 0) return $pos;
323+ } else {
324+ $pos++;
325+ }
246326 }
247-
248- return $out;
327+ return $len;
249328 }
250329
251- private function addDirective(array $code): array {
252- $tok = $this->handler->getTokens()['directive'];
253- $color1 = $this->handler->getColors()['directive'];
254- $color2 = $this->handler->getColors()['register'];
255- $pattern = implode('|', array_map(fn($r) => preg_quote($r, '/'), $tok));
256- $out = [];
257-
258- foreach ($code as $c) {
259- $cur = $c;
260- $cur = preg_replace_callback("/\b({$pattern})\b/i", fn($m) => '<span style="color: '.$color1.';">'.$m[0].'</span>', $cur);
261- $cur = preg_replace_callback(
262- '/^\s*([a-zA-Z_][a-zA-Z0-9_$.@]*)\s+('.$pattern.')\b\s+(.+?)(?:\s*(?:;.*)?)?$/im',
263- function ($m) use ($color1, $color2) {
264- $l = $m[1];
265- $d = $m[2];
266- $v = $m[3];
267-
268- // if (strcasecmp($d, 'equ') === 0) $v = preg_replace('/\$-[a-zA-Z_][a-zA-Z0-9_]*/i', '<span style="color: '.$color1.';">$o</span>', $v);
269- return sprintf(
270- '<span style="color: %s;">%s</span> '.'<span style="color: %s;">%s</span> %s',
271- $color1, $l, $color2, $v, $v
272- );
273- }, $cur);
274-
275- $out[] = $cur;
276- }
277-
278- return $out;
330+ private static function escape(string $s): string {
331+ return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
279332 }
280-}
333+}
@@ -0,0 +1,45 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+/**
5+ * 「```asm」用の汎用アセンブリ
6+ * 1つの記事でx86-64・AArch64・RISC-V・POWER等を全部「asm」で書く事があるので、
7+ * 主なアーキテクチャのレジスター・命令をまとめて使う。
8+ *
9+ * 「#」はコメント(RISC-V・POWER・GAS)、「#数字」は即値(ARM)として扱う。
10+ */
11+class Asm extends Assembly {
12+ protected array $comment = [';', '//', '#'];
13+ protected ?array $multiline = ['/*', '*/'];
14+ protected string $immediatePrefix = '';
15+ protected bool $percentBinary = false;
16+ protected bool $percentMacros = true;
17+
18+ protected array $rules = [
19+ // ARMの即値(#64, #-1, #0x10)はコメントより先に確認する
20+ ['re' => '#[-+]?(?:0[xX][0-9A-Fa-f]+|0[bB][01]+|\d+(?:\.\d+)?)', 'class' => 'number'],
21+ ];
22+
23+ /** @var string[] まとめるアーキテクチャ(8ビット機等は単一文字のレジスターが多いので含めない) */
24+ protected array $archs = [AsmX8664::class, AsmAarch64::class, AsmAarch32::class, AsmRiscv::class, AsmMips::class, AsmPower::class];
25+
26+ public function prepare(string $code): void {
27+ $words = [];
28+ $suffixes = [];
29+ $prefixes = [];
30+ foreach ($this->archs as $class) {
31+ $a = new $class();
32+ $a->prepare('');
33+ [$w2, $s2, $p2] = $a->getTables();
34+ foreach ($w2 as $w => $c) {
35+ // 命令の色を優先(「and」等が他のアーキテクチャでキーワードの場合)
36+ if (!isset($words[$w]) || $c === 'instruction') $words[$w] = $c;
37+ }
38+ $suffixes = array_merge($suffixes, $s2);
39+ $prefixes = array_merge($prefixes, $p2);
40+ }
41+ $this->words = $words;
42+ $this->suffixes = array_values(array_unique($suffixes));
43+ $this->prefixes = array_values(array_unique($prefixes));
44+ }
45+}
@@ -1,40 +1,28 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * 6502・65C02
6+ */
7+class Asm6502 extends Assembly {
8+ protected string $identifier = '[.A-Za-z_@][\w.$]*';
59
6-class Asm6502 implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
10+ protected array $tokens = [
1211 'register' => [
13- // 16ビット
14- // 8ビット
12+ 'a', 'x', 'y', 's', 'p',
1513 ],
16- 'operator' => [
14+ 'instruction' => [
15+ 'adc', 'and', 'asl', 'bcc', 'bcs', 'beq', 'bit', 'bmi', 'bne', 'bpl', 'brk', 'bvc', 'bvs', 'clc', 'cld', 'cli', 'clv',
16+ 'cmp', 'cpx', 'cpy', 'dec', 'dex', 'dey', 'eor', 'inc', 'inx', 'iny', 'jmp', 'jsr', 'lda', 'ldx', 'ldy', 'lsr', 'nop',
17+ 'ora', 'pha', 'php', 'pla', 'plp', 'rol', 'ror', 'rti', 'rts', 'sbc', 'sec', 'sed', 'sei', 'sta', 'stx', 'sty',
18+ 'tax', 'tay', 'tsx', 'txa', 'txs', 'tya',
19+ // 65C02
20+ 'bra', 'phx', 'phy', 'plx', 'ply', 'stz', 'trb', 'tsb', 'stp', 'wai',
21+ 'bbr0', 'bbr1', 'bbr2', 'bbr3', 'bbr4', 'bbr5', 'bbr6', 'bbr7', 'bbs0', 'bbs1', 'bbs2', 'bbs3', 'bbs4', 'bbs5', 'bbs6', 'bbs7',
22+ 'rmb0', 'rmb1', 'rmb2', 'rmb3', 'rmb4', 'rmb5', 'rmb6', 'rmb7', 'smb0', 'smb1', 'smb2', 'smb3', 'smb4', 'smb5', 'smb6', 'smb7',
1723 ],
1824 'directive' => [
25+ 'org', 'equ', 'db', 'dw', 'ds', 'byte', 'word', 'include', 'incbin', 'macro', 'endm', 'if', 'else', 'endif',
1926 ],
2027 ];
21-
22- public function __construct() {
23- global $colorPalette;
24-
25- $this->colors = [
26- 'comment' => $colorPalette['dark']['green'],
27- 'directive' => $colorPalette['dark']['pink'],
28- 'label' => $colorPalette['light']['yellow'],
29- 'number' => $colorPalette['light']['green'],
30- 'operator' => $colorPalette['dark']['blue'],
31- 'register' => $colorPalette['dark']['cyan'],
32- 'string' => $colorPalette['medium']['orange'],
33- ];
34- }
35-
36- public function getColors(): array { return $this->colors; }
37- public function getComment(): ?string { return $this->comment; }
38- public function getMultiline(): ?array { return $this->multiline; }
39- public function getTokens(): array { return $this->tokens; }
40-}
28+}
@@ -1,39 +1,44 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * Motorola 68000(68k)
6+ */
7+class Asm68000 extends Assembly {
8+ protected string $identifier = '[.A-Za-z_][\w.$]*';
9+ // move.l → move、dc.w → dc
10+ protected array $suffixes = ['/\.[bwlsdxp]$/'];
511
6-class Asm68000 implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
12+ protected array $rules = [
13+ // 行頭の「*」もコメント
14+ ['re' => '\*[^\n]*', 'class' => 'comment', 'bol' => true],
15+ ];
1016
11- private array $tokens = [
17+ protected array $tokens = [
1218 'register' => [
13- // 16ビット
19+ 'sp', 'usp', 'ssp', 'pc', 'sr', 'ccr', 'vbr', 'sfc', 'dfc',
1420 ],
15- 'operator' => [
21+ 'instruction' => [
22+ 'move', 'movea', 'moveq', 'movem', 'movep', 'lea', 'pea', 'exg', 'swap', 'ext', 'extb', 'clr', 'tst', 'tas',
23+ 'add', 'adda', 'addi', 'addq', 'addx', 'sub', 'suba', 'subi', 'subq', 'subx', 'muls', 'mulu', 'divs', 'divu', 'neg', 'negx',
24+ 'cmp', 'cmpa', 'cmpi', 'cmpm', 'chk', 'abcd', 'sbcd', 'nbcd',
25+ 'and', 'andi', 'or', 'ori', 'eor', 'eori', 'not', 'lsl', 'lsr', 'asl', 'asr', 'rol', 'ror', 'roxl', 'roxr',
26+ 'btst', 'bset', 'bclr', 'bchg', 'bra', 'bsr', 'jmp', 'jsr', 'rts', 'rte', 'rtr', 'trap', 'trapv', 'link', 'unlk',
27+ 'nop', 'reset', 'stop', 'illegal', 'dbra',
1628 ],
1729 'directive' => [
30+ 'dc', 'ds', 'dcb', 'even', 'odd', 'cnop', 'org', 'equ', 'set', 'rs', 'rsreset', 'rsset', 'section', 'text', 'data', 'bss',
31+ 'xdef', 'xref', 'public', 'include', 'incbin', 'end', 'macro', 'endm', 'rept', 'endr', 'if', 'else', 'endc', 'endif',
32+ 'ifd', 'ifnd', 'ifeq', 'ifne', 'opt', 'align',
1833 ],
1934 ];
2035
21- public function __construct() {
22- global $colorPalette;
23-
24- $this->colors = [
25- 'comment' => $colorPalette['dark']['green'],
26- 'directive' => $colorPalette['dark']['pink'],
27- 'label' => $colorPalette['light']['yellow'],
28- 'number' => $colorPalette['light']['green'],
29- 'operator' => $colorPalette['dark']['blue'],
30- 'register' => $colorPalette['dark']['cyan'],
31- 'string' => $colorPalette['medium']['orange'],
32- ];
36+ public function prepare(string $code): void {
37+ $tokens = $this->tokens;
38+ foreach (['d', 'a', 'fp'] as $p) foreach (self::numbered($p, 0, 7) as $r) $tokens['register'][] = $r;
39+ // 条件付き命令(beq, dbne, sne, trapcc等)
40+ $cc = ['t', 'f', 'hi', 'ls', 'cc', 'hs', 'cs', 'lo', 'ne', 'eq', 'vc', 'vs', 'pl', 'mi', 'ge', 'lt', 'gt', 'le'];
41+ foreach ($cc as $c) array_push($tokens['instruction'], "b{$c}", "db{$c}", "s{$c}", "trap{$c}");
42+ $this->words = $this->buildWords($tokens);
3343 }
34-
35- public function getColors(): array { return $this->colors; }
36- public function getComment(): ?string { return $this->comment; }
37- public function getMultiline(): ?array { return $this->multiline; }
38- public function getTokens(): array { return $this->tokens; }
39-}
44+}
@@ -1,56 +1,52 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * AArch32(ARM・Thumb、GASのUAL記法)
6+ */
7+class AsmAarch32 extends Assembly {
8+ protected array $comment = ['@', '//'];
9+ protected ?array $multiline = ['/*', '*/'];
10+ protected bool $dollarHex = false;
11+ protected bool $percentBinary = false;
12+ // 幅・型(.w, .f32)→ 条件コード(addeq)→ フラグ更新(adds)の順に外す
13+ protected array $suffixes = [
14+ '/\.(?:w|n|[iusfp]?(?:8|16|32|64)|f16|f32|f64)$/',
15+ '/(?:eq|ne|cs|hs|cc|lo|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al)$/',
16+ '/s$/',
17+ ];
518
6-class AsmAarch32 implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
19+ protected array $tokens = [
1220 'register' => [
13- // フロート、SIMD、ベクター
14- 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', // 64
15- 'd10', 'd11', 'd12', 'd13', 'd14', 'd15', 'd16', 'd17', 'd18', 'd19', // 64
16- 'd20', 'd21', 'd22', 'd23', 'd24', 'd25', 'd26', 'd27', 'd28', 'd29', 'd30', 'd31', // 64
17- 's0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', // 32
18- 's10', 's11', 's12', 's13', 's14', 's15', 's16', 's17', 's18', 's19', // 32
19- 's20', 's21', 's22', 's23', 's24', 's25', 's26', 's27', 's28', 's29', 's30', 's31', // 32
20- 'q0', 'q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'q8', 'q9', 'q10', 'q11', 'q12', 'q13', 'q14', 'q15',
21- // GPR
22- 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12',
23- 'r13' /*スタックポインター*/, 'r14' /*リンクレジスター*/, 'r15' /*プログラムカウンター*/,
21+ 'sp', 'lr', 'pc', 'fp', 'ip', 'sl', 'sb', 'apsr', 'cpsr', 'spsr', 'fpscr', 'fpexc',
2422 ],
25- 'operator' => [
26- 'add', 'sub', 'mul', 'umul', 'mla', 'madd', 'msub',
27- 'and', 'orr', 'eor', 'bic', 'mvn', 'eon', 'ldr', 'bl',
28- 'ldm', 'stm', 'ldrd', 'strd', 'ldrex', 'strex',
29- 'vadd', 'vmul', 'vmla', 'blx', 'cmp',
23+ 'instruction' => [
24+ // データ処理
25+ 'add', 'adc', 'sub', 'sbc', 'rsb', 'rsc', 'mul', 'mla', 'mls', 'umull', 'umlal', 'smull', 'smlal', 'sdiv', 'udiv',
26+ 'and', 'orr', 'orn', 'eor', 'bic', 'mvn', 'mov', 'movw', 'movt', 'lsl', 'lsr', 'asr', 'ror', 'rrx',
27+ 'cmp', 'cmn', 'tst', 'teq', 'clz', 'rev', 'rev16', 'rbit', 'ubfx', 'sbfx', 'bfi', 'bfc', 'uxtb', 'uxth', 'sxtb', 'sxth', 'usat', 'ssat',
28+ 'qadd', 'qsub', 'uadd8', 'usub8', 'sel',
29+ // 分岐
30+ 'b', 'bl', 'bx', 'blx', 'cbz', 'cbnz', 'tbb', 'tbh', 'it', 'ite', 'itt', 'itte', 'ittt', 'itee',
31+ // ロード・ストア
32+ 'ldr', 'ldrb', 'ldrh', 'ldrsb', 'ldrsh', 'ldrd', 'str', 'strb', 'strh', 'strd', 'ldm', 'ldmia', 'ldmdb', 'stm', 'stmia', 'stmdb',
33+ 'push', 'pop', 'ldrex', 'strex', 'clrex', 'pld', 'adr',
34+ // システム
35+ 'svc', 'swi', 'bkpt', 'nop', 'wfi', 'wfe', 'sev', 'dmb', 'dsb', 'isb', 'mrs', 'msr', 'cpsie', 'cpsid', 'mrc', 'mcr',
36+ // VFP・NEON
37+ 'vmov', 'vadd', 'vsub', 'vmul', 'vdiv', 'vmla', 'vmls', 'vfma', 'vneg', 'vabs', 'vsqrt', 'vcmp', 'vcvt', 'vldr', 'vstr', 'vldm', 'vstm',
38+ 'vpush', 'vpop', 'vmrs', 'vmsr', 'vld1', 'vst1', 'vdup', 'vand', 'vorr', 'veor', 'vmax', 'vmin',
3039 ],
31- 'directive' => [
32- '.arch armv8-a', 'arch_extension', '.cfi_', '.global', '.globl', '.section',
33- '.equ', '.set', '.byte', '.hword', '.word', '.quad',
34- '.arm', '.code 32', '.thumb', '.code 16',
40+ 'keyword' => [
41+ 'eq', 'ne', 'cs', 'hs', 'cc', 'lo', 'mi', 'pl', 'vs', 'vc', 'hi', 'ls', 'ge', 'lt', 'gt', 'le', 'al',
3542 ],
3643 ];
3744
38- public function __construct() {
39- global $colorPalette;
40-
41- $this->colors = [
42- 'comment' => $colorPalette['dark']['green'],
43- 'directive' => $colorPalette['dark']['pink'],
44- 'label' => $colorPalette['light']['yellow'],
45- 'number' => $colorPalette['light']['green'],
46- 'operator' => $colorPalette['dark']['blue'],
47- 'register' => $colorPalette['dark']['cyan'],
48- 'string' => $colorPalette['medium']['orange'],
49- ];
45+ public function prepare(string $code): void {
46+ $tokens = $this->tokens;
47+ foreach (self::numbered('r', 0, 15) as $r) $tokens['register'][] = $r;
48+ foreach (['s', 'd'] as $p) foreach (self::numbered($p, 0, 31) as $r) $tokens['register'][] = $r;
49+ foreach (self::numbered('q', 0, 15) as $r) $tokens['register'][] = $r;
50+ $this->words = $this->buildWords($tokens);
5051 }
51-
52- public function getColors(): array { return $this->colors; }
53- public function getComment(): ?string { return $this->comment; }
54- public function getMultiline(): ?array { return $this->multiline; }
55- public function getTokens(): array { return $this->tokens; }
56-}
52+}
@@ -1,65 +1,56 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * AArch64(GAS。Appleのアセンブラの「;」コメントも対応)
6+ */
7+class AsmAarch64 extends Assembly {
8+ protected array $comment = ['//', ';'];
9+ protected ?array $multiline = ['/*', '*/'];
10+ protected bool $dollarHex = false;
11+ protected bool $percentBinary = false;
12+ // b.eq → b、v0.4s → v0、ld1.16b等
13+ protected array $suffixes = ['/\.[a-z0-9]+$/'];
514
6-class AsmAarch64 implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
15+ protected array $tokens = [
1216 'register' => [
13- // フロート、SIMD、ベクター
14- 'd0', 'd1', 'd2', 'd3', 'd4', 'd5', 'd6', 'd7', 'd8', 'd9', // 64
15- 'd10', 'd11', 'd12', 'd13', 'd14', 'd15', 'd16', 'd17', 'd18', 'd19', // 64
16- 'd20', 'd21', 'd22', 'd23', 'd24', 'd25', 'd26', 'd27', 'd28', 'd29', 'd30', 'd31', // 64
17- 's0', 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', // 32
18- 's10', 's11', 's12', 's13', 's14', 's15', 's16', 's17', 's18', 's19', // 32
19- 's20', 's21', 's22', 's23', 's24', 's25', 's26', 's27', 's28', 's29', 's30', 's31', // 32
20- 'v0', 'v1', 'v2', 'v3', 'v4', 'v5', 'v6', 'v7', 'v8', 'v9',
21- 'v10', 'v11', 'v12', 'v13', 'v14', 'v15', 'v16', 'v17', 'v18', 'v19',
22- 'v20', 'v21', 'v22', 'v23', 'v24', 'v25', 'v26', 'v27', 'v28', 'v29', 'v30', 'v31',
23- 'q16', 'q17', 'q18', 'q19', 'q20', 'q21', 'q22', 'q23', 'q24', 'q25', 'q26', 'q27', 'q28', 'q29', 'q30', 'q31',
24- // GPR
25- 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9',
26- 'x10', 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19',
27- 'x20', 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28', 'x29', 'x30', 'x31' /*ゼロレジスター*/,
28- 'w0', 'w1', 'w2', 'w3', 'w4', 'w5', 'w6', 'w7', 'w8', 'w9',
29- 'w10', 'w11', 'w12', 'w13', 'w14', 'w15', 'w16', 'w17', 'w18', 'w19',
30- 'w20', 'w21', 'w22', 'w23', 'w24', 'w25', 'w26', 'w27', 'w28', 'w29', 'w30',
31- 'xzr' /*ゼロレジスター*/, 'wzr' /*ゼロレジスター*/,
32- 'sp' /*スタックポインター*/, 'pc' /*プログラムカウンター*/,
17+ 'sp', 'wsp', 'xzr', 'wzr', 'lr', 'fp', 'pc', 'nzcv', 'fpcr', 'fpsr', 'tpidr_el0',
3318 ],
34- 'operator' => [
35- 'add', 'sub', 'mul', 'umul', 'mla', 'madd', 'msub',
36- 'and', 'orr', 'eor', 'bic', 'mvn', 'eon', 'ldr', 'bl',
37- 'csel', 'cinc', 'cset', 'addv', 'smull', 'fmla', 'blr', 'cbz', 'cbnz', 'tbz', 'tbnz',
38- 'adpr', 'movz', 'movk', 'ldp', 'stp', 'ldxr', 'stxr', 'ldadd', 'ldclr', 'ldset',
19+ 'instruction' => [
20+ // データ処理
21+ 'add', 'adds', 'sub', 'subs', 'adc', 'adcs', 'sbc', 'sbcs', 'neg', 'negs', 'ngc', 'cmp', 'cmn', 'tst',
22+ 'mul', 'madd', 'msub', 'mneg', 'smull', 'umull', 'smulh', 'umulh', 'smaddl', 'umaddl', 'sdiv', 'udiv',
23+ 'and', 'ands', 'orr', 'orn', 'eor', 'eon', 'bic', 'bics', 'mvn', 'lsl', 'lsr', 'asr', 'ror',
24+ 'mov', 'movz', 'movn', 'movk', 'adr', 'adrp', 'sxtb', 'sxth', 'sxtw', 'uxtb', 'uxth', 'ubfx', 'sbfx', 'ubfiz', 'sbfiz', 'bfi', 'bfxil',
25+ 'ubfm', 'sbfm', 'bfm', 'extr', 'clz', 'cls', 'rbit', 'rev', 'rev16', 'rev32',
26+ 'csel', 'csinc', 'csinv', 'csneg', 'cset', 'csetm', 'cinc', 'cinv', 'cneg', 'ccmp', 'ccmn',
27+ // 分岐
28+ 'b', 'bl', 'br', 'blr', 'ret', 'cbz', 'cbnz', 'tbz', 'tbnz', 'braa', 'blraa', 'retaa',
29+ // ロード・ストア
30+ 'ldr', 'ldrb', 'ldrh', 'ldrsb', 'ldrsh', 'ldrsw', 'ldur', 'ldurb', 'ldurh', 'str', 'strb', 'strh', 'stur', 'sturb', 'sturh',
31+ 'ldp', 'stp', 'ldnp', 'stnp', 'ldxr', 'stxr', 'ldaxr', 'stlxr', 'ldar', 'stlr', 'ldxp', 'stxp', 'prfm',
32+ 'ldadd', 'ldadda', 'ldaddal', 'ldclr', 'ldset', 'ldeor', 'swp', 'swpal', 'cas', 'casal',
33+ // システム
34+ 'svc', 'hvc', 'smc', 'brk', 'hlt', 'nop', 'yield', 'wfe', 'wfi', 'sev', 'dmb', 'dsb', 'isb', 'mrs', 'msr', 'sys', 'paciasp', 'autiasp', 'bti',
35+ // 浮動小数点・NEON
36+ 'fmov', 'fadd', 'fsub', 'fmul', 'fdiv', 'fmadd', 'fmsub', 'fnmadd', 'fneg', 'fabs', 'fsqrt', 'fmin', 'fmax', 'fminnm', 'fmaxnm', 'fcmp', 'fcsel',
37+ 'fcvt', 'fcvtzs', 'fcvtzu', 'fcvtns', 'scvtf', 'ucvtf', 'frintn', 'frintm', 'frintp', 'frintz',
38+ 'fmla', 'fmls', 'addv', 'addp', 'faddp', 'umov', 'smov', 'ins', 'dup', 'movi', 'zip1', 'zip2', 'uzp1', 'uzp2', 'trn1', 'trn2', 'ext', 'tbl',
39+ 'ld1', 'ld2', 'ld3', 'ld4', 'st1', 'st2', 'st3', 'st4', 'ld1r', 'cnt', 'uaddlv', 'saddlv', 'shl', 'sshr', 'ushr', 'xtn', 'sqxtn', 'uqxtn',
40+ 'cmeq', 'cmgt', 'cmge', 'fcmeq', 'fcmgt', 'bsl', 'bit', 'bif', 'not', 'abs', 'mla', 'mls', 'smlal', 'umlal', 'sdot', 'udot', 'fmlal',
3941 ],
40- 'directive' => [
41- '.arch armv8-a', 'arch_extension', '.cfi_', '.global', '.globl', '.section',
42- '.equ', '.set', '.byte', '.hword', '.word', '.quad',
43- '.req', '.aarch64',
42+ 'keyword' => [
43+ // 条件コード・シフト・拡張
44+ 'eq', 'ne', 'cs', 'hs', 'cc', 'lo', 'mi', 'pl', 'vs', 'vc', 'hi', 'ls', 'ge', 'lt', 'gt', 'le', 'al', 'nv',
45+ 'uxtw', 'sxtx', 'uxtx', 'msl',
4446 ],
4547 ];
4648
47- public function __construct() {
48- global $colorPalette;
49-
50- $this->colors = [
51- 'comment' => $colorPalette['dark']['green'],
52- 'directive' => $colorPalette['dark']['pink'],
53- 'label' => $colorPalette['light']['yellow'],
54- 'number' => $colorPalette['light']['green'],
55- 'operator' => $colorPalette['dark']['blue'],
56- 'register' => $colorPalette['dark']['cyan'],
57- 'string' => $colorPalette['medium']['orange'],
58- ];
49+ public function prepare(string $code): void {
50+ $tokens = $this->tokens;
51+ foreach (['x', 'w'] as $p) foreach (self::numbered($p, 0, 30) as $r) $tokens['register'][] = $r;
52+ foreach (['v', 'q', 'd', 's', 'h', 'b', 'z'] as $p) foreach (self::numbered($p, 0, 31) as $r) $tokens['register'][] = $r;
53+ foreach (self::numbered('p', 0, 15) as $r) $tokens['register'][] = $r;
54+ $this->words = $this->buildWords($tokens);
5955 }
60-
61- public function getColors(): array { return $this->colors; }
62- public function getComment(): ?string { return $this->comment; }
63- public function getMultiline(): ?array { return $this->multiline; }
64- public function getTokens(): array { return $this->tokens; }
65-}
56+}
@@ -1,40 +1,41 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * MIPS(MIPS32/MIPS64、GAS)
6+ */
7+class AsmMips extends Assembly {
8+ protected array $comment = ['#'];
9+ protected ?array $multiline = ['/*', '*/'];
10+ // 「$」はレジスター($t0、$31)なので16進数ではない
11+ protected bool $dollarHex = false;
12+ protected bool $percentBinary = false;
13+ protected string $immediatePrefix = '';
14+ protected string $identifier = '\$\d+|[%$]?[.A-Za-z_][\w.$]*';
15+ // add.s → add、c.eq.d → c.eq → c
16+ protected array $suffixes = ['/\.[a-z0-9]+$/', '/\.[a-z0-9]+$/'];
517
6-class AsmMips implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
18+ protected array $tokens = [
1219 'register' => [
13- // 64ビット
14- // 32ビット
20+ 'zero', 'at', 'v0', 'v1', 'a0', 'a1', 'a2', 'a3', 'k0', 'k1', 'gp', 'sp', 'fp', 'ra', 'hi', 'lo', 'pc',
1521 ],
16- 'operator' => [
22+ 'instruction' => [
23+ 'add', 'addu', 'addi', 'addiu', 'sub', 'subu', 'mult', 'multu', 'mul', 'madd', 'div', 'divu', 'mfhi', 'mflo', 'mthi', 'mtlo',
24+ 'dadd', 'daddu', 'daddi', 'daddiu', 'dsub', 'dsubu', 'dmult', 'ddiv',
25+ 'and', 'andi', 'or', 'ori', 'xor', 'xori', 'nor', 'sll', 'srl', 'sra', 'sllv', 'srlv', 'srav', 'dsll', 'dsrl', 'dsra', 'slt', 'sltu', 'slti', 'sltiu',
26+ 'lui', 'lb', 'lbu', 'lh', 'lhu', 'lw', 'lwu', 'ld', 'lwl', 'lwr', 'll', 'sb', 'sh', 'sw', 'sd', 'swl', 'swr', 'sc',
27+ 'beq', 'bne', 'bgez', 'bgtz', 'blez', 'bltz', 'bgezal', 'bltzal', 'j', 'jal', 'jr', 'jalr', 'syscall', 'break', 'eret', 'sync', 'nop',
28+ 'mfc0', 'mtc0', 'mfc1', 'mtc1', 'lwc1', 'swc1', 'ldc1', 'sdc1', 'movn', 'movz', 'teq', 'ext', 'ins', 'seb', 'seh', 'clz', 'clo',
29+ 'abs', 'cvt', 'c', 'bc1t', 'bc1f', 'mov', 'neg', 'sqrt', 'trunc', 'round', 'floor', 'ceil',
30+ // 疑似命令
31+ 'li', 'la', 'move', 'b', 'bal', 'beqz', 'bnez', 'bge', 'bgt', 'ble', 'blt', 'not', 'negu',
1732 ],
18- 'directive' => [
19- ],
2033 ];
2134
22- public function __construct() {
23- global $colorPalette;
24-
25- $this->colors = [
26- 'comment' => $colorPalette['dark']['green'],
27- 'directive' => $colorPalette['dark']['pink'],
28- 'label' => $colorPalette['light']['yellow'],
29- 'number' => $colorPalette['light']['green'],
30- 'operator' => $colorPalette['dark']['blue'],
31- 'register' => $colorPalette['dark']['cyan'],
32- 'string' => $colorPalette['medium']['orange'],
33- ];
35+ public function prepare(string $code): void {
36+ $tokens = $this->tokens;
37+ $names = array_merge(self::numbered('t', 0, 9), self::numbered('s', 0, 8), self::numbered('f', 0, 31), self::numbered('', 0, 31));
38+ foreach ($names as $r) $tokens['register'][] = $r;
39+ $this->words = $this->buildWords($tokens);
3440 }
35-
36- public function getColors(): array { return $this->colors; }
37- public function getComment(): ?string { return $this->comment; }
38- public function getMultiline(): ?array { return $this->multiline; }
39- public function getTokens(): array { return $this->tokens; }
40-}
41+}
@@ -1,40 +1,50 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * POWER・PowerPC(GAS)
6+ * GASではレジスターをただの数字で書く事が多い(ld 2, 24(12))ので、「r2」「%r2」の形だけを色付けする
7+ */
8+class AsmPower extends Assembly {
9+ protected array $comment = ['#'];
10+ protected ?array $multiline = ['/*', '*/'];
11+ protected bool $dollarHex = false;
12+ protected bool $percentBinary = false;
13+ protected string $immediatePrefix = '';
14+ // add. → add(条件レジスターを更新する形)
15+ protected array $suffixes = ['/\.$/'];
516
6-class AsmPower implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
17+ protected array $tokens = [
1218 'register' => [
13- // 64ビット
14- // 32ビット
19+ 'lr', 'ctr', 'xer', 'msr', 'cr', 'fpscr', 'vrsave', 'vscr', 'sp', 'toc', 'rtoc',
1520 ],
16- 'operator' => [
21+ 'instruction' => [
22+ 'li', 'lis', 'la', 'addi', 'addis', 'add', 'addc', 'adde', 'addze', 'addme', 'addic', 'subf', 'subfc', 'subfe', 'subfic', 'sub', 'subi', 'neg',
23+ 'mullw', 'mulld', 'mulhw', 'mulhwu', 'mulhd', 'mulhdu', 'mulli', 'divw', 'divwu', 'divd', 'divdu', 'modsw', 'moduw',
24+ 'and', 'andc', 'andi', 'andis', 'or', 'orc', 'ori', 'oris', 'xor', 'xori', 'xoris', 'nor', 'nand', 'eqv', 'not', 'mr',
25+ 'slw', 'srw', 'sraw', 'srawi', 'sld', 'srd', 'srad', 'sradi', 'rlwinm', 'rlwimi', 'rlwnm', 'rldicl', 'rldicr', 'rldic', 'rldimi',
26+ 'slwi', 'srwi', 'sldi', 'srdi', 'clrlwi', 'clrldi', 'extsb', 'extsh', 'extsw', 'cntlzw', 'cntlzd', 'popcntw', 'popcntd',
27+ 'lbz', 'lbzu', 'lbzx', 'lhz', 'lhzu', 'lha', 'lhax', 'lwz', 'lwzu', 'lwzx', 'lwa', 'lwax', 'ld', 'ldu', 'ldx', 'lmw', 'lwarx', 'ldarx',
28+ 'stb', 'stbu', 'stbx', 'sth', 'sthu', 'sthx', 'stw', 'stwu', 'stwx', 'std', 'stdu', 'stdx', 'stmw', 'stwcx', 'stdcx',
29+ 'lfs', 'lfd', 'stfs', 'stfd', 'fadd', 'fadds', 'fsub', 'fmul', 'fmuls', 'fdiv', 'fmadd', 'fmr', 'fneg', 'fabs', 'fsqrt', 'fcmpu', 'fctiwz', 'fcfid',
30+ 'cmpw', 'cmpwi', 'cmplw', 'cmplwi', 'cmpd', 'cmpdi', 'cmpld', 'cmpldi', 'cmp', 'cmpi', 'cmpl', 'cmpli',
31+ 'b', 'ba', 'bl', 'bla', 'bc', 'bcl', 'blr', 'blrl', 'bctr', 'bctrl', 'beq', 'bne', 'blt', 'bgt', 'ble', 'bge', 'bso', 'bns', 'bdnz', 'bdz',
32+ 'beqlr', 'bnelr', 'bltlr', 'bgtlr', 'blelr', 'bgelr',
33+ 'mflr', 'mtlr', 'mfctr', 'mtctr', 'mfcr', 'mtcrf', 'mfxer', 'mtxer', 'mfspr', 'mtspr', 'mfmsr', 'mtmsr',
34+ 'sc', 'nop', 'isync', 'sync', 'lwsync', 'eieio', 'dcbf', 'dcbst', 'icbi', 'trap', 'tw', 'twi', 'td', 'tdi',
35+ 'lvx', 'stvx', 'vaddfp', 'vmaddfp', 'vperm', 'lxvd2x', 'stxvd2x', 'xvadddp', 'xvmuldp',
1736 ],
18- 'directive' => [
37+ 'keyword' => [
38+ // シンボル修飾子(msg@toc、x@ha)
39+ 'ha', 'got', 'plt', 'tocbase', 'localentry',
1940 ],
2041 ];
2142
22- public function __construct() {
23- global $colorPalette;
24-
25- $this->colors = [
26- 'comment' => $colorPalette['dark']['green'],
27- 'directive' => $colorPalette['dark']['pink'],
28- 'label' => $colorPalette['light']['yellow'],
29- 'number' => $colorPalette['light']['green'],
30- 'operator' => $colorPalette['dark']['blue'],
31- 'register' => $colorPalette['dark']['cyan'],
32- 'string' => $colorPalette['medium']['orange'],
33- ];
43+ public function prepare(string $code): void {
44+ $tokens = $this->tokens;
45+ foreach (['r', 'f', 'v'] as $p) foreach (self::numbered($p, 0, 31) as $r) $tokens['register'][] = $r;
46+ foreach (self::numbered('vs', 0, 63) as $r) $tokens['register'][] = $r;
47+ foreach (self::numbered('cr', 0, 7) as $r) $tokens['register'][] = $r;
48+ $this->words = $this->buildWords($tokens);
3449 }
35-
36- public function getColors(): array { return $this->colors; }
37- public function getComment(): ?string { return $this->comment; }
38- public function getMultiline(): ?array { return $this->multiline; }
39- public function getTokens(): array { return $this->tokens; }
40-}
50+}
@@ -1,41 +1,53 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * RISC-V(RV32/RV64、GAS)
6+ */
7+class AsmRiscv extends Assembly {
8+ protected array $comment = ['#'];
9+ protected ?array $multiline = ['/*', '*/'];
10+ protected bool $dollarHex = false;
11+ protected bool $percentBinary = false;
12+ protected string $immediatePrefix = '';
13+ // fcvt.w.s → fcvt.w → fcvt、lr.w.aq → lr、c.addi → addi
14+ protected array $suffixes = ['/\.[a-z0-9]+$/', '/\.[a-z0-9]+$/', '/^c\./'];
515
6-class AsmRiscV implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
16+ protected array $tokens = [
1217 'register' => [
13- // 128ビット
14- // 64ビット
15- // 32ビット
18+ 'zero', 'ra', 'sp', 'gp', 'tp', 'fp', 'pc',
1619 ],
17- 'operator' => [
20+ 'instruction' => [
21+ // RV32I/RV64I
22+ 'lui', 'auipc', 'jal', 'jalr', 'beq', 'bne', 'blt', 'bge', 'bltu', 'bgeu', 'lb', 'lh', 'lw', 'lbu', 'lhu', 'lwu', 'ld', 'sb', 'sh', 'sw', 'sd',
23+ 'addi', 'slti', 'sltiu', 'xori', 'ori', 'andi', 'slli', 'srli', 'srai', 'add', 'sub', 'sll', 'slt', 'sltu', 'xor', 'srl', 'sra', 'or', 'and',
24+ 'addiw', 'slliw', 'srliw', 'sraiw', 'addw', 'subw', 'sllw', 'srlw', 'sraw', 'fence', 'ecall', 'ebreak',
25+ // M・A
26+ 'mul', 'mulh', 'mulhsu', 'mulhu', 'div', 'divu', 'rem', 'remu', 'mulw', 'divw', 'divuw', 'remw', 'remuw',
27+ 'lr', 'sc', 'amoswap', 'amoadd', 'amoxor', 'amoand', 'amoor', 'amomin', 'amomax', 'amominu', 'amomaxu',
28+ // F・D
29+ 'flw', 'fsw', 'fld', 'fsd', 'fadd', 'fsub', 'fmul', 'fdiv', 'fsqrt', 'fmin', 'fmax', 'fmadd', 'fmsub', 'fnmadd', 'fnmsub',
30+ 'fsgnj', 'fsgnjn', 'fsgnjx', 'fcvt', 'fmv', 'feq', 'flt', 'fle', 'fclass',
31+ // Zicsr・特権
32+ 'csrrw', 'csrrs', 'csrrc', 'csrrwi', 'csrrsi', 'csrrci', 'csrr', 'csrw', 'csrs', 'csrc', 'mret', 'sret', 'wfi', 'sfence',
33+ // B・V(よく使う物)
34+ 'andn', 'orn', 'xnor', 'clz', 'ctz', 'cpop', 'min', 'max', 'minu', 'maxu', 'rol', 'ror', 'rev8', 'sext', 'zext',
35+ 'vsetvli', 'vsetivli', 'vsetvl', 'vle8', 'vle16', 'vle32', 'vle64', 'vse8', 'vse16', 'vse32', 'vse64', 'vadd', 'vsub', 'vmul', 'vfadd', 'vfmul',
36+ 'vfmacc', 'vredsum', 'vfredusum', 'vmv',
37+ // 疑似命令
38+ 'li', 'la', 'lla', 'mv', 'not', 'neg', 'negw', 'nop', 'j', 'jr', 'ret', 'call', 'tail', 'seqz', 'snez', 'sltz', 'sgtz',
39+ 'beqz', 'bnez', 'blez', 'bgez', 'bltz', 'bgtz', 'bgt', 'ble', 'bgtu', 'bleu', 'fabs', 'fneg',
1840 ],
19- 'directive' => [
20- ],
2141 ];
2242
23- public function __construct() {
24- global $colorPalette;
25-
26- $this->colors = [
27- 'comment' => $colorPalette['dark']['green'],
28- 'directive' => $colorPalette['dark']['pink'],
29- 'label' => $colorPalette['light']['yellow'],
30- 'number' => $colorPalette['light']['green'],
31- 'operator' => $colorPalette['dark']['blue'],
32- 'register' => $colorPalette['dark']['cyan'],
33- 'string' => $colorPalette['medium']['orange'],
34- ];
43+ public function prepare(string $code): void {
44+ $tokens = $this->tokens;
45+ $names = array_merge(
46+ self::numbered('x', 0, 31), self::numbered('f', 0, 31), self::numbered('v', 0, 31),
47+ self::numbered('t', 0, 6), self::numbered('s', 0, 11), self::numbered('a', 0, 7),
48+ self::numbered('ft', 0, 11), self::numbered('fs', 0, 11), self::numbered('fa', 0, 7),
49+ );
50+ foreach ($names as $r) $tokens['register'][] = $r;
51+ $this->words = $this->buildWords($tokens);
3552 }
36-
37- public function getColors(): array { return $this->colors; }
38- public function getComment(): ?string { return $this->comment; }
39- public function getMultiline(): ?array { return $this->multiline; }
40- public function getTokens(): array { return $this->tokens; }
41-}
53+}
@@ -1,56 +1,92 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * x86-64(NASMとGASのAT&T記法の両方)
6+ */
7+class AsmX8664 extends Assembly {
8+ protected array $comment = [';', '#'];
9+ protected string $immediatePrefix = '';
10+ protected bool $percentBinary = false;
11+ protected bool $percentMacros = true;
12+ protected array $prefixes = ['rep', 'repe', 'repne', 'repz', 'repnz', 'lock', 'xacquire', 'xrelease', 'bnd', 'notrack', 'data16', 'addr32', 'rex', 'rex.w'];
513
6-class AsmX8664 implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
14+ protected array $tokens = [
1215 'register' => [
13- // 64ビット
14- 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 'r8', 'r8d', 'r9', 'r9d', 'r10', 'r10d', 'r11', 'r11d', 'r12', 'r12d', 'r13', 'r13d', 'r14', 'r14d', 'r15', 'r15d',
15- // 32ビット
16- 'eax', 'ebx', 'ecx', 'edx', 'esi', 'edi',
17- // 16ビット
18- 'al', 'bl', 'cl', 'dl',
19- 'ah', 'bh', 'ch', 'dh',
20- 'spl', 'bpl', 'sil', 'dil',
16+ 'rax', 'rbx', 'rcx', 'rdx', 'rsi', 'rdi', 'rbp', 'rsp',
17+ 'eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp', 'esp',
18+ 'ax', 'bx', 'cx', 'dx', 'si', 'di', 'bp', 'sp',
19+ 'al', 'bl', 'cl', 'dl', 'ah', 'bh', 'ch', 'dh', 'sil', 'dil', 'bpl', 'spl',
20+ 'rip', 'eip', 'ip', 'rflags', 'eflags', 'flags',
21+ 'cs', 'ds', 'es', 'fs', 'gs', 'ss', 'st',
2122 ],
22- 'operator' => [
23- 'mov', 'lea',
24- 'add', 'sub', 'div', 'imul', 'dec', 'inc',
25- 'xor', 'or', 'and',
26- 'js', 'jmp', 'jz', 'jnz', 'ja', 'jb',
27- 'pop', 'push',
28- 'call', 'ret', 'syscall', 'cmp', 'test', 'stosb',
23+ 'instruction' => [
24+ // データ転送
25+ 'mov', 'movzx', 'movsx', 'movsxd', 'movabs', 'lea', 'xchg', 'cmpxchg', 'cmpxchg8b', 'cmpxchg16b', 'xadd', 'bswap', 'push', 'pop',
26+ 'pushf', 'popf', 'pushfq', 'popfq', 'pusha', 'popa', 'cbw', 'cwde', 'cdqe', 'cwd', 'cdq', 'cqo', 'movbe', 'movnti',
27+ // AT&T記法のサイズ付き
28+ 'movb', 'movw', 'movl', 'movq', 'leaq', 'leal', 'addq', 'addl', 'subq', 'subl', 'pushq', 'popq', 'cmpq', 'cmpl', 'xorl', 'xorq',
29+ 'andq', 'andl', 'orq', 'orl', 'incq', 'incl', 'decq', 'decl', 'imulq', 'imull', 'testq', 'testl', 'shlq', 'shrq', 'sarq', 'callq', 'retq',
30+ 'movzbl', 'movzwl', 'movsbl', 'movswl', 'movslq', 'cltq', 'cqto', 'cltd',
31+ // 算術・論理
32+ 'add', 'adc', 'sub', 'sbb', 'mul', 'imul', 'div', 'idiv', 'inc', 'dec', 'neg', 'cmp', 'test', 'and', 'or', 'xor', 'not',
33+ 'shl', 'shr', 'sal', 'sar', 'rol', 'ror', 'rcl', 'rcr', 'shld', 'shrd', 'bt', 'bts', 'btr', 'btc', 'bsf', 'bsr',
34+ 'lzcnt', 'tzcnt', 'popcnt', 'andn', 'bextr', 'blsi', 'blsmsk', 'blsr', 'bzhi', 'mulx', 'pdep', 'pext', 'rorx', 'sarx', 'shlx', 'shrx',
35+ 'adcx', 'adox', 'crc32',
36+ // 分岐
37+ 'jmp', 'call', 'ret', 'retf', 'iret', 'iretq', 'loop', 'loope', 'loopne', 'jcxz', 'jecxz', 'jrcxz', 'enter', 'leave',
38+ 'int', 'int3', 'into', 'syscall', 'sysret', 'sysenter', 'sysexit',
39+ // 文字列
40+ 'movs', 'movsb', 'movsw', 'movsq', 'stos', 'stosb', 'stosw', 'stosd', 'stosq', 'lods', 'lodsb', 'lodsw', 'lodsd', 'lodsq',
41+ 'scas', 'scasb', 'scasw', 'scasd', 'scasq', 'cmps', 'cmpsb', 'cmpsw', 'cmpsq',
42+ // フラグ・その他
43+ 'clc', 'stc', 'cmc', 'cld', 'std', 'cli', 'sti', 'lahf', 'sahf', 'nop', 'hlt', 'pause', 'ud2', 'cpuid', 'rdtsc', 'rdtscp', 'rdrand', 'rdseed',
44+ 'xgetbv', 'lfence', 'sfence', 'mfence', 'prefetcht0', 'prefetcht1', 'prefetcht2', 'prefetchnta', 'clflush', 'endbr64', 'in', 'out',
45+ // x87
46+ 'fld', 'fst', 'fstp', 'fild', 'fist', 'fistp', 'fadd', 'faddp', 'fsub', 'fsubp', 'fmul', 'fmulp', 'fdiv', 'fdivp', 'fsqrt', 'fsin', 'fcos',
47+ 'fxch', 'fcomi', 'fcomip', 'fldz', 'fld1', 'fninit', 'fwait',
48+ // SSE・AVX
49+ 'movaps', 'movups', 'movapd', 'movupd', 'movss', 'movsd', 'movd', 'movdqa', 'movdqu', 'movhlps', 'movlhps', 'movmskps',
50+ 'addss', 'addsd', 'addps', 'addpd', 'subss', 'subsd', 'subps', 'subpd', 'mulss', 'mulsd', 'mulps', 'mulpd', 'divss', 'divsd', 'divps', 'divpd',
51+ 'sqrtss', 'sqrtsd', 'sqrtps', 'sqrtpd', 'rsqrtss', 'rsqrtps', 'rcpss', 'rcpps', 'minss', 'minsd', 'minps', 'minpd', 'maxss', 'maxsd', 'maxps', 'maxpd',
52+ 'andps', 'andpd', 'andnps', 'andnpd', 'orps', 'orpd', 'xorps', 'xorpd', 'cmpps', 'cmpss', 'comiss', 'comisd', 'ucomiss', 'ucomisd',
53+ 'shufps', 'shufpd', 'unpcklps', 'unpckhps', 'unpcklpd', 'unpckhpd', 'blendps', 'blendvps', 'dpps', 'roundss', 'roundps',
54+ 'cvtsi2ss', 'cvtsi2sd', 'cvtss2si', 'cvtsd2si', 'cvttss2si', 'cvttsd2si', 'cvtss2sd', 'cvtsd2ss', 'cvtdq2ps', 'cvtps2dq', 'cvttps2dq',
55+ 'paddb', 'paddw', 'paddd', 'paddq', 'psubb', 'psubw', 'psubd', 'psubq', 'pmullw', 'pmulld', 'pmuludq', 'pand', 'pandn', 'por', 'pxor',
56+ 'pcmpeqb', 'pcmpeqd', 'pcmpgtd', 'pshufb', 'pshufd', 'pslld', 'psrld', 'psrad', 'psllq', 'psrlq', 'punpcklbw', 'punpckldq', 'packuswb',
57+ 'pmovmskb', 'ptest', 'pminsd', 'pmaxsd', 'pextrd', 'pinsrd', 'pabsd', 'phaddd',
58+ 'vmovaps', 'vmovups', 'vmovapd', 'vmovupd', 'vmovss', 'vmovsd', 'vmovdqa', 'vmovdqu', 'vmovdqa64', 'vmovdqu8', 'vmovdqu32',
59+ 'vaddps', 'vaddpd', 'vaddss', 'vaddsd', 'vsubps', 'vsubpd', 'vmulps', 'vmulpd', 'vdivps', 'vdivpd', 'vsqrtps', 'vminps', 'vmaxps',
60+ 'vandps', 'vorps', 'vxorps', 'vpxor', 'vpand', 'vpor', 'vpaddd', 'vpsubd', 'vpmulld', 'vpshufb', 'vpermd', 'vpermps', 'vperm2f128',
61+ 'vbroadcastss', 'vbroadcastsd', 'vpbroadcastd', 'vinsertf128', 'vextractf128', 'vshufps', 'vblendvps', 'vgatherdps', 'vcvtps2ph', 'vcvtph2ps',
62+ 'vfmadd132ps', 'vfmadd213ps', 'vfmadd231ps', 'vfmadd132ss', 'vfmadd213ss', 'vfmadd231ss', 'vfmadd231pd', 'vfnmadd231ps', 'vfmsub231ps',
63+ 'vzeroupper', 'vzeroall', 'vptest', 'vcmpps', 'vhaddps', 'vdpps', 'vroundps', 'kmovw', 'kmovq', 'vpternlogd', 'vpcompressd', 'vpexpandd',
2964 ],
65+ 'keyword' => [
66+ // サイズ指定等(NASM)
67+ 'byte', 'word', 'dword', 'qword', 'tword', 'oword', 'yword', 'zword', 'ptr', 'rel', 'abs', 'near', 'far', 'short', 'strict', 'wrt', 'seg',
68+ 'rep', 'repe', 'repne', 'repz', 'repnz', 'lock', 'xacquire', 'xrelease', 'bnd', 'notrack',
69+ ],
3070 'directive' => [
31- 'db', 'dw', 'dd', 'dw',
32- 'equ', 'global', 'extern',
33- 'resb', 'resw', 'resd', 'resq',
71+ // NASM
72+ 'section', 'segment', 'global', 'extern', 'common', 'static', 'bits', 'default', 'db', 'dw', 'dd', 'dq', 'dt', 'do', 'dy', 'dz',
73+ 'resb', 'resw', 'resd', 'resq', 'rest', 'reso', 'resy', 'resz', 'equ', 'times', 'incbin', 'align', 'alignb', 'struc', 'endstruc',
74+ 'istruc', 'iend', 'at', 'org', 'cpu', 'use16', 'use32', 'use64', 'absolute', 'sectalign',
3475 ],
3576 ];
3677
37- public function __construct() {
38- global $colorPalette;
39-
40- $this->colors = [
41- 'comment' => $colorPalette['dark']['green'],
42- 'directive' => $colorPalette['dark']['pink'],
43- 'label' => $colorPalette['light']['yellow'],
44- 'number' => $colorPalette['light']['green'],
45- 'operator' => $colorPalette['dark']['blue'],
46- 'register' => $colorPalette['dark']['cyan'],
47- 'string' => $colorPalette['medium']['orange'],
48- ];
78+ public function prepare(string $code): void {
79+ $tokens = $this->tokens;
80+ // r8〜r15、各サイズ
81+ foreach (self::numbered('r', 8, 15) as $r) array_push($tokens['register'], $r, "{$r}d", "{$r}w", "{$r}b", "{$r}l");
82+ // 制御・デバッグ・ベクター・マスク・MMX・x87
83+ foreach ([['cr', 0, 15], ['dr', 0, 15], ['xmm', 0, 31], ['ymm', 0, 31], ['zmm', 0, 31], ['k', 0, 7], ['mm', 0, 7], ['st', 0, 7]] as [$p, $a, $b]) {
84+ foreach (self::numbered($p, $a, $b) as $r) $tokens['register'][] = $r;
85+ }
86+ // 条件付き命令(je, cmovne, setg等)
87+ $cc = ['o', 'no', 'b', 'nb', 'c', 'nc', 'ae', 'nae', 'z', 'nz', 'e', 'ne', 'be', 'nbe', 'a', 'na', 's', 'ns', 'p', 'np', 'pe', 'po',
88+ 'l', 'nl', 'ge', 'nge', 'le', 'nle', 'g', 'ng'];
89+ foreach ($cc as $c) array_push($tokens['instruction'], "j{$c}", "cmov{$c}", "set{$c}");
90+ $this->words = $this->buildWords($tokens);
4991 }
50-
51- public function getColors(): array { return $this->colors; }
52- public function getComment(): ?string { return $this->comment; }
53- public function getInclude(array $code): ?array { return null; }
54- public function getMultiline(): ?array { return $this->multiline; }
55- public function getTokens(): array { return $this->tokens; }
56-}
92+}
@@ -1,66 +1,48 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+/**
5+ * Z80・GBZ80・eZ80
6+ */
7+class AsmZ80 extends Assembly {
8+ // 「af'」の様な裏レジスターも1つの単語にする(「%」は2進数なので接頭辞にしない)
9+ protected string $identifier = '[.A-Za-z_][\w.$]*\'?';
510
6-class AsmZ80 implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = ';';
9- private ?array $multiline = null;
10-
11- private array $tokens = [
11+ protected array $tokens = [
1212 'register' => [
1313 // 16ビット
14- "bc'", "de'", "hl'",
15- 'bc', 'de', 'hl',
16- 'ix' /*指標レジスター*/, 'iy' /*指標レジスター*/,
17- 'sp' /*スタックポインター*/, 'pc' /*プログラムカウンター*/,
14+ "af'", "bc'", "de'", "hl'", 'af', 'bc', 'de', 'hl', 'ix', 'iy', 'ixh', 'ixl', 'iyh', 'iyl', 'sp', 'pc',
1815 // 8ビット
19- 'a', 'b', 'd', 'h', 'f', 'c', 'e', 'l',
20- "a'", "b'", "d'", "h'", "f'", "c'", "e'", "l'",
21- 'a', 'b', 'd', 'h', 'f', 'c', 'e', 'l',
16+ 'a', 'b', 'c', 'd', 'e', 'f', 'h', 'l', 'i', 'r',
17+ "a'", "b'", "c'", "d'", "e'", "f'", "h'", "l'",
18+ // eZ80
19+ 'mb',
2220 ],
23- 'operator' => [
21+ 'instruction' => [
2422 // Z80 + GBZ80 + eZ80
25- 'adc', 'add', 'and', 'bit', 'call', 'ccf', 'cp', 'daa', 'dec', 'di', 'djnz', 'ei',
26- 'halt', 'im0', 'im1', 'im2', 'im', 'inc', 'in', 'jp', 'jr', 'ldd',
23+ 'adc', 'add', 'and', 'bit', 'call', 'ccf', 'cp', 'cpl', 'daa', 'dec', 'di', 'djnz', 'ei',
24+ 'halt', 'im', 'inc', 'in', 'jp', 'jr', 'ld', 'ldd', 'ldh', 'ldi',
2725 'nop', 'otir', 'otdr', 'or', 'pop', 'push', 'retn', 'reti', 'ret', 'res',
28- 'rst', 'rrc', 'rr', 'rdc', 'rl', 'sub', 'srl', 'sra', 'sla', 'set', 'scf', 'sbc', 'xor',
26+ 'rst', 'rrc', 'rrca', 'rr', 'rra', 'rlc', 'rlca', 'rl', 'rla', 'sub', 'srl', 'sra', 'sla', 'set', 'scf', 'sbc', 'xor',
2927 // Z80 + GBZ80のみ
30- 'sll', 'ld',
28+ 'sll', 'sl1',
3129 // eZ80のみ
32- 'ind2', 'indmr', 'indrx', 'indm', 'inimr', 'inim', 'ini2r', 'ini2', 'lea', 'mlt',
33- 'otirx', 'otimr', 'otim', 'oti2r', 'otdrx', 'otdmr', 'otdm', 'otd2r', 'out0', 'outd2',
34- 'outi2', 'pea', 'rsmix', 'slp', 'stmix', 'tstio', 'tst', '.is', 'xxx.S', 'xxx.L', '.il', '.sis', '.lil',
30+ 'ind2', 'ind2r', 'indm', 'indmr', 'indr2', 'indrx', 'ini2', 'ini2r', 'inim', 'inimr', 'inirx', 'lea', 'mlt',
31+ 'otd2r', 'otdm', 'otdmr', 'otdrx', 'oti2r', 'otim', 'otimr', 'otirx', 'out0', 'in0', 'outd2', 'outi2',
32+ 'pea', 'rsmix', 'slp', 'stmix', 'tst', 'tstio',
3533 // Z80のみ
36- 'cpdr', 'cpir', 'cpd', 'cpi', 'exx', 'ex', 'outd', 'inir', 'ini', 'indr', 'ind', 'lddr', 'ldir', 'ldi',
37- 'neg', 'outi', 'outdi', 'out', 'rld', 'rrd',
34+ 'cpdr', 'cpir', 'cpd', 'cpi', 'exx', 'ex', 'outd', 'inir', 'ini', 'indr', 'ind', 'lddr', 'ldir',
35+ 'neg', 'outi', 'out', 'rld', 'rrd',
3836 // GBZ80のみ
39- 'outd', 'stop', 'swap',
37+ 'stop', 'swap',
4038 ],
39+ 'keyword' => [
40+ // 条件
41+ 'nz', 'z', 'nc', 'po', 'pe', 'p', 'm',
42+ ],
4143 'directive' => [
42- // 'db', 'dw', 'dd', 'dw',
43- // 'equ', 'global', 'extern',
44- // 'resb', 'resw', 'resd', 'resq',
44+ 'org', 'equ', 'db', 'dw', 'ds', 'defb', 'defw', 'defs', 'defm', 'dm', 'include', 'incbin', 'end', 'macro', 'endm',
45+ 'section', 'rb', 'rw', 'if', 'else', 'endc', 'endif', 'rept', 'endr', 'export', 'assert', 'align', 'charmap',
4546 ],
4647 ];
47-
48- public function __construct() {
49- global $colorPalette;
50-
51- $this->colors = [
52- 'comment' => $colorPalette['dark']['green'],
53- 'directive' => $colorPalette['dark']['pink'],
54- 'label' => $colorPalette['light']['yellow'],
55- 'number' => $colorPalette['light']['green'],
56- 'operator' => $colorPalette['dark']['blue'],
57- 'register' => $colorPalette['dark']['cyan'],
58- 'string' => $colorPalette['medium']['orange'],
59- ];
60- }
61-
62- public function getColors(): array { return $this->colors; }
63- public function getComment(): ?string { return $this->comment; }
64- public function getMultiline(): ?array { return $this->multiline; }
65- public function getTokens(): array { return $this->tokens; }
66-}
48+}
@@ -0,0 +1,127 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+/**
5+ * アセンブリ言語の基本クラス
6+ *
7+ * 種類:instruction(命令)、register(レジスター)、directive(指令)、keyword(サイズ指定等)、label(ラベル)
8+ * 行の最初の単語は、一覧に無くても命令として扱う(ラベルと指令を除く)
9+ */
10+abstract class Assembly extends Language {
11+ protected array $comment = [';'];
12+ protected ?array $multiline = null;
13+ protected array $strings = ['"', "'"];
14+ protected bool $caseInsensitive = true;
15+ protected bool $functionCalls = false;
16+ protected string $digitSep = '';
17+ // 「%rax」(AT&T)や「$t0」(MIPS)の様な接頭辞付きも1つの単語にする
18+ protected string $identifier = '[%$]?[.A-Za-z_][\w.$]*';
19+
20+ /**
21+ * @var string[] 命令・レジスターの接尾辞を外す正規表現(ARMの条件コード等)
22+ * 順番に適用し、途中の結果も全部候補にする
23+ */
24+ protected array $suffixes = [];
25+
26+ /** @var string 数字の接頭辞(ARMの「#」、6502の「#」等) */
27+ protected string $immediatePrefix = '#?';
28+
29+ /** @var bool 「$FF」を16進数とするか(MIPSでは「$」はレジスター) */
30+ protected bool $dollarHex = true;
31+
32+ /** @var bool 「%1010」を2進数とするか(x86では「%」はレジスター・マクロ) */
33+ protected bool $percentBinary = true;
34+
35+ /** @var bool 「%define」等をNASMのマクロとして扱うか */
36+ protected bool $percentMacros = false;
37+
38+ /** @var string[] 命令の前に付く接頭辞(rep、lock等)。次の単語も命令として扱う */
39+ protected array $prefixes = [];
40+
41+ protected function numberPattern(): string {
42+ $alts = [
43+ '0[xX][0-9A-Fa-f]+',
44+ '0[bB][01]+',
45+ '0[oO][0-7]+',
46+ '[0-9][0-9A-Fa-f]*[hH](?![\w.])',
47+ '\d+(?:\.\d+)?(?:[eE][+-]?\d+)?',
48+ ];
49+ if ($this->dollarHex) $alts[] = '\$[0-9A-Fa-f]+(?![\w.])';
50+ if ($this->percentBinary) $alts[] = '%[01]+(?![\w.])';
51+ return $this->immediatePrefix.'-?(?:'.implode('|', $alts).')';
52+ }
53+
54+ public function classify(string $word, array $ctx): ?string {
55+ if ($word === '.' || $word === '$') return null;
56+ $key = strtolower($word);
57+
58+ // ラベル(「名前:」、又は「名前 db …」「名前 = …」の様な定義)
59+ if ($ctx['next'] === ':' && ($ctx['first'] || $ctx['bol'])) return 'label';
60+ if ($ctx['first'] && ($ctx['next'] === '=' || $this->lookup(strtolower($ctx['nextWord'])) === 'directive')) return 'label';
61+
62+ $atOpcode = $ctx['first'] || in_array(strtolower($ctx['prevWord']), $this->prefixes, true);
63+ $class = $this->lookup($key);
64+ if ($class !== null) {
65+ // 命令の位置以外の命令名はシンボルの可能性が高い(例:「jmp add」)
66+ if ($class === 'instruction' && !$atOpcode) return null;
67+ return $class;
68+ }
69+
70+ if ($this->percentMacros && $key[0] === '%' && strlen($key) > 1) return 'preprocessor';
71+ if ($key[0] === '.') {
72+ // 「.section .text」の様に指令の行なら指令、「jne .done」の様に命令の行ならローカルラベルの参照
73+ if ($atOpcode) return 'directive';
74+ $head = strtolower($ctx['lineWord']);
75+ return ($head !== '' && ($head[0] === '.' || $this->lookup($head) === 'directive')) ? 'directive' : null;
76+ }
77+ if ($atOpcode) return 'instruction';
78+ return null;
79+ }
80+
81+ /**
82+ * 単語表・接尾辞・接頭辞を返す(汎用のAsmがまとめる為)
83+ *
84+ * @return array [単語 => 種類, 接尾辞の正規表現, 接頭辞]
85+ */
86+ public function getTables(): array {
87+ return [$this->words, $this->suffixes, $this->prefixes];
88+ }
89+
90+ /**
91+ * 単語を一覧から探す(接頭辞「%」「$」と接尾辞を外した候補も試す)
92+ *
93+ * @param string $key 小文字の単語
94+ * @return string|null 種類
95+ */
96+ protected function lookup(string $key): ?string {
97+ if ($key === '') return null;
98+ $base = ltrim($key, '%$');
99+ foreach ($this->candidates($key) as $c) {
100+ if (!isset($this->words[$c])) continue;
101+ $class = $this->words[$c];
102+ // 接尾辞を外した候補は命令・指令だけ(「v0.4s」の様に「.」で区切った場合はレジスターも)
103+ // そうしないと「hello」→「hel」+「lo」の様な誤判定が起きる
104+ if ($c !== $key && $c !== $base && $class !== 'instruction' && $class !== 'directive' && !str_contains($key, '.')) continue;
105+ return $class;
106+ }
107+ return null;
108+ }
109+
110+ /**
111+ * 接頭辞・接尾辞を外した候補を作る
112+ *
113+ * @param string $key 小文字の単語
114+ * @return array 候補(元の単語が最初)
115+ */
116+ protected function candidates(string $key): array {
117+ $cands = [$key];
118+ if (strlen($key) > 1 && ($key[0] === '%' || $key[0] === '$')) $cands[] = substr($key, 1);
119+ foreach ($this->suffixes as $re) {
120+ foreach ($cands as $c) {
121+ $s = preg_replace($re, '', $c);
122+ if ($s !== null && $s !== '' && $s !== $c) $cands[] = $s;
123+ }
124+ }
125+ return array_values(array_unique($cands));
126+ }
127+}
@@ -1,14 +1,11 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+class C extends Language {
5+ protected bool $preprocessor = true;
6+ protected array $headerDirectives = ['include', 'include_next', 'embed'];
57
6-class C implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = '//';
9- private array $multiline = ['/*', '*/'];
10-
11- private array $tokens = [
8+ protected array $tokens = [
129 'keyword' => [
1310 // C23
1411 'alignas', 'alignof', 'bool', 'constexpr', 'false', 'nullptr', 'static_assert', 'thread_local', 'true', 'typeof', 'typeof_unqual',
@@ -23,7 +20,7 @@
2320 // ISO
2421 'FILE',
2522 ],
26- 'libc' => [
23+ 'builtin' => [
2724 // ISO C
2825 'assert.h' => [ // C89
2926 'assert',
@@ -178,7 +175,7 @@
178175 // C89
179176 '#if', '#elif', '#else', '#endif', '#ifdef', '#ifndef', '#define', '#undef', '#include', '#line', '#error', '#pragma', '#defined',
180177 ],
181- 'typedef' => [
178+ 'type' => [
182179 'errno.h' => [
183180 'errno_t',
184181 ],
@@ -245,33 +242,31 @@
245242 ],
246243 ];
247244
248- public function __construct() {
249- global $colorPalette;
245+ /** @var array よく使う定数(ヘッダーに関係なく色を付ける) */
246+ protected array $constants = [
247+ 'NULL', 'EOF', 'stdin', 'stdout', 'stderr', 'EXIT_SUCCESS', 'EXIT_FAILURE', 'RAND_MAX',
248+ 'CHAR_BIT', 'CHAR_MIN', 'CHAR_MAX', 'SCHAR_MIN', 'SCHAR_MAX', 'UCHAR_MAX', 'SHRT_MIN', 'SHRT_MAX', 'USHRT_MAX',
249+ 'INT_MIN', 'INT_MAX', 'UINT_MAX', 'LONG_MIN', 'LONG_MAX', 'ULONG_MAX', 'LLONG_MIN', 'LLONG_MAX', 'ULLONG_MAX',
250+ 'INT8_MIN', 'INT8_MAX', 'INT16_MIN', 'INT16_MAX', 'INT32_MIN', 'INT32_MAX', 'INT64_MIN', 'INT64_MAX',
251+ 'UINT8_MAX', 'UINT16_MAX', 'UINT32_MAX', 'UINT64_MAX', 'SIZE_MAX', 'PTRDIFF_MIN', 'PTRDIFF_MAX',
252+ 'FLT_MAX', 'FLT_MIN', 'FLT_EPSILON', 'DBL_MAX', 'DBL_MIN', 'DBL_EPSILON', 'HUGE_VAL', 'INFINITY', 'NAN',
253+ 'M_PI', 'M_E', 'SEEK_SET', 'SEEK_CUR', 'SEEK_END', 'BUFSIZ', 'FILENAME_MAX',
254+ '__FILE__', '__LINE__', '__DATE__', '__TIME__', '__func__', '__STDC__', '__STDC_VERSION__',
255+ ];
250256
251- $this->colors = [
252- 'comment' => $colorPalette['dark']['green'],
253- 'keyword' => $colorPalette['dark']['blue'],
254- 'number' => $colorPalette['light']['green'],
255- 'preprocessor' => $colorPalette['medium']['purple'],
256- 'string' => $colorPalette['medium']['orange'],
257- 'typedef' => $colorPalette['dark']['lime'],
258- 'function' => $colorPalette['light']['yellow'],
259- 'libc' => $colorPalette['light']['yellow'],
260- ];
261- }
262-
263- public function getColors(): array { return $this->colors; }
264- public function getComment(): ?string { return $this->comment; }
265- public function getInclude(array $code): ?array {
266- $headers = [];
267- foreach ($code as $c) {
268- $c = htmlspecialchars($c);
269- if (!str_starts_with($c, '#include')) continue;
270- $header = str_replace(['#include ', '&lt;', '&gt;', '&quot;', '&#039;'], '', $c);
271- $headers[] = trim($header);
257+ /**
258+ * #includeされたヘッダーの関数と型だけに色を付ける
259+ * #includeが1つも無い場合(ブログの抜粋等)は全部に色を付ける
260+ */
261+ public function prepare(string $code): void {
262+ $tokens = $this->tokens;
263+ preg_match_all('~^[ \t]*#[ \t]*include[ \t]*[<"]([^>"\n]+)[>"]~m', $code, $m);
264+ if (!empty($m[1])) {
265+ $inc = array_flip(array_map('trim', $m[1]));
266+ foreach (['builtin', 'type'] as $k) {
267+ if (isset($tokens[$k])) $tokens[$k] = array_intersect_key($tokens[$k], $inc);
268+ }
272269 }
273- return $headers;
270+ $this->words = $this->buildWords(['constant' => $this->constants] + $tokens);
274271 }
275- public function getMultiline(): ?array { return $this->multiline; }
276- public function getTokens(): array { return $this->tokens; }
277-}
272+}
@@ -0,0 +1,45 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+class CSharp extends Language {
5+ protected bool $preprocessor = true;
6+ protected string $digitSep = '_';
7+ // 「@class」の様な逐語的識別子
8+ protected string $identifier = '@?[\p{L}_][\p{L}\p{N}_]*';
9+
10+ protected array $rules = [
11+ // 生文字列 """…"""($"""…"""も)
12+ ['re' => '\$*"""(?s:.*?)(?:"""|\z)', 'class' => 'string'],
13+ // 逐語的文字列 @"…"(""でエスケープ、複数行可)、$@"…"、@$"…"
14+ ['re' => '(?:\$@|@\$|@)"(?:[^"]|"")*"?', 'class' => 'string'],
15+ // 補間文字列 $"…"
16+ ['re' => '\$"(?:[^"\\\\\n]|\\\\.)*"?', 'class' => 'string'],
17+ ];
18+
19+ protected array $tokens = [
20+ 'keyword' => [
21+ 'abstract', 'as', 'base', 'break', 'case', 'catch', 'checked', 'class', 'const', 'continue', 'default', 'delegate', 'do', 'else',
22+ 'enum', 'event', 'explicit', 'extern', 'finally', 'fixed', 'for', 'foreach', 'goto', 'if', 'implicit', 'in', 'interface', 'internal',
23+ 'is', 'lock', 'namespace', 'new', 'operator', 'out', 'override', 'params', 'private', 'protected', 'public', 'readonly', 'ref',
24+ 'return', 'sealed', 'sizeof', 'stackalloc', 'static', 'struct', 'switch', 'this', 'throw', 'try', 'typeof', 'unchecked', 'unsafe',
25+ 'using', 'virtual', 'volatile', 'while',
26+ // 文脈依存(「value」「select」「file」等、変数名によく使う単語は除く)
27+ 'alias', 'and', 'async', 'await', 'get', 'global', 'init', 'managed', 'nameof', 'not', 'notnull', 'or', 'partial', 'record',
28+ 'required', 'scoped', 'set', 'unmanaged', 'var', 'when', 'where', 'with', 'yield',
29+ ],
30+ 'constant' => [
31+ 'true', 'false', 'null',
32+ ],
33+ 'type' => [
34+ 'bool', 'byte', 'sbyte', 'char', 'decimal', 'double', 'float', 'int', 'uint', 'nint', 'nuint', 'long', 'ulong', 'short', 'ushort',
35+ 'object', 'string', 'void', 'dynamic',
36+ 'Boolean', 'Byte', 'SByte', 'Char', 'Decimal', 'Double', 'Single', 'Int16', 'Int32', 'Int64', 'Int128', 'UInt16', 'UInt32', 'UInt64',
37+ 'IntPtr', 'UIntPtr', 'Half', 'Object', 'String', 'Guid', 'DateTime', 'TimeSpan', 'Span', 'ReadOnlySpan', 'Memory', 'ReadOnlyMemory',
38+ 'List', 'Dictionary', 'HashSet', 'Queue', 'Stack', 'LinkedList', 'SortedList', 'SortedDictionary', 'Array', 'Tuple', 'ValueTuple',
39+ 'IEnumerable', 'IEnumerator', 'ICollection', 'IList', 'IDictionary', 'IReadOnlyList', 'IDisposable', 'IComparable', 'IEquatable',
40+ 'Task', 'ValueTask', 'Action', 'Func', 'Predicate', 'EventHandler', 'Exception', 'ArgumentException', 'ArgumentNullException',
41+ 'InvalidOperationException', 'NotImplementedException', 'Console', 'Math', 'MathF', 'Convert', 'Enumerable', 'StringBuilder',
42+ 'Thread', 'CancellationToken', 'Stream', 'File', 'Path', 'Directory', 'Random', 'Vector2', 'Vector3', 'Vector4', 'Matrix4x4', 'Quaternion',
43+ ],
44+ ];
45+}
@@ -1,66 +1,74 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+class Cpp extends C {
5+ protected array $headerDirectives = ['include', 'include_next', 'embed', 'import'];
56
6-class Cpp implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = '//';
9- private array $multiline = ['/*', '*/'];
7+ protected array $rules = [
8+ // 生文字列 R"区切り(…)区切り"
9+ ['re' => '(?:u8|[uUL])?R"(?<rawdelim>[^()\\\\\s]{0,16})\((?s:.*?)\)\k<rawdelim>"', 'class' => 'string'],
10+ // 属性 [[nodiscard]]
11+ ['re' => '\[\[[^\]\n]*\]\]', 'class' => 'attribute'],
12+ ];
1013
11- private array $tokens = [
14+ /** @var array C++のトークン(Cの関数・型は prepare() で追加する) */
15+ protected array $cppTokens = [
1216 'keyword' => [
1317 // C++26
14- 'contract_assert', 'pre', 'post', 'trivially_reallocate_if_eligible', 'replaceable_if_eligible',
18+ 'contract_assert', 'pre', 'post', 'trivially_relocatable_if_eligible', 'replaceable_if_eligible',
1519 // C++20
1620 'char8_t', 'concept', 'consteval', 'constinit', 'co_await', 'co_return', 'co_yield', 'import', 'module', 'requires',
1721 // C++11
1822 'alignas', 'alignof', 'char16_t', 'char32_t', 'constexpr', 'decltype', 'final', 'noexcept', 'nullptr', 'override', 'static_assert', 'thread_local',
19- // C++99
23+ // C++98
2024 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', 'bool', 'break', 'case', 'catch', 'char', 'class', 'compl', 'const', 'const_cast', 'continue',
2125 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline',
22- 'int', 'long', 'mutable', 'namespace', 'new', 'not', 'not_eq', 'operator', 'or', 'or_eq', 'private', 'protected', 'public', 'reflexpr', 'register',
23- 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try',
26+ 'int', 'long', 'mutable', 'namespace', 'new', 'not', 'not_eq', 'operator', 'or', 'or_eq', 'private', 'protected', 'public', 'register',
27+ 'reinterpret_cast', 'return', 'short', 'signed', 'sizeof', 'static', 'static_cast', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try',
2428 'typedef', 'typeid', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t', 'while', 'xor', 'xor_eq',
2529 // TMTS
2630 'atomic_cancel', 'atomic_commit', 'atomic_noexcept', 'synchronized', 'transaction_safe', 'transaction_safe_dynamic',
2731 ],
28- 'preprocessor' => [
29- // C++26
30- '#embed', '__has_embed',
31- // C++23
32- '#elifdef', '#elifndef', '#warning',
33- // C++20
34- '#__has_cpp_attribute', '#export', '#import', '#module',
35- // C++17
36- '#__has_include',
37- // C++11
38- '#_Pragma',
39- // C++99
40- '#if', '#elif', '#else', '#endif', '#ifdef', '#ifndef', '#define', '#undef', '#include', '#line', '#error', '#pragma', '#defined',
32+ 'type' => [
33+ 'std', 'chrono', 'filesystem', 'ranges', 'views', 'this_thread', 'numbers', 'execution', 'pmr',
34+ 'string', 'wstring', 'u8string', 'u16string', 'u32string', 'string_view', 'wstring_view',
35+ 'vector', 'array', 'deque', 'list', 'forward_list', 'map', 'multimap', 'set', 'multiset',
36+ 'unordered_map', 'unordered_multimap', 'unordered_set', 'unordered_multiset', 'flat_map', 'flat_set',
37+ 'stack', 'queue', 'priority_queue', 'span', 'mdspan', 'bitset', 'valarray',
38+ 'pair', 'tuple', 'optional', 'variant', 'any', 'expected', 'unexpected', 'monostate', 'nullopt_t', 'nullptr_t', 'byte',
39+ 'unique_ptr', 'shared_ptr', 'weak_ptr', 'function', 'move_only_function', 'reference_wrapper', 'initializer_list',
40+ 'thread', 'jthread', 'mutex', 'recursive_mutex', 'shared_mutex', 'lock_guard', 'unique_lock', 'scoped_lock', 'condition_variable',
41+ 'atomic', 'atomic_flag', 'future', 'promise', 'stop_token', 'stop_source',
42+ 'istream', 'ostream', 'iostream', 'ifstream', 'ofstream', 'fstream', 'stringstream', 'istringstream', 'ostringstream',
43+ 'size_t', 'ptrdiff_t', 'intptr_t', 'uintptr_t', 'int8_t', 'int16_t', 'int32_t', 'int64_t', 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
44+ 'float16_t', 'float32_t', 'float64_t', 'float128_t', 'bfloat16_t', 'max_align_t',
45+ 'exception', 'runtime_error', 'logic_error', 'invalid_argument', 'out_of_range', 'bad_alloc', 'error_code',
46+ 'iterator', 'const_iterator', 'reverse_iterator', 'allocator', 'hash', 'less', 'greater', 'equal_to',
47+ 'duration', 'time_point', 'steady_clock', 'system_clock', 'high_resolution_clock', 'path', 'regex', 'complex', 'ratio',
4148 ],
42- 'typedef' => [
43- 'int8_t', 'int16_t', 'int32_t', 'int64_t',
44- 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
45- 'size_t',
49+ 'builtin' => [
50+ 'cout', 'cin', 'cerr', 'clog', 'wcout', 'wcin', 'endl', 'flush', 'ws', 'getline', 'print', 'println', 'format',
51+ 'move', 'forward', 'swap', 'exchange', 'make_unique', 'make_shared', 'make_pair', 'make_tuple', 'make_optional', 'tie', 'get',
52+ 'static_pointer_cast', 'dynamic_pointer_cast', 'reinterpret_pointer_cast', 'addressof', 'launder', 'bit_cast', 'to_underlying',
53+ 'sort', 'stable_sort', 'find', 'find_if', 'count', 'count_if', 'copy', 'copy_if', 'fill', 'transform', 'accumulate', 'reduce',
54+ 'for_each', 'remove', 'remove_if', 'reverse', 'unique', 'min', 'max', 'clamp', 'min_element', 'max_element', 'lower_bound', 'upper_bound',
55+ 'all_of', 'any_of', 'none_of', 'iota', 'to_string', 'stoi', 'stol', 'stof', 'stod', 'invoke', 'apply', 'visit', 'holds_alternative',
56+ 'declval', 'is_same_v', 'is_integral_v', 'is_floating_point_v', 'enable_if_t', 'conditional_t', 'decay_t', 'remove_reference_t',
57+ 'numeric_limits', 'begin', 'end', 'cbegin', 'cend', 'rbegin', 'rend', 'size', 'ssize', 'empty', 'data', 'unreachable',
4658 ],
4759 ];
4860
49- public function __construct() {
50- global $colorPalette;
51-
52- $this->colors = [
53- 'comment' => $colorPalette['dark']['green'],
54- 'keyword' => $colorPalette['dark']['blue'],
55- 'number' => $colorPalette['light']['green'],
56- 'preprocessor' => $colorPalette['medium']['purple'],
57- 'string' => $colorPalette['medium']['orange'],
58- 'typedef' => $colorPalette['dark']['lime'],
59- ];
61+ /**
62+ * C++のトークンに、Cのライブラリ関数と型を加える(ヘッダーでは絞らない)
63+ */
64+ public function prepare(string $code): void {
65+ $tokens = $this->cppTokens;
66+ foreach (['builtin', 'type'] as $k) {
67+ foreach ($this->tokens[$k] ?? [] as $list) {
68+ foreach ($list as $w) $tokens[$k][] = $w;
69+ }
70+ }
71+ $tokens['preprocessor'] = $this->tokens['preprocessor'] ?? [];
72+ $this->words = $this->buildWords(['constant' => $this->constants] + $tokens);
6073 }
61-
62- public function getColors(): array { return $this->colors; }
63- public function getComment(): ?string { return $this->comment; }
64- public function getMultiline(): ?array { return $this->multiline; }
65- public function getTokens(): array { return $this->tokens; }
66-}
74+}
@@ -1,65 +1,100 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+class Glsl extends Language {
5+ protected bool $preprocessor = true;
6+ protected array $headerDirectives = ['include'];
7+ protected array $strings = ['"'];
8+ protected string $digitSep = '';
59
6-class Glsl implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = '//';
9-
10- private array $tokens = [
11- 'function' => [
12- 'asin', 'acos', 'atan', 'cos', 'degrees', 'radians', 'sin', 'tan',
13- 'exp', 'exp2', 'inversesqrt', 'log', 'log2', 'pow', 'sqrt',
14- 'abs', 'ceil', 'clamp', 'floor', 'fract', 'max', 'min', 'mix', 'mod', 'sign', 'smoothstep', 'step',
15- 'cross', 'distance', 'dot', 'faceforward', 'ftransform', 'length', 'normalize', 'reflect', 'refract',
16- 'dFdx', 'dFdy', 'fwidth',
17- 'matrixCompMulti',
18- 'all', 'any', 'equal', 'greaterThan', 'greaterThanEqual', 'lessThan', 'lessThanEqual', 'not', 'notEqual',
19- 'texture1D', 'texture1DProj', 'texture2D', 'texture2DProj', 'texture3D', 'texture3DProj', 'textureCube',
20- 'texture1DLod', 'texture1DProjLod', 'texture2DLod', 'texture2DProjLod', 'texture3DProjLod',
21- 'shadow1DLog', 'shadow1DProjLog', 'shadow2DLog', 'shadow2DProjLog',
22- 'noise1', 'noise2', 'noise3', 'noise4',
23- ],
10+ protected array $tokens = [
2411 'keyword' => [
25- 'attribute', 'bool', 'bvec2', 'bvec3', 'bvec4', 'const', 'float', 'genType', 'in', 'int', 'inout', 'ivec2', 'ivec3', 'ivec4', 'mat2', 'mat3', 'mat4', 'out',
26- 'sampler1D', 'sampler1DShadow', 'sampler2D', 'sampler2DShadow', 'sampler3D', 'samplerCube', 'struct', 'uniform', 'varying', 'vec2', 'vec3', 'vec4', 'void',
12+ 'attribute', 'const', 'uniform', 'varying', 'buffer', 'shared', 'layout', 'centroid', 'flat', 'smooth', 'noperspective',
13+ 'patch', 'sample', 'break', 'continue', 'do', 'for', 'while', 'switch', 'case', 'default', 'if', 'else', 'subroutine',
14+ 'in', 'out', 'inout', 'invariant', 'precise', 'discard', 'return', 'struct', 'precision', 'highp', 'mediump', 'lowp',
15+ 'coherent', 'volatile', 'restrict', 'readonly', 'writeonly', 'demote', 'terminateInvocation', 'rayPayloadEXT', 'hitAttributeEXT',
16+ 'callableDataEXT', 'taskPayloadSharedEXT', 'perprimitiveEXT',
17+ // layout()の中(「offset」「points」等、変数名にも使う単語は除く)
18+ 'location', 'binding', 'set', 'push_constant', 'std140', 'std430', 'local_size_x', 'local_size_y', 'local_size_z',
19+ 'input_attachment_index', 'constant_id', 'row_major', 'column_major', 'early_fragment_tests', 'max_vertices',
2720 ],
28- 'preprocessor' => [
29- '#', '#define', '#elif', '#else', '#endif', '#error', '#extension', '#if', '#ifdef', '#ifndef', '#line', '#pragma', '#version', '#undef',
30- '__FILE__', '__LINE__', '__VERSION__',
21+ 'constant' => [
22+ 'true', 'false', '__FILE__', '__LINE__', '__VERSION__', 'GL_ES',
3123 ],
32- 'variables' => [
33- // Vertex
34- 'gl_ClipVertex', 'gl_PointSize', 'gl_Position', 'gl_Color', 'gl_FogCoord', 'gl_Normal', 'gl_SecondaryColor', 'gl_Vertex',
35- 'gl_MultiTexCoord0', 'gl_MultiTexCoord1', 'gl_MultiTexCoord2', 'gl_MultiTexCoord3', 'gl_MultiTexCoord4', 'gl_MultiTexCoord5', 'gl_MultiTexCoord6', 'gl_MultiTexCoord7',
36- 'gl_BackColor', 'gl_BackSecondaryColor', 'gl_FogFragCoord', 'gl_FrontColor', 'gl_FrontSecondaryColor', 'gl_TexCoord',
37- // Fragment
38- 'gl_FragColor', 'gl_FragCoord', 'gl_FragData', 'gl_FragDepth', 'gl_FrontFacing', 'gl_MaxDrawBuffers',
39- // Constant
40- 'gl_MaxClipPlanes', 'gl_MaxCombinedTextureImageUnits', 'gl_MaxDrawBuffers', 'gl_MaxFragmentUniformComponents', 'gl_MaxLights', 'gl_MaxTextureCoords',
41- 'gl_MaxTextureImageUnits', 'gl_MaxTextureUnits', 'gl_MaxVaryingFloats', 'gl_MaxVertexAttribs', 'gl_MaxVertexUniformComponents',
42- // Uniform
43- 'gl_ClipPlane', 'gl_DepthRange', 'gl_DepthRangeParameters', 'gl_EyePlane', 'gl_Fog', 'gl_FogParameters', 'gl_LightModel', 'gl_LightModelParameters',
44- 'gl_LightModelProducts', 'gl_BackLightModelProduct', 'gl_FrontLightModelProduct', 'gl_LightProducts', 'gl_BackLightProduct', 'gl_FrontLightProduct',
45- 'gl_LightSource', 'gl_LightSourceParameters', 'gl_MaterialParameters', 'gl_BackMaterial', 'gl_FrontMaterial',
46- 'gl_ModelViewMatrix', 'gl_ModelViewMatrixInverse', 'gl_ModelViewMatrixTranspose', 'gl_ModelViewMatrixInverseTranspose',
47- 'gl_ModelViewProjectionMatrix', 'gl_ModelViewProjectionMatrixInverse', 'gl_ModelViewProjectionMatrixTranspose', 'gl_ModelViewProjectionMatrixInverseTranspose',
48- 'gl_ObjectPlane', 'gl_Point', 'gl_PointParameters',
49- 'gl_ProjectionMatrix', 'gl_ProjectionMatrixInverse', 'gl_ProjectionMatrixTranspose', 'gl_ProjectionMatrixInverseTranspose', 'gl_NormalMatrix', 'gl_NormalScale',
50- 'gl_TextureEnvColor', 'gl_TextureMatrix', 'gl_TextureMatrixInverse', 'gl_TextureMatrixTranspose', 'gl_TextureMatrixInverseTranspose',
24+ 'type' => [
25+ 'void', 'bool', 'int', 'uint', 'float', 'double', 'atomic_uint', 'genType', 'genIType', 'genUType', 'genBType', 'genDType',
26+ 'int8_t', 'int16_t', 'int32_t', 'int64_t', 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t', 'float16_t', 'float32_t', 'float64_t',
27+ 'accelerationStructureEXT', 'rayQueryEXT',
28+ 'subpassInput', 'subpassInputMS', 'isubpassInput', 'isubpassInputMS', 'usubpassInput', 'usubpassInputMS', 'sampler', 'samplerShadow',
5129 ],
30+ 'builtin' => [
31+ // 角度・三角関数
32+ 'radians', 'degrees', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh',
33+ // 指数
34+ 'pow', 'exp', 'log', 'exp2', 'log2', 'sqrt', 'inversesqrt',
35+ // 共通
36+ 'abs', 'sign', 'floor', 'trunc', 'round', 'roundEven', 'ceil', 'fract', 'mod', 'modf', 'min', 'max', 'clamp', 'mix', 'step', 'smoothstep',
37+ 'isnan', 'isinf', 'floatBitsToInt', 'floatBitsToUint', 'intBitsToFloat', 'uintBitsToFloat', 'fma', 'frexp', 'ldexp',
38+ 'packUnorm2x16', 'packSnorm2x16', 'packUnorm4x8', 'packSnorm4x8', 'unpackUnorm2x16', 'unpackSnorm2x16', 'unpackUnorm4x8', 'unpackSnorm4x8',
39+ 'packHalf2x16', 'unpackHalf2x16', 'packDouble2x32', 'unpackDouble2x32',
40+ // 幾何
41+ 'length', 'distance', 'dot', 'cross', 'normalize', 'faceforward', 'reflect', 'refract', 'ftransform',
42+ // 行列・ベクトル比較
43+ 'matrixCompMult', 'outerProduct', 'transpose', 'determinant', 'inverse',
44+ 'lessThan', 'lessThanEqual', 'greaterThan', 'greaterThanEqual', 'equal', 'notEqual', 'any', 'all', 'not',
45+ // 整数
46+ 'uaddCarry', 'usubBorrow', 'umulExtended', 'imulExtended', 'bitfieldExtract', 'bitfieldInsert', 'bitfieldReverse', 'bitCount', 'findLSB', 'findMSB',
47+ // テクスチャ
48+ 'textureSize', 'textureQueryLod', 'textureQueryLevels', 'textureSamples', 'texture', 'textureProj', 'textureLod', 'textureOffset',
49+ 'texelFetch', 'texelFetchOffset', 'textureProjOffset', 'textureLodOffset', 'textureProjLod', 'textureProjLodOffset',
50+ 'textureGrad', 'textureGradOffset', 'textureProjGrad', 'textureProjGradOffset', 'textureGather', 'textureGatherOffset', 'textureGatherOffsets',
51+ 'texture1D', 'texture1DProj', 'texture1DLod', 'texture1DProjLod', 'texture2D', 'texture2DProj', 'texture2DLod', 'texture2DProjLod',
52+ 'texture3D', 'texture3DProj', 'texture3DLod', 'texture3DProjLod', 'textureCube', 'textureCubeLod',
53+ 'shadow1D', 'shadow1DProj', 'shadow1DLod', 'shadow1DProjLod', 'shadow2D', 'shadow2DProj', 'shadow2DLod', 'shadow2DProjLod',
54+ // 原子操作・画像
55+ 'atomicCounterIncrement', 'atomicCounterDecrement', 'atomicCounter', 'atomicAdd', 'atomicMin', 'atomicMax', 'atomicAnd', 'atomicOr',
56+ 'atomicXor', 'atomicExchange', 'atomicCompSwap', 'imageSize', 'imageSamples', 'imageLoad', 'imageStore', 'imageAtomicAdd',
57+ 'imageAtomicMin', 'imageAtomicMax', 'imageAtomicAnd', 'imageAtomicOr', 'imageAtomicXor', 'imageAtomicExchange', 'imageAtomicCompSwap',
58+ // 微分・補間・ジオメトリ・同期
59+ 'dFdx', 'dFdy', 'dFdxFine', 'dFdyFine', 'dFdxCoarse', 'dFdyCoarse', 'fwidth', 'fwidthFine', 'fwidthCoarse',
60+ 'interpolateAtCentroid', 'interpolateAtSample', 'interpolateAtOffset', 'EmitStreamVertex', 'EndStreamPrimitive', 'EmitVertex', 'EndPrimitive',
61+ 'barrier', 'memoryBarrier', 'memoryBarrierAtomicCounter', 'memoryBarrierBuffer', 'memoryBarrierShared', 'memoryBarrierImage', 'groupMemoryBarrier',
62+ 'subpassLoad', 'noise1', 'noise2', 'noise3', 'noise4',
63+ // サブグループ・レイトレーシング
64+ 'subgroupBarrier', 'subgroupElect', 'subgroupAll', 'subgroupAny', 'subgroupBroadcast', 'subgroupBroadcastFirst', 'subgroupBallot',
65+ 'subgroupAdd', 'subgroupMul', 'subgroupMin', 'subgroupMax', 'subgroupShuffle', 'subgroupShuffleXor',
66+ 'traceRayEXT', 'reportIntersectionEXT', 'executeCallableEXT', 'ignoreIntersectionEXT', 'terminateRayEXT', 'EmitMeshTasksEXT', 'SetMeshOutputsEXT',
67+ ],
5268 ];
5369
54- public function __construct() {
55- global $colorPalette;
56-
57- $this->colors = [
58- ];
70+ public function prepare(string $code): void {
71+ $tokens = $this->tokens;
72+ // ベクトル・行列
73+ foreach (['vec', 'ivec', 'uvec', 'bvec', 'dvec', 'i8vec', 'u8vec', 'i16vec', 'u16vec', 'i64vec', 'u64vec', 'f16vec', 'f64vec'] as $v) {
74+ for ($i = 2; $i <= 4; $i++) $tokens['type'][] = "{$v}{$i}";
75+ }
76+ foreach (['mat', 'dmat', 'f16mat', 'f64mat'] as $m) {
77+ for ($i = 2; $i <= 4; $i++) {
78+ $tokens['type'][] = "{$m}{$i}";
79+ for ($j = 2; $j <= 4; $j++) $tokens['type'][] = "{$m}{$i}x{$j}";
80+ }
81+ }
82+ // サンプラー・テクスチャ・画像
83+ $dims = ['1D', '2D', '3D', 'Cube', '2DRect', '1DArray', '2DArray', 'CubeArray', 'Buffer', '2DMS', '2DMSArray'];
84+ foreach (['', 'i', 'u'] as $p) {
85+ foreach ($dims as $d) {
86+ $tokens['type'][] = "{$p}sampler{$d}";
87+ $tokens['type'][] = "{$p}texture{$d}";
88+ $tokens['type'][] = "{$p}image{$d}";
89+ }
90+ }
91+ foreach (['1D', '2D', 'Cube', '2DRect', '1DArray', '2DArray', 'CubeArray'] as $d) $tokens['type'][] = "sampler{$d}Shadow";
92+ $this->words = $this->buildWords($tokens);
5993 }
6094
61- public function getColors(): array { return $this->colors; }
62- public function getComment(): ?string { return $this->comment; }
63- public function getMultiline(): ?array { return $this->multiline; }
64- public function getTokens(): array { return $this->tokens; }
65-}
95+ public function classify(string $word, array $ctx): ?string {
96+ // 組み込み変数(gl_Position, gl_GlobalInvocationID等)
97+ if (str_starts_with($word, 'gl_')) return 'variable';
98+ return parent::classify($word, $ctx);
99+ }
100+}
@@ -1,40 +1,28 @@
11 <?php
22 namespace Std\Lib\Syntax;
33
4-use Std\Lib\Syntax\SyntaxInterface;
4+class Go extends Language {
5+ protected string $digitSep = '_';
56
6-class Go implements SyntaxInterface {
7- private array $colors = [];
8- private string $comment = '//';
7+ protected array $rules = [
8+ // 生文字列 `…`(複数行可)
9+ ['re' => '`[^`]*`?', 'class' => 'string'],
10+ ];
911
10- private array $tokens = [
12+ protected array $tokens = [
1113 'keyword' => [
1214 'break', 'case', 'chan', 'const', 'continue', 'default', 'defer', 'else', 'fallthrough', 'for', 'func', 'go', 'goto', 'if', 'import', 'interface', 'map',
1315 'package', 'range', 'return', 'select', 'struct', 'switch', 'type', 'var',
14- 'uint8', 'uint16', 'uint32', 'uint64',
15- 'int8', 'int16', 'int32', 'int64',
16- 'float32', 'float64',
17- 'complex64', 'complex128',
18- 'byte', 'rune', 'string', 'bool',
19- 'uint', 'int', 'uintptr',
2016 ],
21- 'operator' => [
22- '+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '&^', '<', '>', '=', '!', '~', '...', ',', '.', ';', ':',
23- '{', '}', '(', ')', '[', ']',
24- '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=', '&^=', '!=', '<=', '>=', ';=',
25- '&&', '||', '<-', '++', '--', '==',
17+ 'constant' => [
18+ 'true', 'false', 'iota', 'nil',
2619 ],
20+ 'type' => [
21+ 'uint8', 'uint16', 'uint32', 'uint64', 'int8', 'int16', 'int32', 'int64', 'float32', 'float64', 'complex64', 'complex128',
22+ 'byte', 'rune', 'string', 'bool', 'uint', 'int', 'uintptr', 'error', 'any', 'comparable',
23+ ],
24+ 'builtin' => [
25+ 'append', 'cap', 'clear', 'close', 'complex', 'copy', 'delete', 'imag', 'len', 'make', 'max', 'min', 'new', 'panic', 'print', 'println', 'real', 'recover',
26+ ],
2727 ];
28-
29- public function __construct() {
30- global $colorPalette;
31-
32- $this->colors = [
33- ];
34- }
35-
36- public function getColors(): array { return $this->colors; }
37- public function getComment(): ?string { return $this->comment; }
38- public function getMultiline(): ?array { return $this->multiline; }
39- public function getTokens(): array { return $this->tokens; }
40-}
28+}
@@ -0,0 +1,87 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+class Hlsl extends Language {
5+ protected bool $preprocessor = true;
6+ protected array $headerDirectives = ['include'];
7+ protected array $strings = ['"'];
8+ protected string $digitSep = '';
9+
10+ protected array $tokens = [
11+ 'keyword' => [
12+ 'if', 'else', 'for', 'while', 'do', 'break', 'continue', 'discard', 'return', 'switch', 'case', 'default',
13+ 'struct', 'class', 'interface', 'namespace', 'typedef', 'cbuffer', 'tbuffer', 'register', 'packoffset', 'static', 'const',
14+ 'uniform', 'volatile', 'extern', 'inline', 'export', 'in', 'out', 'inout', 'groupshared', 'nointerpolation', 'linear',
15+ 'centroid', 'noperspective', 'sample', 'precise', 'row_major', 'column_major', 'snorm', 'unorm', 'globallycoherent',
16+ 'shared', 'template', 'typename', 'using', 'sizeof', 'this', 'enum', 'operator', 'payload', 'vertices', 'indices', 'primitives',
17+ ],
18+ 'constant' => [
19+ 'true', 'false',
20+ ],
21+ 'type' => [
22+ 'void', 'vector', 'matrix', 'string',
23+ 'Texture1D', 'Texture1DArray', 'Texture2D', 'Texture2DArray', 'Texture2DMS', 'Texture2DMSArray', 'Texture3D', 'TextureCube', 'TextureCubeArray',
24+ 'RWTexture1D', 'RWTexture1DArray', 'RWTexture2D', 'RWTexture2DArray', 'RWTexture3D', 'RWTexture2DMS', 'RWTexture2DMSArray',
25+ 'RasterizerOrderedTexture1D', 'RasterizerOrderedTexture2D', 'RasterizerOrderedTexture3D', 'RasterizerOrderedBuffer',
26+ 'FeedbackTexture2D', 'FeedbackTexture2DArray',
27+ 'Buffer', 'RWBuffer', 'StructuredBuffer', 'RWStructuredBuffer', 'ByteAddressBuffer', 'RWByteAddressBuffer',
28+ 'AppendStructuredBuffer', 'ConsumeStructuredBuffer', 'ConstantBuffer', 'TextureBuffer',
29+ 'SamplerState', 'SamplerComparisonState', 'sampler', 'sampler1D', 'sampler2D', 'sampler3D', 'samplerCUBE',
30+ 'RaytracingAccelerationStructure', 'RayDesc', 'RayQuery', 'BuiltInTriangleIntersectionAttributes',
31+ 'InputPatch', 'OutputPatch', 'PointStream', 'LineStream', 'TriangleStream',
32+ ],
33+ 'builtin' => [
34+ 'abort', 'abs', 'acos', 'all', 'AllMemoryBarrier', 'AllMemoryBarrierWithGroupSync', 'any', 'asdouble', 'asfloat', 'asfloat16', 'asin', 'asint',
35+ 'asint16', 'asuint', 'asuint16', 'atan', 'atan2', 'ceil', 'CheckAccessFullyMapped', 'clamp', 'clip', 'cos', 'cosh', 'countbits', 'cross',
36+ 'D3DCOLORtoUBYTE4', 'ddx', 'ddx_coarse', 'ddx_fine', 'ddy', 'ddy_coarse', 'ddy_fine', 'degrees', 'determinant', 'DeviceMemoryBarrier',
37+ 'DeviceMemoryBarrierWithGroupSync', 'distance', 'dot', 'dst', 'errorf', 'EvaluateAttributeAtCentroid', 'EvaluateAttributeAtSample',
38+ 'EvaluateAttributeSnapped', 'exp', 'exp2', 'f16tof32', 'f32tof16', 'faceforward', 'firstbithigh', 'firstbitlow', 'floor', 'fma', 'fmod',
39+ 'frac', 'frexp', 'fwidth', 'GetRenderTargetSampleCount', 'GetRenderTargetSamplePosition', 'GroupMemoryBarrier', 'GroupMemoryBarrierWithGroupSync',
40+ 'InterlockedAdd', 'InterlockedAnd', 'InterlockedCompareExchange', 'InterlockedCompareStore', 'InterlockedExchange', 'InterlockedMax',
41+ 'InterlockedMin', 'InterlockedOr', 'InterlockedXor', 'isfinite', 'isinf', 'isnan', 'ldexp', 'length', 'lerp', 'lit', 'log', 'log10', 'log2',
42+ 'mad', 'max', 'min', 'modf', 'msad4', 'mul', 'noise', 'normalize', 'pow', 'printf', 'radians', 'rcp', 'reflect', 'refract', 'reversebits',
43+ 'round', 'rsqrt', 'saturate', 'sign', 'sin', 'sincos', 'sinh', 'smoothstep', 'sqrt', 'step', 'tan', 'tanh', 'tex1D', 'tex2D', 'tex3D', 'texCUBE',
44+ 'transpose', 'trunc', 'NonUniformResourceIndex', 'select', 'and', 'or',
45+ 'WaveActiveAllEqual', 'WaveActiveAllTrue', 'WaveActiveAnyTrue', 'WaveActiveBallot', 'WaveActiveBitAnd', 'WaveActiveBitOr', 'WaveActiveBitXor',
46+ 'WaveActiveCountBits', 'WaveActiveMax', 'WaveActiveMin', 'WaveActiveProduct', 'WaveActiveSum', 'WaveGetLaneCount', 'WaveGetLaneIndex',
47+ 'WaveIsFirstLane', 'WavePrefixCountBits', 'WavePrefixProduct', 'WavePrefixSum', 'WaveReadLaneAt', 'WaveReadLaneFirst',
48+ 'QuadReadAcrossDiagonal', 'QuadReadAcrossX', 'QuadReadAcrossY', 'QuadReadLaneAt',
49+ 'TraceRay', 'ReportHit', 'IgnoreHit', 'AcceptHitAndEndSearch', 'CallShader', 'DispatchRaysIndex', 'DispatchRaysDimensions',
50+ 'WorldRayOrigin', 'WorldRayDirection', 'ObjectRayOrigin', 'ObjectRayDirection', 'RayTMin', 'RayTCurrent', 'RayFlags', 'InstanceIndex',
51+ 'InstanceID', 'GeometryIndex', 'PrimitiveIndex', 'HitKind', 'ObjectToWorld3x4', 'ObjectToWorld4x3', 'WorldToObject3x4', 'WorldToObject4x3',
52+ 'SetMeshOutputCounts', 'DispatchMesh',
53+ // リソースのメソッド
54+ 'Sample', 'SampleLevel', 'SampleGrad', 'SampleBias', 'SampleCmp', 'SampleCmpLevel', 'SampleCmpLevelZero', 'Load', 'Store', 'GetDimensions',
55+ 'Gather', 'GatherRed', 'GatherGreen', 'GatherBlue', 'GatherAlpha', 'GatherCmp', 'CalculateLevelOfDetail', 'Append', 'Consume',
56+ 'IncrementCounter', 'DecrementCounter', 'Load2', 'Load3', 'Load4', 'Store2', 'Store3', 'Store4',
57+ ],
58+ ];
59+
60+ /** @var string[] 「[」の直後だけ属性として扱う名前(「loop」「branch」等は普通の単語でもある) */
61+ protected array $attributes = [
62+ 'numthreads', 'unroll', 'loop', 'branch', 'flatten', 'fastopt', 'allow_uav_condition', 'call', 'forcecase', 'earlydepthstencil',
63+ 'maxvertexcount', 'domain', 'partitioning', 'outputtopology', 'outputcontrolpoints', 'patchconstantfunc', 'maxtessfactor',
64+ 'instance', 'RootSignature', 'shader', 'WaveSize', 'NodeLaunch', 'NodeDispatchGrid', 'NodeMaxDispatchGrid', 'NumThreads', 'vk',
65+ ];
66+
67+ /** @var string[] スカラー型(ベクトル・行列型を作る元) */
68+ protected array $scalars = [
69+ 'bool', 'int', 'uint', 'dword', 'half', 'float', 'double', 'min16float', 'min10float', 'min16int', 'min12int', 'min16uint',
70+ 'int16_t', 'uint16_t', 'int32_t', 'uint32_t', 'int64_t', 'uint64_t', 'float16_t', 'float32_t', 'float64_t',
71+ ];
72+
73+ public function prepare(string $code): void {
74+ $tokens = $this->tokens;
75+ foreach (self::vectorTypes($this->scalars) as $t) $tokens['type'][] = $t;
76+ $this->words = $this->buildWords($tokens);
77+ }
78+
79+ public function classify(string $word, array $ctx): ?string {
80+ // セマンティクス(: SV_Position, : TEXCOORD0)
81+ if (str_starts_with($word, 'SV_')) return 'variable';
82+ if ($ctx['prev'] === ':' && preg_match('/^[A-Z][A-Z0-9_]*$/', $word)) return 'variable';
83+ // 属性([numthreads(8, 8, 1)])
84+ if ($ctx['prev'] === '[' && in_array($word, $this->attributes, true)) return 'attribute';
85+ return parent::classify($word, $ctx);
86+ }
87+}
@@ -0,0 +1,155 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+/**
5+ * 言語定義の基本クラス
6+ * C系の言語の既定値を持つので、各言語は違う所だけを上書きする
7+ */
8+abstract 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+}
@@ -0,0 +1,62 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+/**
5+ * Objective-C(Cの上位互換)
6+ */
7+class ObjC extends C {
8+ protected array $headerDirectives = ['include', 'include_next', 'import'];
9+
10+ protected array $rules = [
11+ // NSString @"…"
12+ ['re' => '@"(?:[^"\\\\\n]|\\\\.)*"?', 'class' => 'string'],
13+ // @interface, @property, @autoreleasepool等
14+ ['re' => '@[A-Za-z_]\w*', 'class' => 'keyword'],
15+ // リテラル @[…] @{…} @(…)
16+ ['re' => '@(?=[\[{(])', 'class' => 'keyword'],
17+ ];
18+
19+ /** @var array Cに加えるObjective-Cのトークン */
20+ protected array $objcTokens = [
21+ 'keyword' => [
22+ 'self', 'super', 'id', 'instancetype', 'Class', 'SEL', 'IMP', 'BOOL', 'in', 'out', 'inout', 'oneway', 'bycopy', 'byref',
23+ 'nonatomic', 'atomic', 'strong', 'weak', 'copy', 'assign', 'retain', 'readonly', 'readwrite', 'getter', 'setter',
24+ 'nullable', 'nonnull', 'null_resettable', '_Nullable', '_Nonnull', '_Null_unspecified', '__nullable', '__nonnull',
25+ '__bridge', '__bridge_transfer', '__bridge_retained', '__block', '__weak', '__strong', '__unsafe_unretained', '__autoreleasing', '__kindof',
26+ ],
27+ 'constant' => [
28+ 'YES', 'NO', 'nil', 'Nil', 'NULL',
29+ ],
30+ 'type' => [
31+ 'NSObject', 'NSString', 'NSMutableString', 'NSArray', 'NSMutableArray', 'NSDictionary', 'NSMutableDictionary', 'NSSet', 'NSMutableSet',
32+ 'NSNumber', 'NSValue', 'NSData', 'NSMutableData', 'NSDate', 'NSURL', 'NSError', 'NSException', 'NSInteger', 'NSUInteger', 'NSTimeInterval',
33+ 'NSRange', 'NSZone', 'NSNotification', 'NSNotificationCenter', 'NSUserDefaults', 'NSFileManager', 'NSBundle', 'NSThread', 'NSTimer',
34+ 'NSRunLoop', 'NSAutoreleasePool', 'NSProcessInfo', 'NSApplication', 'NSWindow', 'NSView', 'NSViewController', 'NSColor', 'NSImage',
35+ 'UIApplication', 'UIView', 'UIViewController', 'UIWindow', 'UIColor', 'UIImage', 'UILabel', 'UIButton',
36+ 'CGFloat', 'CGRect', 'CGPoint', 'CGSize', 'CGContextRef', 'CFStringRef', 'CFTypeRef', 'dispatch_queue_t', 'dispatch_block_t',
37+ 'MTLDevice', 'MTLCommandQueue', 'MTLCommandBuffer', 'MTLRenderPipelineState', 'MTLBuffer', 'MTLTexture',
38+ ],
39+ 'builtin' => [
40+ 'NSLog', 'NSStringFromClass', 'NSClassFromString', 'NSStringFromSelector', 'NSSelectorFromString', 'NSMakeRange', 'NSApplicationMain',
41+ 'UIApplicationMain', 'CGRectMake', 'CGPointMake', 'CGSizeMake', 'objc_msgSend', 'objc_getClass', 'class_getName', 'sel_registerName',
42+ 'dispatch_async', 'dispatch_sync', 'dispatch_get_main_queue', 'dispatch_get_global_queue', 'dispatch_once', 'dispatch_after',
43+ 'MTLCreateSystemDefaultDevice', 'CFRelease', 'CFRetain',
44+ 'NS_ENUM', 'NS_OPTIONS', 'NS_ASSUME_NONNULL_BEGIN', 'NS_ASSUME_NONNULL_END', 'NS_DESIGNATED_INITIALIZER', 'NS_UNAVAILABLE',
45+ ],
46+ ];
47+
48+ public function prepare(string $code): void {
49+ parent::prepare($code);
50+ foreach ($this->buildWords($this->objcTokens) as $w => $class) {
51+ if (!isset($this->words[$w])) $this->words[$w] = $class;
52+ }
53+ }
54+
55+ public function classify(string $word, array $ctx): ?string {
56+ $class = parent::classify($word, $ctx);
57+ if ($class !== null) return $class;
58+ // メッセージのセレクター部分([obj setValue:x forKey:y])。「Foo : NSObject」の様に空白がある場合は除く
59+ if ($ctx['nextRaw'] === ':') return 'function';
60+ return null;
61+ }
62+}
@@ -0,0 +1,58 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+/**
5+ * Slang(HLSLの上位互換)
6+ */
7+class Slang extends Hlsl {
8+ protected array $rules = [
9+ // 属性 [[vk::binding(0, 0)]]
10+ ['re' => '\[\[[^\]\n]*\]\]', 'class' => 'attribute'],
11+ ];
12+
13+ /** @var array HLSLに加えるSlangのトークン */
14+ protected array $slangTokens = [
15+ 'keyword' => [
16+ 'import', 'module', 'implementing', '__include', '__exported', 'public', 'private', 'internal', 'extension', 'associatedtype',
17+ 'where', 'let', 'var', 'func', 'property', 'get', 'set', '__init', '__subscript', 'This', 'typealias', 'throw', 'throws',
18+ 'try', 'catch', 'defer', 'is', 'as', 'new', 'some', 'dyn', 'ref', '__ref', '__generic', '__target_switch', 'spirv_asm',
19+ '__intrinsic_asm', 'constexpr', 'override', 'no_diff', 'fwd_diff', 'bwd_diff', 'alignof', 'countof',
20+ ],
21+ 'constant' => [
22+ 'none', 'nullptr',
23+ ],
24+ 'type' => [
25+ 'Ptr', 'Optional', 'Tuple', 'Array', 'DescriptorHandle', 'ParameterBlock', 'NativeString', 'String', 'int8_t', 'uint8_t',
26+ 'Sampler1D', 'Sampler2D', 'Sampler3D', 'SamplerCube', 'Sampler1DArray', 'Sampler2DArray', 'SamplerCubeArray', 'Sampler2DShadow',
27+ 'Sampler2DMS', 'Sampler2DMSArray', 'Texture2DShadow', 'TextureCubeShadow', 'RWTexture2DArray', 'Atomic',
28+ 'IDifferentiable', 'IDifferentiablePtrType', 'DifferentialPair', 'IArithmetic', 'IFloat', 'IInteger', 'ILogical', 'IComparable',
29+ 'IRangedValue', 'IArray', 'IRWArray', 'IBufferDataLayout', 'DefaultDataLayout', 'Std140DataLayout', 'Std430DataLayout', 'ScalarDataLayout',
30+ ],
31+ 'builtin' => [
32+ 'detach', 'diffPair', 'updatePrimal', 'updateDiff', 'getDiff', 'getPrimal', 'isDifferentialNull',
33+ 'printf', 'bit_cast', 'reinterpret', 'createDynamicObject', 'getStringHash', 'static_assert',
34+ ],
35+ ];
36+
37+ protected array $attributes = [
38+ // HLSL
39+ 'numthreads', 'unroll', 'loop', 'branch', 'flatten', 'fastopt', 'allow_uav_condition', 'call', 'forcecase', 'earlydepthstencil',
40+ 'maxvertexcount', 'domain', 'partitioning', 'outputtopology', 'outputcontrolpoints', 'patchconstantfunc', 'maxtessfactor',
41+ 'instance', 'RootSignature', 'shader', 'WaveSize', 'NumThreads', 'vk',
42+ // Slang
43+ 'Differentiable', 'ForwardDifferentiable', 'BackwardDifferentiable', 'BackwardDerivative', 'ForwardDerivative', 'PrimalSubstitute',
44+ 'PreferRecompute', 'PreferCheckpoint', 'TreatAsDifferentiable', 'mutating', 'nonmutating', 'ForceInline', 'noinline', 'require',
45+ 'SpecializationConstant', 'push_constant', 'Payload', 'raypayload', 'AutoPyBindCUDA', 'CudaKernel', 'TorchEntryPoint',
46+ 'Format', 'OverloadRank', 'Deprecated', 'KnownBuiltin', 'anyValueSize', 'COM', 'Flags', 'UnscopedEnum', 'ForceUnroll',
47+ ];
48+
49+ public function prepare(string $code): void {
50+ $tokens = $this->tokens;
51+ foreach ($this->slangTokens as $k => $list) {
52+ foreach ($list as $w) $tokens[$k][] = $w;
53+ }
54+ foreach (self::vectorTypes($this->scalars) as $t) $tokens['type'][] = $t;
55+ foreach (self::vectorTypes(['int8_t', 'uint8_t']) as $t) $tokens['type'][] = $t;
56+ $this->words = $this->buildWords($tokens);
57+ }
58+}
@@ -0,0 +1,55 @@
1+<?php
2+namespace Std\Lib\Syntax;
3+
4+class Swift extends Language {
5+ protected bool $nestedComments = true;
6+ protected array $strings = ['"'];
7+ protected string $digitSep = '_';
8+
9+ protected array $rules = [
10+ // 生文字列 #"…"#(複数行の#"""…"""#も)
11+ ['re' => '(?<rawhash>#+)"""(?s:.*?)"""\k<rawhash>|(?<rawhash2>#+)"(?:[^"\n]|"(?!\k<rawhash2>))*"\k<rawhash2>', 'class' => 'string'],
12+ // 複数行の文字列 """…"""
13+ ['re' => '"""(?s:.*?)(?:"""|\z)', 'class' => 'string'],
14+ // コンパイラ指令(#if, #available, #selector等)
15+ ['re' => '#[A-Za-z_]\w*', 'class' => 'preprocessor'],
16+ // 属性(@main, @MainActor, @escaping等)
17+ ['re' => '@[A-Za-z_]\w*', 'class' => 'attribute'],
18+ // 予約語を名前にする `default`
19+ ['re' => '`[^`\n]+`', 'class' => null],
20+ ];
21+
22+ protected array $tokens = [
23+ 'keyword' => [
24+ // 宣言
25+ 'associatedtype', 'class', 'deinit', 'enum', 'extension', 'fileprivate', 'func', 'import', 'init', 'inout', 'internal', 'let',
26+ 'open', 'operator', 'private', 'precedencegroup', 'protocol', 'public', 'rethrows', 'static', 'struct', 'subscript', 'typealias',
27+ 'var', 'actor', 'macro', 'package', 'nonisolated', 'isolated', 'consuming', 'borrowing', 'sending',
28+ // 文
29+ 'break', 'case', 'catch', 'continue', 'default', 'defer', 'do', 'else', 'fallthrough', 'for', 'guard', 'if', 'in', 'repeat',
30+ 'return', 'throw', 'switch', 'where', 'while',
31+ // 式・型
32+ 'as', 'await', 'is', 'self', 'Self', 'super', 'throws', 'try', 'async', 'some', 'any', 'Type', 'Protocol',
33+ // 文脈依存
34+ 'convenience', 'dynamic', 'didSet', 'final', 'get', 'indirect', 'lazy', 'mutating', 'nonmutating', 'optional', 'override',
35+ 'postfix', 'prefix', 'infix', 'required', 'set', 'unowned', 'weak', 'willSet',
36+ ],
37+ 'constant' => [
38+ 'true', 'false', 'nil',
39+ ],
40+ 'type' => [
41+ 'Int', 'Int8', 'Int16', 'Int32', 'Int64', 'Int128', 'UInt', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'UInt128',
42+ 'Float', 'Float16', 'Float32', 'Float64', 'Float80', 'Double', 'CGFloat', 'Bool', 'String', 'Substring', 'Character',
43+ 'Array', 'ContiguousArray', 'Dictionary', 'Set', 'Optional', 'Result', 'Range', 'ClosedRange', 'Void', 'Never', 'Any', 'AnyObject',
44+ 'Error', 'Equatable', 'Hashable', 'Comparable', 'Codable', 'Encodable', 'Decodable', 'Identifiable', 'Sendable', 'Sequence',
45+ 'Collection', 'IteratorProtocol', 'CustomStringConvertible', 'Numeric', 'BinaryInteger', 'FloatingPoint', 'SIMD2', 'SIMD3', 'SIMD4',
46+ 'UnsafePointer', 'UnsafeMutablePointer', 'UnsafeRawPointer', 'UnsafeMutableRawPointer', 'UnsafeBufferPointer', 'OpaquePointer',
47+ 'Data', 'Date', 'URL', 'UUID', 'NSObject', 'Task', 'MainActor', 'View', 'some',
48+ ],
49+ 'builtin' => [
50+ 'print', 'debugPrint', 'dump', 'assert', 'assertionFailure', 'precondition', 'preconditionFailure', 'fatalError',
51+ 'min', 'max', 'abs', 'stride', 'zip', 'sequence', 'repeatElement', 'swap', 'type', 'withUnsafePointer',
52+ 'withUnsafeMutablePointer', 'withUnsafeBytes', 'withExtendedLifetime', 'MemoryLayout', 'readLine', 'numericCast',
53+ ],
54+ ];
55+}
@@ -2,9 +2,43 @@
22 namespace Std\Lib\Syntax;
33
44 interface SyntaxInterface {
5- public function getColors(): array;
6- public function getComment(): ?string;
7- public function getInclude(array $code): ?array;
8- public function getMultiline(): ?array;
9- public function getTokens(): array;
10-}
5+ /**
6+ * コードを解析する前に呼ばれる(単語表の作成等)
7+ *
8+ * @param string $code ハイライトするコード全体
9+ * @return void
10+ */
11+ public function prepare(string $code): void;
12+
13+ /**
14+ * トークンの規則を優先順に返す
15+ * 各規則:['re' => 正規表現(区切り文字なし), 'class' => 種類, 'bol' => 行頭のみか]
16+ * 種類が「ident」の場合、classify()で分類する
17+ *
18+ * @return array 規則の配列
19+ */
20+ public function getRules(): array;
21+
22+ /**
23+ * 識別子を分類する
24+ *
25+ * @param string $word 識別子
26+ * @param array $ctx 文脈(bol, first, prev, prevWord, lineWord, next, nextRaw, nextWord)
27+ * @return string|null 種類、色を付けない場合はnull
28+ */
29+ public function classify(string $word, array $ctx): ?string;
30+
31+ /**
32+ * 入れ子可能な複数行コメントの[開始, 終了](Swift等)
33+ *
34+ * @return array|null
35+ */
36+ public function getNestedComment(): ?array;
37+
38+ /**
39+ * 次の「<…>」をファイル名として扱うプリプロセッサ指令(include等)
40+ *
41+ * @return array
42+ */
43+ public function getHeaderDirectives(): array;
44+}
@@ -0,0 +1,118 @@
1+<?php
2+/*************************************************************
3+# 076 License
4+
5+Copyright (c) テクニカル諏訪子
6+
7+Permission is hereby granted to any person obtaining a copy of the software
8+Little Beast (the "Software") to use, modify, merge, copy, publish, distribute,
9+sublicense, and/or sell copies of the Software, subject to the following conditions:
10+
11+ 1. **Origin Attribution**:
12+ - You must not misrepresent the origin of the Software; you must not claim
13+ you created the original Software.
14+ - If the Software is used in a product, you must either:
15+ a. Provide clear attribution in the product's documentation, user interface,
16+ or other visible areas, **OR**
17+ b. Pay the original developers a fee they specify in writing.
18+ 2. **Usage Restriction**:
19+ - The Software, or any derivative works, dependencies, or libraries
20+ incorporating it, must not be used for censorship or to suppress freedom of
21+ speech, expression, or creativity. Prohibited uses include, but are not
22+ limited to:
23+ - Censorship of so-called "hate speech", visuals, non-mainstream opinions,
24+ ideas, or objective reality.
25+ - Tools or systems designed to restrict access to information or
26+ artistic works.
27+ 3. **Notice Preservation**:
28+ - This license and the above copyright notice must remain intact in all copies
29+ of the source code.
30+
31+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
32+INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
33+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
34+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
35+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
36+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37+ */
38+namespace Std\Test;
39+
40+require_once __DIR__.'/../../../autoload.php';
41+
42+use Std\Lib\Tester;
43+use Std\Lib\Syntax;
44+
45+$test = new Tester([
46+ 'colorOutput' => true,
47+ 'verboseOutput' => true
48+]);
49+
50+// タグを外してデコードすると元のコードに戻るか
51+function syntaxRoundtrip(string $lang, string $code): bool {
52+ $html = (new Syntax($lang))->colorize($code);
53+ return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8') === $code;
54+}
55+
56+$samples = [
57+ 'c' => "#include <stdio.h>\n/* a */ int main(void) { printf(\"%d\\n\", 0x1F); return 0; } // b",
58+ 'cpp' => "auto s = R\"(raw \"x\")\"; [[nodiscard]] std::vector<int> v{1'000};",
59+ 'glsl' => "layout(location = 0) out vec4 fragColor;\nvoid main() { fragColor = texture(tex, gl_FragCoord.xy); }",
60+ 'go' => "func main() { s := `raw\nstring`; fmt.Println(len(s), nil) }",
61+ 'hlsl' => "[numthreads(8, 8, 1)]\nvoid CSMain(uint3 id : SV_DispatchThreadID) { float4x4 m; }",
62+ 'slang' => "[shader(\"compute\")]\nvoid main<T : IFloat>(uniform Ptr<T> p) { let x = detach(1.0); }",
63+ 'swift' => "@main struct A { /* /* nested */ */ let s = #\"raw\"#; #if DEBUG\n print(\"\\(s)\") }",
64+ 'objc' => "#import <Foundation/Foundation.h>\n@interface Foo : NSObject\n@end\nNSLog(@\"%@\", [obj valueForKey:@\"k\"]);",
65+ 'csharp' => "var p = @\"C:\\dir\"; var m = $\"{p}\"; var r = \"\"\"\nraw\n\"\"\";",
66+ 'asm' => "section .text\n_start: mov rax, 1 ; nasm\n mov x8, #64\n li a7, 93 # riscv",
67+ 'asm-x8664' => "%define N 1\nrep movsb\nmovl \$1, %eax",
68+ 'asm-aarch64' => "b.eq done\nadd v0.4s, v1.4s, v2.4s // neon",
69+ 'asm-aarch32' => "addeq r0, r0, #1 @ arm",
70+ 'asm-riscv' => "fcvt.w.s a0, fa0 # x",
71+ 'asm-mips' => "addiu \$sp, \$sp, -8 # x",
72+ 'asm-power' => "addi r3, r3, 1 # x",
73+ 'asm-z80' => "ex af,af' ; x\nld a,'A'",
74+ 'asm-6502' => "lda #\$FF ; x",
75+ 'asm-68000' => "* comment\nmove.l #1,d0",
76+];
77+
78+$test->describe('シンタックスハイライト', function($test) use ($samples): void {
79+ $test->it('全ての言語で、タグを外すと元のコードに戻るはず', function($test) use ($samples): void {
80+ foreach ($samples as $lang => $code) {
81+ $test->assertTrue(syntaxRoundtrip($lang, $code), "{$lang}の往復に失敗");
82+ }
83+ });
84+
85+ $test->it('HTMLをエスケープするはず', function($test): void {
86+ $html = (new Syntax('c'))->colorize('"</code><script>" // <b>');
87+ $test->assertFalse(str_contains($html, '<script>'));
88+ $test->assertStringContains('&lt;/code&gt;', $html);
89+ });
90+
91+ $test->it('トークンを正しく分類するはず', function($test): void {
92+ $html = (new Syntax('c'))->colorize('int x = 42; // c');
93+ $test->assertStringContains('<span class="hl-keyword">int</span>', $html);
94+ $test->assertStringContains('<span class="hl-number">42</span>', $html);
95+ $test->assertStringContains('<span class="hl-comment">// c</span>', $html);
96+
97+ $html = (new Syntax('hlsl'))->colorize('float4 p : SV_Position;');
98+ $test->assertStringContains('<span class="hl-variable">SV_Position</span>', $html);
99+
100+ $html = (new Syntax('asm'))->colorize("loop:\n mov x8, #64");
101+ $test->assertStringContains('<span class="hl-label">loop</span>', $html);
102+ $test->assertStringContains('<span class="hl-number">#64</span>', $html);
103+ });
104+
105+ $test->it('文字列の中の数字やキーワードに色を付けないはず', function($test): void {
106+ $html = (new Syntax('c'))->colorize('"int 42"');
107+ $test->assertEquals('<span class="hl-string">&quot;int 42&quot;</span>', $html);
108+ });
109+
110+ $test->it('別名を解決するはず', function($test): void {
111+ $test->assertEquals('cpp', Syntax::resolve('c++'));
112+ $test->assertEquals('csharp', Syntax::resolve('C#'));
113+ $test->assertEquals('asm-aarch64', Syntax::resolve('arm64'));
114+ $test->assertFalse(Syntax::isSupported('sh'));
115+ });
116+});
117+
118+$test->printSummary();