master
ブランチ master
LittleBeast / src / Std / Lib / Syntax / Assembly.php
5 KB、127行 生 Blame 履歴
1<?php
2namespace Std\Lib\Syntax;
3
4/**
5 * アセンブリ言語の基本クラス
6 *
7 * 種類:instruction(命令)、register(レジスター)、directive(指令)、keyword(サイズ指定等)、label(ラベル)
8 * 行の最初の単語は、一覧に無くても命令として扱う(ラベルと指令を除く)
9 */
10abstract 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}