Pineのコンパイラ
※動作環境として、Linux または WSL (Windows Subsystem for Linux) を想定しています。また、アセンブラとして ナズム を使用するため、事前に sudo apt install nasm などでインストールしておいてください。
import sys
import re
import subprocess
import os
# ==========================================
# 1. 字句解析器 (Lexer)
# ソースコードをトークンのリストに分割します
# ==========================================
def tokenize(code):
rules =[
('LET', r'\blet\b'),
('PRINT', r'\bprint\b'),
('ID', r'*'),
('NUM', r'\d+'),
('ASSIGN',r'='),
('PLUS', r'\+'),
('MINUS', r'-'),
('MUL', r'\*'),
('DIV', r'/'),
('LPAREN',r'\('),
('RPAREN',r'\)'),
('SEMI', r';'),
('WS', r'\s+'),
]
tok_regex = '|'.join(f'(?P<{name}>{pattern})' for name, pattern in rules)
tokens =[]
for mo in re.finditer(tok_regex, code):
kind = mo.lastgroup
value = mo.group(kind)
if kind == 'WS':
continue
tokens.append((kind, value))
return tokens
# ==========================================
# 2. 構文解析器 (Parser)
# 再帰下降構文解析を行い、抽象構文木(AST)を構築します
# ==========================================
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.pos = 0
def current(self):
return self.tokens if self.pos < len(self.tokens) else ('EOF', '')
def consume(self, expected_kind):
kind, val = self.current()
if kind == expected_kind:
self.pos += 1
return val
raise RuntimeError(f"構文エラー: '{expected_kind}' が必要ですが、'{val}' が見つかりました。")
def parse_program(self):
statements = []
while self.current() != 'EOF':
statements.append(self.parse_statement())
return ('Program', statements)
def parse_statement(self):
kind, _ = self.current()
if kind == 'LET':
self.consume('LET')
name = self.consume('ID')
self.consume('ASSIGN')
expr = self.parse_expr()
self.consume('SEMI')
return ('Let', name, expr)
elif kind == 'PRINT':
self.consume('PRINT')
expr = self.parse_expr()
self.consume('SEMI')
return ('Print', expr)
else:
raise RuntimeError(f"構文エラー: 予期しないステートメントの開始 '{self.current()}'")
def parse_expr(self):
node = self.parse_term()
while self.current() in ('PLUS', 'MINUS'):
op = self.consume(self.current())
right = self.parse_term()
node = ('BinOp', op, node, right)
return node
def parse_term(self):
node = self.parse_factor()
while self.current() in ('MUL', 'DIV'):
op = self.consume(self.current())
right = self.parse_factor()
node = ('BinOp', op, node, right)
return node
def parse_factor(self):
kind, val = self.current()
if kind == 'NUM':
self.consume('NUM')
return ('Num', int(val))
elif kind == 'ID':
self.consume('ID')
return ('Var', val)
elif kind == 'LPAREN':
self.consume('LPAREN')
node = self.parse_expr()
self.consume('RPAREN')
return node
else:
raise RuntimeError(f"構文エラー: 式の中に予期しないトークン '{val}'")
# ==========================================
# 3. コード生成器 (Code Generator)
# ASTをたどり、C言語などに頼らない純粋なx86-64アセンブリを出力します
# ==========================================
class CodeGen:
def __init__(self):
self.asm =[]
self.vars = {} # 変数名 -> スタックのオフセット(RBPからの相対位置)
self.var_offset = 0
def emit(self, code):
self.asm.append(code)
def generate(self, ast):
# アセンブリのヘッダ (Linux用のシステムコールなど)
self.emit("section .bss")
self.emit(" digit_space resb 100")
self.emit(" digit_space_pos resb 8")
self.emit("section .text")
self.emit(" global _start")
# エントリポイント
self.emit("_start:")
self.emit(" push rbp")
self.emit(" mov rbp, rsp")
self.emit(" sub rsp, 256") # ローカル変数用のスタック領域を確保
# プログラム本体の生成
for stmt in ast:
self.gen_statement(stmt)
# 正常終了のシステムコール (sys_exit)
self.emit(" mov rax, 60") # sys_exit
self.emit(" mov rdi, 0") # 終了コード 0
self.emit(" syscall")
# --- 標準出力 (print_int) のサブルーチン ---
# OSのシステムコール(sys_write)を直接使って数値を文字列に変換し出力します
self.emit("print_int:")
self.emit(" mov rcx, digit_space")
self.emit(" mov rbx, 10")
self.emit(" mov, byte 10") # 改行コード(\n)
self.emit(" inc rcx")
self.emit(" mov, rcx")
self.emit(".loop:")
self.emit(" xor rdx, rdx")
self.emit(" div rbx")
self.emit(" add dl, 48") # 数値をASCII文字に変換
self.emit(" mov rcx,")
self.emit(" mov, dl")
self.emit(" inc rcx")
self.emit(" mov, rcx")
self.emit(" test rax, rax")
self.emit(" jnz .loop")
self.emit(".print:")
self.emit(" mov rcx,")
self.emit(" mov rax, 1") # sys_write
self.emit(" mov rdi, 1") # stdout
self.emit(" mov rsi, rcx")
self.emit(" mov rdx, 1") # 1バイトずつ出力
self.emit(" syscall")
self.emit(" mov rcx,")
self.emit(" dec rcx")
self.emit(" mov, rcx")
self.emit(" cmp rcx, digit_space")
self.emit(" jge .print")
self.emit(" ret")
return '\n'.join(self.asm)
def gen_statement(self, node):
if node == 'Let':
name, expr = node, node
self.gen_expr(expr) # 式を評価して RAX に結果を入れる
if name not in self.vars:
self.var_offset += 8
self.vars = self.var_offset
offset = self.vars
self.emit(f" mov, rax") # 変数をスタックに保存
elif node == 'Print':
self.gen_expr(node)
self.emit(" call print_int") # RAX の値を出力する関数を呼ぶ
def gen_expr(self, node):
if node == 'Num':
self.emit(f" mov rax, {node}")
elif node == 'Var':
name = node
if name not in self.vars:
raise RuntimeError(f"コンパイルエラー: 未定義の変数 '{name}'")
offset = self.vars
self.emit(f" mov rax,")
elif node == 'BinOp':
op = node
self.gen_expr(node) # 左辺を評価
self.emit(" push rax")
self.gen_expr(node) # 右辺を評価
self.emit(" mov rcx, rax")
self.emit(" pop rax")
if op == '+':
self.emit(" add rax, rcx")
elif op == '-':
self.emit(" sub rax, rcx")
elif op == '*':
self.emit(" imul rax, rcx")
elif op == '/':
self.emit(" cqo")
self.emit(" idiv rcx")
# ==========================================
# 4. コンパイラ・ドライバ
# ==========================================
def main():
if len(sys.argv) != 2:
print("使用法: python pine_compiler.py <file.pine>")
sys.exit(1)
with open(sys.argv, 'r') as f:
code = f.read()
print(" 字句解析・構文解析を行っています...")
try:
tokens = tokenize(code)
parser = Parser(tokens)
ast = parser.parse_program()
except Exception as e:
print(e)
sys.exit(1)
print(" x86-64 アセンブリを生成しています...")
codegen = CodeGen()
try:
asm_code = codegen.generate(ast)
except Exception as e:
print(e)
sys.exit(1)
asm_file = "out.asm"
obj_file = "out.o"
exe_file = "out"
with open(asm_file, 'w') as f:
f.write(asm_code)
print(" アセンブルしています (nasm)...")
try:
subprocess.run(, check=True)
except FileNotFoundError:
print("エラー: 'nasm' コマンドが見つかりません。 'sudo apt install nasm' でインストールしてください。")
sys.exit(1)
print(" リンクして実行ファイルを作成しています (ld)...")
subprocess.run(, check=True)
print(f"\n🎉 コンパイル成功! 実行ファイル '{exe_file}' が作成されました。")
print(f"実行するには: ./{exe_file} と入力してください。")
if __name__ == '__main__':
main()完全なオリジナル言語: 構文規則(トークンの分解やAST木構造)を独自に定義しています。
C言語ランタイム (libc) を使っていない: gcc に依存せず、OSの標準機能(Linuxの標準出力のシステムコールである sys_write や sys_exit )をアセンブリ言語で直接叩いています。これは、Go言語などが自前の実行ファイルを作るのと同じ(非常に低レイヤな)アプローチです。
スタンドアロンの実行ファイル: 生成された 外 ファイルは、PythonもC言語も不要で、OS上でそのまま動く純粋なバイナリファイルです。


コメント