| 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 | } |