initial commit

This commit is contained in:
rus07tam 2025-11-03 12:43:30 +00:00
commit 4a08b87af4
20 changed files with 967 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.zig-cache
.direnv
result
zig-out

24
LICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>

127
README.md Normal file
View file

@ -0,0 +1,127 @@
# Owa
A small experimental virtual machine and interpreter for an interpreted language, written in Zig.
Owa provides a minimal bytecode format, a stack-based VM, and an object model with prototype inheritance and "magic" hooks. It is designed as a playground for building a compact interpreter, adding opcodes, and experimenting with native/built-in functions.
## Features
- **Stack-based VM**: executes a custom bytecode (`OwaInstruction`).
- **Interpreter/threads**: `OwaInterpreter` manages `OwaThread` instances that run frames and bytecode.
- **Frames and locals**: each call executes in an `OwaFrame` with its own instruction pointer and locals map.
- **Object model**: `OwaObject` supports attributes, prototype chain, and a callable function slot.
- **Magic hooks**: predefined magic names (`__call`, `__str`, etc.) enable dynamic behavior.
- **Builtins module**: includes basic objects such as `OWA_NULL` and a native `print` function.
## Getting Started
### Prerequisites
- Zig (0.12+ recommended)
### Build and Run
```bash
zig build
zig build run
```
This builds the `Owa` executable and runs the demo program from `src/main.zig`.
## How It Works
### Bytecode and Instructions
Instructions are defined in `src/vm/instruction.zig` as a tagged union `OwaInstruction` with an underlying `enum(u8)` opcode. Examples include `Nop`, `LoadConst`, `Call`, `Return`, and simple stack/rotation ops. Programs are encoded to bytes and then decoded during execution.
```zig
const encoded = try OwaInstruction.encode_program(&.{
OwaInstruction{ .LoadConst = 0 }, // push builtins.print
OwaInstruction{ .LoadConst = 1 }, // push OWA_NULL
OwaInstruction{ .Call = 1 }, // call print with 1 arg
OwaInstruction{ .Return = {} },
}, allocator);
```
### Interpreter and Threads
- `OwaInterpreter` initializes memory, holds thread and module lists, and can `spawn()` new `OwaThread`s.
- `OwaThread` manages a value stack and a current `OwaFrame`; it repeatedly decodes bytecode and dispatches instructions.
- `OwaFrame` tracks the function being executed, the instruction pointer (`ip`), a link to the previous frame, and a locals map.
### Object Model
`OwaObject` holds:
- `attrs`: attribute map (`StringHashMapUnmanaged`)
- `prototype`: optional prototype link
- `call_fn`: optional function (either bytecode-backed or native)
The VM resolves attribute access via `getAttr` and prototype chaining. Callability is resolved through `call_fn` or the `__call` magic attribute.
### Builtins
The `modules/builtins` module defines core objects and natives:
- `OWA_NULL`: the null value
- `print`: a native function printing its arguments
## Example
`src/main.zig` demonstrates creating an interpreter and running a tiny program that calls the builtin `print` with `OWA_NULL`:
```zig
const std = @import("std");
const owa = @import("Owa");
const modules = @import("modules");
pub fn main() !void {
const allocator = std.heap.page_allocator;
var interpreter = try owa.OwaInterpreter.init(allocator);
var thread1 = try interpreter.spawn();
var func = owa.OwaFunction{
.owa = .{
.code = try owa.OwaInstruction.encode_program(&.{
owa.OwaInstruction{ .LoadConst = 0 }, // builtins.print
owa.OwaInstruction{ .LoadConst = 1 }, // OWA_NULL
owa.OwaInstruction{ .Call = 1 },
owa.OwaInstruction{ .Return = {} },
}, allocator),
.var_const = &.{
&modules.builtins.print,
&modules.builtins.OWA_NULL,
},
},
};
try thread1.execute(&func);
interpreter.deinit();
}
```
## Project Structure
- `src/mod.zig`: re-exports core VM types (instructions, objects, interpreter, etc.).
- `src/main.zig`: demo entry point.
- `src/vm/instruction.zig`: opcodes and encode/decode utilities.
- `src/core/object.zig`: object model and attribute logic.
- `src/core/function.zig`: function union (bytecode vs native) and flags.
- `src/core/frame.zig`: call frame and locals.
- `src/core/thread.zig`: thread state, dispatch loop, and call mechanics.
- `src/core/interpreter.zig`: interpreter lifecycle and thread management.
- `src/core/magic.zig`: magic names like `__call`, `__str`, etc.
- `src/modules/builtins/`: built-in objects and native functions.
- `build.zig`: Zig build configuration (modules, executable, run step).
## Roadmap
- More opcodes (arithmetic, control flow, comparisons, etc.).
- Exception handling and error objects.
- Strings, numbers, and collections as first-class runtime objects.
- Module system and loader.
- Basic standard library.
## License
See `LICENSE` for details.

