#!/usr/bin/env python3
import sys
import re, sys, time
infile = "miniC.mnc"
if __name__ == "__main__":
argv = sys.argv[1:] # コマンドライン引数を取得
nargv = len(argv)
if nargv != 1:
print("Usage: python miniC2.py <source_file>")
sys.exit(1)
if nargv == 1:
infile = argv[0] # 引数から入力ファイル名を取得
# NEW: ASTを綺麗に表示するための関数
[ドキュメント]
def print_ast(node, indent=0):
prefix = " " * indent
if isinstance(node, AST):
# ノードのクラス名を表示
print(f"{prefix}- {node.__class__.__name__}")
# ノードの属性を再帰的に表示
for key, value in vars(node).items():
print(f"{prefix} - {key}:")
print_ast(value, indent + 2)
elif isinstance(node, list):
# リストの中身を再帰的に表示
for i, item in enumerate(node):
print(f"{prefix} - [{i}]:")
print_ast(item, indent + 2)
else:
# それ以外の値(数値や文字列)をそのまま表示
print(f"{prefix} - {node!r}")
# (トークン定義、Tokenクラス、tokenize関数は変更なし)
# ... [Previous code for TOKENS, Token class, tokenize function] ...
# ── トークン定義 ─────────────────────────────────────────
TOKENS = [
('COMMENT', r'#.*'),
('NUMBER', r'\d+(\.\d*)?'),
('STRING', r'"(?:\\.|[^"])*"'),
('RETURN', r'return'),
('BOOL', r'true|false'),
('NULL', r'null'),
('BREAK', r'break'),
('CONTINUE', r'continue'),
('TYPE', r'void|int|double|str|list|dict'),
('KEYWORD', r'print|exit|for|if|elif|else|delete'),
('NAME', r'[A-Za-z_]\w*'),
('OP', r'&&|\|\||\+\+|\+=|==|!=|<=|>=|[+\-*/<>=&!]'),
('LPAREN', r'\('), ('RPAREN', r'\)'),
('LBRACE', r'\{'), ('RBRACE', r'\}'),
('LBRACK', r'\['), ('RBRACK', r'\]'),
('COLON', r':'), ('DOT', r'\.'),
('SEMI', r';'), ('COMMA', r','),
('SKIP', r'[ \t\r]+'),
('NEWLINE', r'\n'),
]
TOK_RE = re.compile('|'.join(f'(?P<{n}>{p})' for n,p in TOKENS))
[ドキュメント]
class Token:
def __init__(self, typ, val, line, col):
self.type, self.value, self.line, self.col = typ, val, line, col
def __repr__(self):
return f"Token({self.type!r},{self.value!r}, L{self.line}:{self.col})"
[ドキュメント]
def tokenize(code):
line_num, line_start = 1, 0
for mo in TOK_RE.finditer(code):
typ = mo.lastgroup; col_num = mo.start() - line_start
if typ == 'NEWLINE':
line_start = mo.end(); line_num += 1; continue
if typ in ('SKIP', 'COMMENT'): continue
yield Token(typ, mo.group(typ), line_num, col_num)
yield Token('EOF','', line_num, 0)
# (ASTノード、パーサは変更なし)
# ... [Previous code for AST nodes and Parser class] ...
# ── AST ノード ───────────────────────────────────
[ドキュメント]
class Number(AST):
def __init__(self,v): self.v = float(v)
def __repr__(self): return f"Number({self.v})"
[ドキュメント]
class String(AST):
def __init__(self,s): self.s = s[1:-1]
def __repr__(self): return f"String({self.s!r})"
[ドキュメント]
class Boolean(AST):
def __init__(self, v): self.v = (v == 'true')
def __repr__(self): return f"Boolean({self.v})"
[ドキュメント]
class Null(AST):
def __repr__(self): return "Null()"
[ドキュメント]
class Var(AST):
def __init__(self,n): self.n = n
def __repr__(self): return f"Var({self.n!r})"
[ドキュメント]
class BinOp(AST):
def __init__(self,op,l,r): self.op, self.l, self.r = op,l,r
def __repr__(self): return f"BinOp({self.op!r}, {self.l!r}, {self.r!r})"
[ドキュメント]
class UnaryOp(AST):
def __init__(self,op,e): self.op, self.e = op,e
def __repr__(self): return f"UnaryOp({self.op!r}, {self.e!r})"
[ドキュメント]
class ListLiteral(AST):
def __init__(self, elements): self.elements = elements
def __repr__(self): return f"ListLiteral({self.elements!r})"
[ドキュメント]
class DictLiteral(AST):
def __init__(self, pairs): self.pairs = pairs
def __repr__(self): return f"DictLiteral({self.pairs!r})"
[ドキュメント]
class IndexAccess(AST):
def __init__(self, collection, index): self.collection, self.index = collection, index
def __repr__(self): return f"IndexAccess({self.collection!r}, {self.index!r})"
[ドキュメント]
class Assign(AST):
def __init__(self,name,expr): self.name,self.expr = name,expr
def __repr__(self): return f"Assign({self.name!r}, {self.expr!r})"
[ドキュメント]
class AddAssign(AST):
def __init__(self,name,expr): self.name,self.expr = name,expr
def __repr__(self): return f"AddAssign({self.name!r}, {self.expr!r})"
[ドキュメント]
class Inc(AST):
def __init__(self,name,inc): self.name,self.inc= name,inc
def __repr__(self): return f"Inc({self.name!r}, {self.inc!r})"
[ドキュメント]
class Print(AST):
def __init__(self, args): self.args = args
def __repr__(self): return f"Print({self.args!r})"
[ドキュメント]
class Exit(AST):
def __repr__(self): return "Exit()"
[ドキュメント]
class Return(AST):
def __init__(self, expr): self.expr = expr
def __repr__(self): return f"Return({self.expr!r})"
[ドキュメント]
class Break(AST):
def __repr__(self): return "Break()"
[ドキュメント]
class Continue(AST):
def __repr__(self): return "Continue()"
[ドキュメント]
class Delete(AST):
def __init__(self, target): self.target = target
def __repr__(self): return f"Delete({self.target!r})"
[ドキュメント]
class Block(AST):
def __init__(self,stmts): self.stmts = stmts
def __repr__(self): return f"Block({self.stmts!r})"
[ドキュメント]
class If(AST):
def __init__(self, cond, then_blk, elifs, else_blk):
self.cond, self.then_blk, self.elifs, self.else_blk = cond,then_blk,elifs,else_blk
def __repr__(self): return f"If({self.cond!r}, {self.then_blk!r}, {self.elifs!r}, {self.else_blk!r})"
[ドキュメント]
class For(AST):
def __init__(self, init, cond, inc, body):
self.init, self.cond, self.inc, self.body = init,cond,inc,body
def __repr__(self): return f"For({self.init!r}, {self.cond!r}, {self.inc!r}, {self.body!r})"
[ドキュメント]
class FuncDef(AST):
def __init__(self,return_type, name,params,body):
self.return_type, self.name,self.params,self.body = return_type, name,params,body
def __repr__(self): return f"FuncDef({self.return_type!r}, {self.name!r}, {self.params!r}, {self.body!r})"
[ドキュメント]
class FuncCall(AST):
def __init__(self,name,args):
self.name,self.args = name,args
def __repr__(self): return f"FuncCall({self.name!r}, {self.args!r})"
# ── 再帰下降パーサ ─────────────────────────────────────
[ドキュメント]
class Parser:
def __init__(self, tokens):
self.tokens = iter(tokens); self._advance()
def _advance(self): self.tok = next(self.tokens)
def _eat(self, typ):
if self.tok.type == typ:
val = self.tok.value; self._advance(); return val
raise SyntaxError(f"L{self.tok.line}:{self.tok.col}: Expected {typ}, got {self.tok.type}")
[ドキュメント]
def parse(self):
stmts = [];
while self.tok.type != 'EOF': stmts.append(self.statement())
return Block(stmts)
[ドキュメント]
def parse_simple_statement_nosemi(self):
name = self._eat('NAME')
if self.tok.type == 'OP' and self.tok.value == '=':
self._advance(); expr = self.expr(); return Assign(Var(name), expr)
if self.tok.type == 'OP' and self.tok.value in ('++', '--'):
op_val = self.tok.value; self._advance(); return Inc(name, +1 if op_val == '++' else -1)
raise SyntaxError(f"L{self.tok.line}:{self.col}: Expected assignment or increment in for-loop clause")
[ドキュメント]
def statement(self):
if self.tok.type == 'KEYWORD':
kw = self.tok.value
if kw == 'print':
self._advance(); self._eat('LPAREN'); args = self.arguments(); self._eat('RPAREN'); self._eat('SEMI'); return Print(args)
if kw == 'exit':
self._advance(); self._eat('SEMI'); return Exit()
if kw == 'for':
self._advance(); self._eat('LPAREN'); init = self.parse_simple_statement_nosemi() if self.tok.type != 'SEMI' else None; self._eat('SEMI'); cond = self.expr() if self.tok.type != 'SEMI' else None; self._eat('SEMI'); inc = self.parse_simple_statement_nosemi() if self.tok.type != 'RPAREN' else None; self._eat('RPAREN'); body = self.block(); return For(init, cond, inc, body)
if kw == 'if':
self._advance(); self._eat('LPAREN'); cond = self.expr(); self._eat('RPAREN'); then_blk = self.block(); elifs, else_blk = [], None
while self.tok.type=='KEYWORD' and self.tok.value=='elif':
self._advance(); self._eat('LPAREN'); elif_cond = self.expr(); self._eat('RPAREN'); elif_blk = self.block(); elifs.append((elif_cond, elif_blk))
if self.tok.type=='KEYWORD' and self.tok.value=='else':
self._advance(); else_blk = self.block()
return If(cond, then_blk, elifs, else_blk)
if kw == 'delete':
self._advance(); target = self.postfix(); self._eat('SEMI'); return Delete(target)
if self.tok.type == 'BREAK': self._advance(); self._eat('SEMI'); return Break()
if self.tok.type == 'CONTINUE': self._advance(); self._eat('SEMI'); return Continue()
if self.tok.type == 'RETURN':
self._advance(); expr = self.expr() if self.tok.type != 'SEMI' else None; self._eat('SEMI'); return Return(expr)
if self.tok.type == 'TYPE':
return_type = self._eat('TYPE')
name = self._eat('NAME')
self._eat('LPAREN'); params = [];
if self.tok.type in ('TYPE', 'NAME'):
if self.tok.type == 'TYPE': self._advance()
params.append(self._eat('NAME'))
while self.tok.type=='COMMA': self._advance(); params.append(self._eat('NAME'))
self._eat('RPAREN')
body = self.block()
return FuncDef(return_type, name, params, body)
node = self.postfix()
if self.tok.type == 'OP' and self.tok.value == '=':
self._advance(); expr = self.expr(); self._eat('SEMI'); return Assign(node, expr)
if isinstance(node, FuncCall):
self._eat('SEMI'); return node
if isinstance(node, Var):
name = node.n
if self.tok.type == 'OP' and self.tok.value == '+=':
self._advance(); expr=self.expr(); self._eat('SEMI'); return AddAssign(name, expr)
if self.tok.type == 'OP' and self.tok.value in ('++', '--'):
op_val = self.tok.value; self._advance(); self._eat('SEMI'); return Inc(name, +1 if op_val == '++' else -1)
raise SyntaxError(f"L{self.tok.line}:{self.tok.col}: Invalid statement {self.tok}")
[ドキュメント]
def block(self):
self._eat('LBRACE'); stmts = []
while self.tok.type != 'RBRACE': stmts.append(self.statement())
self._eat('RBRACE'); return Block(stmts)
[ドキュメント]
def arguments(self):
args = []
if self.tok.type != 'RPAREN':
args.append(self.expr())
while self.tok.type == 'COMMA': self._advance(); args.append(self.expr())
return args
[ドキュメント]
def expr(self): return self._binop(self.logic_and, ['||'])
[ドキュメント]
def logic_and(self): return self._binop(self.equality, ['&&'])
[ドキュメント]
def equality(self): return self._binop(self.comparison, ['==', '!='])
[ドキュメント]
def comparison(self):return self._binop(self.concat, ['<', '>', '<=', '>='])
[ドキュメント]
def concat(self): return self._binop(self.term, ['&'])
[ドキュメント]
def term(self): return self._binop(self.factor, ['+','-'])
[ドキュメント]
def factor(self): return self._binop(self.unary, ['*','/'])
def _binop(self, sub, ops):
left = sub();
while self.tok.type=='OP' and self.tok.value in ops:
op = self.tok.value; self._advance(); right = sub(); left = BinOp(op,left,right)
return left
[ドキュメント]
def unary(self):
if self.tok.type=='OP' and self.tok.value in ('-', '!'):
op = self.tok.value; self._advance(); return UnaryOp(op, self.unary())
return self.postfix()
[ドキュメント]
def postfix(self):
node = self.atom()
while self.tok.type in ('LPAREN', 'LBRACK', 'DOT'):
if self.tok.type == 'LPAREN':
self._advance(); args = self.arguments(); self._eat('RPAREN'); node = FuncCall(node, args)
elif self.tok.type == 'LBRACK':
self._advance(); index_expr = self.expr(); self._eat('RBRACK'); node = IndexAccess(node, index_expr)
elif self.tok.type == 'DOT':
self._advance(); method_name = self._eat('NAME'); self._eat('LPAREN'); args = self.arguments(); self._eat('RPAREN');
node = FuncCall(Var(method_name), [node] + args)
return node
[ドキュメント]
def atom(self):
tok_type = self.tok.type
tok_val = self.tok.value
if tok_type == 'BOOL': self._advance(); return Boolean(tok_val)
if tok_type == 'NULL': self._advance(); return Null()
if tok_type in ('NAME', 'KEYWORD', 'TYPE'):
self._advance(); return Var(tok_val)
if tok_type=='NUMBER': self._advance(); return Number(tok_val)
if tok_type=='STRING': self._advance(); return String(tok_val)
if tok_type=='LPAREN': self._advance(); e = self.expr(); self._eat('RPAREN'); return e
if tok_type == 'LBRACK': self._advance(); elements = self.arguments(); self._eat('RBRACK'); return ListLiteral(elements)
if tok_type == 'LBRACE':
self._advance(); pairs = []
if self.tok.type != 'RBRACE':
while True:
key = self.expr(); self._eat('COLON'); val = self.expr(); pairs.append((key, val))
if self.tok.type != 'COMMA': break
self._advance()
self._eat('RBRACE'); return DictLiteral(pairs)
raise SyntaxError(f"L{self.tok.line}:{self.tok.col}: Bad atom: {self.tok}")
[ドキュメント]
class ReturnValue(Exception):
def __init__(self, value): self.value = value
[ドキュメント]
class BreakLoop(Exception): pass
[ドキュメント]
class ContinueLoop(Exception): pass
# ── インタプリタ ─────────────────────────────────────
[ドキュメント]
class Interpreter:
def __init__(self, ast: Block):
self.ast = ast
# NEW: ファイルハンドル管理用の辞書とIDを追加
self.file_handles = {}
self.next_handle_id = 0
self.builtins = {
'time': self._builtin_time,
'len': self._builtin_len,
'append': self._builtin_append,
'insert': self._builtin_insert,
# NEW: ファイル操作用の組み込み関数を追加
'open': self._builtin_open,
'read': self._builtin_read,
'write': self._builtin_write,
'close': self._builtin_close,
# 追加テスト
'add': self._builtin_add,
}
self.scopes = [self.builtins.copy()]
# NEW: ファイル操作用メソッド
def _get_file_handle(self, handle_id):
handle = self.file_handles.get(int(handle_id))
if handle is None: raise ValueError(f"Invalid file handle: {handle_id}")
return handle
def _builtin_open(self, args):
if len(args) != 2: raise TypeError("open() takes 2 arguments (filepath, mode)")
path, mode = str(args[0]), str(args[1])
try:
file_obj = open(path, mode)
handle_id = self.next_handle_id
self.file_handles[handle_id] = file_obj
self.next_handle_id += 1
return float(handle_id) # スクリプト側には数値としてハンドルを返す
except Exception as e:
raise IOError(f"Could not open file '{path}': {e}") from e
def _builtin_read(self, args):
if len(args) != 1: raise TypeError("read() takes 1 argument (handle)")
file_obj = self._get_file_handle(args[0])
return file_obj.read()
def _builtin_write(self, args):
if len(args) != 2: raise TypeError("write() takes 2 arguments (handle, data)")
file_obj = self._get_file_handle(args[0])
return file_obj.write(str(args[1]))
def _builtin_close(self, args):
if len(args) != 1: raise TypeError("close() takes 1 argument (handle)")
handle_id = int(args[0])
file_obj = self._get_file_handle(handle_id)
file_obj.close()
del self.file_handles[handle_id] # ハンドル管理辞書から削除
return None
def _builtin_add(self, args):
if len(args) != 2: raise TypeError("add() takes 2 arguments (val1, val2)")
return float(args[0]) + float(args[1])
# (以降の組み込み関数、環境管理メソッドは変更なし)
# ... [Previous code for other builtins and env methods] ...
def _builtin_time(self, args):
if args: raise TypeError("time() takes no arguments"); return time.time()
def _builtin_len(self, args):
if len(args) != 1: raise TypeError("len() takes exactly one argument");
arg = args[0]
if isinstance(arg, (str, list, dict)): return float(len(arg))
raise TypeError(f"Object of type {type(arg).__name__} has no len()")
def _builtin_append(self, args):
if len(args) != 2: raise TypeError("append() takes 2 arguments (list, value)");
if not isinstance(args[0], list): raise TypeError("First argument to append must be a list")
args[0].append(args[1]); return None
def _builtin_insert(self, args):
if len(args) != 3: raise TypeError("insert() takes 3 arguments (list, index, value)");
if not isinstance(args[0], list): raise TypeError("First argument to insert must be a list")
args[0].insert(int(args[1]), args[2]); return None
def _env_assign(self, name, value): self.scopes[-1][name] = value
def _env_lookup(self, name):
for scope in reversed(self.scopes):
if name in scope: return scope[name]
raise NameError(f"Name '{name}' is not defined.")
def _env_update(self, name, value):
for scope in reversed(self.scopes):
if name in scope: scope[name] = value; return
raise NameError(f"Name '{name}' is not defined for update.")
[ドキュメント]
def run(self):
try:
self._exec_block(self.ast)
except (ReturnValue, BreakLoop, ContinueLoop) as e:
print(f"Error: '{type(e).__name__}' can only be used inside a function or loop.", file=sys.stderr)
finally: # NEW: プログラム終了時に開いているファイルをすべて閉じる
for handle_id in list(self.file_handles.keys()):
self._builtin_close([handle_id])
# (_exec_block, _exec, _eval, _to_bool, to_strは変更なし)
# ... [Previous code for _exec_block, _exec, _eval, etc.] ...
def _exec_block(self, block: Block):
for stmt in block.stmts: self._exec(stmt)
def _exec(self, node):
t = type(node)
if t is Return: raise ReturnValue(self._eval(node.expr) if node.expr else None)
if t is Break: raise BreakLoop()
if t is Continue: raise ContinueLoop()
if t is Assign:
value = self._eval(node.expr)
assign_to = node.name
if isinstance(assign_to, Var): self._env_assign(assign_to.n, value)
elif isinstance(assign_to, IndexAccess):
collection = self._eval(assign_to.collection)
index = self._eval(assign_to.index)
if isinstance(collection, list): collection[int(index)] = value
else: collection[index] = value # dict
else: raise SyntaxError("LHS of assignment must be a variable or index access.")
elif t is AddAssign:
new_val = self._env_lookup(node.name) + self._eval(node.expr); self._env_update(node.name, new_val)
elif t is Inc:
self._env_update(node.name, self._env_lookup(node.name) + node.inc)
elif t is Print:
print(*(self.to_str(self._eval(a)) for a in node.args))
elif t is Exit:
sys.exit(0)
elif t is Delete:
target = node.target
if isinstance(target, IndexAccess):
collection = self._eval(target.collection); index = self._eval(target.index)
del collection[int(index) if isinstance(collection, list) else index]
else: raise TypeError("delete target must be an index or key access")
elif t is Block:
self._exec_block(node)
elif t is If:
if self._to_bool(self._eval(node.cond)): self._exec_block(node.then_blk)
else:
executed_elif = False
for elif_cond, elif_blk in node.elifs:
if self._to_bool(self._eval(elif_cond)): self._exec_block(elif_blk); executed_elif = True; break
if not executed_elif and node.else_blk: self._exec_block(node.else_blk)
elif t is For:
self.scopes.append({})
try:
if node.init: self._exec(node.init)
while not node.cond or self._to_bool(self._eval(node.cond)):
try:
self._exec_block(node.body)
except ContinueLoop:
if node.inc: self._exec(node.inc)
continue
except BreakLoop:
break
if node.inc: self._exec(node.inc)
finally:
self.scopes.pop()
elif t is FuncDef:
func_info = {'type': 'user_function', 'params': node.params, 'body': node.body, 'return_type': node.return_type}
self._env_assign(node.name, func_info)
elif t is FuncCall:
self._eval(node)
else: raise RuntimeError(f"Unknown exec node {type(node)}")
def _eval(self, node):
t = type(node)
if t is Number: return node.v
if t is String: return node.s
if t is Boolean: return node.v
if t is Null: return None
if t is Var: return self._env_lookup(node.n)
if t is ListLiteral: return [self._eval(e) for e in node.elements]
if t is DictLiteral: return {self._eval(k): self._eval(v) for k, v in node.pairs}
if t is IndexAccess:
collection = self._eval(node.collection); index = self._eval(node.index)
if isinstance(collection, (list, str)): return collection[int(index)]
return collection[index]
if t is FuncCall:
func_obj_node = node.name
if isinstance(func_obj_node, Var) and func_obj_node.n in self.builtins:
func_obj = self.builtins[func_obj_node.n]
evaluated_args = [self._eval(arg) for arg in node.args]
return func_obj(evaluated_args)
func_obj = self._eval(func_obj_node)
evaluated_args = [self._eval(arg) for arg in node.args]
if isinstance(func_obj, dict) and func_obj.get('type') == 'user_function':
params, body = func_obj['params'], func_obj['body']
if len(params) != len(evaluated_args):
raise TypeError(f"Function expects {len(params)} args, got {len(evaluated_args)}")
new_scope = dict(zip(params, evaluated_args)); self.scopes.append(new_scope)
return_value = None
try:
self._exec_block(body)
except ReturnValue as e:
return_value = e.value
finally:
self.scopes.pop()
return return_value
else: raise TypeError(f"Object '{func_obj_node.n if isinstance(func_obj_node, Var) else func_obj}' is not a function")
if t is UnaryOp:
op = node.op; val = self._eval(node.e)
if op == '-': return -val
if op == '!': return not self._to_bool(val)
if t is BinOp:
op = node.op
if op == '&&': return self._to_bool(self._eval(node.l)) and self._to_bool(self._eval(node.r))
if op == '||': return self._to_bool(self._eval(node.l)) or self._to_bool(self._eval(node.r))
l, r = self._eval(node.l), self._eval(node.r)
if op == '&': return str(l) + str(r)
if op == '+': return l + r
if op == '-': return l - r
if op == '*': return l * r
if op == '/':
if r == 0: raise ZeroDivisionError("division by zero in script"); return l / r
if op == '>': return l > r
if op == '<': return l < r
if op == '==': return l == r
if op == '!=': return l != r
if op == '<=': return l <= r
if op == '>=': return l >= r
raise RuntimeError(f"Unknown expr {node}")
def _to_bool(self, value):
if isinstance(value, bool): return value
if value is None: return False
if isinstance(value, (int, float)): return value != 0
return bool(value)
[ドキュメント]
def to_str(self, value):
if value is None: return 'null'
if isinstance(value, bool): return 'true' if value else 'false'
return str(value)
# ── 動作確認用コード ─────────────────────────────────
if __name__ == "__main__":
print()
print(f"infile: {infile}")
with open(infile, 'r', encoding='utf-8') as f:
source = f.read()
tokens = tokenize(source)
ast = Parser(tokens).parse()
print("ast:", ast)
print_ast(ast)
print()
print("run:")
Interpreter(ast).run()