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
}