46
build.zig Normal file
View file

@ -0,0 +1,46 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const owa = b.addModule("Owa", .{
.root_source_file = b.path("src/mod.zig"),
.target = target,
});
const modules = b.addModule("modules", .{
.root_source_file = b.path("src/modules/mod.zig"),
.target = target,
.imports = &.{
.{ .name = "Owa", .module = owa },
},
});
const exe = b.addExecutable(.{
.name = "Owa",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "Owa", .module = owa },
.{ .name = "modules", .module = modules },
},
}),
.use_llvm = true,
});
b.installArtifact(exe);
const run_step = b.step("run", "Run the app");
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
}

81
build.zig.zon Normal file
View file

@ -0,0 +1,81 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = .Owa,
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",
// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0xde4cad10aa799080, // Changing this has security and trust implications.
// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.15.1",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
//.example = .{
// // When updating this field to a new URL, be sure to delete the corresponding
// // `hash`, otherwise you are communicating that you expect to find the old hash at
// // the new URL. If the contents of a URL change this will result in a hash mismatch
// // which will prevent zig from using it.
// .url = "https://example.com/foo.tar.gz",
//
// // This is computed from the file contents of the directory of files that is
// // obtained after fetching `url` and applying the inclusion rules given by
// // `paths`.
// //
// // This field is the source of truth; packages do not come from a `url`; they
// // come from a `hash`. `url` is just one of many possible mirrors for how to
// // obtain a package matching this `hash`.
// //
// // Uses the [multihash](https://multiformats.io/multihash/) format.
// .hash = "...",
//
// // When this is provided, the package is found in a directory relative to the
// // build root. In this case the package's hash is irrelevant and therefore not
// // computed. This field and `url` are mutually exclusive.
// .path = "foo",
//
// // When this is set to `true`, a package is declared to be lazily
// // fetched. This makes the dependency only get fetched if it is
// // actually used.
// .lazy = false,
//},
},
// Specifies the set of files and directories that are included in this package.
// Only files and directories listed here are included in the `hash` that
// is computed for this package. Only files listed here will remain on disk
// when using the zig package manager. As a rule of thumb, one should list
// files required for compilation plus any license(s).
// Paths are relative to the build root. Use the empty string (`""`) to refer to
// the build root itself.
// A directory listed here means that all files within, recursively, are included.
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

61
flake.lock generated Normal file
View file

@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1760038930,
"narHash": "sha256-Oncbh0UmHjSlxO7ErQDM3KM0A5/Znfofj2BSzlHLeVw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0b4defa2584313f3b781240b29d61f6f9f7e0df3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

46
flake.nix Normal file
View file

@ -0,0 +1,46 @@
{
description = "Zig Template";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
nativeBuildInputs = with pkgs; [
zig
zls
python312
];
buildInputs = with pkgs; [
gdb
lldb
nixfmt
];
in
{
devShells.default = pkgs.mkShell { inherit nativeBuildInputs buildInputs; };
packages.default = pkgs.stdenv.mkDerivation {
pname = "Owa";
version = "0.0.1";
src = ./.;
nativeBuildInputs = nativeBuildInputs ++ [
pkgs.zig.hook
];
inherit buildInputs;
};
}
);
}

