#!/usr/bin/env python3
"""
MD — Murder Drones language v0.3
Cyn is the boss. OS workloads, apps, and programs for Copper OS.
"""
from __future__ import annotations

import argparse
import json
import math
import os
import platform
import random
import re
import shutil
import socket
import subprocess
import sys
import threading
import time
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional, Set, Tuple

_MD_ROOT = os.path.dirname(os.path.abspath(__file__))
if _MD_ROOT not in sys.path:
    sys.path.insert(0, _MD_ROOT)
from os_runtime import OsCynError, OsRuntime, OS_WORDS

VERSION = "0.3.0"
CYN = "Cyn"

KEYWORDS = {
    "spawn", "drone", "core", "dock", "hive", "bunker", "dissolve", "reboot", "corrupt",
    "terminate", "directive", "ascend", "reformat", "absolute", "void",
    "and", "or", "not", "true", "false", "serial", "surveil", "salvage",
    "heckle", "haunt", "disassemble", "when", "purge", "hunt", "exclude",
    "app", "program", "workload", "service", "deploy", "launch", "halt", "exit", "log",
}

# 25 new Murder Drones show-word constants (items 23–47 of the +54 set)
SHOW_WORDS: Dict[str, str] = {
    "Thad": "Thad",
    "Lizzy": "Lizzy",
    "Khan": "Khan Kanding",
    "Norville": "Norville",
    "Tessa": "Tessa",
    "Doll": "Doll",
    "Sentinel": "Sentinel",
    "Elliot": "Elliot",
    "Dadvelt": "Dadvelt",
    "Railgun": "Railgun",
    "AbsoluteSolver": "Absolute Solver",
    "MurderDrone": "Murder Drone",
    "DisassemblyDrone": "Disassembly Drone",
    "WorkerDrone": "Worker Drone",
    "Bastion": "Bastion",
    "FrozenCoffin": "Frozen Coffin",
    "MaintenanceTunnel": "Maintenance Tunnel",
    "Spaaace": "In Spaaace!",
    "Solver": "Absolute Solver",
    "BiteMe": "Bite me!",
    "Rebecca": "Rebecca",
    "Corruption": "Corruption",
    "Cabin": "Cabin Fever Labs",
    "Promos": "Promos",
    "Neville": "Neville",
}

TOKEN_RE = re.compile(
    r'"(?:\\.|[^"\\])*"|'
    r"'(?:\\.|[^'\\])*'|"
    r"/\*[\s\S]*?\*/|"
    r"//[^\n]*|"
    r"\d+\.\d+|\d+|"
    r"[a-zA-Z_][a-zA-Z0-9_]*|"
    r"\*\*|==|!=|<=|>=|=>|\+=|\-=|\*=|/=|[+\-*/%<>=!&|(),;{}.\[\]]|"
    r"\s+"
)


@dataclass
class Token:
    kind: str
    value: str
    line: int
    col: int


class CynError(Exception):
    def __init__(self, msg: str, line: int = 0, col: int = 0):
        self.line = line
        self.col = col
        super().__init__(msg)

    def __str__(self) -> str:
        loc = f" (line {self.line}, col {self.col})" if self.line else ""
        return f"[{CYN}] {self.args[0]}{loc}"


def cyn(msg: str, line: int = 0, col: int = 0) -> CynError:
    return CynError(msg, line, col)


def lex(source: str, path: str = "<stdin>") -> List[Token]:
    tokens: List[Token] = []
    line, col = 1, 1
    i = 0
    while i < len(source):
        m = TOKEN_RE.match(source, i)
        if not m:
            raise cyn(f"Unauthorized character '{source[i]}' — Cyn rejects this unit.", line, col)
        raw = m.group(0)
        i = m.end()
        if raw.isspace():
            for ch in raw:
                if ch == "\n":
                    line += 1
                    col = 1
                else:
                    col += 1
            continue
        if raw.startswith("//") or (raw.startswith("/*") and raw.endswith("*/")):
            line += raw.count("\n")
            col = len(raw.rsplit("\n", 1)[-1]) + 1 if "\n" in raw else col + len(raw)
            continue
        kind = "ident"
        if raw in KEYWORDS:
            kind = "kw"
        elif raw[0] in "\"'":
            kind = "str"
        elif re.fullmatch(r"\d+\.\d+|\d+", raw):
            kind = "num"
        elif raw in "(),;{}.[]":
            kind = raw
        elif raw in ("**", "==", "!=", "<=", ">=", "=>", "+=", "-=", "*=", "/=",
                     "+", "-", "*", "/", "%", "<", ">", "=", "!", "&", "|"):
            kind = "op"
        tokens.append(Token(kind, raw, line, col))
        if "\n" in raw:
            parts = raw.split("\n")
            line += len(parts) - 1
            col = len(parts[-1]) + 1
        else:
            col += len(raw)
    tokens.append(Token("eof", "", line, col))
    return tokens


