Skip to content

Commit

Permalink
Origins + more test suite passing (#7)
Browse files Browse the repository at this point in the history
# Description

This PR gets all the testsuite tests passing.

However, that is currently using an arena allocator, and if we instead
use a GPA we find that we're leaking memory. Need to fix this (and in
doing so figure out a good lifetime model)
  • Loading branch information
malcolmstill authored Mar 29, 2024
1 parent 227fbd8 commit 432d4b6
Show file tree
Hide file tree
Showing 75 changed files with 4,729 additions and 555 deletions.
12 changes: 12 additions & 0 deletions TOUR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Tour

`biscuit-zig` is split into a number of modules. The following description should provide helpful orientation:

- `biscuit-schema`
- Contains the `schema.proto` from the official biscuit repo
- Contains `schema.pb.zig` which is generated from `schema.proto` using https://github.com/Arwalk/zig-protobuf. This powers the biscuit deserialization.
- `biscuit-format`
- Provides an intermediate `SerializedBiscuit` type that deserializes the protobuf format and verifies biscuit.
- `biscuit`
- Provides runtime representation of biscuit, this is the main interface a consumer of the library will use.
- A `Biscuit` can be initialized
1 change: 1 addition & 0 deletions biscuit-builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# biscuit-parser
48 changes: 48 additions & 0 deletions biscuit-builder/build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const std = @import("std");

// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});

// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});

const schema = b.dependency("biscuit-schema", .{ .target = target, .optimize = optimize });
const format = b.dependency("biscuit-format", .{ .target = target, .optimize = optimize });
const datalog = b.dependency("biscuit-datalog", .{ .target = target, .optimize = optimize });

_ = b.addModule("biscuit-builder", .{
.root_source_file = .{ .path = "src/root.zig" },
.imports = &.{
.{ .name = "biscuit-schema", .module = schema.module("biscuit-schema") },
.{ .name = "biscuit-format", .module = format.module("biscuit-format") },
.{ .name = "biscuit-datalog", .module = datalog.module("biscuit-datalog") },
},
});

// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
lib_unit_tests.root_module.addImport("biscuit-schema", schema.module("biscuit-schema"));
lib_unit_tests.root_module.addImport("biscuit-format", format.module("biscuit-format"));

const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
}
65 changes: 65 additions & 0 deletions biscuit-builder/build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
.{
.name = "biscuit-builder",
// 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",

// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
//.minimum_zig_version = "0.11.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.
// .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",
//},
.@"biscuit-schema" = .{ .path = "../biscuit-schema" },
.@"biscuit-format" = .{ .path = "../biscuit-format" },
.@"biscuit-datalog" = .{ .path = "../biscuit-datalog" },
},

// 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.
// 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 = .{
// This makes *all* files, recursively, included in this package. It is generally
// better to explicitly list the files and directories instead, to insure that
// fetching from tarballs, file system paths, and version control all result
// in the same contents hash.
"",
// For example...
//"build.zig",
//"build.zig.zon",
//"src",
//"LICENSE",
//"README.md",
},
}
41 changes: 41 additions & 0 deletions biscuit-builder/src/check.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const std = @import("std");
const datalog = @import("biscuit-datalog");
const Predicate = @import("predicate.zig").Predicate;
const Term = @import("term.zig").Term;
const Rule = @import("rule.zig").Rule;

pub const Check = struct {
kind: datalog.Check.Kind,
queries: std.ArrayList(Rule),

pub fn deinit(check: Check) void {
for (check.queries.items) |query| {
query.deinit();
}

check.queries.deinit();
}

pub fn convert(check: Check, allocator: std.mem.Allocator, symbols: *datalog.SymbolTable) !datalog.Check {
var queries = std.ArrayList(datalog.Rule).init(allocator);

for (check.queries.items) |query| {
try queries.append(try query.convert(allocator, symbols));
}

return .{ .kind = check.kind, .queries = queries };
}

pub fn format(check: Check, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
try writer.print("check ", .{});

switch (check.kind) {
.one => try writer.print("if", .{}),
.all => try writer.print("all", .{}),
}

for (check.queries.items) |query| {
try writer.print(" {any}", .{query});
}
}
};
171 changes: 171 additions & 0 deletions biscuit-builder/src/date.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
const std = @import("std");