20
src/core/frame.zig Normal file
View file

@ -0,0 +1,20 @@
const std = @import("std");
const OwaObject = @import("object.zig").OwaObject;
const OwaFunction = @import("function.zig").OwaFunction;
pub const OwaFrame = struct {
prev: ?*OwaFrame,
func: *OwaFunction,
ip: usize,
locals: *std.StringHashMap(*OwaObject),
pub fn init(allocator: std.mem.Allocator, func: *OwaFunction) !*OwaFrame {
const locals = try allocator.create(std.StringHashMap(*OwaObject));
const frame = try allocator.create(OwaFrame);
frame.prev = null;
frame.ip = 0;
frame.func = func;
frame.locals = locals;
return frame;
}
};

40
src/core/function.zig Normal file
View file

@ -0,0 +1,40 @@
const std = @import("std");
const OwaObject = @import("object.zig").OwaObject;
const OwaThread = @import("thread.zig").OwaThread;
pub const OwaFunctionNative = *const fn (thread: *OwaThread, args: []const *OwaObject) anyerror!*OwaObject;
pub const OwaFunctionFlags = packed struct(u16) {
varargs: bool = false,
varkwargs: bool = false,
__reserved3: bool = false,
__reserved4: bool = false,
__reserved5: bool = false,
__reserved6: bool = false,
__reserved7: bool = false,
__reserved8: bool = false,
__reserved9: bool = false,
__reserved10: bool = false,
__reserved11: bool = false,
__reserved12: bool = false,
__reserved13: bool = false,
__reserved14: bool = false,
__reserved15: bool = false,
__reserved16: bool = false,
};
pub const OwaFunction = union(enum) {
owa: struct {
flags: OwaFunctionFlags = .{},
code: []const u8 = "",
n_posonly: u8 = 0,
n_kwonly: u8 = 0,
var_const: []const *OwaObject = &.{},
var_names: []([]const u8) = &.{},
},
native: OwaFunctionNative,
pub fn init_native(native: OwaFunctionNative) OwaFunction {
return OwaFunction{ .native = native };
}
};

43
src/core/interpreter.zig Normal file
View file

@ -0,0 +1,43 @@
const std = @import("std");
const OwaThread = @import("thread.zig").OwaThread;
pub const OwaInterpreterState = enum { idle, running, paused };
pub const OwaInterpreter = struct {
allocator: std.mem.Allocator,
status: OwaInterpreterState,
threads: *std.ArrayList(OwaThread),
modules: *std.ArrayList(OwaThread),
pub fn init(allocator: std.mem.Allocator) !OwaInterpreter {
const threads = try allocator.create(std.ArrayList(OwaThread));
threads.* = std.ArrayList(OwaThread).empty;
const modules = try allocator.create(std.ArrayList(OwaThread));
modules.* = std.ArrayList(OwaThread).empty;
return OwaInterpreter{
.allocator = allocator,
.status = .idle,
.threads = threads,
.modules = modules,
};
}
pub fn deinit(self: *OwaInterpreter) void {
defer self.allocator.destroy(self.modules);
defer self.allocator.destroy(self.threads);
for (self.threads.items) |*thread| {
thread.deinit();
}
for (self.modules.items) |*module| {
module.deinit();
}
}
pub fn spawn(self: *OwaInterpreter) !*OwaThread {
try self.threads.append(self.allocator, try OwaThread.init(self));
return &self.threads.items[self.threads.items.len - 1];
}
};

13
src/core/magic.zig Normal file
View file

@ -0,0 +1,13 @@
pub const OwaMagic = enum {
pub const construct = "__construct";
pub const destruct = "__destruct";
pub const attr_get = "___attr_get";
pub const attr_set = "___attr_set";
pub const attr_del = "___attr_del";
pub const call = "__call";
pub const cell = "__cell";
pub const str = "__str";
};

9
src/core/module.zig Normal file
View file

@ -0,0 +1,9 @@
const std = @import("std");
const OwaObject = @import("object.zig").OwaObject;
pub fn OwaModule(comptime T: type) type {
return struct {
on_load: ?*const fn () anyerror!T = null,
on_unload: ?*const fn () anyerror!T = null,
};
}

