This commit is contained in:
Dario48 2026-04-06 00:01:53 +02:00
commit ce835e9f44
10 changed files with 1048 additions and 0 deletions

3
.envrc Normal file
View file

@ -0,0 +1,3 @@
#!/bin/bash
use flake

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
zig-out
.zig-cache
.direnv

80
build.zig Normal file
View file

@ -0,0 +1,80 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const lazyValues = b.addModule("lazy_evaluation", .{
.root_source_file = b.path("src/lazy.zig"),
.target = target,
.optimize = optimize,
.imports = &.{},
});
const niceties = b.addModule("niceties", .{
.root_source_file = b.path("src/niceties.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "lazy_evaluation", .module = lazyValues },
},
});
lazyValues.addImport("niceties", niceties);
const partial_application = b.addModule("partial_application", .{
.root_source_file = b.path("src/partial.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "lazy_evaluation", .module = lazyValues },
},
});
const exe = b.addExecutable(.{
.name = "fzig",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "partial_application", .module = partial_application },
.{ .name = "niceties", .module = niceties },
.{ .name = "lazy_evaluation", .module = lazyValues },
},
}),
.use_llvm = false,
.use_lld = false,
.linkage = .static,
});
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 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_exe_tests.step);
const exe_check = b.addExecutable(.{
.name = "foo",
.root_module = exe.root_module,
});
const check = b.step("check", "Check if foo compiles");
check.dependOn(&exe_check.step);
}

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 = .fzig,
// 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 = 0x186be502140e04c, // 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.2",
// 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",
},
}

140
flake.lock generated Normal file
View file

@ -0,0 +1,140 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1773646010,
"narHash": "sha256-iYrs97hS7p5u4lQzuNWzuALGIOdkPXvjz7bviiBjUu8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5b2c2d84341b2afb5647081c1386a80d7a8d8605",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"zig-overlay": "zig-overlay",
"zls-flake": "zls-flake"
}
},
"systems": {
"flake": false,
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"zig-overlay": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
],
"systems": "systems"
},
"locked": {
"lastModified": 1773622330,
"narHash": "sha256-eaDMObc4Y+b4nIHMf+2pmS+gcQEnRdY7xU62K0G7wzM=",
"owner": "mitchellh",
"repo": "zig-overlay",
"rev": "a20da6ffe5e036abaadc8b0cbe0c21aedf0a1dc5",
"type": "github"
},
"original": {
"owner": "mitchellh",
"repo": "zig-overlay",
"type": "github"
}
},
"zls-flake": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"zig-overlay": [
"zig-overlay"
]
},
"locked": {
"lastModified": 1773672457,
"narHash": "sha256-Xj7XWqD1ZkCtZfb39pkvVn5Ed+GcwbzvuADBslCPFw8=",
"owner": "zigtools",
"repo": "zls",
"rev": "27532e2b3a83215efa2ab4dc6f016feb8478a88e",
"type": "github"
},
"original": {
"owner": "zigtools",
"repo": "zls",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

46
flake.nix Normal file
View file

@ -0,0 +1,46 @@
{
description = "flake for the fzig library";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
zig-overlay.url = "github:mitchellh/zig-overlay";
zig-overlay.inputs.nixpkgs.follows = "nixpkgs";
zls-flake.url = "github:zigtools/zls";
zls-flake.inputs.zig-overlay.follows = "zig-overlay";
zls-flake.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
inputs@{
self,
nixpkgs,
flake-parts,
zig-overlay,
zls-flake,
...
}:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = nixpkgs.lib.systems.flakeExposed;
imports = [ ];
perSystem =
{
pkgs,
system,
...
}:
let
zig = zig-overlay.packages.${system}.master;
zls = zls-flake.packages.${system}.default;
in
{
devShells.default = pkgs.mkShell {
packages = [
zig
zls
];
};
};
};
}

268
src/lazy.zig Normal file
View file

