# AVM ISA v1 — R Port (Strict Functional Execution)
#
# Mirrors `formal/SilverSight/AVMIsa/` (Lean 4) and `rust/src/avm/` (Rust).
# Pure functions: step(state, program) -> state, run(state, program, fuel) -> state

# ── Instruction type ─────────────────────────────────────────────────

# Instr is a list with fields:
#   op: integer code (0=push_q16, 1=push_bool, 2=pop, 3=dup, 4=swap,
#                     5=load, 6=store, 7=jump, 8=jump_if, 9=prim, 10=halt)
#   arg: integer payload

INSTR_PUSH_Q16  <- 0L
INSTR_PUSH_BOOL <- 1L
INSTR_PUSH_Q0   <- 2L
INSTR_POP       <- 3L
INSTR_DUP       <- 4L
INSTR_SWAP      <- 5L
INSTR_LOAD      <- 6L
INSTR_STORE     <- 7L
INSTR_JUMP      <- 8L
INSTR_JUMP_IF   <- 9L
INSTR_PRIM      <- 10L
INSTR_HALT      <- 11L

# ── Primitives ───────────────────────────────────────────────────────

PRIM_ADD_Q0  <- 0L
PRIM_SUB_Q0  <- 1L
PRIM_ADD_Q16 <- 2L
PRIM_SUB_Q16 <- 3L
PRIM_MUL_Q16 <- 4L
PRIM_DIV_Q16 <- 5L
PRIM_LT_Q16  <- 6L
PRIM_EQ_Q16  <- 7L
PRIM_AND     <- 8L
PRIM_OR      <- 9L
PRIM_NOT     <- 10L

# ── Step Error codes ─────────────────────────────────────────────────

ERR_EMPTY_STACK     <- "empty_stack"
ERR_STACK_UNDERFLOW <- "stack_underflow"
ERR_TYPE_MISMATCH   <- "type_mismatch"
ERR_MISSING_LOCAL   <- "missing_local"
ERR_DIV_BY_ZERO     <- "division_by_zero"
ERR_JUMP_OOB        <- "jump_out_of_bounds"
ERR_STACK_OVERFLOW  <- "stack_overflow"
ERR_HALTED          <- "halted"

# ── AVM constants ───────────────────────────────────────────────────

AVM_CLAMP_MIN <- -2147483647L
AVM_CLAMP_MAX <- 2147483647L
AVM_Q0_MIN    <- -32767L
AVM_Q0_MAX    <- 32767L
AVM_MAX_STACK <- 1024L

# ── Instruction constructors ─────────────────────────────────────────

instr_push_q16  <- function(x) list(op = INSTR_PUSH_Q16,  arg = as.integer(x))
instr_push_bool <- function(x) list(op = INSTR_PUSH_BOOL, arg = as.integer(x))
instr_push_q0   <- function(x) list(op = INSTR_PUSH_Q0,   arg = as.integer(x))
instr_pop       <- function()  list(op = INSTR_POP,        arg = 0L)
instr_dup       <- function()  list(op = INSTR_DUP,        arg = 0L)
instr_swap      <- function()  list(op = INSTR_SWAP,       arg = 0L)
instr_load      <- function(i) list(op = INSTR_LOAD,       arg = as.integer(i))
instr_store     <- function(i) list(op = INSTR_STORE,      arg = as.integer(i))
instr_jump      <- function(t) list(op = INSTR_JUMP,       arg = as.integer(t))
instr_jump_if   <- function(t) list(op = INSTR_JUMP_IF,    arg = as.integer(t))
instr_prim      <- function(p) list(op = INSTR_PRIM,       arg = as.integer(p))
instr_halt      <- function()  list(op = INSTR_HALT,        arg = 0L)

# ── State constructor ────────────────────────────────────────────────

new_state <- function(n_locals = 0L) {
  list(
    pc = 1L,
    stack = list(),       # each element: list(type="q16", val=integer) or list(type="bool", val=logical)
    locals = vector("list", n_locals),  # elements can be NULL
    local_types = vector("list", n_locals),
    halted = FALSE
  )
}

# ── Stack helpers ────────────────────────────────────────────────────

make_q16  <- function(x) list(type = "q16",  val = as.integer(x))
make_q0   <- function(x) list(type = "q0",   val = as.integer(x))
make_bool <- function(x) list(type = "bool", val = as.logical(x))