38
src/core/object.zig Normal file
View file

@ -0,0 +1,38 @@
const std = @import("std");
const builtin = @import("builtin");
const OwaFunction = @import("function.zig").OwaFunction;
const OwaMagic = @import("magic.zig").OwaMagic;
const OwaThread = @import("thread.zig").OwaThread;
pub const OwaObject = struct {
attrs: std.StringHashMapUnmanaged(*OwaObject) = std.StringHashMapUnmanaged(*OwaObject).empty,
prototype: ?*OwaObject = null,
call_fn: ?OwaFunction = null,
cell: []const u8 = "",
pub fn init(allocator: std.mem.Allocator) !*OwaObject {
const obj = try allocator.create(OwaObject);
obj.* = OwaObject{};
return obj;
}
pub fn getAttr(self: *OwaObject, name: []const u8) ?*OwaObject {
if (self.attrs.get(name)) |val| return val;
if (self.prototype) |proto| return proto.getAttr(name);
return null;
}
pub fn setAttr(self: *OwaObject, allocator: std.mem.Allocator, name: []const u8, val: *OwaObject) !void {
try self.attrs.put(allocator, name, val);
}
pub fn delAttr(self: *OwaObject, name: []const u8) void {
_ = self.attrs.remove(name);
}
pub fn getFunc(self: *OwaObject) ?*OwaFunction {
const call_attr = self.getAttr(OwaMagic.call);
if (call_attr) |attr| return attr.getFunc();
return &self.call_fn.?;
}
};

231
src/core/thread.zig Normal file
View file

