so... jai :D
This commit is contained in:
323
src/types.zig
Normal file
323
src/types.zig
Normal file
@@ -0,0 +1,323 @@
|
||||
const std = @import("std");
|
||||
const ast = @import("ast.zig");
|
||||
const Node = ast.Node;
|
||||
|
||||
pub const Type = union(enum) {
|
||||
// Variable-width integers (1–64 bits)
|
||||
signed: u8,
|
||||
unsigned: u8,
|
||||
// Fixed-width floats
|
||||
f32,
|
||||
f64,
|
||||
// Other
|
||||
void_type,
|
||||
boolean,
|
||||
string_type,
|
||||
enum_type: []const u8,
|
||||
struct_type: []const u8,
|
||||
union_type: []const u8,
|
||||
array_type: ArrayTypeInfo,
|
||||
slice_type: SliceTypeInfo,
|
||||
vector_type: VectorTypeInfo,
|
||||
any_type,
|
||||
meta_type: MetaTypeInfo,
|
||||
|
||||
pub const SliceTypeInfo = struct {
|
||||
element_name: []const u8,
|
||||
};
|
||||
|
||||
pub const ArrayTypeInfo = struct {
|
||||
element_name: []const u8,
|
||||
length: u32,
|
||||
};
|
||||
|
||||
pub const VectorTypeInfo = struct {
|
||||
element_name: []const u8,
|
||||
length: u32,
|
||||
};
|
||||
|
||||
pub const MetaTypeInfo = struct {
|
||||
name: []const u8,
|
||||
};
|
||||
|
||||
// Convenience constructors
|
||||
pub fn s(width: u8) Type {
|
||||
return .{ .signed = width };
|
||||
}
|
||||
|
||||
pub fn u(width: u8) Type {
|
||||
return .{ .unsigned = width };
|
||||
}
|
||||
|
||||
pub fn fromName(name: []const u8) ?Type {
|
||||
// Named types (check before variable-width integers since "string" starts with 's')
|
||||
if (std.mem.eql(u8, name, "string")) return .string_type;
|
||||
if (std.mem.eql(u8, name, "bool")) return .boolean;
|
||||
if (std.mem.eql(u8, name, "f32")) return .f32;
|
||||
if (std.mem.eql(u8, name, "f64")) return .f64;
|
||||
if (std.mem.eql(u8, name, "Any")) return .any_type;
|
||||
// Variable-width integers: s1..s64, u1..u64
|
||||
if (name.len >= 2 and (name[0] == 's' or name[0] == 'u')) {
|
||||
const width = std.fmt.parseInt(u8, name[1..], 10) catch return null;
|
||||
if (width < 1 or width > 64) return null;
|
||||
return if (name[0] == 's') Type.s(width) else Type.u(width);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn fromTypeExpr(node: *Node) ?Type {
|
||||
if (node.data != .type_expr) return null;
|
||||
return fromName(node.data.type_expr.name);
|
||||
}
|
||||
|
||||
pub fn isEnum(self: Type) bool {
|
||||
return switch (self) {
|
||||
.enum_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isStruct(self: Type) bool {
|
||||
return switch (self) {
|
||||
.struct_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isUnion(self: Type) bool {
|
||||
return switch (self) {
|
||||
.union_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isAny(self: Type) bool {
|
||||
return switch (self) {
|
||||
.any_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isSlice(self: Type) bool {
|
||||
return switch (self) {
|
||||
.slice_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sliceElementType(self: Type) ?Type {
|
||||
return switch (self) {
|
||||
.slice_type => |info| fromName(info.element_name),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isArray(self: Type) bool {
|
||||
return switch (self) {
|
||||
.array_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isVector(self: Type) bool {
|
||||
return switch (self) {
|
||||
.vector_type => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn vectorElementType(self: Type) ?Type {
|
||||
return switch (self) {
|
||||
.vector_type => |info| fromName(info.element_name),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isFloat(self: Type) bool {
|
||||
return switch (self) {
|
||||
.f32, .f64 => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isInt(self: Type) bool {
|
||||
return self.isSigned() or self.isUnsigned();
|
||||
}
|
||||
|
||||
pub fn isSigned(self: Type) bool {
|
||||
return switch (self) {
|
||||
.signed => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isUnsigned(self: Type) bool {
|
||||
return switch (self) {
|
||||
.unsigned => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn bitWidth(self: Type) u32 {
|
||||
return switch (self) {
|
||||
.signed => |w| w,
|
||||
.unsigned => |w| w,
|
||||
.f32 => 32,
|
||||
.f64 => 64,
|
||||
.boolean => 1,
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
/// Check if this type can be implicitly converted to `target` without `xx`.
|
||||
/// Safe (implicit) conversions:
|
||||
/// - Same type
|
||||
/// - Both unsigned int, target width >= source width
|
||||
/// - Both signed int, target width >= source width
|
||||
/// - Unsigned to signed, target width strictly > source width
|
||||
/// - Any int to any float
|
||||
/// - Float to wider float (f32 → f64)
|
||||
/// Everything else requires `xx`.
|
||||
pub fn isImplicitlyConvertibleTo(self: Type, target: Type) bool {
|
||||
if (std.meta.eql(self, target)) return true;
|
||||
|
||||
const src_float = self.isFloat();
|
||||
const dst_float = target.isFloat();
|
||||
const src_int = self.isInt();
|
||||
|
||||
// Float → wider float
|
||||
if (src_float and dst_float) {
|
||||
return target.bitWidth() >= self.bitWidth();
|
||||
}
|
||||
|
||||
// Int → float (always safe)
|
||||
if (src_int and dst_float) return true;
|
||||
|
||||
// Both unsigned → target width >= source width
|
||||
if (self.isUnsigned() and target.isUnsigned()) {
|
||||
return target.bitWidth() >= self.bitWidth();
|
||||
}
|
||||
|
||||
// Both signed → target width >= source width
|
||||
if (self.isSigned() and target.isSigned()) {
|
||||
return target.bitWidth() >= self.bitWidth();
|
||||
}
|
||||
|
||||
// Unsigned → signed: target must be strictly wider
|
||||
if (self.isUnsigned() and target.isSigned()) {
|
||||
return target.bitWidth() > self.bitWidth();
|
||||
}
|
||||
|
||||
// Everything else requires xx
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Format type name for mangling and display (e.g. "s32", "u8", "f64")
|
||||
pub fn displayName(self: Type, allocator: std.mem.Allocator) ![]const u8 {
|
||||
return switch (self) {
|
||||
.signed => |w| {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
try buf.append(allocator, 's');
|
||||
var tmp: [4]u8 = undefined;
|
||||
const width_str = std.fmt.bufPrint(&tmp, "{d}", .{w}) catch unreachable;
|
||||
try buf.appendSlice(allocator, width_str);
|
||||
return try buf.toOwnedSlice(allocator);
|
||||
},
|
||||
.unsigned => |w| {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
try buf.append(allocator, 'u');
|
||||
var tmp: [4]u8 = undefined;
|
||||
const width_str = std.fmt.bufPrint(&tmp, "{d}", .{w}) catch unreachable;
|
||||
try buf.appendSlice(allocator, width_str);
|
||||
return try buf.toOwnedSlice(allocator);
|
||||
},
|
||||
.f32 => "f32",
|
||||
.f64 => "f64",
|
||||
.boolean => "bool",
|
||||
.string_type => "string",
|
||||
.void_type => "void",
|
||||
.any_type => "Any",
|
||||
.enum_type => |name| name,
|
||||
.struct_type => |name| name,
|
||||
.union_type => |name| name,
|
||||
.slice_type => |info| {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
try buf.appendSlice(allocator, "[]");
|
||||
try buf.appendSlice(allocator, info.element_name);
|
||||
return try buf.toOwnedSlice(allocator);
|
||||
},
|
||||
.array_type => |info| {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
try buf.append(allocator, '[');
|
||||
var tmp: [10]u8 = undefined;
|
||||
const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable;
|
||||
try buf.appendSlice(allocator, len_str);
|
||||
try buf.append(allocator, ']');
|
||||
try buf.appendSlice(allocator, info.element_name);
|
||||
return try buf.toOwnedSlice(allocator);
|
||||
},
|
||||
.vector_type => |info| {
|
||||
var buf = std.ArrayList(u8).empty;
|
||||
try buf.appendSlice(allocator, "Vector(");
|
||||
var tmp: [10]u8 = undefined;
|
||||
const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable;
|
||||
try buf.appendSlice(allocator, len_str);
|
||||
try buf.appendSlice(allocator, ",");
|
||||
try buf.appendSlice(allocator, info.element_name);
|
||||
try buf.append(allocator, ')');
|
||||
return try buf.toOwnedSlice(allocator);
|
||||
},
|
||||
.meta_type => |info| info.name,
|
||||
};
|
||||
}
|
||||
|
||||
/// Widen two types to a common type for binary operations.
|
||||
/// Used for arithmetic type promotion (e.g., s16 + s32 → s32, int + float → float).
|
||||
pub fn widen(a: Type, b: Type) Type {
|
||||
// Same type → return it
|
||||
if (std.meta.eql(a, b)) return a;
|
||||
|
||||
// Vector + vector of same dimensions → return a
|
||||
if (a.isVector() and b.isVector()) return a;
|
||||
// Vector + scalar → return vector (scalar will be broadcast)
|
||||
if (a.isVector() and !b.isVector()) return a;
|
||||
if (b.isVector() and !a.isVector()) return b;
|
||||
|
||||
const a_float = a.isFloat();
|
||||
const b_float = b.isFloat();
|
||||
const a_int = a.isInt();
|
||||
const b_int = b.isInt();
|
||||
|
||||
// Both float → wider float
|
||||
if (a_float and b_float) {
|
||||
return if (a.bitWidth() >= b.bitWidth()) a else b;
|
||||
}
|
||||
|
||||
// int + float → float
|
||||
if (a_int and b_float) return b;
|
||||
if (b_int and a_float) return a;
|
||||
|
||||
// Both signed → wider signed
|
||||
if (a.isSigned() and b.isSigned()) {
|
||||
return Type.s(@intCast(@max(a.bitWidth(), b.bitWidth())));
|
||||
}
|
||||
|
||||
// Both unsigned → wider unsigned
|
||||
if (a.isUnsigned() and b.isUnsigned()) {
|
||||
return Type.u(@intCast(@max(a.bitWidth(), b.bitWidth())));
|
||||
}
|
||||
|
||||
// signed + unsigned (mixed)
|
||||
if (a_int and b_int) {
|
||||
const aw = a.bitWidth();
|
||||
const bw = b.bitWidth();
|
||||
const max_w = @max(aw, bw);
|
||||
// If same width, need one extra bit for sign; otherwise max is enough
|
||||
const need: u32 = if (aw == bw) max_w + 1 else max_w;
|
||||
const capped: u8 = @intCast(@min(need, 128));
|
||||
return Type.s(capped);
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user