Compiler/virtual machine interpreter: Difference between revisions

Content deleted Content added
PureFox (talk | contribs)
Added Wren
Add Zig language implementation
Line 3,760: Line 3,760:
count is: 9
count is: 9
</pre>
</pre>

=={{header|Zig}}==
<lang zig>
const std = @import("std");

pub const VirtualMachineError = error{OutOfMemory};

pub const VirtualMachine = struct {
allocator: *std.mem.Allocator,
stack: [stack_size]i32,
program: std.ArrayList(u8),
sp: usize, // stack pointer
pc: usize, // program counter
string_pool: std.ArrayList([]const u8), // all the strings in the program
globals: std.ArrayList(i32), // all the variables in the program, they are global
output: std.ArrayList(u8), // Instead of outputting to stdout, we do it here for better testing.

const Self = @This();
const stack_size = 32; // Can be arbitrarily increased/decreased as long as we have enough.
const word_size = @sizeOf(i32);

pub fn init(
allocator: *std.mem.Allocator,
program: std.ArrayList(u8),
string_pool: std.ArrayList([]const u8),
globals: std.ArrayList(i32),
) Self {
return VirtualMachine{
.allocator = allocator,
.stack = [_]i32{std.math.maxInt(i32)} ** stack_size,
.program = program,
.sp = 0,
.pc = 0,
.string_pool = string_pool,
.globals = globals,
.output = std.ArrayList(u8).init(allocator),
};
}

pub fn interp(self: *Self) VirtualMachineError!void {
while (true) : (self.pc += 1) {
switch (@intToEnum(Op, self.program.items[self.pc])) {
.push => self.push(self.unpackInt()),
.store => self.globals.items[@intCast(usize, self.unpackInt())] = self.pop(),
.fetch => self.push(self.globals.items[@intCast(usize, self.unpackInt())]),
.jmp => self.pc = @intCast(usize, self.unpackInt() - 1),
.jz => {
if (self.pop() == 0) {
// -1 because `while` increases it with every iteration.
// This doesn't allow to jump to location 0 because we use `usize` for `pc`,
// just arbitrary implementation limitation.
self.pc = @intCast(usize, self.unpackInt() - 1);
} else {
self.pc += word_size;
}
},
.prts => try self.out("{s}", .{self.string_pool.items[@intCast(usize, self.pop())]}),
.prti => try self.out("{d}", .{self.pop()}),
.prtc => try self.out("{c}", .{@intCast(u8, self.pop())}),
.lt => self.binOp(lt),
.le => self.binOp(le),
.gt => self.binOp(gt),
.ge => self.binOp(ge),
.eq => self.binOp(eq),
.ne => self.binOp(ne),
.add => self.binOp(add),
.mul => self.binOp(mul),
.sub => self.binOp(sub),
.div => self.binOp(div),
.mod => self.binOp(mod),
.@"and" => self.binOp(@"and"),
.@"or" => self.binOp(@"or"),
.not => self.push(@boolToInt(self.pop() == 0)),
.neg => self.push(-self.pop()),
.halt => break,
}
}
}

fn push(self: *Self, n: i32) void {
self.sp += 1;
self.stack[self.sp] = n;
}

fn pop(self: *Self) i32 {
std.debug.assert(self.sp != 0);
self.sp -= 1;
return self.stack[self.sp + 1];
}

fn unpackInt(self: *Self) i32 {
const arg_ptr = @ptrCast(*[4]u8, self.program.items[self.pc + 1 .. self.pc + 1 + word_size]);
self.pc += word_size;
var arg_array = arg_ptr.*;
const arg = @ptrCast(*i32, @alignCast(@alignOf(i32), &arg_array));
return arg.*;
}

pub fn out(self: *Self, comptime format: []const u8, args: anytype) VirtualMachineError!void {
try self.output.writer().print(format, args);
}

fn binOp(self: *Self, func: fn (a: i32, b: i32) i32) void {
const a = self.pop();
const b = self.pop();
// Note that arguments are in reversed order because this is how we interact with
// push/pop operations of the stack.
const result = func(b, a);
self.push(result);
}

fn lt(a: i32, b: i32) i32 {
return @boolToInt(a < b);
}
fn le(a: i32, b: i32) i32 {
return @boolToInt(a <= b);
}
fn gt(a: i32, b: i32) i32 {
return @boolToInt(a > b);
}
fn ge(a: i32, b: i32) i32 {
return @boolToInt(a >= b);
}
fn eq(a: i32, b: i32) i32 {
return @boolToInt(a == b);
}
fn ne(a: i32, b: i32) i32 {
return @boolToInt(a != b);
}
fn add(a: i32, b: i32) i32 {
return a + b;
}
fn sub(a: i32, b: i32) i32 {
return a - b;
}
fn mul(a: i32, b: i32) i32 {
return a * b;
}
fn div(a: i32, b: i32) i32 {
return @divTrunc(a, b);
}
fn mod(a: i32, b: i32) i32 {
return @mod(a, b);
}
fn @"or"(a: i32, b: i32) i32 {
return @boolToInt((a != 0) or (b != 0));
}
fn @"and"(a: i32, b: i32) i32 {
return @boolToInt((a != 0) and (b != 0));
}
};

pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var allocator = &arena.allocator;

var arg_it = std.process.args();
_ = try arg_it.next(allocator) orelse unreachable; // program name
const file_name = arg_it.next(allocator);
// We accept both files and standard input.
var file_handle = blk: {
if (file_name) |file_name_delimited| {
const fname: []const u8 = try file_name_delimited;
break :blk try std.fs.cwd().openFile(fname, .{});
} else {
break :blk std.io.getStdIn();
}
};
defer file_handle.close();
const input_content = try file_handle.readToEndAlloc(allocator, std.math.maxInt(usize));

var string_pool = std.ArrayList([]const u8).init(allocator);
var globals = std.ArrayList(i32).init(allocator);
const bytecode = try loadBytecode(allocator, input_content, &string_pool, &globals);
var vm = VirtualMachine.init(allocator, bytecode, string_pool, globals);
try vm.interp();
const result: []const u8 = vm.output.items;
_ = try std.io.getStdOut().write(result);
}

pub const Op = enum(u8) {
fetch,
store,
push,
add,
sub,
mul,
div,
mod,
lt,
gt,
le,
ge,
eq,
ne,
@"and",
@"or",
neg,
not,
jmp,
jz,
prtc,
prts,
prti,
halt,

const from_string = std.ComptimeStringMap(Op, .{
.{ "fetch", .fetch },
.{ "store", .store },
.{ "push", .push },
.{ "add", .add },
.{ "sub", .sub },
.{ "mul", .mul },
.{ "div", .div },
.{ "mod", .mod },
.{ "lt", .lt },
.{ "gt", .gt },
.{ "le", .le },
.{ "ge", .ge },
.{ "eq", .eq },
.{ "ne", .ne },
.{ "and", .@"and" },
.{ "or", .@"or" },
.{ "neg", .neg },
.{ "not", .not },
.{ "jmp", .jmp },
.{ "jz", .jz },
.{ "prtc", .prtc },
.{ "prts", .prts },
.{ "prti", .prti },
.{ "halt", .halt },
});

pub fn fromString(str: []const u8) Op {
return from_string.get(str).?;
}
};

// 100 lines of code to load serialized bytecode, eh
fn loadBytecode(
allocator: *std.mem.Allocator,
str: []const u8,
string_pool: *std.ArrayList([]const u8),
globals: *std.ArrayList(i32),
) !std.ArrayList(u8) {
var result = std.ArrayList(u8).init(allocator);
var line_it = std.mem.split(str, "\n");
while (line_it.next()) |line| {
if (std.mem.indexOf(u8, line, "halt")) |_| {
var tok_it = std.mem.tokenize(line, " ");
const size = try std.fmt.parseInt(usize, tok_it.next().?, 10);
try result.resize(size + 1);
break;
}
}

line_it.index = 0;
const first_line = line_it.next().?;
const strings_index = std.mem.indexOf(u8, first_line, " Strings: ").?;
const globals_size = try std.fmt.parseInt(usize, first_line["Datasize: ".len..strings_index], 10);
const string_pool_size = try std.fmt.parseInt(usize, first_line[strings_index + " Strings: ".len ..], 10);
try globals.resize(globals_size);
try string_pool.ensureCapacity(string_pool_size);
var string_cnt: usize = 0;
while (string_cnt < string_pool_size) : (string_cnt += 1) {
const line = line_it.next().?;
var program_string = try std.ArrayList(u8).initCapacity(allocator, line.len);
var escaped = false;
// Skip double quotes
for (line[1 .. line.len - 1]) |ch| {
if (escaped) {
escaped = false;
switch (ch) {
'\\' => try program_string.append('\\'),
'n' => try program_string.append('\n'),
else => {
std.debug.print("unknown escape sequence: {c}\n", .{ch});
std.os.exit(1);
},
}
} else {
switch (ch) {
'\\' => escaped = true,
else => try program_string.append(ch),
}
}
}
try string_pool.append(program_string.items);
}
while (line_it.next()) |line| {
if (line.len == 0) break;

var tok_it = std.mem.tokenize(line, " ");
const address = try std.fmt.parseInt(usize, tok_it.next().?, 10);
const op = Op.fromString(tok_it.next().?);
result.items[address] = @enumToInt(op);
switch (op) {
.fetch, .store => {
const index_bracketed = tok_it.rest();
const index = try std.fmt.parseInt(i32, index_bracketed[1 .. index_bracketed.len - 1], 10);
insertInt(&result, address + 1, index);
},
.push => {
insertInt(&result, address + 1, try std.fmt.parseInt(i32, tok_it.rest(), 10));
},
.jmp, .jz => {
_ = tok_it.next();
insertInt(&result, address + 1, try std.fmt.parseInt(i32, tok_it.rest(), 10));
},
else => {},
}
}
return result;
}

fn insertInt(array: *std.ArrayList(u8), address: usize, n: i32) void {
const word_size = @sizeOf(i32);
var i: usize = 0;
var n_var = n;
var n_bytes = @ptrCast(*[4]u8, &n_var);
while (i < word_size) : (i += 1) {
array.items[@intCast(usize, address + i)] = n_bytes[@intCast(usize, i)];
}
}
</lang>


=={{header|zkl}}==
=={{header|zkl}}==