Skip to content

Commit

Permalink
Introduced proper arrays (#71)
Browse files Browse the repository at this point in the history
Previously a Scripty expression would immediately evaluate into an iterator, instead of doing expr -> array -> iterator, which made it awkward to implement things like sorting.

The new implementation creates an array of scripty values always, which can then be turned into an array when passed to :loop.

The new Array type and its builtins are implemented in src/context/Array.zig.
We currently don't have many builtins for arrays and crucially we don't have yet sorting because it requires thinking though how to expose the functionality to the user.

That said, builtins for arrays are welcome if anybody is blocked on that.
  • Loading branch information
kristoff-it authored Oct 11, 2024
1 parent 1d66842 commit f9af143
Show file tree
Hide file tree
Showing 16 changed files with 316 additions and 1,020 deletions.
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
.hash = "122014e78d7c69d93595993b3231f3141368e22634b332b0b91a2fb73a8570f147a5",
},
.superhtml = .{
.url = "git+https://github.com/kristoff-it/superhtml#4bcd4944817ae53870da8230d0ea6ed2704476ff",
.hash = "1220a146e2b5a4bc40a6e44a1a8f71ca7e6de49f92ffe6b44da3a211f1a5db5963b9",
.url = "git+https://github.com/kristoff-it/superhtml#36f37aa5aa440805f27d4a9f5203e616a303c6a1",
.hash = "1220dcb13f7d63fdb2d60f528de211e5c6284010a8ca37542b8ef9fe6d77f990cb3d",
},
.ziggy = .{
.url = "git+https://github.com/kristoff-it/ziggy#c66f47bc632c66668d61fa06eda112b41d6e5130",
Expand Down
33 changes: 15 additions & 18 deletions src/context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub const Map = @import("context/Map.zig");
// pub const Slice = @import("context/Slice.zig");
pub const Optional = @import("context/Optional.zig");
pub const Iterator = @import("context/Iterator.zig");
pub const Array = @import("context/Array.zig");

pub const Value = union(enum) {
template: *const Template,
Expand All @@ -93,6 +94,7 @@ pub const Value = union(enum) {
int: Int,
float: Float,
iterator: *context.Iterator,
array: Array,
map_kv: Map.KV,
err: []const u8,

Expand Down Expand Up @@ -145,9 +147,10 @@ pub const Value = union(enum) {
.integer => |i| return .{ .int = .{ .value = i } },
.bytes => |s| return .{ .string = .{ .value = s } },
.array => |a| return .{
.iterator = try context.Iterator.init(gpa, .{
.dynamic_it = .{ .items = a },
}),
.iterator = try Value.Iterator.fromArray(
gpa,
(try Array.init(gpa, ziggy.dynamic.Value, a)).array,
),
},
.tag => |t| {
std.debug.assert(std.mem.eql(u8, t.name, "date"));
Expand Down Expand Up @@ -197,22 +200,16 @@ pub const Value = union(enum) {
.iterator = opt,
} else .{ .err = "$loop is not set" },
*context.Iterator => .{ .iterator = v },
[]const []const u8 => .{
.iterator = try context.Iterator.init(gpa, .{
.string_it = .{ .items = v },
}),
},
[]const []const u8 => try Array.init(gpa, []const u8, v),

[]const Page.Alternative => .{
.iterator = try context.Iterator.init(gpa, .{
.alt_it = .{ .items = v },
}),
},
[]Page.ContentSection => .{
.iterator = try context.Iterator.init(gpa, .{
.content_it = .{ .items = v },
}),
},
// .{
// .iterator = try context.Iterator.init(gpa, .{
// .string_it = .{ .items = v },
// }),
// },

[]const Page.Alternative => try Array.init(gpa, Page.Alternative, v),
[]Page.ContentSection => try Array.init(gpa, Page.ContentSection, v),
else => @compileError("TODO: implement Value.from for " ++ @typeName(@TypeOf(v))),
};
}
Expand Down
170 changes: 170 additions & 0 deletions src/context/Array.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
const Array = @This();

const std = @import("std");
const ziggy = @import("ziggy");
const superhtml = @import("superhtml");
const scripty = @import("scripty");
const context = @import("../context.zig");
const doctypes = @import("doctypes.zig");
const Signature = doctypes.Signature;
const Allocator = std.mem.Allocator;
const Value = context.Value;
const Template = context.Template;
const Site = context.Site;
const Page = context.Page;
const Map = context.Map;

len: usize,
empty: bool,
_items: []const Value,

pub fn init(gpa: Allocator, T: type, items: []const T) error{OutOfMemory}!Value {
if (T == Value) return .{
.array = .{
.len = items.len,
.empty = items.len == 0,
._items = items,
},
};

const boxed_items = try gpa.alloc(Value, items.len);

for (items, boxed_items) |i, *bi| {
bi.* = try Value.from(gpa, i);
}

return .{
.array = .{
.len = items.len,
.empty = items.len == 0,
._items = boxed_items,
},
};
}

// pub fn deinit(iter: *const Iterator, gpa: Allocator) void {
// gpa.destroy(iter);
// }

pub const dot = scripty.defaultDot(Array, Value, false);
pub const description = "An array of items.";
pub const Fields = struct {
pub const len =
\\The length of the array.
;
pub const empty =
\\True when len is 0.
;
};

pub const Builtins = struct {
pub const slice = struct {
pub const signature: Signature = .{
.params = &.{ .Int, .{ .Opt = .Int } },
.ret = .{ .Many = .any },
};
pub const description =
\\Slices an array from the first value (inclusive) to the
\\second value (exclusive).
\\
\\The second value can be omitted and defaults to the array's
\\length, meaning that invoking `slice` with one argunent
\\produces **suffixes** of the original sequence (i.e. it
\\removes a prefix from the original sequence).
\\
\\Note that negative values are not allowed at the moment.
;
pub const examples =
\\$page.tags.slice(0,1)
;
pub fn call(
arr: Array,
gpa: Allocator,
args: []const Value,
) !Value {
const bad_arg = .{ .err = "expected 1 or 2 integer argument(s)" };
if (args.len < 1 or args.len > 2) return bad_arg;

const start = switch (args[0]) {
.int => |i| i.value,
else => return bad_arg,
};
const end: i64 = if (args.len == 1) @intCast(arr.len) else switch (args[1]) {
.int => |i| i.value,
else => return bad_arg,
};

if (start < 0) return Value.errFmt(
gpa,
"start value {} is negative",
.{start},
);

if (end < 0) return Value.errFmt(
gpa,
"end value {} is negative",
.{end},
);

if (start >= arr.len) return Value.errFmt(gpa, "start value {} exceeds array of length {}", .{
start, arr.len,
});

if (end > arr.len) return Value.errFmt(gpa, "end value {} exceeds array of length {}", .{
end, arr.len,
});

if (start > end) return Value.errFmt(gpa, "start value {} is bigger than end value {}!", .{
start, end,
});

const new = arr._items[@intCast(start)..@intCast(end)];

return .{
.array = .{
.len = new.len,
.empty = new.len == 0,
._items = new,
},
};
}
};

pub const at = struct {
pub const signature: Signature = .{
.params = &.{.Int},
.ret = .any,
};
pub const description =
\\Returns the value at the provided index.
;
pub const examples =
\\$page.tags.at(0)
;
pub fn call(
arr: Array,
gpa: Allocator,
args: []const Value,
) !Value {
const bad_arg = .{ .err = "expected 1 integer argument" };
if (args.len != 1) return bad_arg;

const idx = switch (args[0]) {
.int => |i| i.value,
else => return bad_arg,
};

if (idx < 0) return Value.errFmt(
gpa,
"index value {} is negative",
.{idx},
);

if (idx >= arr.len) return Value.errFmt(gpa, "index {} exceeds array of length {}", .{
idx, arr.len,
});

return arr._items[@intCast(idx)];
}
};
};
Loading

0 comments on commit f9af143

Please sign in to comment.