initial commit
This commit is contained in:
commit
4a08b87af4
20 changed files with 967 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use flake
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.zig-cache
|
||||
.direnv
|
||||
result
|
||||
zig-out
|
||||
24
LICENSE
Normal file
24
LICENSE
Normal 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
127
README.md
Normal 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
46
build.zig
Normal 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
81
build.zig.zon
Normal 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
61
flake.lock
generated
Normal 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
46
flake.nix
Normal 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
20
src/core/frame.zig
Normal 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
40
src/core/function.zig
Normal 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
43
src/core/interpreter.zig
Normal 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
13
src/core/magic.zig
Normal 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
9
src/core/module.zig
Normal 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
38
src/core/object.zig
Normal 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
231
src/core/thread.zig
Normal 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
28
src/main.zig
Normal 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
17
src/mod.zig
Normal 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;
|
||||
37
src/modules/builtins/mod.zig
Normal file
37
src/modules/builtins/mod.zig
Normal 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
1
src/modules/mod.zig
Normal file
|
|
@ -0,0 +1 @@
|
|||
pub const builtins = @import("builtins/mod.zig");
|
||||
100
src/vm/instruction.zig
Normal file
100
src/vm/instruction.zig
Normal 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();
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue