#!/usr/bin/env python3
import sys
import re, sys, time


infile = "miniC.mnc"
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 AST: pass

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()