@ -0,0 +1,231 @@
const std = @import("std");
const builtin = @import("builtin");
const OwaFrame = @import("frame.zig").OwaFrame;
const OwaFunction = @import("function.zig").OwaFunction;
const OwaInterpreter = @import("interpreter.zig").OwaInterpreter;
const OwaInstruction = @import("../vm/instruction.zig").OwaInstruction;
const OwaMagic = @import("magic.zig").OwaMagic;
const OwaObject = @import("object.zig").OwaObject;
pub const OwaThreadState = union(enum) {
initializing,
running: struct {
frame: *OwaFrame,
},
finished,
exception: struct {
frame: *OwaFrame,
exception: *OwaObject,
},
};
pub const OwaThread = struct {
interpreter: *OwaInterpreter,
state: OwaThreadState,
stack: *std.ArrayList(*OwaObject),
pub fn init(interpreter: *OwaInterpreter) !OwaThread {
const stack = try interpreter.allocator.create(std.ArrayList(*OwaObject));
stack.* = std.ArrayList(*OwaObject).empty;
return OwaThread{
.interpreter = interpreter,
.state = .initializing,
.stack = stack,
};
}
pub fn deinit(self: *OwaThread) void {
self.stack.deinit(self.interpreter.allocator);
}
pub fn execute(self: *OwaThread, func: *OwaFunction) !void {
self.state = .initializing;
self.state = .{
.running = .{
.frame = try OwaFrame.init(self.interpreter.allocator, func),
},
};
while (self.state == .running) {
try self.step();
}
if (self.state == .initializing) return error.NotValidState;
if (self.state == .exception) return error.NotImplemented;
self.stack.clearRetainingCapacity();
}
fn step(self: *OwaThread) !void {
if (self.state != .running) return;
switch (self.state.running.frame.func.*) {
.owa => {
if (self.state.running.frame.ip >= self.state.running.frame.func.owa.code.len) {
self.state = .finished;
} else {
const code = self.state.running.frame.func.owa.code[self.state.running.frame.ip..];
const parsed = try OwaInstruction.decode(code);
self.state.running.frame.ip += parsed.len;
try self.dispatch(parsed.inst);
}
},
.native => {
const val = try self.state.running.frame.func.native(self, &.{});
try self.stack.append(self.interpreter.allocator, val);
self.prev_frame();
},
}
}
fn dispatch(self: *OwaThread, instr: OwaInstruction) !void {
switch (instr) {
.Nop => |_| {},
.PopTop => |_| {
_ = self.stack.pop();
},
.DupTop => |_| {
const x = self.stack.getLast();
try self.stack.append(self.interpreter.allocator, x);
},
.RotTwo => |_| {
if (self.stack.items.len < 2) return;
const a = self.stack.pop().?;
const b = self.stack.pop().?;
try self.stack.append(self.interpreter.allocator, a);
try self.stack.append(self.interpreter.allocator, b);
},
.RotThree => |_| {
if (self.stack.items.len < 3) return;
const a = self.stack.pop().?;
const b = self.stack.pop().?;
const c = self.stack.pop().?;
try self.stack.append(self.interpreter.allocator, a);
try self.stack.append(self.interpreter.allocator, b);
try self.stack.append(self.interpreter.allocator, c);
},
.RotFour => |_| {
if (self.stack.items.len < 4) return;
const a = self.stack.pop().?;
const b = self.stack.pop().?;
const c = self.stack.pop().?;
const d = self.stack.pop().?;
try self.stack.append(self.interpreter.allocator, a);
try self.stack.append(self.interpreter.allocator, b);
try self.stack.append(self.interpreter.allocator, c);
try self.stack.append(self.interpreter.allocator, d);
},
.LoadConst => |arg| {
const val = self.state.running.frame.func.owa.var_const[arg];
try self.stack.append(self.interpreter.allocator, val);
},
.LoadFast => |arg| {
const name = self.state.running.frame.func.owa.var_names[arg];
const val = self.state.running.frame.locals.get(name);
try self.stack.append(self.interpreter.allocator, val.?);
},
.LoadAttr => |name_idx| {
const name = self.state.running.frame.func.owa.var_names[name_idx];
var obj = self.stack.pop().?;
const val = obj.getAttr(name).?;
try self.stack.append(self.interpreter.allocator, val);
},
.StoreFast => |arg| {
const name = self.state.running.frame.func.owa.var_names[arg];
const val = self.stack.pop();
try self.state.running.frame.locals.put(name, val.?);
},
.StoreAttr => |name_idx| {
const name = self.state.running.frame.func.owa.var_names[name_idx];
var obj = self.stack.pop().?;
const val = self.stack.pop().?;
try obj.setAttr(self.interpreter.allocator, name, val);
},
.DeleteFast => |arg| {
const name = self.state.running.frame.func.owa.var_names[arg];
_ = self.state.running.frame.locals.remove(name);
},
.DeleteAttr => |name_idx| {
const name = self.state.running.frame.func.owa.var_names[name_idx];
var obj = self.stack.pop().?;
obj.delAttr(name);
},
.Return => |_| {
const val = self.stack.pop();
try self.stack.append(self.interpreter.allocator, val.?);
self.prev_frame();
},
.ReturnConst => |arg| {
const val = self.state.running.frame.func.owa.var_const[arg];
try self.stack.append(self.interpreter.allocator, val);
self.prev_frame();
},
.ReturnFast => |arg| {
const name = self.state.running.frame.func.owa.var_names[arg];
const val = self.state.running.frame.locals.get(name);
try self.stack.append(self.interpreter.allocator, val.?);
self.prev_frame();
},
.ReturnAttr => |name_idx| {
const name = self.state.running.frame.func.owa.var_names[name_idx];
var obj = self.stack.pop().?;
const val = obj.getAttr(name).?;
try self.stack.append(self.interpreter.allocator, val);
self.prev_frame();
},
.Call => |args_count| {
var args: []*OwaObject = try self.interpreter.allocator.alloc(*OwaObject, args_count);
if (args_count >= self.stack.items.len) unreachable;
for (0..args_count) |i| {
args[i] = self.stack.pop().?;
}
const func = self.stack.pop().?;
if (func.call_fn != null) {
_ = try self.new_frame(func);
}
},
.Jump => |value| {
if (value >= self.state.running.frame.func.owa.code.len) unreachable;
self.state.running.frame.ip = value;
},
else => {
unreachable;
},
}
}
pub fn call(self: *OwaThread, obj: *OwaObject) !*OwaObject {
const prev_depth = self.get_depth();
_ = try self.new_frame(&obj.call_fn.?);
while (self.state == .running and self.get_depth() != prev_depth) {
try self.step();
}
if (self.state == .initializing or self.stack.capacity == 0) return error.NotValidState;
if (self.state == .exception) return error.NotImplemented;
return self.stack.pop().?;
}
fn get_depth(self: @This()) usize {
if (self.state == .finished or self.state == .initializing) return 0;
var depth: usize = 1;
var frame = if (self.state == .exception) self.state.exception.frame else self.state.running.frame;
while (frame.prev != null) {
depth += 1;
frame = frame.prev.?;
}
return depth;
}
inline fn new_frame(self: *OwaThread, obj: *OwaObject) !*OwaFrame {
const prev = self.state.running.frame;
const func = obj.getFunc().?;
self.state.running.frame = try OwaFrame.init(self.interpreter.allocator, func);
self.state.running.frame.prev = prev;
return self.state.running.frame;
}
inline fn prev_frame(self: *OwaThread) void {
if (self.state.running.frame.prev != null) {
self.state.running.frame = self.state.running.frame.prev.?;
} else {
self.state = .finished;
}
}
};

