Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement rules for types. #5

Merged
merged 16 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
const std = @import("std");

/// Creates a new test suite, makes a test step, and returns the test step.
pub fn add_tests(b: *std.Build, name: []const u8, desc: []const u8, path: []const u8, target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode) *std.Build.Step {
const new_tests = b.addTest(.{
.root_source_file = .{ .path = path },
.target = target,
.optimize = optimize,
});

const run_new_tests = b.addRunArtifact(new_tests);

const new_tests_step = b.step(name, desc);
new_tests_step.dependOn(&run_new_tests.step);

return new_tests_step;
}

// TODO: Continue splitting the project into multiple sub-modules and implement this function.
// pub fn add_module(b: *std.Build, name: []const u8, test_desc: []const u8, path: []const u8, target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode) *std.Build.Step {
// const new_module = b.addModule // ???
// }

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
Expand All @@ -12,14 +33,8 @@ pub fn build(b: *std.Build) void {
});
b.installArtifact(lib);

const main_tests = b.addTest(.{
.root_source_file = .{ .path = "src/tests.zig" },
.target = target,
.optimize = optimize,
});

const run_main_tests = b.addRunArtifact(main_tests);

const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_main_tests.step);

test_step.dependOn(add_tests(b, "common_tests", "Run the unit tests for the Common library.", "src/common_tests.zig", target, optimize));
test_step.dependOn(add_tests(b, "rule_tests", "Run the unit tests for the Rule type.", "src/rule_tests.zig", target, optimize));
}
278 changes: 278 additions & 0 deletions src/common.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
/// Metaplasia Common Utils
/// ©2023 Cristian Vasilache (NNCV / Nylvon)
///
/// Common functionality shared across the project.
pub const Common = @This();
const std = @import("std");

/// Small container for look-ups.
/// TODO: Refactor using this type.
pub const LookupData = struct {
lookup_name: []const u8,
lookup_mode: LookupMode,
};

/// Restricts look-up to only fields or declarations, or does not restrict at all.
/// If Any is selected, fields are prioritised over declarations in the look-up order.
pub const LookupMode = enum { Field, Declaration, Any };

//
// Convenience wrappers
//

/// Generates lookup info for a field given a name.
pub fn FindField(name: []const u8) LookupData {
return LookupData{
.lookup_name = name,
.lookup_mode = .Field,
};
}

/// Generates lookup info for a declaration given a name.
pub fn FindDeclaration(name: []const u8) LookupData {
return LookupData{
.lookup_name = name,
.lookup_mode = .Declaration,
};
}

/// Generates lookup info for a member given a name.
pub fn FindAny(name: []const u8) LookupData {
return LookupData{
.lookup_name = name,
.lookup_mode = .Any,
};
}

/// Used by FindInType to detail why the function call failed.
pub const LookupError = error{
InvalidType,
NotFound,
TypeHasNoFields,
TypeHasNoDeclarations,
TypeHasNoFieldsOrDeclarations,
};

/// Contains the type that the declaration was gotten from as well as the declaration.
pub const FullDeclaration = struct {
decl: std.builtin.Type.Declaration,
type: type,
is_const: bool,
};

/// Contains the type that the enum field was gotten from as well as the enum field.
pub const FullEnumField = struct {
field: std.builtin.Type.EnumField,
type: type,
};

/// This enum is used to identify the active part of the
/// TypeItem union wrapper.
pub const TypeItemKind = enum {
Declaration,
StructField,
EnumField,
UnionField,
};

/// The mutability of a TypeItem entity.
pub const TypeItemMutability = enum { Variable, Constant };

/// A Type Item is a part of a type, it is a wrapper over
/// fields and declarations of all types.
pub const TypeItem = union(TypeItemKind) {
// NOTE: Should be kept up to date with the language specification.
Declaration: FullDeclaration,
StructField: std.builtin.Type.StructField,
EnumField: FullEnumField,
UnionField: std.builtin.Type.UnionField,

/// The error set that can be returned by the functions of the TypeItem wrapper.
pub const Error = error{
InvalidType,
};

/// Returns the type of the entity described.
pub fn GetType(comptime self: @This()) type {
switch (self) {
.Declaration => |d| return @TypeOf(@field(d.type, d.decl.name)),
.StructField => |sf| return sf.type,
.EnumField => |ef| return ef.type,
.UnionField => |uf| return uf.type,
}
}

/// Returns the name of the entity described.
pub fn GetName(comptime self: @This()) []const u8 {
switch (self) {
.Declaration => |d| return d.decl.name,
.StructField => |sf| return sf.name,
.EnumField => |ef| return ef.field.name,
.UnionField => |uf| return uf.name,
}
}

/// Returns the default value of the entity described.
/// If the entity doesn't have a default value, null will be returned.
/// If the entity cannot have a default value, an error will be returned.
pub fn GetDefaultValue(comptime self: @This()) switch (self) {
.StructField => ?*const anyopaque,
else => TypeItem.Error,
} {
switch (self) {
.StructField => |sf| return sf.default_value,
else => return TypeItem.Error.InvalidType,
}
}

/// Returns whether the entity described has a constant value.
pub fn GetIsConstant(comptime self: @This()) bool {
switch (self) {
.Declaration => |d| return d.is_const,
else => return false,
}
}

/// Returns whether the entity described has a non-constant value.
pub fn GetIsVariable(comptime self: @This()) bool {
return !self.GetIsConstant();
}
};