is_q16  <- function(v) identical(v$type, "q16")
is_q0   <- function(v) identical(v$type, "q0")
is_bool <- function(v) identical(v$type, "bool")

# ── Q16_16 helpers ───────────────────────────────────────────────────

Q16_SCALE <- 65536L

# AVM symmetric clamp
avm_clamp <- function(x) {
  min(AVM_CLAMP_MAX, max(AVM_CLAMP_MIN, as.integer(x)))
}

# V6 signed comparison
lt_q16_v6 <- function(a, b) {
  sa <- a < 0L
  sb <- b < 0L
  if (sa != sb) sa else a < b
}

q16_mul <- function(a, b) {
  # Floor division matching Lean Int.ediv: (a * b) %/% 65536
  prod <- as.numeric(a) * as.numeric(b)
  avm_clamp(prod %/% Q16_SCALE)
}

q16_div <- function(a, b) {
  if (b == 0L) stop(ERR_DIV_BY_ZERO)
  # Floor division matching Lean Int.ediv: (y * 65536) %/% x
  num <- as.numeric(a) * Q16_SCALE
  avm_clamp(num %/% as.numeric(b))
}

# ── Primitive execution ──────────────────────────────────────────────

avm_q0_clamp <- function(x) {
  min(AVM_Q0_MAX, max(AVM_Q0_MIN, as.integer(x)))
}

exec_prim <- function(op, a, b = NULL) {
  if (op == PRIM_ADD_Q0) {
    if (!is_q0(a) || !is_q0(b)) stop(ERR_TYPE_MISMATCH)
    make_q0(avm_q0_clamp(as.numeric(a$val) + as.numeric(b$val)))
  } else if (op == PRIM_SUB_Q0) {
    if (!is_q0(a) || !is_q0(b)) stop(ERR_TYPE_MISMATCH)
    make_q0(avm_q0_clamp(as.numeric(a$val) - as.numeric(b$val)))
  } else if (op == PRIM_ADD_Q16) {
    if (!is_q16(a) || !is_q16(b)) stop(ERR_TYPE_MISMATCH)
    x <- as.numeric(a$val) + as.numeric(b$val)
    make_q16(avm_clamp(x))
  } else if (op == PRIM_SUB_Q16) {
    if (!is_q16(a) || !is_q16(b)) stop(ERR_TYPE_MISMATCH)
    x <- as.numeric(a$val) - as.numeric(b$val)
    make_q16(avm_clamp(x))
  } else if (op == PRIM_MUL_Q16) {
    if (!is_q16(a) || !is_q16(b)) stop(ERR_TYPE_MISMATCH)
    make_q16(q16_mul(a$val, b$val))
  } else if (op == PRIM_DIV_Q16) {
    if (!is_q16(a) || !is_q16(b)) stop(ERR_TYPE_MISMATCH)
    make_q16(q16_div(a$val, b$val))
  } else if (op == PRIM_LT_Q16) {
    if (!is_q16(a) || !is_q16(b)) stop(ERR_TYPE_MISMATCH)
    make_bool(lt_q16_v6(a$val, b$val))
  } else if (op == PRIM_EQ_Q16) {
    if (!is_q16(a) || !is_q16(b)) stop(ERR_TYPE_MISMATCH)
    make_bool(a$val == b$val)
  } else if (op == PRIM_AND) {
    if (!is_bool(a) || !is_bool(b)) stop(ERR_TYPE_MISMATCH)
    make_bool(a$val && b$val)
  } else if (op == PRIM_OR) {
    if (!is_bool(a) || !is_bool(b)) stop(ERR_TYPE_MISMATCH)
    make_bool(a$val || b$val)
  } else if (op == PRIM_NOT) {
    if (!is_bool(a)) stop(ERR_TYPE_MISMATCH)
    make_bool(!a$val)
  } else {
    stop("unknown prim")
  }
}

# ── Step (pure function) ─────────────────────────────────────────────