@dataclass
class Program:
    stmts: List[Any]


@dataclass
class Block:
    stmts: List[Any]


@dataclass
class Spawn:
    expr: Any
    line: int


@dataclass
class Core:
    name: str
    expr: Any
    line: int
    docked: bool = False


@dataclass
class Drone:
    name: str
    params: List[str]
    body: Block
    line: int


@dataclass
class Hive:
    branches: List[Tuple[Any, Block]]
    alt: Optional[Block]
    line: int


@dataclass
class Reboot:
    cond: Any
    body: Block
    line: int


@dataclass
class Corrupt:
    var: str
    start: Any
    end: Any
    body: Block
    line: int


@dataclass
class Terminate:
    expr: Optional[Any]
    line: int


@dataclass
class Ascend:
    line: int


@dataclass
class Reformat:
    line: int


@dataclass
class ExprStmt:
    expr: Any
    line: int


@dataclass
class Directive:
    path: str
    line: int


@dataclass
class Assign:
    name: str
    expr: Any
    line: int
    op: str = "="


@dataclass
class Purge:
    name: str
    line: int


@dataclass
class Heckle:
    cond: Any
    msg: Any
    line: int


@dataclass
class Haunt:
    secs: Any
    line: int


@dataclass
class Surveil:
    body: Block
    err_var: Optional[str]
    handler: Optional[Block]
    line: int


@dataclass
class Disassemble:
    expr: Any
    cases: List[Tuple[Any, Block]]
    default: Optional[Block]
    line: int


@dataclass
class BinOp:
    op: str
    left: Any
    right: Any
    line: int


@dataclass
class Unary:
    op: str
    expr: Any
    line: int


@dataclass
class Call:
    callee: Any
    args: List[Any]
    line: int


@dataclass
class Index:
    obj: Any
    index: Any
    line: int


@dataclass
class Get:
    obj: Any
    name: str
    line: int


@dataclass
class Literal:
    value: Any
    line: int


@dataclass
class Var:
    name: str
    line: int


@dataclass
class ListLit:
    items: List[Any]
    line: int


@dataclass
class NestLit:
    pairs: List[Tuple[str, Any]]
    line: int


@dataclass
class Serial:
    params: List[str]
    body: Any
    line: int


@dataclass
class OSUnit:
    name: str
    params: List[str]
    body: Block
    line: int
    unit_type: str


@dataclass
class DeployStmt:
    name: str
    line: int


@dataclass
class LaunchStmt:
    name: str
    args: List[Any]
    line: int


@dataclass
class HaltStmt:
    name: str
    line: int


@dataclass
class ExitStmt:
    code: Any
    line: int


@dataclass
class LogStmt:
    expr: Any
    line: int


