#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; }