28
src/main.zig Normal file
View file

@ -0,0 +1,28 @@
const std = @import("std");
const owa = @import("Owa");
const modules = @import("modules");
pub fn main() !void {
const allocator = std.heap.page_allocator;
var interpreter = try owa.OwaInterpreter.init(allocator);
var thread1 = try interpreter.spawn();
var func2 = owa.OwaFunction{
.owa = .{
.code = try owa.OwaInstruction.encode_program(&.{
owa.OwaInstruction{ .LoadConst = 0 }, // BUILTIN_PRINT
owa.OwaInstruction{ .LoadConst = 1 }, // OWA_NULL
owa.OwaInstruction{ .Call = 1 },
owa.OwaInstruction{ .Return = {} },
}, allocator),
.var_const = &.{
&modules.builtins.print,
&modules.builtins.OWA_NULL,
},
},
};
try thread1.execute(&func2);
interpreter.deinit();
}

17
src/mod.zig Normal file
View file

@ -0,0 +1,17 @@
pub const OwaInstruction = @import("vm/instruction.zig").OwaInstruction;
pub const OwaOpcode = @import("vm/instruction.zig").OwaOpcode;
pub const OwaFrame = @import("core/frame.zig").OwaFrame;
pub const OwaFunction = @import("core/function.zig").OwaFunction;
pub const OwaFunctionNative = @import("core/function.zig").OwaFunctionNative;
pub const OwaFunctionFlags = @import("core/function.zig").OwaFunctionFlags;
pub const OwaInterpreter = @import("core/interpreter.zig").OwaInterpreter;
pub const OwaInterpreterState = @import("core/interpreter.zig").OwaInterpreterState;
pub const OwaMagic = @import("core/magic.zig").OwaMagic;
pub const OwaModule = @import("core/module.zig").OwaModule;
pub const OwaObject = @import("core/object.zig").OwaObject;
pub const OwaThread = @import("core/thread.zig").OwaThread;
pub const OwaThreadState = @import("core/thread.zig").OwaThreadState;

View file

@ -0,0 +1,37 @@
const std = @import("std");
const owa = @import("Owa");
pub var OWA_TYPE = owa.OwaObject{};
pub var OWA_OBJECT = owa.OwaObject{
.prototype = &OWA_TYPE,
};
pub var OWA_FUNCTION = owa.OwaObject{
.prototype = &OWA_TYPE,
};
pub var OWA_NULL = owa.OwaObject{
.prototype = &OWA_TYPE,
};
pub var OWA_STR = owa.OwaObject{
.prototype = &OWA_TYPE,
};
pub fn createFunction(allocator: owa.OwaAllocator, func: owa.OwaFunctionNative) !*owa.OwaObject {
const obj = try owa.OwaObject.init(allocator);
obj.call_fn = .{ .native = func };
obj.prototype = &OWA_FUNCTION;
return obj;
}
fn _owaBuiltinPrint(_: *owa.OwaThread, args: []const *owa.OwaObject) !*owa.OwaObject {
std.debug.print("{any}\n", .{args});
return &OWA_NULL;
}
pub var print = owa.OwaObject{
.prototype = &OWA_FUNCTION,
.call_fn = .{ .native = _owaBuiltinPrint },
};