@ -0,0 +1,268 @@
pub const allocator = if (@hasDecl(@import("root"), "lazy_allocator")) @import("root").lazy_allocator else @import("std").heap.page_allocator;
const std = @import("std");
const niceties = @import("niceties");
fn genClears(comptime Self: type) type {
return struct {
pub fn noop(_: Self) void {}
pub fn defaultClear(self: Self) void {
allocator.destroy(self._data);
}
pub fn arrayClear(self: Self) void {
allocator.free(self);
}
};
}
fn genHMClears(comptime Self: type) type {
return struct {
pub fn noop(self: *Self) void {
self.value.deinit();
}
pub fn defaultClear(self: *Self) void {
allocator.destroy(self._data);
self.values.deinit();
}
pub fn arrayClear(self: *Self) void {
allocator.free(self._data);
self.values.deinit();
}
};
}
pub fn LazyValue(comptime T: type, comptime data: type) type {
return struct {
pub fn cast(self: @This(), comptime resoult: type) resoult {
std.debug.assert(niceties.structCompare(@This(), resoult));
return @as(*const resoult, @ptrCast(&self)).*;
}
pub fn get(self: @This()) T {
return self._retrieve(self);
}
pub fn getClear(self: *@This()) T {
defer self.deinit();
return self.get();
}
pub fn deinit(self: *@This()) void {
self.references -= 1;
if (self.references == 0)
self._clear(self);
}
pub const clears = genClears(@This());
_retrieve: *const fn (self: @This()) T,
_clear: *const fn (self: @This()) void = clears.noop,
value: ?T = null,
_data: data,
references: usize = 1,
};
}
pub fn LazyValue_(comptime T: type) type {
return struct {
pub fn cast(self: @This(), comptime resoult: type) resoult {
std.debug.assert(niceties.structCompare(@This(), resoult));
return @as(*const resoult, @ptrCast(&self)).*;
}
pub fn get(self: @This()) T {
return self._retrieve(self);
}
pub fn getClear(self: @This()) T {
defer self.deinit();
return self.get();
}
pub fn deinit(self: @This()) void {
self._clear(self);
}
pub const clears = genClears(@This());
_retrieve: *const fn (@This()) T,
_clear: *const fn (@This()) void = clears.defaultClear,
value: ?T = null,
_data: *anyopaque,
};
}
pub fn LazyFunction_(comptime T: type, comptime ret: type, comptime args: type) type {
return struct {
pub fn cast(self: @This(), comptime resoult: type) resoult {
std.debug.assert(niceties.structCompare(@This(), resoult));
return @as(*const resoult, @ptrCast(&self)).*;
}
pub fn get(self: @This(), arg: args) ret {
return self._retrieve(self, arg);
}
pub fn getClear(self: @This(), arg: args) ret {
defer self.deinit();
return self.get(arg);
}
pub const clears = genHMClears(@This());
_retrieve: *const fn (@This(), args) ret,
_clear: *const fn (@This()) void = clears.defaultClear,
value: std.AutoHashMap(args, T) = .init(allocator),
_data: *anyopaque,
};
}
pub fn LazyFunction(comptime T: type, comptime err: type, comptime data: type, comptime args: type, comptime threadsafe: bool) type {
return struct {
pub fn cast(self: @This(), comptime resoult: type) resoult {
std.debug.assert(niceties.structCompare(@This(), resoult));
return @as(*const resoult, @ptrCast(&self)).*;
}
pub fn get(self: *@This(), arg: args) err!T {
return self._retrieve(self, arg);
}
pub fn getClear(self: *@This(), arg: args) err!T {
defer self.deinit();
return self.get(arg);
}
pub fn deinit(self: *@This()) void {
self._clear(self);
}
pub const clears = genHMClears(@This());
_retrieve: *const fn (*@This(), args) err!T,
_clear: *const fn (*@This()) void = clears.noop,
value: std.AutoHashMap(args, T) = .init(allocator),
_data: data,
Io: if (threadsafe) std.Io else void,
Mutex: if (threadsafe) std.Io.Mutex else void = if (threadsafe) std.Io.Mutex.init else {},
};
}
pub fn LazyArray(comptime T: type, comptime err: type, comptime data: type, comptime threadsafe: bool) type {
std.debug.assert(@hasField(data, "arr"));
std.debug.assert(@hasField(data, "partial"));
return struct {
pub fn get(self: *@This(), arg: usize) err!T {
std.log.debug("called [ get ]", .{});
return self.backing.get(arg);
}
pub fn getClear(self: *@This(), arg: usize) err!T {
defer self.backing.deinit();
return self.backing.get(arg);
}
pub fn deinit(self: *@This()) void {
self.backing.deinit();
}
pub fn all(self: *@This()) (error{OutOfMemory} || err)![]T {
var tmp: []T = try allocator.alloc(T, self._arrSize(self.*));
for (0..self._arrSize(self.*)) |i| {
tmp[i] = try self.get(i);
}
return tmp;
}
pub fn allIO(self: *@This(), Io: std.Io) (error{OutOfMemory} || err)![]T {
const tmpF: []std.Io.Future(err!T) = try allocator.alloc(std.Io.Future(err!T), self._arrSize(self.*));
defer allocator.free(tmpF);
const tmp: []T = try allocator.alloc(T, self._arrSize(self.*));
for (0..self._arrSize(self.*)) |i| {
tmpF[i] = Io.async(@This().get, .{ self, i });
}
for (tmp, tmpF) |*t, *tf| {
t.* = try tf.await(Io);
}
return tmp;
}
pub const BackingType = LazyFunction(T, err, data, usize, threadsafe);
backing: BackingType,
_arrSize: *const fn (@This()) usize,
};
}
pub fn generator(
comptime T: type,
comptime err: type,
comptime data: type,
comptime limit: enum(usize) {
unlimited = std.math.maxInt(usize),
_,
pub fn fromNum(comptime num: usize) @This() {
return @enumFromInt(num);
}
pub fn toNum(self: @This()) usize {
return @intFromEnum(self);
}
},
) type {
return struct {
const g = @This();
pub fn toLazyArray(self: g) LazyArray(T, err, void, false) {
const t = LazyArray(T, err, g, false);
return t{
._arrSize = limit.toNum(),
.backing = .{
._retrieve = (struct {
pub fn retrieve(s: t, i: usize) err!T {
return s.backing._data._retrieve(s.backing._data, i);
}
}).retrieve,
._data = self,
._clear = (struct {
pub fn clear(s: t) void {
s.backing._data._clear;
}
}).clear,
},
};
}
pub fn next(self: *g) (error{limitReached} || err)!T {
defer self.index += 1;
switch (limit) {
.unlimited => {},
_ => if (limit.toNum() < self.index) return error.limitReache,
}
return self._retrieve(self, self.index);
}
pub fn nextN(self: *g, n: usize) (error{outOfMemory} || err)![]T {
const tmp = try allocator.alloc(T, n);
for (tmp) |*t| {
t.* = self.next();
}
return tmp;
}
pub fn nextNIO(self: *g, Io: std.Io, n: usize) (error{outOfMemory} || err)![]T {
const tmpf = try allocator.alloc(std.Io.Future(err!T), n);
defer allocator.free(tmpf);
const tmp = try allocator.alloc(T, n);
for (tmpf) |*t| {
t.* = Io.async(next, .{self});
}
for (tmp, tmpf) |*t, f| {
t.* = try f.await(Io);
}
return tmp;
}
pub fn deinit(self: g) void {
self._clear(self);
}
pub const clears = genClears(g);
_retrieve: *const fn (self: *g, usize) T,
_clear: *const fn (self: g) void = clears.noop,
index: usize = 0,
_data: data,
};
}

