/// offset_of returns the byte offset of a field within the type T. /// /// Note: T must be a struct type. /// /// MyType :: struct { x: int; y: int; z: int; }; /// offset_of(MyType, #code y); // 8 /// offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand { #run (loc: Source_Code_Location) { if !TypeIsStruct(T) { CompileError("jc: offset_of can only be used on struct types", loc = loc); } }(loc); return #run -> int { t: T = ---; return (*t.#insert ident).(*void) - (*t).(*void); }; } /// offset_of returns the byte offset of a field within the type of value. /// /// Note: If offset_of is given a pointer value, it will use the type pointed to. /// /// value := struct{ x: int; y: int; z: int; }.{}; /// offset_of(value, #code y); // 8 /// offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand { type :: #run -> Type { info := T.(*Type_Info); ok, pinfo := TypeIsPointer(T); if ok { // @question(judah): do we want it to traverse all the way up to a non-pointer type? // I opted against because if you have a *T, you only want offset_of to get an offset // from that pointer. What would you do with a field offset from **T? if TypeIsPointer(pinfo.pointer_to) { CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc); } info = pinfo.pointer_to; } return get_type(info); }; return offset_of(type, ident, loc = loc); } /// align_of returns the alignment of type T. align_of :: ($T: Type) -> int #expand { return #run -> int { if size_of(T) == 0 { return 0; } return offset_of(struct{ _: u8; t: T; }, #code t); }; } /// default_of returns a value of type T as if it was just instantiated. /// /// Note: default_of will call the initializer for aggregate types, so you /// may want zero_of instead. default_of :: ($T: Type) -> T #expand { default: T; return default; } /// undefined_of returns a value of type T that has not been initialized. undefined_of :: ($T: Type) -> T #expand { uninit: T = ---; return uninit; } /// zero_of returns a value of type T that has been zero-initialized. /// /// Note: zero_of will not call the initializer for aggregate types, so you /// may want default_of instead. zero_of :: ($T: Type) -> T #expand { zero := undefined_of(T); MemZero(*zero); return zero; } /// min_of returns the minimum value T can represent. /// /// Note: T must be an integer, float, or enum type. min_of :: ($T: Type, loc := #caller_location) -> T #expand { return #run -> T { info := T.(*Type_Info); if info.type == { case .INTEGER; i := info.(*Type_Info_Integer); if i.runtime_size == { case 1; return (ifx i.signed then -0x80 else 0).(T, no_check); case 2; return (ifx i.signed then -0x8000 else 0).(T, no_check); case 4; return (ifx i.signed then -0x8000_0000 else 0).(T, no_check); case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check); case ; CompileError("jc: unknown integer size", loc = loc); } case .FLOAT; if info.runtime_size == { case 4; return (0h0080_0000).(T, no_check); case 8; return (0h00100000_00000000).(T, no_check); case ; CompileError("jc: unknown float size", loc = loc); } case .ENUM; i := info.(*Type_Info_Enum); if i.values.count == 0 { return 0; } min: T = i.values[0].(T, no_check); if i.internal_type.signed { for i.values if it.(T) < min { min = it.(T); } } else { for i.values if it.(T) < min { min = it.(T); } } return min; case; CompileError("jc: min_of requires an enum, integer, or float type", loc = loc); } return 0; }; } /// max_of returns the maximum value T can represent. /// /// Note: T must be an integer, float, or enum type. max_of :: ($T: Type, loc := #caller_location) -> T #expand { return #run -> T { info := T.(*Type_Info); if info.type == { case .INTEGER; i := info.(*Type_Info_Integer); if i.runtime_size == { case 1; return (ifx i.signed then 0x7f else 0xff).(T, no_check); case 2; return (ifx i.signed then 0x7fff else 0xffff).(T, no_check); case 4; return (ifx i.signed then 0x7fff_ffff else 0xffff_ffff).(T, no_check); case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check); case ; CompileError("jc: unknown integer size", loc = loc); } case .FLOAT; if info.runtime_size == { case 4; return (0h7F7FFFFF).(T, no_check); case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check); case ; CompileError("jc: unknown float size", loc = loc); } case .ENUM; i := info.(*Type_Info_Enum); if i.values.count == 0 { return 0; } max := i.values[0].(T, no_check); if i.internal_type.signed { for i.values if xx it > max { max = xx it; } } else { for i.values if xx it > max { max = xx it; } } return max; case; CompileError("jc: max_of requires an enum, integer, or float type", loc = loc); } return 0; }; } /// range_of returns the minimum and maximum values T can represent. /// /// Note: T must be an integer, float, or enum type. range_of :: ($T: Type, loc := #caller_location) -> (T, T) #expand { return min_of(T, loc = loc), max_of(T, loc = loc); } #scope_file #if RunTests #run { Test("min_of/max_of:enums", t => { U8Enum :: enum u8 { lo :: -1; hi :: +1; } S8Enum :: enum s8 { lo :: -2; hi :: -1; } { Expect(min_of(U8Enum) == U8Enum.lo); Expect(min_of(S8Enum) == S8Enum.lo); Expect(max_of(U8Enum) == U8Enum.hi); Expect(max_of(S8Enum) == S8Enum.hi); } U16Enum :: enum u16 { lo :: -1; hi :: +1; } S16Enum :: enum s16 { lo :: -2; hi :: -1; } { Expect(min_of(U16Enum) == U16Enum.lo); Expect(min_of(S16Enum) == S16Enum.lo); Expect(max_of(U16Enum) == U16Enum.hi); Expect(max_of(S16Enum) == S16Enum.hi); } U32Enum :: enum u32 { lo :: -1; hi :: +1; } S32Enum :: enum s32 { lo :: -2; hi :: -1; } { Expect(min_of(U32Enum) == U32Enum.lo); Expect(min_of(S32Enum) == S32Enum.lo); Expect(max_of(U32Enum) == U32Enum.hi); Expect(max_of(S32Enum) == S32Enum.hi); } U64Enum :: enum u64 { lo :: -1; hi :: +1; } S64Enum :: enum s64 { lo :: -2; hi :: -1; } { Expect(min_of(U64Enum) == U64Enum.lo); Expect(min_of(S64Enum) == S64Enum.lo); Expect(max_of(U64Enum) == U64Enum.hi); Expect(max_of(S64Enum) == S64Enum.hi); } // @note(judah): just making sure this compiles lo, hi := range_of(U64Enum); Expect(lo == U64Enum.lo); Expect(hi == U64Enum.hi); }); }