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