RosettaCodeData/Task/Compiler-virtual-machine-in.../Nim/compiler-virtual-machine-in...

322 lines
9.3 KiB
Nim

import os, parseutils, strutils, strscans, strformat
type
Value = int32
BytesValue = array[4, byte]
Address = int32
OpCode = enum
opFetch = "fetch"
opStore = "store"
opPush = "push"
opJmp = "jmp"
opJz = "jz"
opAdd = "add"
opSub = "sub"
opMul = "mul"
opDiv = "div"
opMod = "mod"
opLt = "lt"
opgt = "gt"
opLe = "le"
opGe = "ge"
opEq = "eq"
opNe = "ne"
opAnd = "and"
opOr = "or"
opNeg = "neg"
opNot = "not"
opPrtc = "prtc"
opPrti = "prti"
opPrts = "prts"
opHalt = "halt"
opInvalid = "invalid"
# Virtual machine description.
VM = object
stack: seq[Value] # Evaluation stack.
memory: seq[byte] # Memory to store program.
data: seq[Value] # Data storage.
strings: seq[string] # String storage.
pc: Address # Program counter.
# Exceptions.
LoadingError = object of CatchableError
RuntimeError = object of CatchableError
####################################################################################################
# Running program.
proc checkStackLength(vm: VM; minLength: int) {.inline.} =
## Check that evaluation stack contains at least "minLength" elements.
if vm.stack.len < minLength:
raise newException(RuntimeError, &"not enough operands on the stack (pc = {vm.pc}).")
#---------------------------------------------------------------------------------------------------
proc getOperand(vm: var VM): Value =
## Get a 32 bits operand.
type Union {.union.} = object
value: Value
bytes: BytesValue
if vm.pc + 4 >= vm.memory.len:
raise newException(RuntimeError, &"out of memory (pc = {vm.pc}).")
var aux: Union
let address = vm.pc + 1
for idx in 0..3:
aux.bytes[idx] = vm.memory[address + idx]
result = aux.value
#---------------------------------------------------------------------------------------------------
proc run(vm: var VM) =
## Run a program loaded in VM memory.
vm.pc = 0
while true:
if vm.pc notin 0..vm.memory.high:
raise newException(RuntimeError, &"out of memory (pc = {vm.pc}).")
let opcode = OpCode(vm.memory[vm.pc])
case opcode
of opFetch, opStore:
let index = vm.getOperand()
if index notin 0..vm.data.high:
raise newException(RuntimeError, &"wrong memory index (pc = {vm.pc}).")
if opcode == opFetch:
vm.stack.add(vm.data[index])
else:
vm.checkStackLength(1)
vm.data[index] = vm.stack.pop()
inc vm.pc, 4
of opPush:
let value = vm.getOperand()
vm.stack.add(value)
inc vm.pc, 4
of opJmp:
let offset = vm.getOperand()
inc vm.pc, offset
of opJz:
let offset = vm.getOperand()
vm.checkStackLength(1)
let value = vm.stack.pop()
inc vm.pc, if value == 0: offset else: 4
of opAdd..opOr:
# Two operands instructions.
vm.checkStackLength(2)
let op2 = vm.stack.pop()
let op1 = vm.stack.pop()
case range[opAdd..opOr](opcode)
of opAdd:
vm.stack.add(op1 + op2)
of opSub:
vm.stack.add(op1 - op2)
of opMul:
vm.stack.add(op1 * op2)
of opDiv:
vm.stack.add(op1 div op2)
of opMod:
vm.stack.add(op1 mod op2)
of opLt:
vm.stack.add(Value(op1 < op2))
of opgt:
vm.stack.add(Value(op1 > op2))
of opLe:
vm.stack.add(Value(op1 <= op2))
of opGe:
vm.stack.add(Value(op1 >= op2))
of opEq:
vm.stack.add(Value(op1 == op2))
of opNe:
vm.stack.add(Value(op1 != op2))
of opAnd:
vm.stack.add(op1 and op2)
of opOr:
vm.stack.add(op1 or op2)
of opNeg..opPrts:
# One operand instructions.
vm.checkStackLength(1)
let op = vm.stack.pop()
case range[opNeg..opPrts](opcode)
of opNeg:
vm.stack.add(-op)
of opNot:
vm.stack.add(not op)
of opPrtc:
stdout.write(chr(op))
of opPrti:
stdout.write(op)
of opPrts:
if op notin 0..vm.strings.high:
raise newException(RuntimeError, &"wrong string index (pc = {vm.pc}).")
stdout.write(vm.strings[op])
of opHalt:
break
of opInvalid:
discard # Not possible.
inc vm.pc
####################################################################################################
# Loading assembly file.
proc parseHeader(line: string): tuple[dataSize, stringCount: int] =
## Parse the header.
if not line.scanf("Datasize: $s$i $sStrings: $i", result.dataSize, result.stringCount):
raise newException(LoadingError, "Wrong header in code.")
#---------------------------------------------------------------------------------------------------
import re
proc parseString(line: string; linenum: int): string =
## Parse a string.
if not line.startsWith('"'):
raise newException(LoadingError, "Line $1: incorrect string.".format(linenum))
# Can't use "unescape" as it is confused by "\\n" and "\n".
result = line.replacef(re"([^\\])(\\n)", "$1\n").replace(r"\\", r"\").replace("\"", "")
#---------------------------------------------------------------------------------------------------
proc parseValue(line: string; linenum: int; pos: var int; msg: string): int32 =
## Parse an int32 value.
var value: int
pos += line.skipWhitespace(pos)
let parsed = line.parseInt(value, pos)
if parsed == 0:
raise newException(LoadingError, "Line $1: ".format(linenum) & msg)
pos += parsed
result = int32(value)
#---------------------------------------------------------------------------------------------------
proc parseOpcode(line: string; linenum: int; pos: var int): OpCode =
## Parse an opcode.
var opstring: string
pos += line.skipWhitespace(pos)
let parsed = line.parseIdent(opstring, pos)
if parsed == 0:
raise newException(LoadingError, "Line $1: opcode expected".format(linenum))
pos += parsed
result = parseEnum[OpCode](opstring, opInvalid)
if result == opInvalid:
raise newException(LoadingError, "Line $1: invalid opcode encountered".format(linenum))
#---------------------------------------------------------------------------------------------------
proc parseMemoryIndex(line: string; linenum: int; pos: var int): int32 =
## Parse a memory index (int32 value between brackets).
var memIndex: int
pos += line.skipWhitespace(pos)
let str = line.captureBetween('[', ']', pos)
if str.parseInt(memIndex) == 0 or memIndex < 0:
raise newException(LoadingError, "Line $1: invalid memory index".format(lineNum))
pos += str.len + 2
result = int32(memIndex)
#---------------------------------------------------------------------------------------------------
proc parseOffset(line: string; linenum: int; pos: var int): int32 =
## Parse an offset (int32 value between parentheses).
var offset: int
pos += line.skipWhitespace(pos)
let str = line.captureBetween('(', ')', pos)
if str.parseInt(offset) == 0:
raise newException(LoadingError, "Line $1: invalid offset".format(linenum))
pos += str.len + 2
result = int32(offset)
#---------------------------------------------------------------------------------------------------
proc load(vm: var VM; code: string) =
## Load an assembly code into VM memory.
# Analyze header.
let lines = code.splitlines()
let (dataSize, stringCount) = parseHeader(lines[0])
vm.data.setLen(dataSize)
vm.strings.setLen(stringCount)
# Load strings.
for idx in 1..stringCount:
vm.strings[idx - 1] = lines[idx].parseString(idx + 1)
# Load code.
var pc: Address = 0
for idx in (stringCount + 1)..lines.high:
var pos = 0
let line = lines[idx]
if line.len == 0: continue
# Process address.
let address = line.parseValue(idx + 1, pos, "address expected")
if address != pc:
raise newException(LoadingError, "Line $1: wrong address".format(idx + 1))
# Process opcode.
let opcode = line.parseOpcode(idx + 1, pos)
vm.memory.add(byte(opcode))
# Process operand.
case opcode
of opFetch, opStore:
# Find memory index.
let memIndex = line.parseMemoryIndex(idx + 1, pos)
vm.memory.add(cast[BytesValue](Value(memIndex)))
inc pc, 5
of opJmp, opJz:
# Find offset.
let offset = line.parseOffset(idx + 1, pos)
vm.memory.add(cast[BytesValue](Value(offset)))
# Find and check branch address.
let branchAddress = line.parseValue(idx + 1, pos, "branch address expected")
if branchAddress != pc + offset + 1:
raise newException(LoadingError, "Line $1: wrong branch address".format(idx + 1))
inc pc, 5
of opPush:
# Find value.
let value = line.parseValue(idx + 1, pos, "value expected")
vm.memory.add(cast[BytesValue](Value(value)))
inc pc, 5
else:
inc pc
#———————————————————————————————————————————————————————————————————————————————————————————————————
let code = if paramCount() == 0: stdin.readAll() else: paramStr(1).readFile()
var vm: VM
vm.load(code)
vm.run()