95
src/main.zig Normal file
View file

@ -0,0 +1,95 @@
const std = @import("std");
const partial_application = @import("partial_application");
const niceties = @import("niceties");
const generator = @import("lazy_evaluation").generator;
fn genSlowAdd(time: i64) fn (std.Io, u16, u16) u16 {
return struct {
pub fn slowAdd(Io: std.Io, a: u16, b: u16) u16 {
Io.sleep(.fromMicroseconds(time), .real) catch std.log.err("cancelled sleep", .{});
return add(a, b);
}
}.slowAdd;
}
fn slowAdd(Io: std.Io, a: u16, b: u16) u16 {
Io.sleep(.fromMilliseconds(1), .real) catch std.log.err("cancelled sleep", .{});
return add(a, b);
}
fn add(a: u16, b: u16) u16 {
return a + b;
}
fn getArray(comptime T: type, comptime num: comptime_int, comptime @"fn": fn (comptime index: usize) T) [num]T {
@setEvalBranchQuota(100000);
var tmp: [num]T = undefined;
inline for (0..tmp.len) |i| {
tmp[i] = @"fn"(i);
}
return tmp;
}
fn testSpeed(comptime size: comptime_int, Io: std.Io, @"fn": anytype) !void {
std.log.info("with size {}: ", .{size});
{
const genType = generator(u16, void, void, .fromNum(size));
const array = genType{
._data = {},
._retrieve = (struct {
pub fn fun(_: *genType, index: usize) u16 {
return @intCast(index);
}
}).fun,
};
var array2 = try niceties.mapP(u16, &array, &@"fn", .ThreadUnsafe, {});
defer array2.deinit();
var array3 = try niceties.mapPl(u16, array2, &@"fn", .ThreadUnsafe, {});
defer array3.deinit();
//std.log.info("with all(): ", .{});
const start = std.Io.Timestamp.now(Io, .real);
//std.log.info("array: {any}, resoult: {any}, result2: {any}", .{ array, array2.all(), array3.all() });
_ = try array2.all();
_ = try array3.all();
const end = std.Io.Timestamp.now(Io, .real);
std.log.info(" with all(): {f}", .{start.durationTo(end)});
}
{
const array = getArray(u16, size, (struct {
pub fn fun(comptime index: usize) u16 {
return @intCast(index);
}
}).fun);
var array2 = try niceties.mapP(u16, &array, &@"fn", .ThreadSafe, Io);
defer array2.deinit();
var array3 = try niceties.mapPl(u16, array2, &@"fn", .ThreadSafe, Io);
defer array3.deinit();
//std.log.info("with allIO(): ", .{});
const start = std.Io.Timestamp.now(Io, .real);
//std.log.info("array: {any}, resoult: {any}, result2: {any}", .{ array, array2.allIO(Io), array3.allIO(Io) });
_ = try array2.allIO(Io);
_ = try array3.allIO(Io);
const end = std.Io.Timestamp.now(Io, .real);
std.log.info(" with allIO(): {f}", .{start.durationTo(end)});
}
}
pub fn main(init: std.process.Init) !void {
comptime var i = 1;
std.log.info(
\\fn slowAdd(Io: std.Io, a: u16, b: u16) u16 {{
\\ Io.sleep(.fromMicroseconds(1000000/size), .real) catch std.log.err("cancelled sleep", .{{}});
\\ return add(a, b);
\\}}
, .{});
inline while (i < (1 << @typeInfo(u16).int.bits)) : (i *= 2) {
var addOne = partial_application.genPartial(genSlowAdd(1000000 / i)).new;
addOne.init(.{ init.io, 1 });
try testSpeed(i, init.io, addOne);
}
}

