const std = @import("std");
const mem = std.mem;
const log = std.log.scoped(.bytecode);

/// NOTE: This must be mirrored in bytecode.rs.
pub const Opcode = enum(u8) {
    // Push literal values onto the stack.
    nil,
    false,
    true,
    tag,
    number, // (float: f32)
    rgba, // (r: u8, g: u8, b: u8, a: u8)

    // Duplicate existing values.
    local, // (index: u8) push a value relative to the bottom of the current stack window
    set_local, // (index: u8) set the value of a value relative to the bottom of the current stack window
    capture, // (index: u8) push a value captured by the current closure
    def, // (index: u16) get the value of a definition
    set_def, // (index: u16) set the value of a definition

    // Create lists.
    list, // (len: u16)

    // Create literal functions.
    function, // (params: u8, then: u16), at `then`: (local_count: u8, capture_count: u8, captures: [capture_count](source: u8, index: u8))

    // Control flow.
    jump, // (offset: u16)
    jump_if_not, // (offset: u16)
    field, // (count: u8, tags: [count]u16) look up a closure capture named by a tag. this is used to implement records with fields

    // Function calls.
    call, // (argc: u8)
    system, // (index: u8, argc: u8) fast path for system calls

    ret, // must be the last opcode; opcodes after .ret are treated as invalid

    _, // invalid opcodes
};

// Constants used by the .function opcode to indicate capture sources.
pub const capture_local: u8 = 0;
pub const capture_capture: u8 = 1;

pub const Chunk = struct {
    bytecode: []const u8,
};

pub const Loc = u16;

pub const Defs = struct {
    num_defs: usize,
    num_tags: usize,
    // Unlike the Rust version, we currently do not store strings here, because we still don't
    // support stack traces.
    // The VM therefore never needs the names of Defs for any practical purposes.

    pub fn parse(
        a: mem.Allocator,
        // These strings are expected to contain a list of names, where each name is terminated
        // by a newline (LF).
        defs_string: []const u8,
        tags_string: []const u8,
    ) !*Defs {
        const d = try a.create(Defs);
        errdefer a.destroy(d);

        d.* = .{
            .num_defs = mem.count(u8, defs_string, "\n"),
            .num_tags = mem.count(u8, tags_string, "\n"),
        };

        return d;
    }

    pub fn destroy(defs: *Defs, a: mem.Allocator) void {
        a.destroy(defs);
    }
};