pub const Date = struct {
year: i32,
month: u8,
day: u8,
hour: u8,
minute: u8,
second: u8,
nanosecond: u32,
// Timezone offset in minutes from UTC; can be negative
utc_offset: i32,

pub fn eql(left: Date, right: Date) bool {
return left.year == right.year and
left.month == right.month and
left.day == right.day and
left.hour == right.hour and
left.minute == right.minute and
left.second == right.second and
left.nanosecond == right.nanosecond;
}

pub fn lt(left: Date, right: Date) bool {
if (left.year < right.year) return true;
if (left.year > right.year) return false;

std.debug.assert(left.year == right.year);

if (left.month < right.month) return true;
if (left.month > right.month) return false;

std.debug.assert(left.month == right.month);

if (left.day < right.day) return true;
if (left.day > right.day) return false;

std.debug.assert(left.day == right.day);

if (left.hour < right.hour) return true;
if (left.hour > right.hour) return false;

std.debug.assert(left.hour == right.hour);

if (left.minute < right.minute) return true;
if (left.minute > right.minute) return false;

std.debug.assert(left.minute == right.minute);

if (left.second < right.second) return true;
if (left.second > right.second) return false;

std.debug.assert(left.second == right.second);

if (left.nanosecond < right.nanosecond) return true;
if (left.nanosecond > right.nanosecond) return false;

std.debug.assert(left.nanosecond == right.nanosecond);

return false;
}

pub fn gt(left: Date, right: Date) bool {
if (left.year > right.year) return true;
if (left.year < right.year) return false;

std.debug.assert(left.year == right.year);

if (left.month > right.month) return true;
if (left.month < right.month) return false;

std.debug.assert(left.month == right.month);

if (left.day > right.day) return true;
if (left.day < right.day) return false;

std.debug.assert(left.day == right.day);

if (left.hour > right.hour) return true;
if (left.hour < right.hour) return false;

std.debug.assert(left.hour == right.hour);

if (left.minute > right.minute) return true;
if (left.minute < right.minute) return false;

std.debug.assert(left.minute == right.minute);

if (left.second > right.second) return true;
if (left.second < right.second) return false;

std.debug.assert(left.second == right.second);

if (left.nanosecond > right.nanosecond) return true;
if (left.nanosecond < right.nanosecond) return false;

std.debug.assert(left.nanosecond == right.nanosecond);

return false;
}

pub fn lteq(left: Date, right: Date) bool {
return left.eq(right) or left.lt(right);
}

pub fn gteq(left: Date, right: Date) bool {
return left.eq(right) or left.gt(right);
}

pub fn format(date: Date, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
return writer.print("{}-{}-{}T{}:{}:{}Z", .{ date.year, date.month, date.day, date.hour, date.minute, date.second });
}

// FIXME: leap seconds?
pub fn unixEpoch(date: Date) u64 {
var total_days: usize = 0;

const date_year = @as(usize, @intCast(date.year));

for (1970..date_year) |year| {
if (isLeapYear(usize, year)) {
total_days += 366;
} else {
total_days += 365;
}
}

for (1..date.month) |month| {
total_days += daysInMonth(usize, date_year, @as(u8, @intCast(month)));
}

for (1..date.day) |_| {
total_days += 1;
}

var total_seconds: u64 = total_days * 24 * 60 * 60;

total_seconds += @as(u64, date.hour) * 60 * 60;
total_seconds += @as(u64, date.minute) * 60;
total_seconds += date.second;

return total_seconds;
}

pub fn isDayMonthYearValid(comptime T: type, year: T, month: u8, day: u8) bool {
return switch (month) {
// 30 days has september, april june and november
4, 6, 9, 11 => day <= 30,
1, 3, 5, 7, 8, 10, 12 => day <= 31,
2 => if (isLeapYear(T, year)) day <= 29 else day <= 28,
else => false,
};
}
};

pub fn daysInMonth(comptime T: type, year: T, month: u8) u8 {
return switch (month) {
4, 6, 9, 11 => 30,
1, 3, 5, 7, 8, 10, 12 => 31,
2 => if (isLeapYear(T, year)) 29 else 28,
else => unreachable,
};
}

fn isLeapYear(comptime T: type, year: T) bool {
if (@mod(year, 400) == 0) return true;
if (@mod(year, 100) == 0) return false;
if (@mod(year, 4) == 0) return true;

return false;
}
Loading

0 comments on commit 432d4b6

Please sign in to comment.