182
src/niceties.zig Normal file
View file

@ -0,0 +1,182 @@
const std = @import("std");
const LazyValue = @import("lazy_evaluation").LazyValue;
const LazyArray = @import("lazy_evaluation").LazyArray;
pub fn structCompare(comptime A: type, comptime B: type) bool {
std.debug.assert(@typeInfo(A) == .@"struct");
std.debug.assert(@typeInfo(B) == .@"struct");
const fa = @typeInfo(A).@"struct".fields;
const fb = @typeInfo(B).@"struct".fields;
if (fa.len != fb.len) return false;
outer: inline for (fa) |af| {
inline for (fb) |bf| {
if (bf.type == af.type and std.mem.eql(u8, bf.name, af.name))
continue :outer;
}
return false;
}
return true;
}
fn fnType(@"fn": anytype, input: anytype) type {
s: switch (@typeInfo(@TypeOf(@"fn"))) {
.@"fn" => return @typeInfo(@TypeOf(@"fn")).@"fn".return_type.? orelse @TypeOf(@"fn"(input[0])),
.pointer => continue :s @typeInfo(@TypeOf(@"fn".*)),
else => @compileError("must pass a function to map"),
}
}
fn mapType(input: anytype, @"fn": anytype) type {
s: switch (@typeInfo(@TypeOf(input))) {
.array => return fnType(@"fn", input),
.pointer => switch (@typeInfo(@TypeOf(input)).pointer.size) {
.many, .slice => return fnType(@"fn", input),
else => continue :s @typeInfo(@TypeOf(input.*)),
},
else => @compileError("must pass an array, a slice or a many pointer to map"),
}
}
pub fn map(alloc: std.mem.Allocator, array: anytype, fun: anytype) ![]fnType(fun, array) {
std.debug.assert(@typeInfo(@TypeOf(array)) == .array);
var new = try alloc.alloc(fnType(fun, array), array.len);
for (array, &new) |a, n| {
n.* = if (@typeInfo(@TypeOf(fun)).@"fn".return_type) |_| fun(a) else try fun(a);
}
return new;
}
fn arrayType(T: type) type {
s: switch (@typeInfo(T)) {
.array => |a| return a.child,
.pointer => |p| switch (p.size) {
.many, .slice => return p.child,
else => continue :s @typeInfo(p.child),
},
else => @compileError("argoument must either be array, slice or many pointer"),
}
}
fn mapPdataType(partial: type, arr: type) type {
return struct {
partial: partial,
arr: arr,
};
}
fn mapParrayType(comptime T: type, comptime p: type, comptime arr: type, comptime threadsafety: bool) type {
return LazyArray(T, error{ notEnoughArgs, OutOfMemory }, mapPdataType(p, arr), threadsafety);
}
pub fn mapP(comptime T: type, array: anytype, partial: anytype, comptime threadSafety: enum { ThreadSafe, ThreadUnsafe }, Io: if (threadSafety == .ThreadSafe) std.Io else void) !mapParrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe) {
switch (@typeInfo(@TypeOf(partial))) {
.pointer => {
std.debug.assert(@typeInfo(@TypeOf(partial.*)) == .@"struct");
std.debug.assert(std.meta.hasFn(@TypeOf(partial.*), "callAtomic") and
@hasDecl(@TypeOf(partial.*), "returnType"));
},
.@"struct" => std.debug.assert(std.meta.hasFn(@TypeOf(partial), "callAtomic") and
std.meta.hasMethod(@TypeOf(partial), "returnType")),
else => std.debug.assert(false),
}
return mapParrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe){
._arrSize = (struct {
pub fn arrSize(self: mapParrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe)) usize {
return self.backing._data.arr.len;
}
}).arrSize,
.backing = .{
._retrieve = (struct {
pub fn retrieve(self: *mapParrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe).BackingType, index: usize) error{ notEnoughArgs, OutOfMemory }!T {
{
if (threadSafety == .ThreadSafe)
self.Mutex.lockUncancelable(self.Io);
defer if (threadSafety == .ThreadSafe) self.Mutex.unlock(self.Io);
if (self.value.get(index)) |val| return val;
}
const val = (try self._data.partial.callAtomic(.{self._data.arr[index]})).getClear();
if (threadSafety == .ThreadSafe)
self.Mutex.lockUncancelable(self.Io);
defer if (threadSafety == .ThreadSafe) self.Mutex.unlock(self.Io);
try self.value.put(index, val);
return val;
}
}).retrieve,
._data = (mapPdataType(@TypeOf(partial), @TypeOf(array))){
.partial = partial,
.arr = array,
},
.Io = Io,
},
};
}
fn mapPldataType(partial: type, arr: type) type {
return struct {
partial: partial,
arr: arr,
};
}
fn mapPlarrayType(comptime T: type, comptime p: type, comptime arr: type, comptime threadsafety: bool) type {
return LazyArray(T, error{ notEnoughArgs, OutOfMemory }, mapPldataType(p, arr), threadsafety);
}
pub fn mapPl(comptime T: type, array: anytype, partial: anytype, comptime threadSafety: enum { ThreadSafe, ThreadUnsafe }, Io: if (threadSafety == .ThreadSafe) std.Io else void) !mapPlarrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe) {
switch (@typeInfo(@TypeOf(partial))) {
.pointer => {
std.debug.assert(@typeInfo(@TypeOf(partial.*)) == .@"struct");
std.debug.assert(std.meta.hasFn(@TypeOf(partial.*), "callAtomic") and
@hasDecl(@TypeOf(partial.*), "returnType"));
},
.@"struct" => std.debug.assert(std.meta.hasFn(@TypeOf(partial), "callAtomic") and
std.meta.hasMethod(@TypeOf(partial), "returnType")),
else => std.debug.assert(false),
}
return mapPlarrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe){
._arrSize = (struct {
pub fn arrSize(self: mapPlarrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe)) usize {
return self.backing._data.arr._arrSize(self.backing._data.arr);
}
}).arrSize,
.backing = .{
._retrieve = (struct {
pub fn retrieve(self: *mapPlarrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe).BackingType, index: usize) error{ notEnoughArgs, OutOfMemory }!T {
{
if (threadSafety == .ThreadSafe)
self.Mutex.lockUncancelable(self.Io);
defer if (threadSafety == .ThreadSafe) self.Mutex.unlock(self.Io);
if (self.value.get(index)) |val| return val;
}
const val = (try self._data.partial.callAtomic(.{try self._data.arr.get(index)})).getClear();
if (threadSafety == .ThreadSafe)
self.Mutex.lockUncancelable(self.Io);
defer if (threadSafety == .ThreadSafe) self.Mutex.unlock(self.Io);
try self.value.put(index, val);
return val;
}
}).retrieve,
._clear = (struct {
pub fn clear(self: *mapPlarrayType(T, @TypeOf(partial), @TypeOf(array), threadSafety == .ThreadSafe).BackingType) void {
self._data.arr.deinit();
self.value.deinit();
}
}).clear,
._data = (mapPldataType(@TypeOf(partial), @TypeOf(array))){
.partial = partial,
.arr = array,
},
.Io = Io,
},
};
}