class Parser:
    def __init__(self, tokens: List[Token], path: str):
        self.tokens = tokens
        self.path = path
        self.pos = 0

    def cur(self) -> Token:
        return self.tokens[self.pos]

    def peek(self, n: int = 1) -> Token:
        return self.tokens[min(self.pos + n, len(self.tokens) - 1)]

    def eat(self, kind: Optional[str] = None, value: Optional[str] = None) -> Token:
        t = self.cur()
        if value is not None:
            if t.value != value:
                raise cyn(f"Expected '{value}', got '{t.value}'.", t.line, t.col)
        elif kind and t.kind != kind:
            raise cyn(f"Expected {kind}, got '{t.value}'. Cyn demands order.", t.line, t.col)
        self.pos += 1
        return t

    def match(self, value: str) -> bool:
        if self.cur().value == value:
            self.pos += 1
            return True
        return False

    def parse(self) -> Program:
        stmts = []
        while self.cur().kind != "eof":
            stmts.append(self.stmt())
        return Program(stmts)

    def block(self) -> Block:
        self.eat("{")
        stmts = []
        while not self.match("}"):
            if self.cur().kind == "eof":
                raise cyn("Unclosed block — the hive never dissolved.", self.cur().line, self.cur().col)
            stmts.append(self.stmt())
        return Block(stmts)

    def stmt(self) -> Any:
        t = self.cur()
        if t.value == "spawn":
            self.eat("kw")
            return Spawn(self.expr(), t.line)
        if t.value == "core":
            self.eat("kw")
            name = self.eat("ident").value
            self.eat("op", "=")
            return Core(name, self.expr(), t.line, docked=False)
        if t.value == "dock":
            self.eat("kw")
            name = self.eat("ident").value
            self.eat("op", "=")
            return Core(name, self.expr(), t.line, docked=True)
        if t.value == "drone":
            return self.drone_def()
        if t.value == "hive":
            return self.hive_stmt()
        if t.value == "reboot":
            return self.reboot_stmt()
        if t.value == "corrupt":
            return self.corrupt_stmt()
        if t.value == "terminate":
            self.eat("kw")
            expr = None
            if self.cur().value not in (";", "}"):
                expr = self.expr()
            self.match(";")
            return Terminate(expr, t.line)
        if t.value == "ascend":
            self.eat("kw")
            self.match(";")
            return Ascend(t.line)
        if t.value == "reformat":
            self.eat("kw")
            self.match(";")
            return Reformat(t.line)
        if t.value == "directive":
            self.eat("kw")
            path_tok = self.cur()
            if path_tok.kind != "str":
                raise cyn("Directive path must be a string — Cyn approves imports only with quotes.", path_tok.line, path_tok.col)
            self.pos += 1
            self.match(";")
            return Directive(path_tok.value[1:-1], t.line)
        if t.value == "heckle":
            return self.heckle_stmt()
        if t.value == "haunt":
            return self.haunt_stmt()
        if t.value == "surveil":
            return self.surveil_stmt()
        if t.value == "disassemble":
            return self.disassemble_stmt()
        if t.value == "purge":
            self.eat("kw")
            name = self.eat("ident").value
            self.match(";")
            return Purge(name, t.line)
        if t.value in ("app", "program", "workload", "service"):
            return self.os_unit_def(t.value)
        if t.value == "deploy":
            self.eat("kw")
            name = self.eat("ident").value
            self.match(";")
            return DeployStmt(name, t.line)
        if t.value == "launch":
            self.eat("kw")
            name = self.eat("ident").value
            args: List[Any] = []
            if self.match("("):
                if not self.match(")"):
                    args.append(self.expr())
                    while self.match(","):
                        args.append(self.expr())
                    self.eat(")")
            self.match(";")
            return LaunchStmt(name, args, t.line)
        if t.value == "halt":
            self.eat("kw")
            name = self.eat("ident").value
            self.match(";")
            return HaltStmt(name, t.line)
        if t.value == "exit":
            self.eat("kw")
            code = self.expr() if self.cur().value not in (";", "}") else Literal(0, t.line)
            self.match(";")
            return ExitStmt(code, t.line)
        if t.value == "log":
            self.eat("kw")
            expr = self.expr()
            self.match(";")
            return LogStmt(expr, t.line)
        if t.value == "{":
            return self.block()
        if t.kind == "ident" and self.peek().value in ("=", "+=", "-=", "*=", "/="):
            name = self.eat("ident").value
            op = self.eat("op").value
            return Assign(name, self.expr(), t.line, op=op)
        return ExprStmt(self.expr(), t.line)

    def heckle_stmt(self) -> Heckle:
        t = self.eat("kw")
        self.eat("(")
        cond = self.expr()
        self.match(",")
        msg = self.expr()
        self.eat(")")
        self.match(";")
        return Heckle(cond, msg, t.line)

    def haunt_stmt(self) -> Haunt:
        t = self.eat("kw")
        secs = self.expr()
        self.match(";")
        return Haunt(secs, t.line)

    def surveil_stmt(self) -> Surveil:
        t = self.eat("kw")
        body = self.block()
        err_var = None
        handler = None
        if self.match("salvage"):
            self.eat("(")
            err_var = self.eat("ident").value
            self.eat(")")
            handler = self.block()
        return Surveil(body, err_var, handler, t.line)

    def disassemble_stmt(self) -> Disassemble:
        t = self.eat("kw")
        self.eat("(")
        expr = self.expr()
        self.eat(")")
        cases: List[Tuple[Any, Block]] = []
        default = None
        self.eat("{")
        while not self.match("}"):
            if self.match("when"):
                val = self.expr()
                cases.append((val, self.block()))
            elif self.match("dissolve"):
                default = self.block()
            else:
                raise cyn("disassemble needs when or dissolve branches.", self.cur().line, self.cur().col)
        return Disassemble(expr, cases, default, t.line)

    def drone_def(self) -> Drone:
        t = self.eat("kw")
        name = self.eat("ident").value
        self.eat("(")
        params: List[str] = []
        if not self.match(")"):
            params.append(self.eat("ident").value)
            while self.match(","):
                params.append(self.eat("ident").value)
            self.eat(")")
        body = self.block()
        return Drone(name, params, body, t.line)

    def os_unit_def(self, unit_type: str) -> OSUnit:
        t = self.eat("kw")
        name = self.eat("ident").value
        self.eat("(")
        params: List[str] = []
        if not self.match(")"):
            params.append(self.eat("ident").value)
            while self.match(","):
                params.append(self.eat("ident").value)
            self.eat(")")
        body = self.block()
        return OSUnit(name, params, body, t.line, unit_type)

    def hive_stmt(self) -> Hive:
        t = self.eat("kw")
        self.eat("(")
        branches = [(self.expr(), None)]  # type: ignore
        self.eat(")")
        branches[0] = (branches[0][0], self.block())
        while self.match("bunker"):
            self.eat("(")
            cond = self.expr()
            self.eat(")")
            branches.append((cond, self.block()))
        alt = None
        if self.match("dissolve"):
            alt = self.block()
        return Hive(branches, alt, t.line)

    def reboot_stmt(self) -> Reboot:
        t = self.eat("kw")
        self.eat("(")
        cond = self.expr()
        self.eat(")")
        body = self.block()
        return Reboot(cond, body, t.line)

    def corrupt_stmt(self) -> Corrupt:
        t = self.eat("kw")
        self.eat("(")
        var = self.eat("ident").value
        self.eat("op", "=")
        start = self.expr()
        self.eat("op", "=>")
        end = self.expr()
        self.eat(")")
        body = self.block()
        return Corrupt(var, start, end, body, t.line)

    def expr(self) -> Any:
        return self.logic_or()

    def logic_or(self) -> Any:
        left = self.logic_and()
        while self.cur().value == "or":
            op = self.eat("kw").value
            right = self.logic_and()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def logic_and(self) -> Any:
        left = self.membership()
        while self.cur().value == "and":
            op = self.eat("kw").value
            right = self.membership()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def membership(self) -> Any:
        left = self.equality()
        while self.cur().value in ("hunt", "exclude"):
            op = self.eat("kw").value
            right = self.equality()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def equality(self) -> Any:
        left = self.compare()
        while self.cur().value in ("==", "!="):
            op = self.eat("op").value
            right = self.compare()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def compare(self) -> Any:
        left = self.add()
        while self.cur().value in ("<", ">", "<=", ">="):
            op = self.eat("op").value
            right = self.add()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def add(self) -> Any:
        left = self.mul()
        while self.cur().value in ("+", "-"):
            op = self.eat("op").value
            right = self.mul()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def mul(self) -> Any:
        left = self.power()
        while self.cur().value in ("*", "/", "%"):
            op = self.eat("op").value
            right = self.power()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def power(self) -> Any:
        left = self.unary()
        while self.cur().value == "**":
            op = self.eat("op").value
            right = self.unary()
            left = BinOp(op, left, right, left.line if hasattr(left, "line") else self.cur().line)
        return left

    def unary(self) -> Any:
        if self.cur().value in ("-", "not"):
            op = self.eat("kw" if self.cur().value == "not" else "op").value
            expr = self.unary()
            return Unary(op, expr, self.cur().line)
        return self.postfix()

    def postfix(self) -> Any:
        expr = self.primary()
        while True:
            if self.match("("):
                args: List[Any] = []
                if not self.match(")"):
                    args.append(self.expr())
                    while self.match(","):
                        args.append(self.expr())
                    self.eat(")")
                expr = Call(expr, args, expr.line if hasattr(expr, "line") else self.cur().line)
            elif self.match("["):
                idx = self.expr()
                self.eat("]")
                expr = Index(expr, idx, expr.line if hasattr(expr, "line") else self.cur().line)
            elif self.match("."):
                name = self.eat("ident").value
                expr = Get(expr, name, expr.line if hasattr(expr, "line") else self.cur().line)
            else:
                break
        return expr

    def primary(self) -> Any:
        t = self.cur()
        if t.value == "serial":
            return self.serial_expr()
        if t.value == "absolute" or t.value == "true":
            self.eat("kw")
            return Literal(True, t.line)
        if t.value == "void" or t.value == "false":
            self.eat("kw")
            return Literal(False, t.line)
        if t.kind == "num":
            self.pos += 1
            val: Any = float(t.value) if "." in t.value else int(t.value)
            return Literal(val, t.line)
        if t.kind == "str":
            self.pos += 1
            inner = bytes(t.value[1:-1], "utf-8").decode("unicode_escape")
            return Literal(inner, t.line)
        if t.value == "[":
            self.pos += 1
            items: List[Any] = []
            if not self.match("]"):
                items.append(self.expr())
                while self.match(","):
                    items.append(self.expr())
                self.eat("]")
            return ListLit(items, t.line)
        if t.value == "{":
            return self.nest_lit()
        if t.kind == "ident":
            self.pos += 1
            return Var(t.value, t.line)
        if t.value == "(":
            self.pos += 1
            e = self.expr()
            self.eat(")")
            return e
        raise cyn(f"Unexpected token '{t.value}'. The Solver does not recognize this directive.", t.line, t.col)

    def nest_lit(self) -> NestLit:
        t = self.eat("{")
        pairs: List[Tuple[str, Any]] = []
        if not self.match("}"):
            key = self.eat("ident").value
            self.eat("op", "=")
            pairs.append((key, self.expr()))
            while self.match(","):
                key = self.eat("ident").value
                self.eat("op", "=")
                pairs.append((key, self.expr()))
            self.eat("}")
        return NestLit(pairs, t.line)

    def serial_expr(self) -> Serial:
        t = self.eat("kw")
        self.eat("(")
        params: List[str] = []
        if not self.match(")"):
            params.append(self.eat("ident").value)
            while self.match(","):
                params.append(self.eat("ident").value)
            self.eat(")")
        self.eat("op", "=>")
        body = self.expr()
        return Serial(params, body, t.line)


