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

376 lines
8.4 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- Icon -*-
#
# The Rosetta Code virtual machine in Icon. Migrated from the
# ObjectIcon.
#
# See https://rosettacode.org/wiki/Compiler/virtual_machine_interpreter
#
record VirtualMachine(code, global_data, strings, stack, pc)
global opcode_names
global opcode_values
global op_halt
global op_add
global op_sub
global op_mul
global op_div
global op_mod
global op_lt
global op_gt
global op_le
global op_ge
global op_eq
global op_ne
global op_and
global op_or
global op_neg
global op_not
global op_prtc
global op_prti
global op_prts
global op_fetch
global op_store
global op_push
global op_jmp
global op_jz
global whitespace_chars
procedure main(args)
local f_inp, f_out
local vm
whitespace_chars := ' \t\n\r\f\v'
initialize_opcodes()
if 3 <= *args then {
write("Usage: ", &progname, " [INPUT_FILE [OUTPUT_FILE]]")
exit(1)
}
if 1 <= *args then {
f_inp := open(args[1], "r") | {
write(&errout, "Failed to open ", args[1], " for reading.")
exit(1)
}
} else {
f_inp := &input
}
if 2 <= *args then {
f_out := open(args[2], "w") | {
write(&errout, "Failed to open ", args[2], " for writing.")
exit(1)
}
} else {
f_out := &output
}
vm := VirtualMachine()
read_assembly_code(f_inp, vm)
run_vm(f_out, vm)
end
procedure initialize_opcodes()
local i
opcode_names :=
["halt", "add", "sub", "mul", "div",
"mod", "lt", "gt", "le", "ge",
"eq", "ne", "and", "or", "neg",
"not", "prtc", "prti", "prts", "fetch",
"store", "push", "jmp", "jz"]
opcode_values := table()
every i := 1 to *opcode_names do
opcode_values[opcode_names[i]] := char(i)
op_halt := opcode_values["halt"]
op_add := opcode_values["add"]
op_sub := opcode_values["sub"]
op_mul := opcode_values["mul"]
op_div := opcode_values["div"]
op_mod := opcode_values["mod"]
op_lt := opcode_values["lt"]
op_gt := opcode_values["gt"]
op_le := opcode_values["le"]
op_ge := opcode_values["ge"]
op_eq := opcode_values["eq"]
op_ne := opcode_values["ne"]
op_and := opcode_values["and"]
op_or := opcode_values["or"]
op_neg := opcode_values["neg"]
op_not := opcode_values["not"]
op_prtc := opcode_values["prtc"]
op_prti := opcode_values["prti"]
op_prts := opcode_values["prts"]
op_fetch := opcode_values["fetch"]
op_store := opcode_values["store"]
op_push := opcode_values["push"]
op_jmp := opcode_values["jmp"]
op_jz := opcode_values["jz"]
end
procedure int2bytes (n)
local bytes
# The VM is little-endian.
bytes := "****"
bytes[1] := char (iand(n, 16rFF))
bytes[2] := char(iand(ishift(n, -8), 16rFF))
bytes[3] := char(iand(ishift(n, -16), 16rFF))
bytes[4] := char(iand(ishift(n, -24), 16rFF))
return bytes
end
procedure bytes2int(bytes, i)
local n0, n1, n2, n3, n
# The VM is little-endian.
n0 := ord(bytes[i])
n1 := ishift(ord(bytes[i + 1]), 8)
n2 := ishift(ord(bytes[i + 2]), 16)
n3 := ishift(ord(bytes[i + 3]), 24)
n := ior (n0, ior (n1, ior (n2, n3)))
# Do not forget to extend the sign bit.
return (if n3 <= 16r7F then n else ior(n, icom(16rFFFFFFFF)))
end
procedure read_assembly_code(f, vm)
local data_size, number_of_strings
local line, ch
local i
local address
local opcode
# Read the header line.
line := read(f) | bad_vm()
line ? {
tab(many(whitespace_chars))
tab(match("Datasize")) | bad_vm()
tab(many(whitespace_chars))
tab(any(':')) | bad_vm()
tab(many(whitespace_chars))
data_size :=
integer(tab(many(&digits))) | bad_vm()
tab(many(whitespace_chars))
tab(match("Strings")) | bad_vm()
tab(many(whitespace_chars))
tab(any(':')) | bad_vm()
tab(many(whitespace_chars))
number_of_strings :=
integer(tab(many(&digits))) | bad_vm()
}
# Read the strings.
vm.strings := list(number_of_strings)
every i := 1 to number_of_strings do {
vm.strings[i] := ""
line := read(f) | bad_vm()
line ? {
tab(many(whitespace_chars))
tab(any('"')) | bad_vm()
while ch := tab(any(~'"')) do {
if ch == '\\' then {
ch := tab(any('n\\')) | bad_vm()
vm.strings[i] ||:=
(if (ch == "n") then "\n" else "\\")
} else {
vm.strings[i] ||:= ch
}
}
}
}
# Read the code.
vm.code := ""
while line := read(f) do {
line ? {
tab(many(whitespace_chars))
address := integer(tab(many(&digits))) | bad_vm()
tab(many(whitespace_chars))
opcode := tab(many(~whitespace_chars)) | bad_vm()
vm.code ||:= opcode_values[opcode]
case opcode of {
"push": {
tab(many(whitespace_chars))
vm.code ||:=
int2bytes(integer(tab(many(&digits)))) |
int2bytes(integer(tab(any('-')) ||
tab(many(&digits)))) |
bad_vm()
}
"fetch" | "store": {
tab(many(whitespace_chars))
tab(any('[')) | bad_vm()
tab(many(whitespace_chars))
vm.code ||:=
int2bytes(integer(tab(many(&digits)))) |
bad_vm()
tab(many(whitespace_chars))
tab(any(']')) | bad_vm()
}
"jmp" | "jz": {
tab(many(whitespace_chars))
tab(any('(')) | bad_vm()
tab(many(whitespace_chars))
vm.code ||:=
int2bytes(integer(tab(many(&digits)))) |
int2bytes(integer(tab(any('-')) ||
tab(many(&digits)))) |
bad_vm()
tab(many(whitespace_chars))
tab(any(')')) | bad_vm()
tab(many(whitespace_chars))
tab(many(&digits)) | bad_vm()
}
default: {
# Do nothing
}
}
}
}
# Create a global data area.
vm.global_data := list(data_size, &null)
initialize_vm(vm)
end
procedure run_vm(f_out, vm)
initialize_vm(vm)
continue_vm(f_out, vm)
end
procedure continue_vm(f_out, vm)
while vm.code[vm.pc] ~== op_halt do
step_vm(f_out, vm)
end
procedure step_vm(f_out, vm)
local opcode
opcode := vm.code[vm.pc]
vm.pc +:= 1
case opcode of {
op_add: binop(vm, "+")
op_sub: binop(vm, "-")
op_mul: binop(vm, "*")
op_div: binop(vm, "/")
op_mod: binop(vm, "%")
op_lt: comparison(vm, "<")
op_gt: comparison(vm, ">")
op_le: comparison(vm, "<=")
op_ge: comparison(vm, ">=")
op_eq: comparison(vm, "=")
op_ne: comparison(vm, "~=")
op_and: logical_and(vm)
op_or: logical_or(vm)
op_neg: negate(vm)
op_not: logical_not(vm)
op_prtc: printc(f_out, vm)
op_prti: printi(f_out, vm)
op_prts: prints(f_out, vm)
op_fetch: fetch_global(vm)
op_store: store_global(vm)
op_push: push_argument(vm)
op_jmp: jump(vm)
op_jz: jump_if_zero(vm)
default: bad_opcode()
}
end
procedure negate(vm)
vm.stack[1] := -vm.stack[1]
end
procedure binop(vm, func)
vm.stack[2] := func(vm.stack[2], vm.stack[1])
pop(vm.stack)
end
procedure comparison(vm, func)
vm.stack[2] := (if func(vm.stack[2], vm.stack[1]) then 1 else 0)
pop(vm.stack)
end
procedure logical_and(vm)
vm.stack[2] :=
(if vm.stack[2] ~= 0 & vm.stack[1] ~= 0 then 1 else 0)
pop(vm.stack)
end
procedure logical_or(vm)
vm.stack[2] :=
(if vm.stack[2] ~= 0 | vm.stack[1] ~= 0 then 1 else 0)
pop(vm.stack)
end
procedure logical_not(vm)
vm.stack[1] := (if vm.stack[1] ~= 0 then 0 else 1)
end
procedure printc(f_out, vm)
writes(f_out, char(pop(vm.stack)))
end
procedure printi(f_out, vm)
writes(f_out, pop(vm.stack))
end
procedure prints(f_out, vm)
writes(f_out, vm.strings[pop(vm.stack) + 1])
end
procedure fetch_global(vm)
push(vm.stack, vm.global_data[get_argument(vm) + 1])
vm.pc +:= 4
end
procedure store_global(vm)
vm.global_data[get_argument(vm) + 1] := pop(vm.stack)
vm.pc +:= 4
end
procedure push_argument(vm)
push(vm.stack, get_argument(vm))
vm.pc +:= 4
end
procedure jump(vm)
vm.pc +:= get_argument(vm)
end
procedure jump_if_zero(vm)
if pop(vm.stack) = 0 then
vm.pc +:= get_argument(vm)
else
vm.pc +:= 4
end
procedure get_argument(vm)
return bytes2int(vm.code, vm.pc)
end
procedure initialize_vm(vm)
# The program counter starts at 1, for convenient indexing into
# the code[] array. Icon indexing starts at 1 (for a *very* good
# reason, but thats a topic for another day).
vm.pc := 1
vm.stack := []
end
procedure bad_vm()
write(&errout, "Bad VM.")
exit(1)
end
procedure bad_opcode()
write(&errout, "Bad opcode.")
exit(1)
end