1
src/modules/mod.zig Normal file
View file

@ -0,0 +1 @@
pub const builtins = @import("builtins/mod.zig");

100
src/vm/instruction.zig Normal file
View file

@ -0,0 +1,100 @@
const std = @import("std");
pub const OwaOpcode = @typeInfo(OwaInstruction).@"union".tag_type.?;
pub const OWA_INSTRUCTION_COUNT = @typeInfo(OwaInstruction).@"union".fields.len;
pub const OwaInstruction = union(enum(u8)) {
Nop: void = 0x00,
PopTop: void = 0x01,
DupTop: void = 0x02,
RotTwo: void = 0x03,
RotThree: void = 0x04,
RotFour: void = 0x05,
LoadConst: usize = 0x06,
LoadFast: usize = 0x07,
LoadAttr: usize = 0x08,
LoadItem: usize = 0x09,
StoreFast: usize = 0x0A,
StoreAttr: usize = 0x0B,
StoreItem: usize = 0x0C,
DeleteFast: usize = 0x0D,
DeleteAttr: usize = 0x0E,
DeleteItem: usize = 0x0F,
Return: void = 0x10,
ReturnConst: usize = 0x11,
ReturnFast: usize = 0x12,
ReturnAttr: usize = 0x13,
Call: usize = 0x14,
Jump: usize = 0x15,
Unknown: void = 0xFF,
pub fn encode(self: OwaInstruction, allocator: std.mem.Allocator) ![]const u8 {
inline for (@typeInfo(OwaInstruction).@"union".fields, @typeInfo(OwaOpcode).@"enum".fields) |field, e_field| {
if (std.mem.eql(u8, field.name, @tagName(self))) {
const size: comptime_int = @sizeOf(field.type);
const total_size: comptime_int = size + 1;
const args = try allocator.alloc(u8, total_size);
args[0] = e_field.value;
switch (field.type) {
void => {},
usize => {
std.mem.writeInt(usize, args[1..total_size], @field(self, field.name), .big);
},
else => {
std.debug.panic("Not implemented type: {any} of .{s}\n", .{ field.type, field.name });
unreachable;
},
}
return args;
}
}
var fallback = try allocator.alloc(u8, 1);
fallback[0] = 0x00;
return fallback;
}
pub fn decode(raw: []const u8) !struct { inst: OwaInstruction, len: usize } {
if (raw.len < 1) return error.InstructionTooShort;
const opcode = raw[0];
var ret_size: usize = 1;
var inst: OwaInstruction = OwaInstruction{ .Nop = {} };
inline for (@typeInfo(OwaOpcode).@"enum".fields, @typeInfo(OwaInstruction).@"union".fields) |o, field| {
if (o.value == opcode) {
const size = @sizeOf(field.type);
ret_size = size;
if (raw.len < size + 1) return error.InvalidArgumentCount;
const args: *const [size]u8 = raw[1 .. size + 1];
switch (field.type) {
void => {
inst = @unionInit(OwaInstruction, field.name, {});
},
usize => {
const val = std.mem.readInt(usize, args, .big);
inst = @unionInit(OwaInstruction, field.name, val);
},
else => {
std.debug.panic("Not implemented type: {any} of .{s}\n", .{ field.type, field.name });
unreachable;
},
}
}
}
return .{ .inst = inst, .len = ret_size + 1 };
}
pub fn encode_program(program: []const OwaInstruction, allocator: std.mem.Allocator) ![]const u8 {
var buffer = std.array_list.Managed(u8).init(allocator);
for (program) |instr| {
const encoded = try instr.encode(allocator);
try buffer.appendSlice(encoded);
}
return buffer.toOwnedSlice();
}
};