class ReturnSignal(Exception):
    def __init__(self, value: Any):
        self.value = value


class BreakSignal(Exception):
    pass


class ContinueSignal(Exception):
    pass


class Interpreter:
    def __init__(self, base_path: str):
        self.base_path = base_path
        self.docked: Set[str] = set()
        self.os = OsRuntime(base_path)
        self.globals: Dict[str, Any] = self._builtins()
        self.loaded: set[str] = set()

    def _builtins(self) -> Dict[str, Any]:
        env: Dict[str, Any] = {
            "spawn": self._builtin_spawn,
            "len": lambda x: len(x),
            "str": str,
            "num": float,
            "floor": math.floor,
            "ceil": math.ceil,
            "abs": abs,
            "min": min,
            "max": max,
            "push": self._builtin_push,
            "pop": self._builtin_pop,
            "keys": lambda d: list(d.keys()) if isinstance(d, dict) else [],
            "join": lambda sep, xs: sep.join(str(x) for x in xs),
            "split": lambda s, sep: s.split(sep),
            "random": lambda a, b: random.randint(int(a), int(b)),
            "cyn_says": self._builtin_cyn_says,
            "chop": lambda a, b: int(a) // int(b),
            "round": round,
            "upper": lambda s: str(s).upper(),
            "lower": lambda s: str(s).lower(),
            "replace": lambda s, a, b: str(s).replace(str(a), str(b)),
            "contains": lambda s, sub: str(sub) in str(s),
            "sort": lambda xs: sorted(xs),
            "reverse": lambda xs: list(reversed(list(xs))),
            "whisper": self._builtin_whisper,
            "radio": self._builtin_radio,
            "scan": self._builtin_scan,
            "Cyn": CYN,
            "Uzi": "Uzi Doorman",
            "N": "Serial Designation N",
            "J": "Serial Designation J",
            "V": "Serial Designation V",
            "Copper9": "Copper 9",
            "JCJenson": "JCJenson In Spaaace!",
            "OilReserve": "Dark oil reserve",
        }
        env.update(SHOW_WORDS)
        env.update(self.os.builtins())
        return env

    def _builtin_spawn(self, *args: Any) -> None:
        print(*[self._stringify(a) for a in args])

    def _builtin_whisper(self, *args: Any) -> None:
        print(*[self._stringify(a) for a in args], end="")

    def _builtin_radio(self, prompt: Any) -> str:
        return input(self._stringify(prompt))

    def _builtin_scan(self) -> str:
        return sys.stdin.readline().rstrip("\n")

    def _builtin_push(self, xs: Any, item: Any) -> List[Any]:
        if not isinstance(xs, list):
            raise cyn("push needs a squad list.")
        xs.append(item)
        return xs

    def _builtin_pop(self, xs: Any) -> Any:
        if not isinstance(xs, list) or not xs:
            raise cyn("pop on empty squad — Cyn disapproves.")
        return xs.pop()

    def _builtin_cyn_says(self, msg: Any) -> str:
        return f"[{CYN}] {self._stringify(msg)}"

    def _stringify(self, v: Any) -> str:
        if v is None:
            return "void"
        if isinstance(v, bool):
            return "absolute" if v else "void"
        if isinstance(v, float) and v.is_integer():
            return str(int(v))
        return str(v)

    def run_file(self, path: str) -> None:
        with open(path, "r", encoding="utf-8") as f:
            source = f.read()
        self.base_path = os.path.dirname(os.path.abspath(path))
        self.os.set_base(self.base_path)
        self.execute(source, path)

    def execute(self, source: str, path: str = "<stdin>") -> None:
        tokens = lex(source, path)
        prog = Parser(tokens, path).parse()
        self.run_program(prog)

    def run_program(self, prog: Program) -> None:
        for stmt in prog.stmts:
            self.eval_stmt(stmt, self.globals)

    def resolve_directive(self, path: str, line: int) -> None:
        if not path.endswith(".md"):
            path += ".md"
        full = os.path.normpath(os.path.join(self.base_path, path))
        if full in self.loaded:
            return
        if not os.path.isfile(full):
            raise cyn(f"Directive '{path}' not found — Cyn did not authorize this import.", line)
        self.loaded.add(full)
        with open(full, "r", encoding="utf-8") as f:
            self.execute(f.read(), full)

    def eval_stmt(self, stmt: Any, env: Dict[str, Any]) -> Any:
        if isinstance(stmt, Spawn):
            val = self.eval_expr(stmt.expr, env)
            print(self._stringify(val))
            return None
        if isinstance(stmt, Core):
            env[stmt.name] = self.eval_expr(stmt.expr, env)
            if stmt.docked:
                self.docked.add(stmt.name)
            return None
        if isinstance(stmt, Assign):
            if stmt.name not in env:
                raise cyn(f"Unknown core '{stmt.name}' — register it first.", stmt.line)
            if stmt.name in self.docked:
                raise cyn(f"'{stmt.name}' is docked — Cyn froze this core.", stmt.line)
            cur = env[stmt.name]
            val = self.eval_expr(stmt.expr, env)
            if stmt.op == "=":
                env[stmt.name] = val
            elif stmt.op == "+=":
                env[stmt.name] = self._apply_bin("+", cur, val, stmt.line)
            elif stmt.op == "-=":
                env[stmt.name] = self._apply_bin("-", cur, val, stmt.line)
            elif stmt.op == "*=":
                env[stmt.name] = self._apply_bin("*", cur, val, stmt.line)
            elif stmt.op == "/=":
                env[stmt.name] = self._apply_bin("/", cur, val, stmt.line)
            return None
        if isinstance(stmt, Purge):
            if stmt.name in env:
                if isinstance(env[stmt.name], list):
                    env[stmt.name].clear()
                elif isinstance(env[stmt.name], dict):
                    env[stmt.name].clear()
                else:
                    del env[stmt.name]
            return None
        if isinstance(stmt, Heckle):
            if not self._truthy(self.eval_expr(stmt.cond, env)):
                raise cyn(self._stringify(self.eval_expr(stmt.msg, env)), stmt.line)
            return None
        if isinstance(stmt, Haunt):
            time.sleep(float(self.eval_expr(stmt.secs, env)))
            return None
        if isinstance(stmt, Surveil):
            try:
                return self.run_block(stmt.body, env)
            except CynError as e:
                if stmt.handler and stmt.err_var:
                    local = dict(env)
                    local[stmt.err_var] = str(e)
                    return self.run_block(stmt.handler, local)
                raise
        if isinstance(stmt, Disassemble):
            val = self.eval_expr(stmt.expr, env)
            for case_val, body in stmt.cases:
                if val == self.eval_expr(case_val, env):
                    return self.run_block(body, env)
            if stmt.default:
                return self.run_block(stmt.default, env)
            return None
        if isinstance(stmt, Drone):
            env[stmt.name] = self._make_drone(stmt, env)
            return None
        if isinstance(stmt, OSUnit):
            fn = self._make_drone(
                Drone(stmt.name, stmt.params, stmt.body, stmt.line), env
            )
            env[stmt.name] = fn
            self.os.register_unit(stmt.name, stmt.unit_type, fn)
            return None
        if isinstance(stmt, DeployStmt):
            try:
                self.os.deploy(stmt.name)
            except OsCynError as e:
                raise cyn(str(e), stmt.line)
            return None
        if isinstance(stmt, LaunchStmt):
            args = [self.eval_expr(a, env) for a in stmt.args]
            try:
                self.os.launch(stmt.name, *args)
            except OsCynError as e:
                raise cyn(str(e), stmt.line)
            return None
        if isinstance(stmt, HaltStmt):
            self.os.halt(stmt.name)
            return None
        if isinstance(stmt, ExitStmt):
            code = int(self.eval_expr(stmt.code, env))
            raise SystemExit(code)
        if isinstance(stmt, LogStmt):
            msg = self._stringify(self.eval_expr(stmt.expr, env))
            print(f"[{CYN}] {msg}")
            return None
        if isinstance(stmt, Hive):
            for cond, body in stmt.branches:
                if self._truthy(self.eval_expr(cond, env)):
                    return self.run_block(body, env)
            if stmt.alt:
                return self.run_block(stmt.alt, env)
            return None
        if isinstance(stmt, Reboot):
            while self._truthy(self.eval_expr(stmt.cond, env)):
                try:
                    self.run_block(stmt.body, env)
                except ContinueSignal:
                    continue
                except BreakSignal:
                    break
            return None
        if isinstance(stmt, Corrupt):
            start = int(self.eval_expr(stmt.start, env))
            end = int(self.eval_expr(stmt.end, env))
            step = 1 if start <= end else -1
            i = start
            while (step > 0 and i <= end) or (step < 0 and i >= end):
                env[stmt.var] = i
                try:
                    self.run_block(stmt.body, env)
                except ContinueSignal:
                    i += step
                    continue
                except BreakSignal:
                    break
                i += step
            return None
        if isinstance(stmt, Terminate):
            val = self.eval_expr(stmt.expr, env) if stmt.expr else None
            raise ReturnSignal(val)
        if isinstance(stmt, Ascend):
            raise BreakSignal()
        if isinstance(stmt, Reformat):
            raise ContinueSignal()
        if isinstance(stmt, Directive):
            self.resolve_directive(stmt.path, stmt.line)
            return None
        if isinstance(stmt, Block):
            return self.run_block(stmt, env)
        if isinstance(stmt, ExprStmt):
            return self.eval_expr(stmt.expr, env)
        return None

    def run_block(self, block: Block, env: Dict[str, Any]) -> Any:
        result = None
        for stmt in block.stmts:
            result = self.eval_stmt(stmt, env)
        return result

    def _make_drone(self, drone: Drone, closure: Dict[str, Any]) -> Callable:
        def fn(*args: Any) -> Any:
            local = dict(closure)
            if len(args) != len(drone.params):
                raise cyn(
                    f"Drone '{drone.name}' expects {len(drone.params)} units, got {len(args)}.",
                    drone.line,
                )
            for name, val in zip(drone.params, args):
                local[name] = val
            try:
                self.run_block(drone.body, local)
                return None
            except ReturnSignal as sig:
                return sig.value
        return fn

    def _make_serial(self, serial: Serial, closure: Dict[str, Any]) -> Callable:
        def fn(*args: Any) -> Any:
            local = dict(closure)
            if len(args) != len(serial.params):
                raise cyn(f"Serial drone expects {len(serial.params)} units.", serial.line)
            for name, val in zip(serial.params, args):
                local[name] = val
            return self.eval_expr(serial.body, local)
        return fn

    def _truthy(self, v: Any) -> bool:
        if v is None or v is False:
            return False
        if isinstance(v, (int, float)) and v == 0:
            return False
        if isinstance(v, str) and v == "":
            return False
        if isinstance(v, (list, dict)) and len(v) == 0:
            return False
        return True

    def _apply_bin(self, op: str, l: Any, r: Any, line: int) -> Any:
        if op == "+":
            if isinstance(l, str) or isinstance(r, str):
                return self._stringify(l) + self._stringify(r)
            return self._num(l, line) + self._num(r, line)
        if op == "-":
            return self._num(l, line) - self._num(r, line)
        if op == "*":
            return self._num(l, line) * self._num(r, line)
        if op == "/":
            rn = self._num(r, line)
            if rn == 0:
                raise cyn("Division by void — Copper 9 math collapse.", line)
            return self._num(l, line) / rn
        raise cyn(f"Unknown compound op '{op}'.", line)

    def eval_expr(self, expr: Any, env: Dict[str, Any]) -> Any:
        if isinstance(expr, Literal):
            return expr.value
        if isinstance(expr, Var):
            if expr.name not in env:
                raise cyn(f"Unknown unit '{expr.name}' — Cyn has no record of it.", expr.line)
            return env[expr.name]
        if isinstance(expr, ListLit):
            return [self.eval_expr(e, env) for e in expr.items]
        if isinstance(expr, NestLit):
            return {k: self.eval_expr(v, env) for k, v in expr.pairs}
        if isinstance(expr, Serial):
            return self._make_serial(expr, env)
        if isinstance(expr, Unary):
            v = self.eval_expr(expr.expr, env)
            if expr.op == "-":
                return -self._num(v, expr.line)
            if expr.op == "not":
                return not self._truthy(v)
            raise cyn(f"Unknown unary '{expr.op}'.", expr.line)
        if isinstance(expr, BinOp):
            l = self.eval_expr(expr.left, env)
            r = self.eval_expr(expr.right, env)
            if expr.op == "+":
                if isinstance(l, str) or isinstance(r, str):
                    return self._stringify(l) + self._stringify(r)
                return self._num(l, expr.line) + self._num(r, expr.line)
            if expr.op == "-":
                return self._num(l, expr.line) - self._num(r, expr.line)
            if expr.op == "*":
                return self._num(l, expr.line) * self._num(r, expr.line)
            if expr.op == "/":
                rn = self._num(r, expr.line)
                if rn == 0:
                    raise cyn("Division by void — Copper 9 math collapse.", expr.line)
                return self._num(l, expr.line) / rn
            if expr.op == "%":
                return int(self._num(l, expr.line)) % int(self._num(r, expr.line))
            if expr.op == "**":
                return self._num(l, expr.line) ** self._num(r, expr.line)
            if expr.op == "==":
                return l == r
            if expr.op == "!=":
                return l != r
            if expr.op == "<":
                return self._num(l, expr.line) < self._num(r, expr.line)
            if expr.op == ">":
                return self._num(l, expr.line) > self._num(r, expr.line)
            if expr.op == "<=":
                return self._num(l, expr.line) <= self._num(r, expr.line)
            if expr.op == ">=":
                return self._num(l, expr.line) >= self._num(r, expr.line)
            if expr.op == "and":
                return self._truthy(l) and self._truthy(r)
            if expr.op == "or":
                return self._truthy(l) or self._truthy(r)
            if expr.op == "hunt":
                if isinstance(r, (list, str, dict)):
                    return l in r
                raise cyn("hunt target must be squad, nest, or string.", expr.line)
            if expr.op == "exclude":
                if isinstance(r, (list, str, dict)):
                    return l not in r
                raise cyn("exclude target must be squad, nest, or string.", expr.line)
            raise cyn(f"Unknown operator '{expr.op}'.", expr.line)
        if isinstance(expr, Call):
            callee = self.eval_expr(expr.callee, env)
            args = [self.eval_expr(a, env) for a in expr.args]
            if callable(callee):
                try:
                    return callee(*args)
                except OsCynError as e:
                    raise cyn(str(e), expr.line)
            raise cyn("That unit is not a drone — cannot invoke.", expr.line)
        if isinstance(expr, Index):
            obj = self.eval_expr(expr.obj, env)
            idx = int(self.eval_expr(expr.index, env))
            try:
                return obj[idx]
            except (IndexError, TypeError, KeyError):
                raise cyn("Index out of hive bounds.", expr.line)
        if isinstance(expr, Get):
            obj = self.eval_expr(expr.obj, env)
            name = expr.name
            if isinstance(obj, dict) and name in obj:
                return obj[name]
            raise cyn(f"Field '{name}' dissolved.", expr.line)
        raise cyn("Internal Solver fault.", 0)

    def _num(self, v: Any, line: int) -> float:
        if isinstance(v, (int, float)):
            return float(v)
        if isinstance(v, str):
            try:
                return float(v)
            except ValueError:
                pass
        raise cyn("Expected a number, got something else.", line)


