ptls.dev/online
output =
  program
  |> runGetOutput
  |> printLines

------------------------------------------------------------------------------

n = 80122

program = [
  Const(2),
  Store(0),            -- store value 2 in slot 0 (initial divisor)
  Const(n),
  Store(1),            -- store value n in slot 1 (initial dividend)
  Label("checkDone"),
  Load(1),             -- load dividend
  Const(1),
  Eq,                  -- compare dividend to value 1
  JmpIf("end"),        -- jump to end if dividend == 1
  Label("checkDiv"), 
  Load(1),             -- load dividend
  Load(0),             -- load divisor
  Mod,
  Const(0),
  Eq,                  -- compare dividend % dividor == 0 
  JmpIf("divisible"),
  Load(0),             -- load divisor
  Const(1),
  Add,                 -- increment divisor
  Store(0),            -- store new divisor
  Jmp("checkDiv"),     -- check end condition
  Label("divisible"),
  Load(0),             -- load divisor
  Print,               -- print divisor (current factor)
  Load(1),             -- load dividend
  Load(0),             -- load divisor
  Div,
  Store(1),            -- store dividend / divisor as new dividend
  Jmp("checkDone"),    -- check end condition
  Label("end"),
  Const("done"),
  Print,               -- print "done"
  Exit,
]

------------------------------------------------------------------------------
-- Supported commands:
--
-- "push" and "pop" refer to vm stack
-- "advance" = jump to next instruction
--
-- Eq        - pop top 2 vals (b, a), push a == b, advance
-- Add       - pop top 2 vals (b, a), push a + b, advance
-- Sub       - pop top 2 vals (b, a), push a - b, advance
-- Mul       - pop top 2 vals (b, a), push a * b, advance
-- Div       - pop top 2 vals (b, a), push a / b, advance
-- Mod       - pop top 2 vals (b, a), push a % b, advance
-- Print     - pop top val a, set vm output to a, advance
-- Const arg - push arg, advance
-- Store arg - pop value, value at index arg in vm slots array , advance
-- Load  arg - get value at index arg in vm slots array, push value, advance
-- JmpIf arg - pop value, jump to label arg if value, else advance 
-- Jmp   arg - jump to label arg

vmFromInsts(program) = VM {
  slots  = zeroArray(8)
  insts  = convertJumps(program)
  index  = 0
  stack  = []
  outVal = None
}

------------------------------------------------------------------------------

runGetOutput(insts) =
  vmFromInsts(insts)
  |> iterate(compose(setOutput(None), eval))
  |> takeUntil(vm => vm.insts[vm.index] == Exit)
  |> map(vm => vm.outVal)
  |> filter(notEq(None))

------------------------------------------------------------------------------

eval(vm) = cond {
  case is(Const, inst)
    vm
    |> pushVal(arg)
    |> advance

  case is(Print, inst)
    vm
    |> popVals(1)
    |> setOutput(valAt(0, vm))
    |> advance

  case is(Load, inst)
    vm
    |> load(arg)
    |> advance

  case is(Store, inst)
    vm
    |> store(arg)
    |> popVals(1)
    |> advance

  case is(JmpIf, inst)
    vm
    |> popVals(1)
    |> jumpIf(valAt(0, vm), arg)

  case is(Jmp, inst)
    vm
    |> jumpIf(true, arg)

  case is(Eq,  inst) vm |> binaryOp(eq)
  case is(Add, inst) vm |> binaryOp(add)
  case is(Sub, inst) vm |> binaryOp(sub)
  case is(Mul, inst) vm |> binaryOp(mul)
  case is(Div, inst) vm |> binaryOp(div)
  case is(Mod, inst) vm |> binaryOp(mod)

} where {
  inst = vm.insts[vm.index]
  arg = unwrap(inst)
}

------------------------------------------------------------------------------

advance(vm)        = vm with $.index += 1
pushVal(arg, vm)   = vm with $.stack = [arg] ++ vm.stack
popVals(n, vm)     = vm with $.stack = drop(n, vm.stack)
setOutput(val, vm) = vm with $.outVal = val
store(arg, vm)     = vm with $.slots[arg] = head(vm.stack)
load(arg, vm)      = pushVal(vm.slots[arg], vm)

jumpIf(pred, arg, vm) =
  if pred
  then vm with $.index = arg
  else advance(vm) 

valAt(n, vm) = at(n, vm.stack)

binaryOp(op, vm) =
 vm
 |> popVals(2)
 |> pushVal(op(valAt(0, vm), valAt(1, vm)))
 |> advance

------------------------------------------------------------------------------

showInst(inst) =
 format("[ {<5} ] {}", [getLabel(inst), argOrBlank(inst)])

argOrBlank(inst) =
  if is(PtlsTuple, inst) then unwrap(inst) else ""

------------------------------------------------------------------------------

convertJumps(insts) =
  insts
  |> filter(notIs(Label))
  |> map(convertJump(inds))
  |> toArray
  where inds = getLabelInds(insts)

------------------------------------------------------------------------------

convertJump(inds, inst) = cond {
  case is(Jmp, inst) Jmp(inds[unwrap(inst)])
  case is(JmpIf, inst) JmpIf(inds[unwrap(inst)])
  else inst
}

------------------------------------------------------------------------------

getLabelInds(insts) =
  insts
  |> reduce(scanInst, (0, {})) -- keep track of (currentIndex, indexMap)
  |> at(1) -- return index map

------------------------------------------------------------------------------

scanInst(pair, inst) = 
  if not is(Label, inst)
  then (ind + 1, inds)
  else (ind, newInds)
  where {
    newInds = inds with $[unwrap(inst)] = ind
    (ind, inds) = pair
  }