150
src/partial.zig Normal file
View file

@ -0,0 +1,150 @@
const std = @import("std");
const LazyValue = @import("lazy_evaluation").LazyValue;
const allocator = @import("lazy_evaluation").allocator;
fn intCoerce(a: std.builtin.Type, b: std.builtin.Type) bool {
return (a == .int and b == .comptime_int) or
(a == .int and b == .int and
((a.int.bits >= b.int.bits and a.int.signedness == b.int.signedness) or
(a.int.bits >= 2 * b.int.bits and a.int.signedness == .signed and b.int.signedness == .unsigned)));
}
fn floatCoerce(a: std.builtin.Type, b: std.builtin.Type) bool {
return (a == .float and b == .comptime_float) or
(a == .float and b == .comptime_int) or
(a == .float and b == .int and
((a.float.bits == 16 and
((b.int.signedness == .signed and b.int.bits == 12) or
(b.int.signedness == .unsigned and b.int.bits == 11))) or
(a.float.bits == 32 and
((b.int.signedness == .signed and b.int.bits == 25) or
(b.int.signedness == .unsigned and b.int.bits == 24))) or
(a.float.bits == 64 and
((b.int.signedness == .signed and b.int.bits == 54) or
(b.int.signedness == .unsigned and b.int.bits == 53))) or
(a.float.bits == 80 and
((b.int.signedness == .signed and b.int.bits == 65) or
(b.int.signedness == .unsigned and b.int.bits == 64))) or
(a.float.bits == 128 and
((b.int.signedness == .signed and b.int.bits == 114) or
(b.int.signedness == .unsigned and b.int.bits == 113)))));
}
fn coerces(a: type, b: type) bool {
const infoA = @typeInfo(a);
const infoB = @typeInfo(b);
return a == b or
b == @TypeOf(undefined) or
(infoA == .optional and (b == @TypeOf(null) or b == infoA.optional.child)) or
intCoerce(infoA, infoB) or
floatCoerce(infoA, infoB);
}
pub fn genPartial(@"fn": anytype) type {
std.debug.assert(@typeInfo(@TypeOf(@"fn")) == .@"fn");
const info = @typeInfo(@TypeOf(@"fn")).@"fn";
var types: [info.params.len]type = undefined;
for (&types, info.params) |*t, param| {
t.* = param.type orelse *anyopaque;
}
const argStruct = @Tuple(
&types,
);
return struct {
pub const returnType = LazyValue(info.return_type.?, argStruct);
const Self = @This();
pub inline fn @"type"(comptime _: Self) type {
return Self;
}
fn retrieve(args: returnType) info.return_type.? {
if (args.value) |val| return val;
return @call(.auto, @"fn", args._data);
}
pub fn call(self: *Self, args: anytype) ?returnType {
std.log.debug("called [ call ]", .{});
const typeinfo = @typeInfo(@TypeOf(args));
std.debug.assert(typeinfo == .@"struct");
std.debug.assert(typeinfo.@"struct".is_tuple);
std.debug.assert(typeinfo.@"struct".fields.len <= @typeInfo(@TypeOf(self.args)).@"struct".fields.len - self.index);
self.callNoExec(args);
if (@typeInfo(@TypeOf(self.args)).@"struct".fields.len == self.index)
return returnType{
._retrieve = Self.retrieve,
._data = self.args,
.value = null,
}
else
return null;
}
pub const atomicErr = error{notEnoughArgs};
pub fn callAtomic(self: Self, args: anytype) atomicErr!returnType {
const typeinfo = @typeInfo(@TypeOf(args));
std.debug.assert(typeinfo == .@"struct");
std.debug.assert(typeinfo.@"struct".is_tuple);
std.debug.assert(typeinfo.@"struct".fields.len == @typeInfo(@TypeOf(self.args)).@"struct".fields.len - self.index);
var tmp = self;
tmp.self = &tmp;
return tmp.call(args) orelse atomicErr.notEnoughArgs;
}
pub fn call_(self: *Self, args: anytype) void {
self.callNoExec(args);
_ = @call(.auto, @"fn", self.args);
}
fn callNoExec(self: *Self, args: anytype) void {
std.log.debug("{any}", .{args});
const nArgs = @typeInfo(@TypeOf(args)).@"struct".fields.len;
const totalFields = @typeInfo(@TypeOf(self.args)).@"struct".fields.len;
switch (self.index) {
inline 0...totalFields - 1 => |base| {
// Comptime: does this base index align with the types of args?
const valid = comptime blk: {
if (base + nArgs > totalFields) break :blk false;
for (0..nArgs) |i| {
if (!coerces(@TypeOf(self.args[base + i]), @TypeOf(args[i])))
break :blk false;
}
break :blk true;
};
// Body is NOT compiled for invalid (base, args-type) pairs
if (valid) {
inline for (0..nArgs) |i| {
self.args[base + i] = args[i];
}
}
},
else => unreachable,
}
self.index += nArgs;
}
index: usize,
args: argStruct,
self: *Self,
fn setArg(self: *Self, val: anytype) void {
return switch (self.index) {
inline 0...@typeInfo(@TypeOf(self.args)).@"struct".fields.len - 1 => |i| self.args[i] = val,
else => unreachable,
};
}
pub const new: Self = .{
.args = undefined,
.index = 0,
.self = undefined,
};
pub fn init(self: *Self, args: anytype) void {
self.self = self;
self.callNoExec(args);
}
};
}