/// Internal use only. Looks up a field from a type-info struct.
inline fn UnsafeFieldLookup(comptime target: type, comptime lookup_name: []const u8) LookupError!TypeItem {
comptime {
const type_info = switch (@typeInfo(target)) {
.Struct => |s| s,
.Enum => |e| e,
.Union => |u| u,
else => unreachable,
};

if (type_info.fields.len == 0) return LookupError.TypeHasNoFields;

for (type_info.fields) |field| {
if (std.mem.eql(u8, field.name, lookup_name)) {
switch (@typeInfo(target)) {
.Struct => return TypeItem{ .StructField = field },
.Enum => return TypeItem{ .EnumField = FullEnumField{ .field = field, .type = target } },
.Union => return TypeItem{ .UnionField = field },
else => unreachable,
}
}
}

// If we're here, we haven't found it.
return LookupError.NotFound;
}
}

/// Internal use only. Looks up a declaration from a type-info struct.
inline fn UnsafeDeclarationLookup(comptime target: type, comptime lookup_name: []const u8) LookupError!TypeItem {
comptime {
const type_info = switch (@typeInfo(target)) {
.Struct => |s| s,
.Enum => |e| e,
.Union => |u| u,
else => unreachable,
};

if (type_info.decls.len == 0) return LookupError.TypeHasNoDeclarations;

for (type_info.decls) |decl| {
if (std.mem.eql(u8, decl.name, lookup_name))
return TypeItem{ .Declaration = FullDeclaration{
.decl = decl,
.type = target,
.is_const = @typeInfo(@TypeOf(&@field(target, decl.name))).Pointer.is_const,
} };
}

// If we're here, we haven't found it.
return LookupError.NotFound;
}
}

/// Internal use only. Looks up a field or declaraton from a type-info struct.
inline fn UnsafeAnyLookup(comptime target: type, comptime lookup_name: []const u8) LookupError!TypeItem {
comptime {
const type_info = switch (@typeInfo(target)) {
.Struct => |s| s,
.Enum => |e| e,
.Union => |u| u,
else => unreachable,
};

if (type_info.fields.len == 0 and type_info.decls.len == 0) return LookupError.TypeHasNoFieldsOrDeclarations;

for (type_info.fields) |field| {
if (std.mem.eql(u8, field.name, lookup_name)) {
switch (@typeInfo(target)) {
.Struct => return TypeItem{ .StructField = field },
.Enum => return TypeItem{ .EnumField = FullEnumField{ .field = field, .type = target } },
.Union => return TypeItem{ .UnionField = field },
else => @compileError("Please do not use this function on its own, as this function does not do any checks on the inputs!"),
}
}
}

for (type_info.decls) |decl| {
if (std.mem.eql(u8, decl.name, lookup_name))
return TypeItem{ .Declaration = FullDeclaration{
.decl = decl,
.type = target,
.is_const = @typeInfo(@TypeOf(&@field(target, decl.name))).Pointer.is_const,
} };
}

// If we're here, we haven't found it.
return LookupError.NotFound;
}
}

/// Internal use only. Looks up parameters from structs and unions.
inline fn UnsafeLookup(comptime target: type, comptime lookup_name: []const u8, comptime lookup_mode: LookupMode) LookupError!TypeItem {
comptime {
switch (lookup_mode) {
// Search for a field
.Field => {
return UnsafeFieldLookup(target, lookup_name);
},
// Search for a declaration
.Declaration => {
return UnsafeDeclarationLookup(target, lookup_name);
},
// Search for either
.Any => {
return UnsafeAnyLookup(target, lookup_name);
},
}
}
}

/// Tries to find a field or declaration given a name and a specified look-up mode.
/// Returns the type of the field/declaration if found, otherwise errors with details.
pub fn FindInType(comptime from: type, comptime lookup_name: []const u8, comptime lookup_mode: LookupMode) LookupError!TypeItem {
switch (@typeInfo(from)) {
// Structs can have both fields and declarations
.Struct, .Union, .Enum => {
return UnsafeLookup(from, lookup_name, lookup_mode);
},
.Pointer, .Vector, .Optional, .Array, .ErrorUnion => {
return FindInType(GetBaseType(from), lookup_name, lookup_mode);
},
else => return LookupError.InvalidType,
}
}

/// Gets the base type of any pointer/vector/array/etc type.
pub fn GetBaseType(comptime from: type) type {
switch (@typeInfo(from)) {
.Pointer => |ptr| return GetBaseType(ptr.child),
.Vector => |vec| return GetBaseType(vec.child),
.Optional => |opt| return GetBaseType(opt.child),
.Array => |arr| return GetBaseType(arr.child),
.ErrorUnion => |eru| return GetBaseType(eru.payload),
else => return from,
}
}
Loading