summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.envrc1
-rw-r--r--.gitignore2
-rw-r--r--build.zig81
-rw-r--r--build.zig.zon81
-rw-r--r--default.nix4
-rw-r--r--src/Dict.zig66
-rw-r--r--src/Interpreter.zig39
-rw-r--r--src/Object.zig40
-rw-r--r--src/Str.zig22
-rw-r--r--src/c.h1
-rw-r--r--src/main.zig22
-rw-r--r--src/root.zig10
12 files changed, 369 insertions, 0 deletions
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..1d953f4
--- /dev/null
+++ b/.envrc
@@ -0,0 +1 @@
+use nix
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d8c8979
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+.zig-cache
+zig-out
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..9057c47
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,81 @@
+const std = @import("std");
+
+fn pythonName(b: *std.Build) []const u8 {
+ const version = b.run(&.{
+ "python",
+ "-c",
+ "import sysconfig; print(sysconfig.get_config_var('LDVERSION'), end='')",
+ });
+
+ var name = b.allocator.alloc(u8, "python".len + version.len) catch @panic("OOM");
+ @memcpy(name[0..6], "python");
+ @memcpy(name[6..], version);
+
+ return name;
+}
+
+pub fn build(b: *std.Build) void {
+ const target = b.standardTargetOptions(.{});
+ const optimize = b.standardOptimizeOption(.{});
+
+ const translate_c = b.addTranslateC(.{
+ .root_source_file = b.path("src/c.h"),
+ .target = target,
+ .optimize = optimize,
+ });
+ translate_c.linkSystemLibrary("python3", .{});
+
+ const mod = b.addModule("zpy", .{
+ .root_source_file = b.path("src/root.zig"),
+ .target = target,
+ .imports = &.{
+ .{ .name = "c", .module = translate_c.createModule() },
+ },
+ });
+ const python_name = pythonName(b);
+ defer b.allocator.free(python_name);
+ mod.linkSystemLibrary(python_name, .{});
+
+
+ const exe = b.addExecutable(.{
+ .name = "zpy",
+ .root_module = b.createModule(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ .imports = &.{
+ .{ .name = "zpy", .module = mod },
+ },
+ }),
+ });
+
+ 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);
+ }
+
+ const mod_tests = b.addTest(.{
+ .root_module = mod,
+ });
+
+ const run_mod_tests = b.addRunArtifact(mod_tests);
+
+ const exe_tests = b.addTest(.{
+ .root_module = exe.root_module,
+ });
+
+ const run_exe_tests = b.addRunArtifact(exe_tests);
+
+ const test_step = b.step("test", "Run tests");
+ test_step.dependOn(&run_mod_tests.step);
+ test_step.dependOn(&run_exe_tests.step);
+
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..2e06641
--- /dev/null
+++ b/build.zig.zon
@@ -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 = .zpy,
+ // 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 = 0xa0c184fae359507c, // 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.16.0",
+ // 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",
+ },
+}
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..93b80aa
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,4 @@
+{ pkgs ? import <nixpkgs> { }, ... }:
+pkgs.mkShell {
+ packages = with pkgs; [ zig python3 pkg-config];
+}
diff --git a/src/Dict.zig b/src/Dict.zig
new file mode 100644
index 0000000..89964cf
--- /dev/null
+++ b/src/Dict.zig
@@ -0,0 +1,66 @@
+const std = @import("std");
+const c = @import("c");
+
+const zpy = @import("root.zig");
+
+const Self = @This();
+
+object: zpy.Object,
+
+pub const Key = union(enum) {
+ string: [*c]const u8,
+ object: zpy.Object,
+};
+
+pub fn new() zpy.Error!Self {
+ if (c.PyDict_New()) |object| {
+ return .{ .object = .fromPtr(object) };
+ }
+
+ return error.Exception;
+}
+
+pub fn clear(self: *Self) void {
+ c.PyDict_Clear(self.object.ptr);
+}
+
+pub fn contains(self: *const Self, key: Key) bool {
+ return switch (key) {
+ .string => |str| c.PyDict_ContainsString(self.object.ptr, str),
+ .object => |obj| c.PyDict_Contains(self.object.ptr, obj.ptr),
+ } == 1;
+}
+
+pub fn copy(self: *const Self) Self {
+ return .{ .object = .fromPtr(c.PyDict_Copy(self.object.ptr)) };
+}
+
+pub fn set(self: *Self, key: Key, value: zpy.Object) !void {
+ const ret = switch (key) {
+ .string => |str| c.PyDict_SetItemString(self.object.ptr, str, value.ptr),
+ .object => |obj| c.PyDict_SetItem(self.object.ptr, obj.ptr, value.ptr),
+ };
+
+ if (ret == -1) return error.Exception;
+}
+
+pub fn get(self: *const Self, key: Key) ?zpy.Object {
+ return .fromC(switch (key) {
+ .string => |str| c.PyDict_GetItemString(self.object.ptr, str),
+ .object => |obj| c.PyDict_GetItem(self.object.ptr, obj.ptr),
+ });
+}
+
+pub fn delete(self: *Self, key: Key) !void {
+ const ret = switch (key) {
+ .string => |str| c.PyDict_DelItemString(self.object.ptr, str),
+ .object => |obj| c.PyDict_DelItem(self.object.ptr, obj.ptr),
+ };
+
+ if (ret == -1) return error.Exception;
+}
+
+pub fn deinit(self: *Self) void {
+ self.object.decref();
+ self.* = undefined;
+}
diff --git a/src/Interpreter.zig b/src/Interpreter.zig
new file mode 100644
index 0000000..6862706
--- /dev/null
+++ b/src/Interpreter.zig
@@ -0,0 +1,39 @@
+const c = @import("c");
+
+const zpy = @import("root.zig");
+
+const Self = @This();
+
+globals: zpy.Dict,
+
+pub fn init() !Self {
+ c.Py_Initialize();
+
+ const self: Self = .{
+ .globals = try .new(),
+ };
+
+ return self;
+}
+
+pub const RunOptions = struct {
+ locals: ?*zpy.Dict = null,
+};
+
+pub fn run(self: *Self, str: [*c]const u8, options: RunOptions) zpy.Error!zpy.Object {
+ if (c.PyRun_String(
+ str,
+ c.Py_file_input,
+ self.globals.object.ptr,
+ if (options.locals) |loc| loc.object.ptr else null,
+ )) |object| {
+ return .fromPtr(object);
+ }
+
+ return error.Exception;
+}
+
+pub fn deinit(self: *Self) void {
+ c.Py_Finalize();
+ self.* = undefined;
+}
diff --git a/src/Object.zig b/src/Object.zig
new file mode 100644
index 0000000..782071f
--- /dev/null
+++ b/src/Object.zig
@@ -0,0 +1,40 @@
+const std = @import("std");
+const c = @import("c");
+const zpy = @import("root.zig");
+
+const Self = @This();
+
+ptr: *c.PyObject,
+
+pub fn fromPtr(ptr: *c.PyObject) Self {
+ return .{ .ptr = ptr };
+}
+
+
+pub fn fromC(ptr: [*c]c.PyObject) ?Self {
+ return if (ptr) |p| .fromPtr(p) else null;
+}
+
+pub fn decref(self: *Self) void {
+ c.Py_DecRef(self.ptr);
+}
+
+pub fn repr(self: *const Self) zpy.Str {
+ return .{ .object = .fromPtr(c.PyObject_Repr(self.ptr)) };
+}
+
+pub fn call(self: *Self, args: anytype) zpy.Error!zpy.Object {
+ _ = args;
+ if (c.PyObject_CallObject(self.ptr, null)) |object| {
+ return .fromPtr(object);
+ }
+
+ return error.Exception;
+}
+
+pub fn format(self: *const Self, writer: *std.Io.Writer) !void {
+ var str = self.repr();
+ defer str.deinit();
+
+ try writer.print("{s}", .{str.asUtf8() catch return error.WriteFailed});
+}
diff --git a/src/Str.zig b/src/Str.zig
new file mode 100644
index 0000000..eaca378
--- /dev/null
+++ b/src/Str.zig
@@ -0,0 +1,22 @@
+
+const c = @import("c");
+const zpy = @import("root.zig");
+
+const Self = @This();
+
+object: zpy.Object,
+
+
+pub fn asUtf8(self: *Self) zpy.Error![]const u8 {
+ var size: c.Py_ssize_t = undefined;
+ const string: [*]const u8 = c.PyUnicode_AsUTF8AndSize(self.object.ptr, &size);
+
+ if (size == -1) return error.Exception;
+
+ return string[0..@as(usize, @intCast(size))];
+}
+
+pub fn deinit(self: *Self) void {
+ self.object.decref();
+ self.* = undefined;
+}
diff --git a/src/c.h b/src/c.h
new file mode 100644
index 0000000..576fc6d
--- /dev/null
+++ b/src/c.h
@@ -0,0 +1 @@
+#include <Python.h>
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..a39c79f
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,22 @@
+const std = @import("std");
+const Io = std.Io;
+
+const zpy = @import("zpy");
+
+pub fn main() !void {
+ var py: zpy.Interpreter = try .init();
+ defer py.deinit();
+
+ var locals = try zpy.Dict.new();
+ defer locals.deinit();
+
+ _ = try py.run("def test(): print('hello')", .{
+ .locals = &locals,
+ });
+
+ var test_fn = locals.get(.{ .string = "test" }) orelse unreachable;
+ defer test_fn.decref();
+ const result = try test_fn.call(.{});
+
+ std.debug.print("test() = {f}\n", .{result});
+}
diff --git a/src/root.zig b/src/root.zig
new file mode 100644
index 0000000..0e3d9e4
--- /dev/null
+++ b/src/root.zig
@@ -0,0 +1,10 @@
+const std = @import("std");
+
+pub const Object = @import("Object.zig");
+pub const Interpreter = @import("Interpreter.zig");
+pub const Dict = @import("Dict.zig");
+pub const Str = @import("Str.zig");
+
+pub const Error = error {
+ Exception,
+};