step <- function(s, prog) {
  if (s$halted) stop(ERR_HALTED)
  if (s$pc < 1L || s$pc > length(prog)) {
    return(list(pc = s$pc, stack = s$stack, locals = s$locals,
                local_types = s$local_types, halted = TRUE))
  }

  instr <- prog[[s$pc]]
  stack <- s$stack
  locals <- s$locals
  local_types <- s$local_types
  pc <- s$pc + 1L
  halted <- FALSE

  # Stack depth check (push, dup, load grow the stack)
  if (instr$op %in% c(INSTR_PUSH_Q16, INSTR_PUSH_BOOL, INSTR_DUP, INSTR_LOAD) &&
      length(stack) >= AVM_MAX_STACK) stop(ERR_STACK_OVERFLOW)

  if (instr$op == INSTR_PUSH_Q16) {
    stack <- c(stack, list(make_q16(instr$arg)))
  } else if (instr$op == INSTR_PUSH_BOOL) {
    stack <- c(stack, list(make_bool(instr$arg != 0L)))
  } else if (instr$op == INSTR_PUSH_Q0) {
    stack <- c(stack, list(make_q0(instr$arg)))
  } else if (instr$op == INSTR_POP) {
    if (length(stack) == 0L) stop(ERR_EMPTY_STACK)
    stack <- stack[-length(stack)]
  } else if (instr$op == INSTR_DUP) {
    if (length(stack) == 0L) stop(ERR_EMPTY_STACK)
    stack <- c(stack, stack[length(stack)])
  } else if (instr$op == INSTR_SWAP) {
    if (length(stack) < 2L) stop(ERR_STACK_UNDERFLOW)
    n <- length(stack)
    tmp <- stack[[n]]; stack[[n]] <- stack[[n-1]]; stack[[n-1]] <- tmp
  } else if (instr$op == INSTR_LOAD) {
    i <- instr$arg + 1L
    if (i > length(locals)) stop(ERR_MISSING_LOCAL)
    if (is.null(locals[[i]])) stop(ERR_MISSING_LOCAL)
    # Restore from local type tag
    lt <- local_types[[i]]
    if (identical(lt, "q16")) stack <- c(stack, list(make_q16(locals[[i]])))
    else if (identical(lt, "q0")) stack <- c(stack, list(make_q0(locals[[i]])))
    else stack <- c(stack, list(make_bool(locals[[i]])))
  } else if (instr$op == INSTR_STORE) {
    i <- instr$arg + 1L
    if (length(stack) == 0L) stop(ERR_EMPTY_STACK)
    if (i > length(locals)) stop(ERR_MISSING_LOCAL)
    v <- stack[[length(stack)]]
    stack <- stack[-length(stack)]
    locals[[i]] <- v$val
    local_types[[i]] <- v$type
  } else if (instr$op == INSTR_JUMP) {
    target <- instr$arg + 1L
    if (target < 1L || target > length(prog)) stop(ERR_JUMP_OOB)
    pc <- target
  } else if (instr$op == INSTR_JUMP_IF) {
    if (length(stack) == 0L) stop(ERR_EMPTY_STACK)
    cond <- stack[[length(stack)]]
    stack <- stack[-length(stack)]
    if (is_bool(cond) && cond$val) {
      target <- instr$arg + 1L
      if (target < 1L || target > length(prog)) stop(ERR_JUMP_OOB)
      pc <- target
    } else if (!is_bool(cond)) {
      stop(ERR_TYPE_MISMATCH)
    }
  } else if (instr$op == INSTR_PRIM) {
    p <- instr$arg
    arity <- if (p == PRIM_NOT) 1L else 2L
    if (length(stack) < arity) stop(ERR_STACK_UNDERFLOW)
    if (arity >= 2L) {
      b <- stack[[length(stack)]]
      stack <- stack[-length(stack)]
    } else {
      b <- NULL
    }
    a <- stack[[length(stack)]]
    stack <- stack[-length(stack)]
    result <- exec_prim(p, a, b)
    stack <- c(stack, list(result))
  } else if (instr$op == INSTR_HALT) {
    halted <- TRUE
  }

  list(pc = pc, stack = stack, locals = locals,
       local_types = local_types, halted = halted)
}

# ── Run (fuel-bounded) ──────────────────────────────────────────────

run <- function(initial, prog, fuel = 1000L) {
  s <- initial
  for (i in seq_len(fuel)) {
    if (s$halted) return(s)
    s <- step(s, prog)
  }
  s
}