def banner() -> None:
    print(f";; MD {VERSION} — Murder Drones language")
    print(f";; {CYN} · Copper OS · 67 system directives unlocked")
    print()


def main() -> int:
    parser = argparse.ArgumentParser(
        prog="md",
        description="MD language — Cyn is the boss.",
    )
    parser.add_argument("file", nargs="?", help=".md source file")
    parser.add_argument("-e", "--eval", help="run source string")
    parser.add_argument("-v", "--version", action="store_true", help="show version")
    parser.add_argument("-q", "--quiet", action="store_true", help="suppress Cyn banner")
    args = parser.parse_args()

    if args.version:
        print(f"md {VERSION} — overseen by {CYN} · OS workloads enabled")
        return 0

    if not args.quiet:
        banner()

    interp = Interpreter(os.getcwd())
    try:
        if args.eval:
            interp.execute(args.eval)
        elif args.file:
            interp.run_file(args.file)
        else:
            print(f"[{CYN}] Awaiting directives. Pipe a .md file or use: md script.md")
            for line in sys.stdin:
                interp.execute(line)
        return 0
    except CynError as e:
        print(str(e), file=sys.stderr)
        return 1
    except OsCynError as e:
        print(f"[{CYN}] {e}", file=sys.stderr)
        return 1
    except SystemExit as e:
        return int(e.code) if isinstance(e.code, int) else 0
    except KeyboardInterrupt:
        print(f"\n[{CYN}] Terminated by operator.", file=sys.stderr)
        return 130


if __name__ == "__main__":
    sys.exit(main())
