[Arm64] ストア命令

ストア命令(STR)はレジスタに格納されている値をメモリに書き込む
転送元 Wt, Xt
命令 STR, STR, STRH, STRB

### (1)STR ダブルワード(64ビット) → 64ビット
64ビットレジスタのデータをメモリにコピー

  STR   Xt, [base], #simm9      // ポストインデックス
  STR   Xt, [base, #simm9]!     // プレインデックス
  STUR  Xt, [base {,#simm9}]    
  STR   Xt, [base {,#uimm12}]  
  STR   Xt, [base, Wm {,SXTW|UXTW {#0 | #3}} ]  // レジスタオフセット
  STR   Xt, [base, Xm {,LSL|SXTX {#0 | #3}} ]   // レジスタオフセット

### (2)STR ワード(32ビット) → 32ビット

  STR   Wt, [base], #simm9       // ポストインデックス
  STR   Wt, [base, #simm9]!      // プレインデックス
  STUR  Wt, [base {,#simm9} ]    
  STR   Wt, [base {,#imm12} ]    
  STR   Wt, [base, Wm {,SXTW|UXTW {#0 | #2}} ]  // レジスタオフセット
  STR   Wt, [base, Xm {,LSL|SXTX {#0 | #2}} ]   // レジスタオフセット

### (3)STRH ハーフワード(32ビット) → 16ビット

  STRH  Wt, [base], #simm9      // ポストインデックス
  STRH  Wt, [base, #simm9]!     // プレインデックス
  STURH Wt, [base {,#simm9}]
  STRH  Wt, [base {,#uimm12}]   // 符号なしオフセット
  STRH  Wt, [base, Wm {,SXTW|UXTW {#0 | #1}} ]  // レジスタオフセット
  STRH  Wt, [base, Xm {,LSL|SXTX {#0 | #1}} ]   // レジスタオフセット

### (4)STRB バイト(32ビット) → 8ビット

  STRB  Wt, [base], #simm9        // ポストインデックス
  STRB  Wt, [base, #simm9]!       // プレインデックス
  STURB Wt, [base {,#simm9}]
  STRB  Wt, [base {,#uimm12}]     // 符号なしオフセット
  STRB  Wt, [base, Wm {,SXTW|UXTW {#0}} ]  // レジスタオフセット
  STRB  Wt, [base, Xm {,LSL|SXTX {#0}} ]   // レジスタオフセット

### PC相対アドレス計算
ラベル位置のアドレスをレジスタに設定

ADR, ADRP

    ADR     Xd, label
    ADRP    Xd, label

### レジスタペアのロード/ストア命令

    LDP   Xt1, Xt2, [base,  #simm7]!     // プレインデックス
    LDP   Wt1, Wt2, [base,  #simm7]!     // プレインデックス

      // プレインデックス
      base = base + simm7;
      Rt1 = memory[base];
      Rt2 = memory[base + 4|8];

    LDP   Xt1, Xt2, [base], #simm7      // ポストインデックス
    LDP   Wt1, Wt2, [base], #simm7      // ポストインデックス

      // ポストインデックス
      Rt1 = memory[base];
      Rt2 = memory[base + 4|8];
      base = base + simm7;

    LDP   Xt1, Xt2, [base, #simm7]      // base への書き戻しなし
    LDP   Wt1, Wt2, [base, #simm7]      // base への書き戻しなし

      Rt1 = memory[base];
      Rt2 = memory[base + simm7];

LDPは連続したメモリから2つのレジスタに読み込む命令

    LDP   Xt1, Xt2, [base], #simm7
    LDP   Wt1, Wt2, [base], #simm7

    LDP     Xt1, Xt2, [base, #simm7]!
    LDP     Wt1, Wt2, [base, #simm7]!

    LDP     Xt1, Xt2, [Xn {, #simm7}]
    LDP     Wt1, Wt2, [Xn {, #simm7}]

STP命令は二つのレジスタの内容(Xt1, Xt2)をメモリアドレスを保持するレジスタ (Xn) にオフセットの値を加えたアドレスを先頭とするメモリに書き込み、レジスタをスタックに退避する場合によく使う

    STP     Xt1, Xt2, [base], #simm7
    STP     Wt1, Wt2, [base], #simm7
    STP     Xt1, Xt2, [base, #simm7]!
    STP     Wt1, Wt2, [base, #simm7]!
    STP     Xt1, Xt2, [base {, #simm7}]
    STP     Wt1, Wt2, [base {, #simm7}]

[Arm64] ロード命令

ロード命令(LDR)はメモリに格納されている値をレジスタに読み込み、ストア命令はレジスタに入っている値をメモリに書き出す

LDR : メモリからレジスタ
STR : レジスタからストア
MOV : レジスタからレジスタ

ロード/ストア命令は読み書きするメモリの位置を指定する必要があり、アドレッシングという
x86, x64, ARM, PowerPCによってアドレッシングの方法が異なる
ARM64ではイミディエートオフセットと レジスターオフセットなどがある。
ベースレジスタのオフセット(加算)を使用する
イミディエートオフセットは定数、レジスターオフセットはレジスタの格納された値

LDR Xt, [base, #simm9]

	base = base + simm9;
	Xt = memory[base];
LDR Xt, [base, #uimm12]
	Xt = [base + uimm12 * scale];

### レジスタオフセット
ベースレジスタ(Xn または SP)が保持しているメモリアドレスにインデックス用のレジスタが格納しているオフセット値を加えたメモリアドレスをXtにコピー

LDR Xt, [base, Wm {, SXTW|UXTW {#0|#3}}]
LDR Xt, [base, Xm {, LSL|SXTX {#0|#3}}]

	Xt = memory[base + {* 8}];

### ロード命令
メモリが64, 32, 16, 8ビットあり、レジスタが32ビット(Wt)と64ビット(Xt)があるため、ビットに応じてそれぞれ命令がある
ビット幅を増やすには符号拡張とするか、ゼロ拡張を行う

### ロード命令の詳細
6種類のアドレッシング
9ビット符号付即値+ポストインデックス
9ビット符号付即値+プレインデックス
9ビット符号付即値
12ビット符号なし即値
32ビットレジスタオフセット
64ビットレジスタオフセット

### (1)LDR ダブルワード(64ビット) → 64ビット

  LDR   Xt, [base], #simm9       // ポストインデックス
  LDR   Xt, [base,  #simm9]!     // プレインデックス
  LDUR  Xt, [base,  #simm9]
  LDR   Xt, [base {,#uimm12} ]   // 符号なしオフセット(8の倍数) [0 - 32760] 
  LDR   Xt, [base, Wm {,SXTW|UXTW {#0 | #3}} ]  // レジスタオフセット
  LDR   Xt, [base, Xm {,LSL|SXTX {#0 | #3}} ]  // レジスタオフセット

### (2)LDR ワード(32ビット) → 32ビット

  LDR   Wt, [base], #simm9     // ポストインデックス
  LDR   Wt, [base,  #simm9]!   // プレインデックス
  LDUR  Wt, [base,  #simm9]
  LDR   Wt, [base {,#uimm12} ]  // 符号なしオフセット(4の倍数) [0 - 16380] 
  LDR   Wt, [base, Wm {,SXTW|UXTW {#0 | #2}} ] 
  LDR   Wt, [base, Xm {,LSL|SXTX {#0 | #2}} ] 

### (3)LDRSW 符号付きワード(32ビット) → 64ビット

  LDRSW  Xt, [base], #simm9     // ポストインデックス
  LDRSW  Xt, [base,  #simm9]!    // プレインデックス
  LDURSW Xt, [base,  #simm9]
  LDRSW  Xt, [base {,#uimm12} ]  // 符号なしオフセット(4の倍数) [0 - 16380]
  LDRSW  Xt, [base, Wm {,SXTW|UXTW {#0 | #2}} ]
  LDRSW  Xt, [base, Xm {,LSL|SXTX {#0 | #2}} ]

### (4)LDRH ハーフワード(16ビット) → 32ビット

  LDRH  Wt, [base], #simm9      // ポストインデックス
  LDRH  Wt, [base,  #simm9]!     // プレインデックス
  LDURH Wt, [base,  #simm9]
  LDRH  Wt, [base {,#uimm12} ]   // 符号なしオフセット(2の倍数) [0 - 8190]
  LDRH  Wt, [base, Wm {,SXTW|UXTW {#0 | #1}} ]
  LDRH  Wt, [base, Xm {,LSL|SXTX  {#0 | #1}} ]

### (5) LDRSH 符号付きハーフワード(16ビット) → 32ビット

  LDRSH  Wt, [base], #simm9     // ポストインデックス
  LDRSH  Wt, [base,  #simm9]!   // プレインデックス
  LDURSH Wt, [base,  #simm9]
  LDRSH  Wt, [base {,#uimm12} ]  // 符号なしオフセット(2の倍数) [0 - 8190]
  LDRSH  Wt, [base, Wm {,SXTW|UXTW {#0 | #1}} ] 
  LDRSH  Wt, [base, Xm {,LSL|SXTX  {#0 | #1}} ] 

### (6) LDRSH 符号付きハーフワード(16ビット) → 64ビット

  LDRSH  Xt, [base], #simm9     // ポストインデックス
  LDRSH  Xt, [base,  #simm9]!   // プレインデックス
  LDURSH Xt, [base,  #simm9]
  LDRSH  Xt, [base {,#uimm12} ]  // 符号なしオフセット(2の倍数) [0 - 8190]
  LDRSH  Xt, [base, Wm {,SXTW|UXTW {#0 | #1}} ] 
  LDRSH  Xt, [base, Xm {,LSL|SXTX {#0 | #1}} ] 

### (7)LDRB バイト(8ビット) → 32ビット

  LDRB  Wt, [base], #simm9     // ポストインデックス
  LDRB  Wt, [base,  #simm9]!    // プレインデックス
  LDURB Wt, [base,  #simm9]
  LDRB  Wt, [base {,#uimm12} ]  // 符号なしオフセット [0 - 4095]
  LDRB  Wt, [base, Wm {,SXTW|UXTW {#0}} ]
  LDRB  Wt, [base, Xm {,LSL|SXTX  {#0}} ]

### (8)LDRSB 符号付きバイト(8ビット) → 32ビット

  LDRSB  Wt, [base], #simm9     // ポストインデックス
  LDRSB  Wt, [base,  #simm9]!   // プレインデックス
  LDURSB Wt, [base,  #simm9]
  LDRSB  Wt, [base {,#uimm12} ]  // 符号なしオフセット [0 - 4095]
  LDRSB  Wt, [base, Wm {,SXTW|UXTW {#0}} ] 
  LDRSB  Wt, [base, Xm {,LSL|SXTX {#0}} ] 

### (9)LDRSB 符号付きバイト(8ビット) → 64ビット

  LDRSB  Xt, [base], #simm9     // ポストインデックス
  LDRSB  Xt, [base,  #simm9]!   // プレインデックス
  LDURSB Xt, [base,#simm9]
  LDRSB  Xt, [base {,#uimm12} ]  // 符号なしオフセット [0 - 4095]
  LDRSB  Xt, [base, Wm {,SXTW|UXTW {#0}} ] 
  LDRSB  Xt, [base, Xm {,LSL|SXTX  {#0}} ] 

### PC相対リテラルアドレッシング
ラベルの位置にあるデータをレジスタに読み込む

LDR     Xt, label
LDRSW   Xt, label
LDR     Wt, label

[Arm64] レジスタ

31本の汎用レジスタ、スタックポインタ、ゼロレジスタ、プログラムカウンタ、フラグレジスタ、32本のVectorレジスタ、浮動小数点を制御するFPCRレジスタ、FPSRレジスタを持っている

### 汎用レジスタ
x0からx30となっている
wで指定すると32ビットレジスタとして使用できる

### スタックポインタ、ゼロレジスタ、プログラムカウンタ
スタックトップのアドレスを保持するスタックポインタ、常にビットが0になっているゼロレジスタ、実行中のメモリアドレスを保持するプログラムカウンタがある

### NZCV レジスタ (フラグレジスタ)
演算命令の結果を保持するフラグはNZCVレジスタに保持される
条件分岐命令は、レジスタの各ビットの状態で分岐するかしないかを決定する
EQ, NE, CS, CC, MI, PL, VS, VC, HI, LS, GE, LT, GT, LE, AL

### Vectorレジスタ
複数のベクトル要素を同時演算するためにある 0~31の値

### FPCRレジスタ、FPSRレジスタ

レジスタとはマイクロプロセッサ(MPU/CPU)内部にある演算や実行状態の保持に用いる記憶素子

Arm64アセンブラの実行

hello.s

.text
        .global _start
_start:
        mov     x2,     #13     // x2 length
        adr     x1,     msg     // x1 string address
        mov     x0,     #1      // x0 stdout
        mov     x8,     #64
        svc     #0              // sys_write
        mov     x0,     xzr
        mov     x8,     #93
        svc     #0              // exit
msg:
        .asciz  "hello, world\n"

asでアセンブルしてオブジェクトファイルを作り、リンカを使って実行ファイルを作成する
$ ld -o hello hello.o
$ ./hello
hello, world

ファイルサイズ
$ ls -l hello*
-rwxrwxr-x 1 vagrant vagrant 936 Jan 19 18:14 hello
-rw-rw-r– 1 vagrant vagrant 816 Jan 19 18:14 hello.o
-rw-rw-r– 1 vagrant vagrant 386 Jan 19 17:48 hello.s

シンボル情報の表示
$ nm hello
00000000004100a6 T __bss_end__
00000000004100a6 T _bss_end__
00000000004100a6 T __bss_start
00000000004100a6 T __bss_start__
00000000004100a6 T _edata
00000000004100a8 T __end__
00000000004100a8 T _end
0000000000400098 t msg
0000000000400078 T _start

objdumpによる逆アセンブル
$ objdump -d hello

hello: file format elf64-littleaarch64

Disassembly of section .text:

0000000000400078 <_start>:
400078: d28001a2 mov x2, #0xd // #13
40007c: 100000e1 adr x1, 400098
400080: d2800020 mov x0, #0x1 // #1
400084: d2800808 mov x8, #0x40 // #64
400088: d4000001 svc #0x0
40008c: aa1f03e0 mov x0, xzr
400090: d2800ba8 mov x8, #0x5d // #93
400094: d4000001 svc #0x0

0000000000400098 :
400098: 6c6c6568 .word 0x6c6c6568
40009c: 77202c6f .word 0x77202c6f
4000a0: 646c726f .word 0x646c726f
4000a4: Address 0x00000000004000a4 is out of bounds.

16進数、32ビットなどで出力も可

$ strings hello
hello, world
hello.o
__bss_start__
__bss_end__
__bss_start
__end__
_edata
_end
.symtab
.strtab
.shstrtab
.text

includeした場合

.include    "stdio.s"
.text
.global _start
_start:
    adr     x0, msg
    bl      OutAsciiZ
    bl      Exit
msg:
    .asciz  "Hello Hpscript\n"

$ as -o sample.o sample.s
$ ld -o sample sample.o
$ ./sample

ARM64とアセンブラ

ARM64 base instructions
https://developer.arm.com/documentation/ddi0602/2023-12/Base-Instructions?lang=en

汎用レジスタの数が30個、汎用レジスタのサイズが64ビット
よく使われる命令
– mov : レジスタ間のコピー
– ldr : メモリからレジスタに読み出し
– b : 無条件分岐
– bl : サブルーチン呼び出し
– add : 加算
– cmp : 比較
– str : レジスタからメモリへ格納
– ldb : スタックから取り出し
– stp : スタックへ格納
– adrp : レジスタにアドレスを設定
– cbz : 比較して0なら分岐
– b.eq : 等しければ分岐
– ret : サブルーチンから戻る
– sub : 減算
– b.ne : 異なれば分岐
– adr : レジスタにアドレスを設定
– cbnz : 比較して非0なら分岐
– and : ビット積

スタックへの複数レジスタの退避/復帰が2つのレジスタに限定されたLDP/STPに変わっている
ARM64では全ての命令を32ビットで表現

### メモリ、バイト、レジスタ、エンディアン
アセンブリでは2進数やエンディアンという概念を知っておく必要がある
8ビットが1バイト
アナログとは中間も扱うもので、デジタルは0と1のみ
10進数より16進数の方が使われる
補数(complement)を使うことで演算処理が早くなる
64ビットCPUではレジスタから16エクサバイトのメモリを指定できる
64bit レジスタはbytes, Half word, Word, Double Wordを扱える

### エンディアン
レジスタの内容の小さい側をメモリアドレスの小さい側に格納するのをリトルエンディアンと呼ぶ
x86, x86064, arm64はリトルエンディアン、PowerPCはバイエンディアン

Ubuntu(arm64)でassemblerの実行

NASMをインストールする
$ sudo apt install nasm

$ uname -sr
Linux 5.15.0-76-generic

$ nasm -v
NASM version 2.15.05

$ as -v
GNU assembler version 2.38 (aarch64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.38

$ ld -v
GNU ld (GNU Binutils for Ubuntu) 2.38

$ gcc –version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

hello.s

.text
        .global _start
_start:
        mov     x2,  #13    // x2  length
        adr     x1,  msg    // x1  string address
        mov     x0,  #1     // x0  stdout
        mov     x8,  #64
        svc     #0          // sys_write
        mov     x0,  xzr
        mov     x8,  #93
        svc     #0          // exit
msg:
        .asciz  "hello, world\n"

$ as -o hello.o hello.s
$ ld -o hello hello.o
$ ./hello
hello, world

ちなみに、CPUアーキテクチャがx86ではないので x86-64で記述するとエラーになる。

### x86-64とarm64の違い
x86 x64 は CISC(Complex Instruction Set Computer) と呼ばれる命令セット
「複雑な処理」を「少ない命令」で実行しようとする思想です。

arm は RISC(Reduced Instruction Set Computer) と呼ばれる命令セット

x64は高速でパワフル、PCでよく使用される
ARM64は低電力でモバイルデバイスに向いている

アセンブラ入門

### 基礎用語
アセンブラは1行1件で記述され、最大で4つのフィールドを持つ
コメント、オペランド、ニーモニック、名前
ニーモニックは、CPU命令と、ディレクティブ(アセンブラに対する支持)がある。

### ディレクティブ
mov ax, 1234H

cpu命令、レジスタ名は小文字
ディレクティブやラベルは大文字

B 2進数、O,Q 8進数、D 10進数、H 16進数
16進数は0を先頭につける

MSG1 DB ‘ABCDE’

@@: ラベル
@B 直近の先頭行側の@@ラベル参照
@F 直近の最終行側の@@ラベル参照

システムマクロ
ローカルラベル機能がある

### 機械語
CPUの命令のことを機械語(マシン語)という
メモリから1バイトの命令コードをCPU内の実行ユニット部が読み込むと命令デコーダによって解析され、命令タイプが決定する
e.g. 01001000 という命令が来たら 8086CPUでは AXレジスタの内容を1だけ減じる という仕事をする
2進数は覚えにくいので、16進数で4ビットごとに1桁として数え、0~Fで表現する
16ビットは2バイト

48という16進数の数字からAXレジスタから1減じる という意味を読み取ることができないので、機械語と1対1に対応しながら表意的な名前を与える方法が考え出され、それがアセンブラというプログラミング言語
dec ax という記述

mov ax, MYDT1 -> A1 0101
add ax,MYDT2 -> 03 06 0103
mov MYDT3, ax -> A3 0105

MYDT1 DW 10
MYDT2 DW 20
MYDT3 DW 0

より自然な表記が可能となり、C, C++, Javaなどの高級言語が生まれた
Cの場合は、Cコンパイラが機械語に変換
アセンブラソースプログラムが機械語に変換

[プログラミング言語の作り方] グループ化の()対応を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算
– 符号
– グループ化の()対応

### source.3

a = (5-2) * 3;
print("a = (5-2) * 3 = ", a);

b = 2 * (3 - 5);
print("b = 2 * (3 - 5) = ", b);

### parser.js

function paren(){

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();

        console.log();
        op += expect(tokens,")");
        return right;
    }
    return value();
}

printなどの関数がleftに来る場合は、opが()になるが、計算上の()の場合は、opはreturnされない。
その場合の()で囲む場合は、tokens.shift()の直前で処理をする
L ASTのleftとrightの処理順序が重要

[プログラミング言語の作り方] 符号処理を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算
– 符号

### source.3

a = -2 * 3;
print("a = -2 * 3 = ", a);

b = -2 * -3;
print("b = -2 * -3 = ", b);

### parser.js
単項の符号に対応するため、flagを追加。右結合なので、リセットでcommaであること、単項であるため、左辺読まずに最後のreturnでfunccallを呼ぶ

function flag(){

    var op;
    while(op = accept(tokens,"+","-")){

        var right = comma();

        if(op == "+") return right;

        return {left:{left:"0", op:"-",right:"1"}, op:"*",right};
    }
    return funccall();
}

$ node interpretor.js
token =[
‘a’, ‘=’,
‘-‘, ‘2’,
‘*’, ‘3’,
‘;’, ‘print’,
‘(‘, ‘”a = -2 * 3 = “‘,
‘,’, ‘a’,
‘)’, ‘;’,
‘b’, ‘=’,
‘-‘, ‘2’,
‘*’, ‘-‘,
‘3’, ‘;’,
‘print’, ‘(‘,
‘”b = -2 * -3 = “‘, ‘,’,
‘b’, ‘)’,
‘;’
]
ast={
left: {
left: {
left: {
left: {
left: ‘a’,
op: ‘=’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: { left: ‘2’, op: ‘*’, right: ‘3’ }
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”a = -2 * 3 = “‘, op: ‘,’, right: ‘a’ }
}
},
op: ‘;’,
right: {
left: ‘b’,
op: ‘=’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: {
left: ‘2’,
op: ‘*’,
right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: ‘3’
}
}
}
}
},
op: ‘;’,
right: {
left: ‘print’,
op: ‘()’,
right: { left: ‘”b = -2 * -3 = “‘, op: ‘,’, right: ‘b’ }
}
},
op: ‘;’,
right: undefined
}
a = -2 * 3 = -6
b = -2 * -3 = 6

余白の有無関係なしに”-“をopとして解釈してしまうから、加算、引き算の後に、符号の処理をするのね
return {left:{left:”0″, op:”-“,right:”1″}, op:”*”,right};とすることで、-3がASTでは以下のように -1 * 3 と表される

right: {
left: { left: ‘0’, op: ‘-‘, right: ‘1’ },
op: ‘*’,
right: ‘3’
}

[プログラミング言語の作り方] 掛け算、割り算を実装

言語仕様
– 文字列
– 整数123, 実数123.4
– グローバル変数、変数の値の参照
– 代入演算子(=)
– print関数でカンマ区切りで複数の引数指定
– 複数文は、セミコロンで区切る
– // から行末まではコメント
– 足し算、引き算
– 掛け算、割り算

### source.3

a = 1.24 / 2 - 1;
print("a = 1.24 / 2 - 1 = ", a);

b = 5 - 2 * 3;
print("b = 5 - 2 * 3 = ", b);

### parse.js

module.exports = parser;
var {expect, accept, show, error} = require("./utils.js");

var tokens;

function parser(t){
    tokens = t;
    var ast = semi();
    if(tokens.length>0){
        show("ast=", ast);
        show("処理後tokens =", tokens);
        error("tokensが余っています。");
    }
    return ast;
}

function value(){
    if(tokens.length == 0) return;
    return tokens.shift();
}

function funccall(){
    var left = value();

    var op;
    while(op = accept(tokens, "(")){
        var right = comma();
        op += expect(tokens, ")");

        left = {left, op, right};
    }
    return left;
}

function mul(){

    var left = funccall();

    var op;
    while(op = accept(tokens,"*","/")){

        var right = funccall();

        left = {left, op, right};
    }
    return left;
}

function plus(){

    var left = mul();

    var op;
    while(op = accept(tokens,"+","-")){

        var right = mul();

        left = {left, op, right};
    }
    return left;
}

function assign(){
    var left = plus();

    var op;
    while(op = accept(tokens, "=")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

function comma(){
    var left = assign();

    var op;
    while(op = accept(tokens,",")){
        var right = assign();
        left = {left, op, right};
    }
    return left;
}

function semi(){
    var left = comma();

    var op;
    while(op = accept(tokens, ";")){
        var right = comma();
        left = {left, op, right};
    }
    return left;
}

### run.js

const { error } = require("./utils");

module.exports = run;

var global = {};

function run(a){
    if(!a) return;
    if(!a.op){
        if(a[0] == '"') return a.substr(1, a.length-2);
        if(/\d/.test(a[0])) return 1*a;
        if(global.hasOwnProperty(a)) return global[a];
        return a;
    } else if (a.op==";"){
        run(a.left);
        run(a.right);
    } else if (a.op==","){
        return [run(a.left), run(a.right)].flat();
    } else if (a.op=="="){
        return global[run(a.left)] = run(a.right);
    } else if (a.op=="+"){
        return run(a.left) + run(a.right);
    } else if (a.op=="-"){
        return run(a.left) - run(a.right);
    } else if (a.op=="*"){
        return run(a.left) * run(a.right);
    } else if (a.op=="/"){
        return run(a.left) / run(a.right);
    } else if(a.op == "()") {
        var func = run(a.left);
        if(func == "print"){
            var msg = [run(a.right)].flat().join("");
            console.log(msg);
        } else {
            error("未実装の関数呼び出し func=", func);
        } 
    } else {
        error("未実装の演算子 op=", a.op)
    }
} 

ASTのparse.jsで優先したいopをより深くvalueに近い位置でcallすれば、mulはplusよりも優先して処理される。