mcap/modules/Basic/Print.jai

1310 lines
43 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#scope_file
USE_SIMD :: (CPU == .X64);
SIMD_WIDTH :: 16; // Bytes.
#scope_export
Formatter :: struct {
value : Any;
}
FormatInt :: struct {
using #as formatter : Formatter;
base := 10;
minimum_digits := 1;
padding : u8 = #char "0"; // What character to use to pad the input if it’s less than minimum_digits long
digits_per_comma : u16 = 0; // 0 means don't draw commas.
comma_string := ""; // What to draw between the digits.
}
FormatFloat :: struct {
#as using formatter : Formatter;
width := -1;
trailing_width := -1;
Zero_Removal :: enum { YES; NO; ONE_ZERO_AFTER_DECIMAL; };
zero_removal := Zero_Removal.YES; // This will also remove the decimal if it's the last character.
Mode :: enum { DECIMAL; SCIENTIFIC; SHORTEST; };
mode := Mode.DECIMAL;
// @Cleanup: We should make the thousand_separators work the same way
// as comma_string in integers, because why would it be different?
// Also, possibly these should be utf32 characters? I have a bit of
// reticence about making them strings, since it's just a lot of strings
// to put into Context.
thousand_separators := false;
thousand_separator_character : u8 = #char ",";
decimal_separator_character : u8 = #char ".";
exponent_character : u8 = #char "e";
Positive_Number_Prefix :: enum { NONE; PLUS; SPACE; };
positive_number_prefix := Positive_Number_Prefix.NONE;
}
FormatStruct :: struct {
#as using formatter : Formatter;
draw_type_name := false;
// "short form" means don't draw names, just draw values with separators. Example: {1, 3, 5}
// "long form" means draw name, then separator_between_name_and_value, then value. Example: {x = 1; y = 3; z = 5;}
use_long_form_if_more_than_this_many_members := 5; // Set this to -1 to us long form always.
separator_between_name_and_value := " = ";
short_form_separator_between_fields := ", ";
long_form_separator_between_fields := "; "; // You could put a newline in here, for example.
begin_string := "{";
end_string := "}";
indentation_width : s32 = 4; // Used when outputting structs in long form if newlines are enabled.
use_newlines_if_long_form := false;
}
FormatArray :: struct {
#as using formatter : Formatter;
separator := ", ";
begin_string := "[";
end_string := "]";
printing_stopped_early_string := "...";
draw_separator_after_last_element := false;
stop_printing_after_this_many_elements := 100; // Don't spam by default. If you set this to -1, it means don't ever stop printing.
}
Print_Style :: struct {
// default_format_* ignore the Formatter part of each type.
// They are just here so you can set the configuration parameters
// like base, width, etc.
// Maybe we should clean this up and make them two separate things,
// but, this way of configuring printing is experimental for now.
// If it sticks, we'll work on it more. -jblow, 4 October 2020
default_format_int: FormatInt;
default_format_float: FormatFloat;
default_format_struct: FormatStruct;
default_format_array: FormatArray;
default_format_absolute_pointer: FormatInt;
default_format_absolute_pointer.base = 16;
default_format_absolute_pointer.digits_per_comma = 4;
default_format_absolute_pointer.comma_string = "_";
struct_printer: (builder: *String_Builder, any: Any, struct_printer_data: *void) -> bool; // Return true if it's handled; false if Print should engage its default print routine.
struct_printer_data: *void;
// This is a worker variable used by Print. You probably don't
// want to change it!
indentation_depth: s32;
state_flags: enum u32 {
INSIDE_DATA_STRUCTURE_PRINTING :: 0x1;
}
log_runtime_errors := true;
}
#add_context print_style: Print_Style;
// Not yet deprecating the format* functions, but would like to.
// It's just still slightly inconvenient not to use them... -jblow, 21 December 2022
formatInt :: (value : Any, base := 10, minimum_digits := 1, padding: u8 = #char "0", digits_per_comma : u16 = 0, comma_string := "") -> FormatInt /*#deprecated "Use FormatInt.{...} instead."*/ {
result : FormatInt;
result.value = value;
result.base = base;
result.minimum_digits = minimum_digits;
result.padding = padding;
result.digits_per_comma = digits_per_comma;
result.comma_string = comma_string;
return result;
}
formatFloat :: (value : Any, width := -1, trailing_width := -1, mode := FormatFloat.Mode.DECIMAL, zero_removal := FormatFloat.Zero_Removal.YES) -> FormatFloat /*#deprecated "Use FormatFloat.{...} instead."*/ {
result : FormatFloat;
result.value = value;
result.width = width;
result.trailing_width = trailing_width;
result.mode = mode;
result.zero_removal = zero_removal;
return result;
}
formatStruct :: (value: Any, use_long_form_if_more_than_this_many_members := 5, use_newlines_if_long_form := false) -> FormatStruct /*#deprecated "Use FormatStruct.{...} instead."*/ {
result: FormatStruct;
result.value = value;
result.use_newlines_if_long_form = use_newlines_if_long_form;
result.use_long_form_if_more_than_this_many_members = use_long_form_if_more_than_this_many_members;
return result;
}
//#scope_file
/*
buffer_to_string_without_copy :: (using builder: String_Builder) -> string {
bytes := base_buffer.occupied;
for allocated_extra_buffers bytes += it.occupied;
assert(occupied < SIZE);
result : string;
result.data = data.data;
result.count = occupied;
return result;
}
*/
get_s64_from_void_pointer :: (pointer: *void, size_in_bytes: int) -> s64 {
if size_in_bytes == {
case 1; return (.*) cast(*s8) pointer;
case 2; return (.*) cast(*s16) pointer;
case 4; return (.*) cast(*s32) pointer;
case 8; return (.*) cast(*s64) pointer;
case;
if context.print_style.log_runtime_errors {
log_error("Integer size is %.\n", size_in_bytes);
}
assert(false);
return 0;
}
return 0; // @Temporary: Shut up @Incomplete control flow analysis.
}
get_u64_from_void_pointer :: (pointer: *void, size_in_bytes: int) -> u64 {
if size_in_bytes == {
case 1; return (.*) cast(*u8) pointer;
case 2; return (.*) cast(*u16) pointer;
case 4; return (.*) cast(*u32) pointer;
case 8; return (.*) cast(*u64) pointer;
case;
if context.print_style.log_runtime_errors {
log_error("Integer size is %.\n", size_in_bytes);
}
assert(false);
return 0;
}
return 0; // @Temporary: Shut up @Incomplete control flow analysis.
}
print_u64_base_n :: (builder: *String_Builder, v: u64, format: FormatInt) {
base_64_characters :: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_.";
base := cast(u64) format.base;
assert(base >= 2);
assert(base <= 64);
MAX_OUTPUT_LENGTH :: 64;
assert(format.minimum_digits >= 0);
digits := format.minimum_digits;
if digits > MAX_OUTPUT_LENGTH digits = MAX_OUTPUT_LENGTH;
output : [MAX_OUTPUT_LENGTH] u8;
end := output.data + MAX_OUTPUT_LENGTH;
p := end;
while v || (digits > 0) {
place := v % base;
v = v / base;
p -= 1;
if place || v || p == end - 1 {
p.* = base_64_characters[place];
} else {
p.* = format.padding;
}
digits -= 1;
}
if p == end && digits > 0 {
p -= 1;
p.* = format.padding;
}
assert(p >= output.data);
if format.digits_per_comma && format.comma_string {
s: string;
s.data = p;
s.count = end - p;
per := format.digits_per_comma;
before := s.count % per;
if before {
t := s;
t.count = before;
append(builder, t);
advance(*s, before);
if s.count append(builder, format.comma_string); // This could be a number with fewer digits than digits_per_comma...!
}
while s.count > 0 {
t := s;
t.count = per;
append(builder, t);
if s.count > per append(builder, format.comma_string);
advance(*s, per);
}
} else {
append(builder, p, end - p);
}
}
print_float :: (builder : *String_Builder, format : FormatFloat) {
// @Cleanup: We should collapse this extra call.
stb_print_float(builder, format);
}
print_integer :: (builder : *String_Builder, format : FormatInt) {
any := format.value;
assert(any.type != null);
type := any.type;
while type.type == .VARIANT {
variant := cast(*Type_Info_Variant) type;
type = variant.variant_of;
}
assert(type.type == .INTEGER);
info := cast(*Type_Info_Integer) type;
v : u64 = ---;
if format.base == 10 {
if info.signed {
v_signed := get_s64_from_void_pointer(any.value_pointer, info.runtime_size);
if v_signed < 0 {
append(builder, "-");
v = cast,no_check(u64) -v_signed;
} else {
v = cast(u64) v_signed;
}
} else {
v = get_u64_from_void_pointer(any.value_pointer, info.runtime_size);
}
} else {
v = get_u64_from_void_pointer(any.value_pointer, info.runtime_size);
}
print_u64_base_n(builder, v, format);
}
#scope_export
print_character :: (_c: u8) {
// A ridiculous way of printing a character.
c := _c;
s: string;
s.data = *c;
s.count = 1;
write_string(s);
}
print :: (format_string: string, args: .. Any, to_standard_error := false) -> bytes_printed: s64 {
builder: String_Builder;
print_to_builder(*builder, format_string, ..args);
written := write_builder(*builder, to_standard_error = to_standard_error);
return written;
} @PrintLike
sprint :: (format_string: string, args: .. Any) -> string {
builder: String_Builder;
print_to_builder(*builder, format_string, ..args);
return builder_to_string(*builder);
} @PrintLike
tprint :: (format_string: string, args: .. Any) -> string {
builder: String_Builder;
builder.allocator = temp;
print_to_builder(*builder, format_string, ..args);
return builder_to_string(*builder,, temp);
} @PrintLike
//
// log() formats a message, then sends it to context.logger.
// You can pass flags. source_identifier and data get copied from context.
//
log :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) {
builder: String_Builder;
print_to_builder(*builder, format_string, ..args);
s := builder_to_string(*builder,, temp);
if !s.count return;
info: Log_Info;
info.source_identifier = context.log_source_identifier;
info.location = loc;
info.common_flags = flags;
info.user_flags = user_flags;
info.section = section;
context.logger(s, context.logger_data, info);
} @PrintLike
// Log function with section pointer as first parameter
log :: (section : *Log_Section, format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0) {
log(format_string, ..args, loc, flags, user_flags, section);
} @PrintLike
log_warning :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) {
// In case people prefer to say log_error instead of putting a flags argument on the end,
// we provide this for convenience. Not really sure if it's worth it, but here you go.
new_flags := flags | .WARNING;
log(format_string, ..args, loc, new_flags, user_flags, section);
} @PrintLike
log_error :: (format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0, section : *Log_Section = null) {
// In case people prefer to say log_error instead of putting a flags argument on the end,
// we provide this for convenience. Not really sure if it's worth it, but here you go.
new_flags := flags | .ERROR;
log(format_string, ..args, loc, new_flags, user_flags, section);
} @PrintLike
// Log error function with section pointer as first parameter
log_error :: (section : *Log_Section, format_string: string, args: .. Any, loc := #caller_location, flags := Log_Flags.NONE, user_flags : u32 = 0) {
new_flags := flags | .ERROR;
log(format_string, ..args, loc, new_flags, user_flags, section);
}
append_indentation :: (builder: *String_Builder, format: *FormatStruct) {
spaces16 :: " ";
num_spaces := context.print_style.indentation_depth * format.indentation_width;
if num_spaces <= 0 return;
div := num_spaces / 16;
mod := num_spaces % 16;
for 1..div append(builder, spaces16);
append(builder, spaces16.data, mod);
}
// Put this in file scope?
append_struct_info :: (builder: *String_Builder, info: *Type_Info_Struct, base_pointer: *void, format : *FormatStruct = null) {
using Type_Info_Tag;
if info.textual_flags & .UNION {
//
// If a union is tagged, we can now print it. I sure hope it isn't corrupted!
//
tag_member: *Type_Info_Struct_Member;
if info.textual_flags & .UNION_IS_TAGGED {
// But at least print the tag value...
// Guard against corrupted data in a 0-member case...
tag_member = ifx info.members then *info.members[0];
}
if tag_member {
address := (cast(*u8) base_pointer) + tag_member.offset_in_bytes; // offset_in_bytes should be always 0.
tag_u64: u64;
size_valid := true;
if tag_member.type.runtime_size == {
case 1; tag_u64 = address.(*u8 ).*.(u64);
case 2; tag_u64 = address.(*u16).*.(u64);
case 4; tag_u64 = address.(*u32).*.(u64);
case 8; tag_u64 = address.(*u64).*;
case; size_valid = false;
}
member_index: s32 = -1;
if size_valid {
for info.tagged_union_bindings {
if it.constant_value == tag_u64 {
member_index = it.member_index;
break;
}
}
}
tag_any := Any.{tag_member.type, address};
if (member_index < 0) || (member_index >= info.members.count) { // We don't have a valid member, so, just print the tag.
print(builder, "{%, ?}", tag_any);
} else {
value := info.members[member_index];
value_any := Any.{value.type, base_pointer + value.offset_in_bytes};
print(builder, "{%, %}", tag_any, value_any);
}
} else {
append(builder, "(untagged union)");
}
return;
}
if !format format = *context.print_style.default_format_struct;
if format.draw_type_name append(builder, info.name);
append(builder, format.begin_string);
use_newlines := false;
long_form_limit := format.use_long_form_if_more_than_this_many_members;
DoSkipMember :: inline (member: *Type_Info_Struct_Member) -> bool {
// For now, don't draw constant members, since you are probably using this for
// debugging and that can be spammy when you declare lots of constants (and it
// adds no information usually!) -jblow, 10 September 2015
// ... Similarly for imported, since we usually just imported
// stuff from the base class that we already printed or are
// about to print.
if member.flags & (.CONSTANT | .IMPORTED | .OVERLAY) return true;
if member.type.runtime_size == 0 return true; // Skip 'void' elements, as these can't hold values.
return false;
}
printable_count := 0;
for * info.members {
if DoSkipMember(it) continue;
printable_count += 1;
}
long_form := false;
if (long_form_limit < 0) || (printable_count > long_form_limit) {
long_form = true;
use_newlines = format.use_newlines_if_long_form;
}
if use_newlines append(builder, "\n");
context.print_style.indentation_depth += 1;
cursor := 0;
for * info.members {
if DoSkipMember(it) continue;
defer cursor += 1;
if use_newlines append_indentation(builder, format);
if long_form {
append(builder, it.name);
append(builder, format.separator_between_name_and_value);
}
may_be_uninitialized := false;
for note: it.notes {
if note == "---" {
may_be_uninitialized = true;
break;
}
}
address := (cast(*u8) base_pointer) + it.offset_in_bytes;
any: Any = ---;
any.type = it.type;
any.value_pointer = address;
print_item_to_builder(builder, any, may_be_uninitialized=may_be_uninitialized);
if long_form {
append(builder, format.long_form_separator_between_fields);
} else {
if cursor < printable_count - 1 append(builder, format.short_form_separator_between_fields);
}
if use_newlines {
append(builder, "\n");
}
}
context.print_style.indentation_depth -= 1;
if use_newlines append_indentation(builder, format);
append(builder, format.end_string);
}
print_type_to_builder :: (builder: *String_Builder, info: *Type_Info, print_parameters_matching_defaults := false) {
using Type_Info_Tag;
if info.type == STRUCT {
info_struct := cast(*Type_Info_Struct) info;
if info_struct.name {
append(builder, info_struct.name);
} else {
if info_struct.textual_flags & .UNION {
append(builder, "(anonymous union)");
} else {
append(builder, "(anonymous struct)");
}
}
if info_struct.polymorph_source_struct {
append(builder, "(");
for * param: info_struct.specified_parameters {
// @Speed: param_matches_default is n-squared.
if !print_parameters_matching_defaults {
if param_matches_default(info_struct, param) continue;
}
if it_index append(builder, ", ");
append(builder, param.name);
append(builder, "=");
assert(param.offset_into_constant_storage >= 0, "COuld find param % of struct % in constant storage!", it_index, info_struct.*);
any: Any;
any.type = param.type;
any.value_pointer = info_struct.constant_storage.data + param.offset_into_constant_storage;
print_item_to_builder(builder, any);
}
append(builder, ")");
}
} else if info.type == POINTER {
info_pointer := cast(*Type_Info_Pointer) info;
append(builder, "*");
print_type_to_builder(builder, info_pointer.pointer_to);
} else if info.type == ARRAY {
info_array := cast(*Type_Info_Array) info;
if info_array.array_type == .FIXED {
append(builder, "[");
print_item_to_builder(builder, info_array.array_count);
append(builder, "]");
} else if info_array.array_type == .VIEW {
append(builder, "[]");
} else if info_array.array_type == .RESIZABLE {
append(builder, "[..]");
} else {
assert(false);
}
append(builder, " ");
print_type_to_builder(builder, info_array.element_type);
} else if info.type == TYPE {
append(builder, "Type");
} else if info.type == ENUM {
info_enum := cast(*Type_Info_Enum)(info);
if info_enum.name {
append(builder, info_enum.name);
} else {
append(builder, "(anonymous enum)");
}
} else if info.type == STRING {
append(builder, "string");
} else if info.type == PROCEDURE {
info_proc := cast(*Type_Info_Procedure)(info);
append(builder, "procedure (");
for info_proc.argument_types {
if it_index != 0 append(builder, ", ");
print_type_to_builder(builder, it);
}
append(builder, ")");
if info_proc.return_types {
append(builder, " -> ");
if info_proc.return_types.count > 1 append(builder, "(");
for info_proc.return_types {
if it_index != 0 append(builder, ", ");
print_type_to_builder(builder, it);
}
if info_proc.return_types.count > 1 append(builder, ")");
}
f := info_proc.procedure_flags;
if f & .IS_ELSEWHERE append(builder, " #elsewhere");
if f & .IS_COMPILE_TIME_ONLY append(builder, " #compiler");
if f & .IS_C_CALL append(builder, " #c_call");
if f & .IS_INTRINSIC append(builder, " #intrinsic");
} else if info.type == CODE { // @redundant with other "code" in here... !
append(builder, "Code");
} else if info.type == VARIANT {
tiv := cast(*Type_Info_Variant) info;
if tiv.name {
append(builder, tiv.name);
} else {
// It's anonymous; print out something kinda reasonable.
append(builder, "#type");
if tiv.variant_flags & .DISTINCT append(builder, ",distinct");
if tiv.variant_flags & .ISA append(builder, ",isa");
append(builder, " ");
print_type_to_builder(builder, tiv.variant_of);
}
} else if info.type == POLYMORPHIC_VARIABLE {
append(builder, "$"); // " This is here just temporarily, to un-confuse emacs syntax highlighting. Yay.
} else if info.type == ANY {
append(builder, "Any");
} else {
element_type := "";
// Quick hack!
size := info.runtime_size;
if info.type == {
case .INTEGER;
tii := cast(*Type_Info_Integer) info;
if size == 1 {
if tii.signed element_type = "s8";
else element_type = "u8";
} else if size == 2 {
if tii.signed element_type = "s16";
else element_type = "u16";
} else if size == 4 {
if tii.signed element_type = "s32";
else element_type = "u32";
} else {
if tii.signed element_type = "s64";
else element_type = "u64";
}
case .FLOAT;
if info == type_info(float) element_type = "float"; // We will never print 'float' if this *Type_Info comes from antoher workspace then...
if info.runtime_size == 4 element_type = "float32";
else element_type = "float64";
case .BOOL; element_type = "bool";
case .VOID; element_type = "void";
}
if element_type {
append(builder, element_type);
} else {
append(builder, "(some type)");
}
}
}
print_item_to_builder :: (builder: *String_Builder, item: Any, inside_struct_or_array := true, may_be_uninitialized := false) {
using Type_Info_Tag;
if (!item.value_pointer) && !item.type { // We could get an uninitialized Any from the user, if they are printing a declaration that was already an Any, so that the cast was a no-op.
append(builder, "Any.{}");
return;
}
t := item.type.type;
if t == {
case STRING;
s := get_full_sized_string(item);
if inside_struct_or_array {
// Print the string in quotes, escaping any characters.
// A little slow for now.
append(builder, "\"");
for 0..s.count-1 {
c := s[it];
if c == 10 {
append(builder, "\\n".data, 2);
} else {
append(builder, *c, 1);
}
}
append(builder, "\"");
} else {
append(builder, s);
}
case BOOL;
b := (.*) cast(*u8) item.value_pointer;
if b == 1 {
append(builder, "true");
} else if b == 0 {
append(builder, "false");
} else {
print_to_builder(builder, "invalid(%)", b);
}
case INTEGER;
default := context.print_style.default_format_int;
default.value = item;
print_integer(builder, default);
case FLOAT;
default := context.print_style.default_format_float;
default.value = item;
print_float(builder, default);
case POINTER;
pt := cast(*Type_Info_Pointer)(item.type);
if pt.pointer_to.type == .STRUCT {
printer := context.print_style.struct_printer;
printed := ifx printer then printer(builder, item, context.print_style.struct_printer_data);
if printed return;
}
u64_val := (.*) cast(*u64) item.value_pointer;
if u64_val {
default := context.print_style.default_format_absolute_pointer;
default.value = u64_val;
print_integer(builder, default);
} else {
append(builder, "null");
}
case STRUCT;
info_struct := cast(*Type_Info_Struct) item.type;
printer := context.print_style.struct_printer;
printed := ifx printer then printer(builder, item, context.print_style.struct_printer_data);
if printed return;
expansion: *Type_Info_Struct_Member;
if (!inside_struct_or_array) && item.value_pointer expansion = isFormatter(info_struct); // The check for item.value_pointer is a safety in case we get passed bad data.
if expansion {
expander := cast(*Formatter) item.value_pointer;
// If it's a silent default-setter, set the new default pointer, then bail.
if item.type == type_info(FormatInt) {
format := (.*) cast(*FormatInt) item.value_pointer;
if check_null_formatter(format, builder) return;
print_integer(builder, format);
return;
} else if item.type == type_info(FormatFloat) {
format := (.*) cast(*FormatFloat) item.value_pointer;
if check_null_formatter(format, builder) return;
print_float(builder, format);
return;
} else if item.type == type_info(FormatStruct) {
format := cast(*FormatStruct) item.value_pointer;
if check_null_formatter(format, builder) return;
new_info_struct := cast(*Type_Info_Struct) format.value.type;
value_pointer := format.value.value_pointer;
if format.value.type.type == POINTER { // We let people wrap a pointer to a struct, since often that's what you'll have...
tip := cast(*Type_Info_Pointer)format.value.type;
if tip.pointer_to.type == STRUCT {
new_info_struct = cast(*Type_Info_Struct) tip.pointer_to;
value_pointer = (.*) cast(**void) format.value.value_pointer;
}
}
if new_info_struct.type == STRUCT {
append_struct_info(builder, new_info_struct, value_pointer, format);
return;
} else {
log_error("Attempt to pass a non-struct inside a FormatStruct.\n");
append(builder, "error");
return;
}
} else if item.type == type_info(FormatArray) {
format := cast(*FormatArray) item.value_pointer;
new_info := cast(*Type_Info_Array) format.value.type;
if check_null_formatter(format, builder) return;
print_array(builder, format.value, new_info, format, may_be_uninitialized);
return;
}
}
append_struct_info(builder, info_struct, item.value_pointer);
case ARRAY;
format := *context.print_style.default_format_array;
info_array := cast(*Type_Info_Array) item.type;
print_array(builder, item, info_array, format, may_be_uninitialized);
case PROCEDURE;
pv := (.*) cast(**void) item.value_pointer; // @Hack
if pv {
append(builder, "procedure 0x");
print_item_to_builder(builder, pv);
// append(builder, ")");
} else {
append(builder, "null");
}
case VARIANT;
printer := context.print_style.struct_printer;
printed := ifx printer then printer(builder, item, context.print_style.struct_printer_data);
if printed return;
info_variant := cast(*Type_Info_Variant) item.type;
variant_item := item;
variant_item.type = info_variant.variant_of;
print_item_to_builder(builder, variant_item, inside_struct_or_array, may_be_uninitialized);
case ENUM;
// Kind of ugly, but let's go for it.
info_enum := cast(*Type_Info_Enum) item.type;
info_int := cast(*Type_Info_Integer) info_enum.internal_type;
assert(info_int.type == INTEGER);
size := info_int.runtime_size;
if info_enum.enum_type_flags & .FLAGS {
target_value := get_u64_from_void_pointer(item.value_pointer, size); // Treat flags as unsigned, even for signed types. Simpler.
if target_value == 0 {
// Check for a named 0-value before printing 0.
name := "0";
for info_enum.values if it == 0 {
name = info_enum.names[it_index];
break;
}
append(builder, name);
return;
}
// I felt like doing this in unsigned for some reason!
flags_total := target_value;
flags_printed : u64 = 0;
first := true;
for info_enum.values {
value := cast,no_check(u64) it;
matching := flags_total & value;
if matching != value matching = 0; // We need to match all of the bits in the enum member.
if !matching continue;
if !(matching & ~flags_printed) continue;
flags_printed |= matching;
if !first append(builder, " | ");
append(builder, info_enum.names[it_index]);
first = false;
}
unmatched := flags_total & ~flags_printed;
if unmatched {
if !first append(builder, " | 0x");
format := FormatInt.{base=16};
print_u64_base_n(builder, unmatched, format);
}
} else {
assert(info_enum.names.count == info_enum.values.count);
target_value_s64 := get_s64_from_void_pointer(item.value_pointer, size);
if info_int.signed {
for info_enum.values if target_value_s64 == it {
append(builder, info_enum.names[it_index]);
return;
}
} else {
target_value_u64 := get_u64_from_void_pointer(item.value_pointer, size);
for info_enum.values if target_value_u64 == cast,no_check(u64) it {
append(builder, info_enum.names[it_index]);
return;
}
}
if target_value_s64 {
append(builder, "(enum out of range: ");
// Print an unsigned value if the enum is unsigned,
// otherwise we allow it to be signed.
if info_int.signed print_item_to_builder(builder, target_value_s64);
else print_item_to_builder(builder, get_u64_from_void_pointer(item.value_pointer, size));
append(builder, ")");
} else {
print_item_to_builder(builder, target_value_s64);
}
}
case TYPE;
ti := (.*) cast(**Type_Info) item.value_pointer;
if ti {
print_type_to_builder(builder, ti);
} else {
// If it's a runtime value that is 0, this is void
// (by convention of the compiler starting in 2026).
// For static values, we generate the correct *Type_Info
// at compile-time, but for dynamic values we don't have
// an extra 'if' that gets generated. (Though we could
// consider this.)
append(builder, "void");
}
/*
append(builder, "(Type, address ");
default : FormatInt;
default.base = 16;
default.value = cast(s64) ti;
print_integer(builder, default);
append(builder, ")");
*/
case VOID;
append(builder, "void");
case CODE;
append(builder, "Code");
case OVERLOAD_SET;
append(builder, "(procedure overloads)");
case ANY; // We can get here when printing an array or struct of Any.
if may_be_uninitialized {
append(builder, "(potentially uninitialized Any)");
} else {
new_any := (.*) cast(*Any) item.value_pointer;
if new_any.type {
print_item_to_builder(builder, new_any);
} else {
if new_any.value_pointer append(builder, "(Any with null 'type' field)");
else append(builder, "Any.{}");
}
}
case;
// This is a type we don't know or don't yet handle.
append(builder, "(corrupted type tag ");
print_item_to_builder(builder, cast(int)t);
append(builder, ")");
}
}
my_is_digit :: (c: s64) -> bool {
relative := c - #char "0";
return (relative >= 0) && (relative <= 9);
}
// @Cleanup: Can use to_integer in Basic.jai now, but would have to
// deal with the cursor situation.
parse_number :: (s: string, cursor: s64) -> (end_cursor: s64, sum: s64) {
sum := 0;
while cursor < s.count {
c := s[cursor];
if !my_is_digit(c) break;
sum *= 10;
sum += cast(int) c - #char "0";
cursor += 1;
}
return cursor, sum;
}
print_to_builder :: (builder : *String_Builder, format_string : string, args: .. Any) -> bool {
implicit_index_cursor := 0;
#if USE_SIMD {
PERCENTS :: "%%%%%%%%%%%%%%%%";
PercentsData := PERCENTS.data;
#asm {
movdqu.x percents:, [PercentsData];
}
}
cursor := 0;
printed := 0;
while cursor < format_string.count {
#if 0 && USE_SIMD { // Disabled for now, as we don't currently handle character 31 in this loop. Once we do, re-enable.
// Hardcoded for SSE2 currently, since that has wide support.
// For other CPUs, etc, we will ... do something!
// If we have at least SIMD_WIDTH characters in our buffer, use
// SSE2 to scan that many characters at a time, looking for the
// first % character.
// We also do this kind of thing in modules/String, in
// find_character_from_left*, so, maybe we could just move that stuff
// into Basic and use it here, inlined.
if format_string.count - cursor >= SIMD_WIDTH {
point := format_string.data + cursor;
mask: u32 = ---;
advance: u32;
#asm {
movdqu.x test:, [point];
pcmpeqb.x test, percents;
pmovmskb.x mask, test;
bsf.d advance, mask;
}
if mask {
cursor += advance;
// Fall through and do the normal handling below.
// If we had a goto we could skip past the redundant c != #char, hmm.
} else {
cursor += 16;
continue;
}
}
}
c := format_string.data[cursor];
if c != #char "%" {
if c == 31 {
append(builder, format_string.data + printed, cursor - printed);
append(builder, "%");
cursor += 1;
printed = cursor;
continue;
}
cursor += 1;
continue;
}
append(builder, format_string.data + printed, cursor - printed);
cursor += 1; // Skip the %.
value := implicit_index_cursor; // Will get bumped below.
if cursor < format_string.count {
next := format_string.data[cursor];
if my_is_digit(next) {
if next == #char "0" { // %00 outputs the empty string; %0 is like a regular %.
cursor += 1;
if (cursor < format_string.count) && (format_string.data[cursor] == #char "0") {
// Empty string.
cursor += 1;
printed = cursor;
continue;
} else {
// Fall through and do the thing.
}
} else {
// @Robustness: We probably want to test that parse_number does not overflow!
cursor, value = parse_number(format_string, cursor);
value -= 1; // Arguments are 0-based indices from here on.
}
}
}
if value < 0 {
if context.print_style.log_runtime_errors {
log_error("Invalid negative \% index % in the format string at character %. Skipping.\n", value, cursor);
}
implicit_index_cursor = 0;
printed = cursor;
continue;
}
if value >= args.count {
if context.print_style.log_runtime_errors {
do_free := false;
arguments_string := "(There are no insertable arguments.)";
if value == 1 {
arguments_string = "(There is only 1 insertable argument.)";
} else {
arguments_string = sprint("(There are only % insertable arguments.)", value);
do_free = true;
}
log_error("Invalid \% index % in the format string \"%\" at character %. %\n", value+1, format_string, cursor, arguments_string);
if do_free free(arguments_string);
}
implicit_index_cursor = 0;
printed = cursor;
continue;
}
print_item_to_builder(builder, args[value], false);
implicit_index_cursor = value + 1;
printed = cursor; // Next time we append, start here.
}
append(builder, format_string.data + printed, cursor - printed);
if builder.failed return false;
return true;
} @PrintLike
print :: print_to_builder;
print_sequence_to_builder :: (builder : *String_Builder, args: .. Any) {
for args {
print_item_to_builder(builder, it);
}
}
isFormatter :: (info: *Type_Info_Struct) -> *Type_Info_Struct_Member {
for * info.members {
if it.type == type_info(Formatter) return it;
}
return null;
}
print_bytes :: (a: *void, length: int, format_string := "% ") {
b := cast(*u8) a;
for 0..length-1 {
print(format_string, formatInt(b[it], base=16));
}
print("\n");
}
print_array :: (builder: *String_Builder, any: Any, info_array: *Type_Info_Array, format: FormatArray, may_be_uninitialized := false) {
// Figure out count and data.
count: s64;
data: *void;
if info_array.array_type == .FIXED {
count = info_array.array_count;
data = any.value_pointer;
} else {
count = (.*) cast(*s64) (any.value_pointer);
data = (.*) cast(**void)(any.value_pointer + 8);
}
// Now start printing.
append(builder, format.begin_string);
if may_be_uninitialized {
print(builder, "% potentially uninitialized elements", count);
} else {
to_print := count;
limit := format.stop_printing_after_this_many_elements;
if limit >= 0 to_print = min(to_print, limit);
etype := info_array.element_type;
for 0..to_print-1 {
pointer := data + info_array.element_type.runtime_size * it;
item: Any = ---;
item.type = etype;
item.value_pointer = pointer;
print_item_to_builder(builder, item);
if format.draw_separator_after_last_element || (it < to_print-1) {
append(builder, format.separator);
}
}
if to_print < count append(builder, format.printing_stopped_early_string);
}
append(builder, format.end_string);
}
type_to_string :: (info: *Type_Info) -> string { // Returns a string in Temporary_Storage.
// A relatively inefficient way to do this! If you are printing a bunch of stuff
// into the same builder, you can use print_type_to_builder() instead!
builder: String_Builder;
builder.allocator = temp;
print_type_to_builder(*builder, info);
return builder_to_string(*builder,, temp);
}
#scope_file
get_full_sized_string :: (any: Any) -> string {
assert(any.type && (any.type.type == .STRING));
tis := cast(*Type_Info_String) any.type;
return (.*) cast(*string) any.value_pointer;
}
check_null_formatter :: (format: Formatter, builder: *String_Builder) -> bool {
if !format.value.type {
append(builder, "(null Any)");
return true;
}
return false;
}
my_c_style_strlen :: (x: *u8) -> int { // @Cleanup: Remove this when we know what is going on.
p := x;
while p.* { p += 1; assert(p - x < 2048);}
return p - x;
}
//
// When printing structs, we print out the parameters,
// but for terseness, we may not want to print them
// if they match the default. This function is how we test that.
//
param_matches_default :: (info: *Type_Info_Struct, param: *Type_Info_Struct_Member) -> bool {
source := info.polymorph_source_struct;
if !source return true;
for * source_param: source.parameters {
if source_param.name != param.name continue;
if source_param.type.type == .PROCEDURE {
// If a procedure type is polymorphic, match against it
// even if the type changed (all procedures are the same
// size right now, though this may change if we do closures),
// and the value determines matching, and we especially
// want matching in the case of 'null' procedure pointers.
} else {
if source_param.type != param.type continue;
}
if source_param.offset_into_constant_storage < 0 continue;
size := param.type.runtime_size;
if size < 0 continue;
pa := source.constant_storage.data + source_param.offset_into_constant_storage;
pb := info .constant_storage.data + param .offset_into_constant_storage;
delta := memcmp(pa, pb, size);
return delta == 0;
}
return false;
}