diff --git a/base/dependencies/macros.hpp b/base/dependencies/macros.hpp index ffb70b2..ee0474d 100644 --- a/base/dependencies/macros.hpp +++ b/base/dependencies/macros.hpp @@ -57,6 +57,11 @@ #define bitfield_is_set( Type, Field, Mask ) ( (scast(Type, Mask) & scast(Type, Field)) == scast(Type, Mask) ) #endif +#ifndef GEN_ASSERT_SLICE +#define GEN_ASSERT_SLICE( slice ) \ + GEN_ASSERT( ((slice).Len >= 0) & (((slice).Len == 0) | ((slice).Ptr != nullptr)) ) +#endif + // Mainly intended for forcing the base library to utilize only C-valid constructs or type coercion #ifndef GEN_C_LIKE_CPP #define GEN_C_LIKE_CPP 0 diff --git a/base/dependencies/simd.hpp b/base/dependencies/simd.hpp new file mode 100644 index 0000000..3e146e8 --- /dev/null +++ b/base/dependencies/simd.hpp @@ -0,0 +1,63 @@ +#ifdef INTELLISENSE_DIRECTIVES +#pragma once +#include "debug.hpp" +#endif + +#pragma region SIMD + +// Lane names use bit widths: sixteen unsigned 8-bit lanes or four unsigned 32-bit lanes. +// Vector operations are available on Windows x64. +#if defined(GEN_SYSTEM_WINDOWS) && (defined(_M_X64) || defined(__x86_64__)) +typedef __m128i u8x16; +typedef __m128i u32x4; + +// Unaligned loads require 16 or 8 readable bytes respectively. load8 zeros lanes 8 through 15. +internal forceinline u8x16 u8x16_load (void const* address) { return _mm_loadu_si128(rcast(__m128i const*, address)); } +internal forceinline u8x16 u8x16_load8(void const* address) { return _mm_loadl_epi64(rcast(__m128i const*, address)); } + +internal forceinline u8x16 u8x16_splat(u8 value) { return _mm_set1_epi8(scast(char, value)); } + +internal forceinline u8x16 u8x16_equal(u8x16 lhs, u8x16 rhs) { return _mm_cmpeq_epi8(lhs, rhs); } + +// Extract each lane's high bit. Bit 0 represents lane 0 at the lowest address. +internal forceinline u16 u8x16_mask(u8x16 value) { return scast(u16, _mm_movemask_epi8(value)); } + +// Reads 16 bytes without requiring alignment. +internal forceinline u32x4 u32x4_load(void const* address) { return _mm_loadu_si128(rcast(__m128i const*, address)); } + +internal forceinline u32x4 u32x4_splat(u32 value) { return _mm_set1_epi32(scast(s32, value)); } + +internal forceinline u32x4 u32x4_equal(u32x4 lhs, u32x4 rhs) { return _mm_cmpeq_epi32(lhs, rhs); } + +// Extract each lane's high bit into bits 0 through 3. Higher result bits are zero. +internal forceinline u8 u32x4_mask(u32x4 value) { return scast(u8, _mm_movemask_ps(_mm_castsi128_ps(value))); } +#endif + +// Requires a nonzero mask. Returns the lowest set-bit index, from 0 through 31. +s32 u32_mask_first_set(u32 mask); + +#if defined(GEN_SYSTEM_WINDOWS) +internal forceinline +s32 u32_mask_first_set(u32 mask) { + GEN_ASSERT(mask != 0); + unsigned long bit_index = 0; + _BitScanForward(& bit_index, scast(unsigned long, mask)); + return scast(s32, bit_index); +} +#endif + +#if ! defined(GEN_SYSTEM_WINDOWS) +internal forceinline +s32 u32_mask_first_set(u32 mask) { + GEN_ASSERT(mask != 0); + s32 bit = 0; + u32 bits = mask; + while ((bits & 1u) == 0u) { + bits >>= 1; + bit += 1; + } + return bit; +} +#endif + +#pragma endregion SIMD diff --git a/base/dependencies/strings.cpp b/base/dependencies/strings.cpp index 5bd3a47..f149710 100644 --- a/base/dependencies/strings.cpp +++ b/base/dependencies/strings.cpp @@ -1,5 +1,6 @@ #ifdef INTELLISENSE_DIRECTIVES # pragma once +# include "simd.hpp" # include "hashing.cpp" #endif @@ -174,4 +175,153 @@ StrBuilder strbuilder_visualize_whitespace(StrBuilder const str) return result; } +#if defined(GEN_SYSTEM_WINDOWS) && (defined(_M_X64) || defined(__x86_64__)) +internal +ssize strbuilder_find_template_byte(char const* text, ssize length, char needle) +{ + ssize idx = 0; + for (; (length - idx) >= 16; ) + { + char const* at = text + idx; + u8x16 chunk = u8x16_load(at); + u8x16 splat = u8x16_splat(scast(u8, needle)); + u16 mask = u8x16_mask(u8x16_equal(chunk, splat)); + if (mask != 0) { + s32 bit_index = u32_mask_first_set(mask); + return idx + scast(ssize, bit_index); + } + idx += 16; + } + for (; (length - idx) >= 8; ) + { + char const* at = text + idx; + u8x16 chunk = u8x16_load8(at); + u8x16 splat = u8x16_splat(scast(u8, needle)); + u16 mask = u8x16_mask(u8x16_equal(chunk, splat)) & 0xFF; + if (mask != 0) { + s32 bit_index = u32_mask_first_set(mask); + return idx + scast(ssize, bit_index); + } + idx += 8; + } + for (; idx < length; ++ idx) { + if (text[idx] == needle) return idx; + } + return length; +} +#else +internal +ssize strbuilder_find_template_byte(char const* text, ssize length, char needle) { + for (ssize idx = 0; idx < length; ++ idx) + { + if (text[idx] == needle) + return idx; + } + return length; +} +#endif + +#if defined(GEN_SYSTEM_WINDOWS) && (defined(_M_X64) || defined(__x86_64__)) +internal +StrTemplateSlot* strbuilder_find_template_slot(Slice_StrTemplateSlot slots, Str key) +{ + u32 key_hash = crc32(key.Ptr, key.Len); + ssize idx = 0; + for (; (slots.Len - idx) >= 4; idx += 4) + { + u32 packed[4]; + packed[0] = crc32(slots.Ptr[idx + 0].Key.Ptr, slots.Ptr[idx + 0].Key.Len); + packed[1] = crc32(slots.Ptr[idx + 1].Key.Ptr, slots.Ptr[idx + 1].Key.Len); + packed[2] = crc32(slots.Ptr[idx + 2].Key.Ptr, slots.Ptr[idx + 2].Key.Len); + packed[3] = crc32(slots.Ptr[idx + 3].Key.Ptr, slots.Ptr[idx + 3].Key.Len); + + u32x4 packed_vec = u32x4_load(packed); + u32x4 want_vec = u32x4_splat(key_hash); + u8 mask = u32x4_mask(u32x4_equal(packed_vec, want_vec)); + + for (s32 lane = 0; lane < 4; ++ lane) + { + if ((mask & (1u << lane)) != 0) { + StrTemplateSlot* slot = & slots.Ptr[idx + lane]; + if (str_are_equal(slot[0].Key, key)) return slot; + } + } + } + for (; idx < slots.Len; ++ idx) + { + StrTemplateSlot* slot = & slots.Ptr[idx]; + u32 slot_hash = crc32(slot[0].Key.Ptr, slot[0].Key.Len); + if (slot_hash == key_hash) { + if (str_are_equal(slot[0].Key, key)) return slot; + } + } + return nullptr; +} +#else +internal +StrTemplateSlot* strbuilder_find_template_slot(Slice_StrTemplateSlot slots, Str key) { + u32 key_hash = crc32(key.Ptr, key.Len); + for (ssize idx = 0; idx < slots.Len; ++ idx) + { + StrTemplateSlot* slot = & slots.Ptr[idx]; + u32 slot_hash = crc32(slot[0].Key.Ptr, slot[0].Key.Len); + if (slot_hash == key_hash && str_are_equal(slot[0].Key, key)) + return slot; + } + return nullptr; +} +#endif + +bool strbuilder_append_template(StrBuilder* output, Slice_StrTemplateSlot slots, Str format) +{ + GEN_ASSERT(output != nullptr && output[0] != nullptr); + StrBuilderHeader* header = strbuilder_get_header(output[0]); + GEN_ASSERT((header[0].Length >= 0) & (header[0].Capacity >= header[0].Length)); + GEN_ASSERT_SLICE(format); + GEN_ASSERT_SLICE(slots); + for (ssize slot_idx = 0; slot_idx < slots.Len; ++ slot_idx) { + GEN_ASSERT_SLICE(slots.Ptr[slot_idx].Key); + GEN_ASSERT_SLICE(slots.Ptr[slot_idx].Value); + } + + bool result = false; + if (format.Len == 0) { + result = true; + goto finish; + } + + for (ssize cursor = 0; cursor < format.Len;) + { + if (format.Ptr[cursor] == '<') + { + ssize key_at = cursor + 1; + ssize key_max = format.Len - key_at; + ssize key_len = strbuilder_find_template_byte(format.Ptr + key_at, key_max, '>'); + if (key_len == key_max) goto finish; + + Str key = { format.Ptr + key_at, key_len }; + StrTemplateSlot* slot = strbuilder_find_template_slot(slots, key); + if (slot == nullptr) goto finish; + + if (slot[0].Value.Len > 0) { + bool appended = strbuilder_append_c_str_len(output, slot[0].Value.Ptr, slot[0].Value.Len); + if (appended == false) goto finish; + } + cursor = key_at + key_len + 1; + continue; + } + + ssize literal_max = format.Len - cursor; + ssize literal_len = strbuilder_find_template_byte(format.Ptr + cursor, literal_max, '<'); + if (literal_len > 0) { + bool appended = strbuilder_append_c_str_len(output, format.Ptr + cursor, literal_len); + if (appended == false) goto finish; + } + cursor += literal_len; + } + result = true; +finish: + return result; +} + #pragma endregion StrBuilder diff --git a/base/dependencies/strings.hpp b/base/dependencies/strings.hpp index d3bb578..25249dc 100644 --- a/base/dependencies/strings.hpp +++ b/base/dependencies/strings.hpp @@ -113,6 +113,8 @@ Str to_str_from_c_str( char const* bad_str ) { // I kept it for simplicty of porting but its not necessary to keep it that way. #pragma region StrBuilder struct StrBuilderHeader; +struct StrTemplateSlot; +struct Slice_StrTemplateSlot; #if GEN_COMPILER_C typedef char* StrBuilder; @@ -122,12 +124,39 @@ struct StrBuilder; forceinline usize strbuilder_grow_formula(usize value); -GEN_API StrBuilder strbuilder_make_reserve (AllocatorInfo allocator, ssize capacity); -GEN_API StrBuilder strbuilder_make_length (AllocatorInfo allocator, char const* str, ssize length); -GEN_API bool strbuilder_make_space_for (StrBuilder* str, char const* to_append, ssize add_len); -GEN_API bool strbuilder_append_c_str_len (StrBuilder* str, char const* c_str_to_append, ssize length); -GEN_API void strbuilder_trim (StrBuilder str, char const* cut_set); -GEN_API StrBuilder strbuilder_visualize_whitespace(StrBuilder const str); +GEN_API StrBuilder strbuilder_make_reserve (AllocatorInfo allocator, ssize capacity); +GEN_API StrBuilder strbuilder_make_length (AllocatorInfo allocator, char const* str, ssize length); +GEN_API bool strbuilder_make_space_for (StrBuilder* str, char const* to_append, ssize add_len); +GEN_API bool strbuilder_append_c_str_len(StrBuilder* str, char const* c_str_to_append, ssize length); + +struct StrTemplateSlot { Str Key; Str Value; }; +struct Slice_StrTemplateSlot { StrTemplateSlot* Ptr; ssize Len; }; + +// format and slot slices must not alias output. output may reallocate. +GEN_API bool strbuilder_append_template(StrBuilder* output, Slice_StrTemplateSlot slots, Str format); + +#ifndef strtmpl_slot +# if GEN_COMPILER_C +# define strtmpl_slot(key, value) (GEN_NS StrTemplateSlot){ txt(key), (value) } +# else +# define strtmpl_slot(key, value) GEN_NS StrTemplateSlot{ txt(key), (value) } +# endif +#endif + +#ifndef strtmpl_slots +# define strtmpl_slots( ... ) __VA_ARGS__ +#endif + +#ifndef strtmpl_append +#define strtmpl_append( output, result, entries, format ) do { \ + GEN_NS StrTemplateSlot gencpp_tmpl_slot_array[] = { entries }; \ + GEN_NS Slice_StrTemplateSlot gencpp_tmpl_slot_slice = { gencpp_tmpl_slot_array, count_of(gencpp_tmpl_slot_array) }; \ + (result) = GEN_NS strbuilder_append_template((output), gencpp_tmpl_slot_slice, (format)); \ + } while ( 0 ) +#endif + +GEN_API void strbuilder_trim (StrBuilder str, char const* cut_set); +GEN_API StrBuilder strbuilder_visualize_whitespace(StrBuilder const str); StrBuilder strbuilder_make_c_str (AllocatorInfo allocator, char const* str); StrBuilder strbuilder_make_str (AllocatorInfo allocator, Str str); diff --git a/base/gen.dep.hpp b/base/gen.dep.hpp index 8283b8d..9ad011a 100644 --- a/base/gen.dep.hpp +++ b/base/gen.dep.hpp @@ -13,6 +13,7 @@ GEN_NS_BEGIN #include "dependencies/printing.hpp" #include "dependencies/containers.hpp" #include "dependencies/hashing.hpp" +#include "dependencies/simd.hpp" #include "dependencies/strings.hpp" #include "dependencies/filesystem.hpp" #include "dependencies/timing.hpp" diff --git a/base/helpers/undef.macros.h b/base/helpers/undef.macros.h index 0bc7a48..e2e646f 100644 --- a/base/helpers/undef.macros.h +++ b/base/helpers/undef.macros.h @@ -141,6 +141,7 @@ #undef GEN_ASSERT #undef GEN_ASSERT_MSG #undef GEN_ASSERT_NOT_NULL +#undef GEN_ASSERT_SLICE #undef GEN_PANIC #undef GEN_FATAL @@ -165,6 +166,9 @@ #undef get_hashtable_underlying_type #undef txt +#undef strtmpl_slot +#undef strtmpl_slots +#undef strtmpl_append #undef NOMINMAX #undef VC_EXTRALEAN diff --git a/gen_c_library/c_library.cpp b/gen_c_library/c_library.cpp index 3e9fcf5..294b582 100644 --- a/gen_c_library/c_library.cpp +++ b/gen_c_library/c_library.cpp @@ -195,6 +195,9 @@ int gen_main() (Macro { txt("cast_to_str"), MT_Expression, MF_Functional }), (Macro { txt("current"), MT_Expression, MF_Null }), (Macro { txt("txt"), MT_Expression, MF_Functional }), + (Macro { txt("strtmpl_slot"), MT_Expression, MF_Functional }), + (Macro { txt("strtmpl_slots"), MT_Expression, MF_Functional }), + (Macro { txt("strtmpl_append"), MT_Statement, MF_Functional }), (Macro { txt("GEN_FILE_OPEN_PROC"), MT_Statement, MF_Functional | MF_Expects_Body }), (Macro { txt("GEN_FILE_READ_AT_PROC"), MT_Statement, MF_Functional | MF_Expects_Body }), (Macro { txt("GEN_FILE_WRITE_AT_PROC"), MT_Statement, MF_Functional | MF_Expects_Body }), @@ -268,6 +271,7 @@ int gen_main() Code header_debug = scan_file( path_base "dependencies/debug.hpp" ); Code header_string_ops = scan_file( path_base "dependencies/string_ops.hpp" ); Code header_hashing = scan_file( path_base "dependencies/hashing.hpp" ); + Code header_simd = scan_file( path_base "dependencies/simd.hpp" ); Code header_timing = scan_file( path_base "dependencies/timing.hpp" ); CodeBody parsed_header_memory = parse_file( path_base "dependencies/memory.hpp" ); @@ -1443,7 +1447,17 @@ R"(#define ( code ) _Generic( (code), \ ); // Append slots for(Str type : code_typenames ) { - generic_selector.append_fmt("%S : %S,\\\n", type, fn->Name ); + bool appended = false; + strtmpl_append(& generic_selector, appended, strtmpl_slots( + strtmpl_slot("type", type), + strtmpl_slot("fn", fn->Name) + ), + code( : ,\\\n) + ); + if (appended == false) { + GEN_FATAL("builder_print selector template failed"); + return 1; + } } generic_selector.append(txt("default: gen_generic_selection_fail \\\n")); generic_selector.append(txt("\t)\tGEN_RESOLVED_FUNCTION_CALL( builder, (Code)code )")); @@ -1791,6 +1805,7 @@ R"(#define ( code ) _Generic( (code), \ Code r_header_string_ops = refactor(header_string_ops); Code rf_containers = refactor_and_format(containers); Code r_header_hashing = refactor(header_hashing); + Code r_header_simd = refactor(header_simd); Code rf_header_strings = refactor_and_format(header_strings); Code rf_header_filesystem = refactor_and_format(header_filesystem); Code r_header_timing = refactor(header_timing); @@ -1877,6 +1892,7 @@ R"(#define ( code ) _Generic( (code), \ header.print( fmt_newline); header.print( rf_containers); header.print( r_header_hashing ); + header.print( r_header_simd ); header.print( rf_header_strings); header.print( rf_header_filesystem); header.print( r_header_timing ); @@ -2026,6 +2042,7 @@ R"(#define ( code ) _Generic( (code), \ header.print( fmt_newline); header.print( rf_containers); header.print( r_header_hashing ); + header.print( r_header_simd ); header.print( rf_header_strings); header.print( rf_header_filesystem); header.print( r_header_timing ); diff --git a/gen_c_library/c_library.refactor b/gen_c_library/c_library.refactor index 9e1d93b..4da6332 100644 --- a/gen_c_library/c_library.refactor +++ b/gen_c_library/c_library.refactor @@ -68,10 +68,12 @@ word txt, gen_txt // Basic Types word u8, gen_u8 +word u8x16, gen_u8x16 word s8, gen_s8 word u16, gen_u16 word s16, gen_s16 word u32, gen_u32 +word u32x4, gen_u32x4 word s32, gen_s32 word u64, gen_u64 word s64, gen_s64 @@ -91,6 +93,13 @@ word to_sptr, gen_to_sptr word to_mem_ptr, gen_to_mem_ptr word to_mem_ptr_const, gen_to_mem_ptr_const +// SIMD + +namespace u8x16_, gen_u8x16_ +namespace u32x4_, gen_u32x4_ + +word u32_mask_first_set, gen_u32_mask_first_set + // Debug word assert_handler, gen_assert_handler @@ -230,10 +239,13 @@ namespace str_, gen_str_ word cast_to_str, gen_cast_to_str -word StrBuilderHeader, gen_StrBuilderHeader -word StrBuilder, gen_StrBuilder +word StrBuilderHeader, gen_StrBuilderHeader +word StrBuilder, gen_StrBuilder +word StrTemplateSlot, gen_StrTemplateSlot +word Slice_StrTemplateSlot, gen_Slice_StrTemplateSlot namespace strbuilder_, gen_strbuilder_ +namespace strtmpl_, gen_strtmpl_ word StrCached, gen_StrCached diff --git a/gen_segmented/segmented.cpp b/gen_segmented/segmented.cpp index 7707419..a3a4331 100644 --- a/gen_segmented/segmented.cpp +++ b/gen_segmented/segmented.cpp @@ -50,6 +50,7 @@ int gen_main() Code printing = scan_file( path_base "dependencies/printing.hpp" ); Code containers = scan_file( path_base "dependencies/containers.hpp" ); Code hashing = scan_file( path_base "dependencies/hashing.hpp" ); + Code simd = scan_file( path_base "dependencies/simd.hpp" ); Code strings = scan_file( path_base "dependencies/strings.hpp" ); Code filesystem = scan_file( path_base "dependencies/filesystem.hpp" ); Code timing = scan_file( path_base "dependencies/timing.hpp" ); @@ -70,6 +71,7 @@ int gen_main() builder_print( header, printing ); builder_print( header, containers ); builder_print( header, hashing ); + builder_print( header, simd ); builder_print( header, strings ); builder_print( header, filesystem ); builder_print( header, timing ); diff --git a/gen_singleheader/singleheader.cpp b/gen_singleheader/singleheader.cpp index df1f7f1..d96f473 100644 --- a/gen_singleheader/singleheader.cpp +++ b/gen_singleheader/singleheader.cpp @@ -81,6 +81,7 @@ int gen_main() Code printing = scan_file( path_base "dependencies/printing.hpp" ); Code containers = scan_file( path_base "dependencies/containers.hpp" ); Code hashing = scan_file( path_base "dependencies/hashing.hpp" ); + Code simd = scan_file( path_base "dependencies/simd.hpp" ); Code strings = scan_file( path_base "dependencies/strings.hpp" ); Code filesystem = scan_file( path_base "dependencies/filesystem.hpp" ); Code timing = scan_file( path_base "dependencies/timing.hpp" ); @@ -97,6 +98,7 @@ int gen_main() header.print( printing ); header.print( containers ); header.print( hashing ); + header.print( simd ); header.print( strings ); header.print( filesystem ); header.print( timing ); diff --git a/gen_unreal_engine/unreal.cpp b/gen_unreal_engine/unreal.cpp index 9927346..5264afe 100644 --- a/gen_unreal_engine/unreal.cpp +++ b/gen_unreal_engine/unreal.cpp @@ -144,6 +144,7 @@ int gen_main() Code printing = scan_file( path_base "dependencies/printing.hpp" ); Code containers = scan_file( path_base "dependencies/containers.hpp" ); Code hashing = scan_file( path_base "dependencies/hashing.hpp" ); + Code simd = scan_file( path_base "dependencies/simd.hpp" ); Code strings = scan_file( path_base "dependencies/strings.hpp" ); Code filesystem = scan_file( path_base "dependencies/filesystem.hpp" ); Code timing = scan_file( path_base "dependencies/timing.hpp" ); @@ -166,6 +167,7 @@ int gen_main() header.print( printing ); header.print( containers ); header.print( hashing ); + header.print( simd ); header.print( strings ); header.print( filesystem ); header.print( timing ); diff --git a/test/c_library/dependencies/simd.c b/test/c_library/dependencies/simd.c new file mode 100644 index 0000000..5175cd7 --- /dev/null +++ b/test/c_library/dependencies/simd.c @@ -0,0 +1,90 @@ +gen_internal void expect_simd_lanes() +{ +#if defined(GEN_SYSTEM_WINDOWS) && (defined(_M_X64) || defined(__x86_64__)) + gen_u8 storage[32]; + gen_mem_set(storage, 0x11, 32); + gen_u8 pattern[16] = { + 0x00, 0x80, 0xFF, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D + }; + gen_mem_copy(storage + 1, pattern, 16); + gen_u8x16 loaded = gen_u8x16_load(storage + 1); + gen_u8x16 again = gen_u8x16_load(storage + 1); + gen_u16 same = gen_u8x16_mask(gen_u8x16_equal(loaded, again)); + gen_u16 zero_mask = gen_u8x16_mask(gen_u8x16_equal(loaded, gen_u8x16_splat(0x00))); + gen_u16 high_bit = gen_u8x16_mask(gen_u8x16_equal(loaded, gen_u8x16_splat(0x80))); + gen_u16 top = gen_u8x16_mask(gen_u8x16_equal(loaded, gen_u8x16_splat(0xFF))); + expect(same == 0xFFFF, gen_txt("simd u8 equal load")); + expect(zero_mask == 0x0001, gen_txt("simd byte 0")); + expect(high_bit == 0x0002, gen_txt("simd byte 0x80")); + expect(top == 0x0004, gen_txt("simd byte 0xFF")); + + gen_u8 high_lane[16]; + gen_mem_set(high_lane, 0x22, 16); + high_lane[15] = 0xFF; + gen_u8x16 high_loaded = gen_u8x16_load(high_lane); + gen_u16 lane15 = gen_u8x16_mask(gen_u8x16_equal(high_loaded, gen_u8x16_splat(0xFF))); + expect(lane15 == 0x8000, gen_txt("simd lane 15")); + expect(gen_u32_mask_first_set(lane15) == 15, gen_txt("simd first set lane 15")); + + gen_u8 window[24]; + gen_mem_set(window, 0xEE, 24); + window[1] = 0x00; + window[2] = 0x80; + window[3] = 0xFF; + window[4] = 0x11; + window[5] = 0x22; + window[6] = 0x33; + window[7] = 0x44; + window[8] = 0x55; + gen_u8x16 loaded8 = gen_u8x16_load8(window + 1); + gen_u16 upper = gen_u8x16_mask(gen_u8x16_equal(loaded8, gen_u8x16_splat(0x00))); + gen_u16 low_ff = gen_u8x16_mask(gen_u8x16_equal(loaded8, gen_u8x16_splat(0xFF))); + gen_u16 low_80 = gen_u8x16_mask(gen_u8x16_equal(loaded8, gen_u8x16_splat(0x80))); + gen_u16 byte7 = gen_u8x16_mask(gen_u8x16_equal(loaded8, gen_u8x16_splat(0x55))); + expect((upper & 0xFF00) == 0xFF00, gen_txt("simd load8 upper zero")); + expect((upper & 0x0001) == 0x0001, gen_txt("simd load8 low zero")); + expect(low_ff == 0x0004, gen_txt("simd load8 low 0xFF")); + expect(low_80 == 0x0002, gen_txt("simd load8 low 0x80")); + expect(byte7 == 0x0080, gen_txt("simd load8 byte 7")); + gen_u16 sentinel = gen_u8x16_mask(gen_u8x16_equal(loaded8, gen_u8x16_splat(0xEE))); + expect(sentinel == 0, gen_txt("simd load8 ignores tail")); + gen_u8x16 loaded16 = gen_u8x16_load(window + 1); + gen_u16 tail = gen_u8x16_mask(gen_u8x16_equal(loaded16, gen_u8x16_splat(0xEE))); + expect(tail == 0xFF00, gen_txt("simd load16 sees load8 tail")); + + gen_u32 hashes[4] = { 0x00000000u, 0x7FFFFFFFu, 0x80000000u, 0xFFFFFFFFu }; + gen_u32x4 hash_loaded = gen_u32x4_load(hashes); + gen_u8 lane0 = gen_u32x4_mask(gen_u32x4_equal(hash_loaded, gen_u32x4_splat(0x00000000u))); + gen_u8 lane1 = gen_u32x4_mask(gen_u32x4_equal(hash_loaded, gen_u32x4_splat(0x7FFFFFFFu))); + gen_u8 lane2 = gen_u32x4_mask(gen_u32x4_equal(hash_loaded, gen_u32x4_splat(0x80000000u))); + gen_u8 lane3 = gen_u32x4_mask(gen_u32x4_equal(hash_loaded, gen_u32x4_splat(0xFFFFFFFFu))); + expect(lane0 == 0x01, gen_txt("simd u32 lane 0")); + expect(lane1 == 0x02, gen_txt("simd u32 lane 1")); + expect(lane2 == 0x04, gen_txt("simd u32 bit31 lane")); + expect(lane3 == 0x08, gen_txt("simd u32 lane 3")); + expect((lane2 & 0xF0) == 0, gen_txt("simd u32 mask low bits")); + + gen_u32 all_values[4] = { 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u }; + gen_u32x4 all_loaded = gen_u32x4_load(all_values); + gen_u8 all_mask = gen_u32x4_mask(gen_u32x4_equal(all_loaded, gen_u32x4_splat(0x80000000u))); + expect(all_mask == 0x0F, gen_txt("simd u32 all bit31")); + gen_u8 none_mask = gen_u32x4_mask(gen_u32x4_equal(all_loaded, gen_u32x4_splat(0x7FFFFFFFu))); + expect(none_mask == 0x00, gen_txt("simd u32 none")); + + gen_u8 hash_storage[20]; + gen_mem_set (hash_storage, 0xA5, 20); + gen_mem_copy(hash_storage + 1, hashes, 16); + gen_u32x4 unaligned = gen_u32x4_load(hash_storage + 1); + gen_u8 unaligned_mask = gen_u32x4_mask(gen_u32x4_equal(unaligned, gen_u32x4_splat(0x80000000u))); + expect(unaligned_mask == 0x04, gen_txt("simd u32 unaligned bit31")); + + expect(gen_u32_mask_first_set(0x1u) == 0, gen_txt("simd first set bit 0")); + expect(gen_u32_mask_first_set(0x14u) == 2, gen_txt("simd first set lowest")); + expect(gen_u32_mask_first_set(0x8000u) == 15, gen_txt("simd first set bit 15")); + expect(gen_u32_mask_first_set(0x80000000u) == 31, gen_txt("simd first set bit 31")); + expect(gen_u32_mask_first_set(0xFFFFFFFFu) == 0, gen_txt("simd first set all bits")); +#endif +} diff --git a/test/c_library/dependencies/strings.c b/test/c_library/dependencies/strings.c new file mode 100644 index 0000000..bd9e3ae --- /dev/null +++ b/test/c_library/dependencies/strings.c @@ -0,0 +1,631 @@ +gen_internal gen_Str bounded_format(char* storage, gen_ssize storage_len, char const* bytes, gen_ssize len) { + gen_mem_set(storage, '!', storage_len); + if (len > 0) + gen_mem_copy(storage, bytes, len); + storage[storage_len - 1] = '\0'; + gen_Str result = { storage, len }; + return result; +} + +gen_internal void expect_template_expansion(gen_Str label, gen_Str format, gen_StrTemplateSlot* slots, gen_ssize slot_count, gen_Str expansion) { + gen_StrBuilder output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), label); + gen_Slice_StrTemplateSlot slice = { 0, 0 }; + if (slot_count != 0) { + slice.Ptr = slots; + slice.Len = slot_count; + } + bool appended = gen_strbuilder_append_template(& output, slice, format); + expect(appended, label); + gen_Str got = gen_strbuilder_to_str(output); + expect(got.Len == 4 + expansion.Len, label); + expect(gen_str_starts_with(got, gen_txt("KEEP")), label); + if (got.Len >= 4 + expansion.Len) { + gen_Str tail = { got.Ptr + 4, expansion.Len }; + expect(gen_str_are_equal(tail, expansion), label); + } + gen_StrBuilderHeader* header = gen_strbuilder_get_header(output); + expect(header->Allocator.Proc == gen_heap_allocator_proc, label); + gen_strbuilder_free(& output); +} + +gen_internal void expect_template_text(gen_Str label, gen_Str format, gen_StrTemplateSlot* slots, gen_ssize slot_count, gen_Str expansion) { + expect_template_expansion(label, format, slots, slot_count, expansion); +} + +gen_internal void expect_literal_boundary(gen_ssize mark) { + char format_storage[64]; + char format_bytes [40]; + for (gen_ssize idx = 0; idx < mark; ++ idx) format_bytes[idx] = 'L'; + gen_Str format = bounded_format(format_storage, 64, format_bytes, mark); + gen_Str expansion = { format_bytes, mark }; + expect(format_storage[mark] == '!', gen_txt("literal boundary")); + expect_template_expansion(gen_txt("literal boundary"), format, 0, 0, expansion); +} + +gen_internal void expect_open_boundary(gen_ssize mark) { + char format_storage [64]; + char format_bytes [48]; + char expansion_bytes[48]; + gen_ssize format_len = 0; + for (gen_ssize idx = 0; idx < mark; ++ idx) + format_bytes[format_len ++] = 'L'; + format_bytes[format_len ++] = '<'; + format_bytes[format_len ++] = 'k'; + format_bytes[format_len ++] = '>'; + format_bytes[format_len ++] = 'Z'; + gen_Str format = bounded_format(format_storage, 64, format_bytes, format_len); + gen_StrTemplateSlot slot = {0}; + slot.Key = gen_txt("k"); + slot.Value = gen_txt("V"); + gen_ssize expansion_len = 0; + for (gen_ssize idx = 0; idx < mark; ++ idx) + expansion_bytes[expansion_len ++] = 'L'; + expansion_bytes[expansion_len ++] = 'V'; + expansion_bytes[expansion_len ++] = 'Z'; + gen_Str expansion = { expansion_bytes, expansion_len }; + expect(format.Ptr[mark] == '<', gen_txt("open boundary")); + expect(format_storage[format_len] == '!', gen_txt("open boundary")); + expect_template_expansion(gen_txt("open boundary"), format, & slot, 1, expansion); +} + +gen_internal void expect_close_boundary(gen_ssize mark) { + char format_storage[64]; + char format_bytes [48]; + char key_storage [40]; + gen_ssize key_len = mark - 1; + gen_mem_set(key_storage, '!', 40); + for (gen_ssize idx = 0; idx < key_len; ++ idx) + key_storage[idx] = 'k'; + key_storage[39] = '\0'; + gen_Str key = { key_storage, key_len }; + gen_ssize format_len = 0; + format_bytes[format_len ++] = '<'; + for (gen_ssize idx = 0; idx < key_len; ++ idx) + format_bytes[format_len ++] = 'k'; + format_bytes[format_len ++] = '>'; + format_bytes[format_len ++] = 'Z'; + gen_Str format = bounded_format(format_storage, 64, format_bytes, format_len); + gen_StrTemplateSlot slot = {0}; + slot.Key = key; + slot.Value = gen_txt("V"); + expect(format.Ptr [mark] == '>', gen_txt("close boundary")); + expect(key.Ptr [key.Len] == '!', gen_txt("close boundary")); + expect(format_storage[format_len] == '!', gen_txt("close boundary")); + expect_template_expansion(gen_txt("close boundary"), format, & slot, 1, gen_txt("VZ")); +} + +gen_internal void expect_template_shapes() +{ + char literal_storage[8]; + char literal_bytes [6] = { 'a', 'b', 'c', 'D', 'E', 'F' }; + gen_Str literal = bounded_format(literal_storage, 8, literal_bytes, 3); + expect(literal_storage[3] == '!', gen_txt("literal sentinel")); + expect_template_expansion(gen_txt("literal bounded"), literal, 0, 0, gen_txt("abc")); + + char empty_storage[4]; + gen_Str empty_format = bounded_format(empty_storage, 4, empty_storage, 0); + expect(empty_storage[0] == '!', gen_txt("empty sentinel")); + expect_template_expansion(gen_txt("empty format"), empty_format, 0, 0, gen_txt("")); + + gen_StrTemplateSlot name_slot = {0}; + name_slot.Key = gen_txt("name"); + name_slot.Value = gen_txt("world"); + expect_template_text(gen_txt("one slot"), gen_txt("hello !"), & name_slot, 1, gen_txt("hello world!")); + + gen_StrTemplateSlot repeat_slot = {0}; + repeat_slot.Key = gen_txt("w"); + repeat_slot.Value = gen_txt("v"); + expect_template_text(gen_txt("repeated slot"), gen_txt("--"), & repeat_slot, 1, gen_txt("v-v-v")); + + gen_StrTemplateSlot adjacent[2] = {0}; + adjacent[0].Key = gen_txt("a"); + adjacent[0].Value = gen_txt("X"); + adjacent[1].Key = gen_txt("b"); + adjacent[1].Value = gen_txt("Y"); + expect_template_text(gen_txt("adjacent slots"), gen_txt(""), adjacent, 2, gen_txt("XYX")); + + gen_StrTemplateSlot raw_slots[3] = {0}; + raw_slots[0].Key = gen_txt("raw"); + raw_slots[0].Value = gen_txt("ac"); + raw_slots[1].Key = gen_txt("angles"); + raw_slots[1].Value = gen_txt("<>"); + raw_slots[2].Key = gen_txt("b"); + raw_slots[2].Value = gen_txt("SHOULD_NOT"); + expect_template_text(gen_txt("value angles literal"), gen_txt(""), raw_slots, 3, gen_txt("ac<>")); + + char empty_value_storage[2] = { '!', '!' }; + gen_StrTemplateSlot empty_slot = {0}; + empty_slot.Key = gen_txt("e"); + empty_slot.Value = (gen_Str){ empty_value_storage, 0 }; + expect_template_text(gen_txt("empty value"), gen_txt("AB"), & empty_slot, 1, gen_txt("AB")); + + gen_StrTemplateSlot empty_key = {0}; + empty_key.Key = (gen_Str){ 0, 0 }; + empty_key.Value = gen_txt("Z"); + expect_template_text(gen_txt("empty key"), gen_txt("<>"), & empty_key, 1, gen_txt("Z")); + + gen_StrTemplateSlot null_value = {0}; + null_value.Key = gen_txt("e"); + null_value.Value = (gen_Str){ 0, 0 }; + expect(null_value.Value.Ptr == 0, gen_txt("null empty value pointer")); + expect(null_value.Value.Len == 0, gen_txt("null empty value length")); + expect_template_text(gen_txt("null empty value"), gen_txt("AB"), & null_value, 1, gen_txt("AB")); + + char nul_value_bytes[4] = { 'a', '\0', 'b', '!' }; + char nul_expected [5] = { '[', 'a', '\0', 'b', ']' }; + gen_StrTemplateSlot nul_slot = {0}; + nul_slot.Key = gen_txt("n"); + nul_slot.Value = (gen_Str){ nul_value_bytes, 3 }; + gen_Str nul_expansion = { nul_expected, 5 }; + expect_template_text(gen_txt("embedded nul"), gen_txt("[]"), & nul_slot, 1, nul_expansion); + + gen_StrTemplateSlot duffle[2] = {0}; + duffle[0].Key = gen_txt("operands[0]"); + duffle[0].Value = gen_txt("eax"); + duffle[1].Key = gen_txt("mod_rm.bits"); + duffle[1].Value = gen_txt("11"); + expect_template_text(gen_txt("duffle keys"), gen_txt("mov , ;"), duffle, 2, gen_txt("mov eax, 11;")); +} + +gen_internal void fill_lookup_slots(gen_StrTemplateSlot* slots, gen_ssize slot_count) { + gen_Str keys[9] = { + gen_txt("k0"), gen_txt("k1"), gen_txt("k2"), gen_txt("k3"), gen_txt("k4"), + gen_txt("k5"), gen_txt("k6"), gen_txt("k7"), gen_txt("k8") + }; + gen_Str values[9] = { + gen_txt("V0"), gen_txt("V1"), gen_txt("V2"), gen_txt("V3"), gen_txt("V4"), + gen_txt("V5"), gen_txt("V6"), gen_txt("V7"), gen_txt("V8") + }; + for (gen_ssize idx = 0; idx < slot_count; ++ idx) { + slots[idx].Key = keys[idx]; + slots[idx].Value = values[idx]; + } +} + +gen_internal void expect_template_lookup() +{ + gen_StrTemplateSlot slots[9] = {0}; + expect_template_text(gen_txt("lookup 0"), gen_txt("none"), 0, 0, gen_txt("none")); + + fill_lookup_slots(slots, 1); + expect_template_text(gen_txt("lookup 1"), gen_txt(""), slots, 1, gen_txt("V0")); + + fill_lookup_slots(slots, 2); + expect_template_text(gen_txt("lookup 2"), gen_txt(""), slots, 2, gen_txt("V1V0")); + + fill_lookup_slots(slots, 3); + expect_template_text(gen_txt("lookup 3"), gen_txt("--"), slots, 3, gen_txt("V2-V0-V1")); + + fill_lookup_slots(slots, 4); + expect_template_text(gen_txt("lookup 4"), gen_txt(""), slots, 4, gen_txt("V3V0V2V1")); + + fill_lookup_slots(slots, 5); + expect_template_text(gen_txt("lookup 5 tail"), gen_txt(""), slots, 5, gen_txt("V4V0V3")); + + fill_lookup_slots(slots, 8); + expect_template_text(gen_txt("lookup 8 lanes"), gen_txt(""), slots, 8, gen_txt("V7V4V3V0")); + + fill_lookup_slots(slots, 9); + expect_template_text(gen_txt("lookup 9 tail"), gen_txt(""), slots, 9, gen_txt("V8V5V6V4")); + + gen_StrTemplateSlot dup_small[3] = {0}; + dup_small[0].Key = gen_txt("dup"); dup_small[0].Value = gen_txt("FIRST"); + dup_small[1].Key = gen_txt("other"); dup_small[1].Value = gen_txt("X"); + dup_small[2].Key = gen_txt("dup"); dup_small[2].Value = gen_txt("SECOND"); + expect_template_text(gen_txt("duplicate first wins"), gen_txt(""), dup_small, 3, gen_txt("FIRSTX")); + + gen_StrTemplateSlot dup_lanes[4] = {0}; + dup_lanes[0].Key = gen_txt("same"); dup_lanes[0].Value = gen_txt("A"); + dup_lanes[1].Key = gen_txt("same"); dup_lanes[1].Value = gen_txt("B"); + dup_lanes[2].Key = gen_txt("other"); dup_lanes[2].Value = gen_txt("C"); + dup_lanes[3].Key = gen_txt("same"); dup_lanes[3].Value = gen_txt("D"); + expect_template_text(gen_txt("duplicate lane first wins"), gen_txt(""), dup_lanes, 4, gen_txt("AC")); + + gen_StrTemplateSlot dup_tail[5] = {0}; + dup_tail[0].Key = gen_txt("dup"); dup_tail[0].Value = gen_txt("FIRST"); + dup_tail[1].Key = gen_txt("k1"); dup_tail[1].Value = gen_txt("V1"); + dup_tail[2].Key = gen_txt("k2"); dup_tail[2].Value = gen_txt("V2"); + dup_tail[3].Key = gen_txt("k3"); dup_tail[3].Value = gen_txt("V3"); + dup_tail[4].Key = gen_txt("dup"); dup_tail[4].Value = gen_txt("TAIL"); + expect_template_text(gen_txt("duplicate tail first wins"), gen_txt(""), dup_tail, 5, gen_txt("FIRST")); +} + +gen_internal void expect_template_boundaries() { + gen_ssize marks[12] = { 7, 8, 9, 15, 16, 17, 23, 24, 25, 31, 32, 33 }; + for (gen_ssize idx = 0; idx < gen_count_of(marks); ++ idx) { + expect_literal_boundary(marks[idx]); + expect_open_boundary(marks[idx]); + expect_close_boundary(marks[idx]); + } +} + +gen_internal void expect_template_growth(gen_ssize printf_maxlen) +{ + gen_ssize huge_len = printf_maxlen + 64; + char* huge = gen_rcast(char*, gen_alloc(gen_heap(), huge_len + 1)); + expect(huge != 0, gen_txt("growth alloc")); + if (huge == 0) return; + gen_mem_set(huge, 'Q', huge_len); + huge[huge_len] = '!'; + + gen_Str value = { huge, huge_len }; + gen_StrTemplateSlot slot = {0}; + slot.Key = gen_txt("blob"); + slot.Value = value; + + char format_storage[16]; + gen_Str format = gen_txt(""); + gen_StrBuilder output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_capacity(output) == 1, gen_txt("growth initial capacity")); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), gen_txt("growth prefix")); + + gen_Slice_StrTemplateSlot slice = { & slot, 1 }; + bool appended = gen_strbuilder_append_template(& output, slice, format); + expect(appended, gen_txt("growth append")); + + gen_Str got = gen_strbuilder_to_str(output); + expect(got.Len == 4 + huge_len, gen_txt("growth length")); + expect(got.Len > printf_maxlen, gen_txt("growth beyond printf max")); + expect(gen_str_starts_with(got, gen_txt("KEEP")), gen_txt("growth keeps prefix")); + + gen_ssize capacity = gen_strbuilder_capacity(output); + expect(capacity >= got.Len, gen_txt("growth capacity")); + if (got.Len == 4 + huge_len && capacity >= got.Len) { + gen_b32 payload_matches = true; + for (gen_ssize idx = 0; idx < got.Len - 4; ++ idx) { + if (got.Ptr[4 + idx] != 'Q') payload_matches = false; + } + expect(payload_matches, gen_txt("growth bytes")); + } + + gen_StrBuilderHeader* header = gen_strbuilder_get_header(output); + expect(header->Allocator.Proc == gen_heap_allocator_proc, gen_txt("growth allocator")); + expect(huge[huge_len] == '!', gen_txt("growth sentinel")); + gen_strbuilder_free(& output); + gen_allocator_free(gen_heap(), huge); +} + +gen_internal void expect_template_crc_collision() { + gen_Str plumless = gen_txt("plumless"); + gen_Str buckeroo = gen_txt("buckeroo"); + gen_u32 plum_hash = gen_crc32(plumless.Ptr, plumless.Len); + gen_u32 buck_hash = gen_crc32(buckeroo.Ptr, buckeroo.Len); + expect(plum_hash == buck_hash, gen_txt("crc collision equal")); + expect(plum_hash == 0x4ddb0c25, gen_txt("crc collision value")); + expect(gen_str_are_equal(plumless, buckeroo) == false, gen_txt("crc collision keys differ")); + + gen_StrTemplateSlot scalar[2] = {0}; + scalar[0].Key = plumless; scalar[0].Value = gen_txt("WRONG"); + scalar[1].Key = buckeroo; scalar[1].Value = gen_txt("RIGHT"); + expect_template_text(gen_txt("crc scalar 2"), gen_txt(""), scalar, 2, gen_txt("RIGHT")); + + gen_StrTemplateSlot lanes[4] = {0}; + lanes[0].Key = plumless; lanes[0].Value = gen_txt("WRONG"); + lanes[1].Key = gen_txt("k1"); lanes[1].Value = gen_txt("V1"); + lanes[2].Key = buckeroo; lanes[2].Value = gen_txt("RIGHT"); + lanes[3].Key = gen_txt("k3"); lanes[3].Value = gen_txt("V3"); + expect_template_text(gen_txt("crc lane reject"), gen_txt(""), lanes, 4, gen_txt("RIGHT")); + + gen_StrTemplateSlot tail[5] = {0}; + tail[0].Key = plumless; tail[0].Value = gen_txt("WRONG"); + tail[1].Key = gen_txt("k1"); tail[1].Value = gen_txt("V1"); + tail[2].Key = gen_txt("k2"); tail[2].Value = gen_txt("V2"); + tail[3].Key = gen_txt("k3"); tail[3].Value = gen_txt("V3"); + tail[4].Key = buckeroo; tail[4].Value = gen_txt("TAIL"); + expect_template_text(gen_txt("crc tail after false hit"), gen_txt(""), tail, 5, gen_txt("TAIL")); +} + +gen_internal void* resize_fail_allocator_proc(void* allocator_data, gen_AllocType type, gen_ssize size, gen_ssize alignment + , void* old_memory, gen_ssize old_size + , gen_u64 flags +){ + if (type == EAllocation_RESIZE) { + gen_s32* attempts = gen_rcast(gen_s32*, allocator_data); + attempts[0] = attempts[0] + 1; + return 0; + } + return gen_heap_allocator_proc(0, type, size, alignment, old_memory, old_size, flags); +} + +gen_internal void expect_template_resize_fail(gen_ssize printf_maxlen) +{ + gen_ssize huge_len = printf_maxlen + 64; + char* huge = gen_rcast(char*, gen_alloc(gen_heap(), huge_len + 1)); + expect(huge != 0, gen_txt("resize fail alloc")); + if (huge == 0) return; + gen_mem_set(huge, 'L', huge_len); + huge[huge_len] = '!'; + gen_Str huge_text = { huge, huge_len }; + + gen_s32 literal_attempts = 0; + gen_AllocatorInfo literal_alloc = {0}; + literal_alloc.Proc = resize_fail_allocator_proc; + literal_alloc.Data = & literal_attempts; + gen_StrBuilder literal_output = gen_strbuilder_make_reserve(literal_alloc, 4); + expect(gen_strbuilder_append_str(& literal_output, gen_txt("KEEP")), gen_txt("resize fail literal prefix")); + + gen_Slice_StrTemplateSlot literal_slots = { 0, 0 }; + bool literal_appended = gen_strbuilder_append_template(& literal_output, literal_slots, huge_text); + expect(literal_appended == false, gen_txt("resize fail literal")); + + gen_Str literal_got = gen_strbuilder_to_str(literal_output); + expect(literal_got.Len == 4, gen_txt("resize fail literal length")); + expect(gen_str_are_equal(literal_got, gen_txt("KEEP")), gen_txt("resize fail literal keep")); + expect(literal_attempts > 0, gen_txt("resize fail literal attempts")); + gen_strbuilder_free(& literal_output); + + gen_s32 value_attempts = 0; + gen_AllocatorInfo value_alloc = {0}; + value_alloc.Proc = resize_fail_allocator_proc; + value_alloc.Data = & value_attempts; + + gen_StrTemplateSlot slot = {0}; + slot.Key = gen_txt("blob"); + slot.Value = huge_text; + char format_storage[16]; + + gen_Str format = gen_txt(""); + gen_StrBuilder value_output = gen_strbuilder_make_reserve(value_alloc, 4); + expect(gen_strbuilder_append_str(& value_output, gen_txt("KEEP")), gen_txt("resize fail value prefix")); + + gen_Slice_StrTemplateSlot value_slots = { & slot, 1 }; + bool value_appended = gen_strbuilder_append_template(& value_output, value_slots, format); + expect(value_appended == false, gen_txt("resize fail value")); + + gen_Str value_got = gen_strbuilder_to_str(value_output); + expect(value_got.Len == 4, gen_txt("resize fail value length")); + expect(gen_str_are_equal(value_got, gen_txt("KEEP")), gen_txt("resize fail value keep")); + expect(value_attempts > 0, gen_txt("resize fail value attempts")); + + gen_strbuilder_free(& value_output); + gen_allocator_free(gen_heap(), huge); +} + +gen_internal gen_Str counted_template_value (gen_s32* reads) { reads[0] = reads[0] + 1; return gen_txt("ONCE"); } +gen_internal gen_Str counted_template_format(gen_s32* reads) { reads[0] = reads[0] + 1; return gen_txt(":"); } +gen_internal gen_StrBuilder* counted_template_output(gen_StrBuilder* output, gen_s32* reads) { reads[0] = reads[0] + 1; return output; } +gen_internal bool* counted_template_result(bool* result, gen_s32* reads) { reads[0] = reads[0] + 1; return result; } + +gen_internal void expect_template_slice() +{ + gen_StrTemplateSlot slots[2] = { + gen_strtmpl_slot("a", gen_txt("X")), + gen_strtmpl_slot("b", gen_txt("Y")) + }; + gen_Slice_StrTemplateSlot slice = { slots, 2 }; + gen_StrBuilder output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), gen_txt("slice prefix")); + + bool appended = gen_strbuilder_append_template(& output, slice, gen_txt("")); + expect(appended, gen_txt("slice append")); + expect(gen_str_are_equal(gen_strbuilder_to_str(output), gen_txt("KEEPYXY")), gen_txt("slice text")); + expect(gen_str_are_equal(slots[0].Key, gen_txt("a")), gen_txt("slice key")); + expect(gen_str_are_equal(slots[0].Value, gen_txt("X")), gen_txt("slice value")); + gen_strbuilder_free(& output); + + slice.Ptr[0].Value = gen_txt("Z"); + gen_StrBuilder mutated_output = gen_strbuilder_make_reserve(gen_heap(), 1); + bool mutated_appended = gen_strbuilder_append_template(& mutated_output, slice, gen_txt("")); + expect(mutated_appended, gen_txt("slice mutate append")); + expect(gen_str_are_equal(gen_strbuilder_to_str(mutated_output), gen_txt("YZY")), gen_txt("slice mutate text")); + gen_strbuilder_free(& mutated_output); + + gen_Slice_StrTemplateSlot empty_slice = { 0, 0 }; + gen_StrBuilder empty_output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& empty_output, gen_txt("KEEP")), gen_txt("zero slice prefix")); + + bool empty_appended = gen_strbuilder_append_template(& empty_output, empty_slice, gen_txt("none")); + expect(empty_appended, gen_txt("zero slice append")); + expect(gen_str_are_equal(gen_strbuilder_to_str(empty_output), gen_txt("KEEPnone")), gen_txt("zero slice text")); + expect(empty_slice.Ptr == 0, gen_txt("zero slice pointer")); + expect(empty_slice.Len == 0, gen_txt("zero slice length")); + gen_strbuilder_free(& empty_output); +} + +gen_internal void expect_template_wrapper() { + gen_StrBuilder output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), gen_txt("wrapper prefix")); + bool appended = false; + gen_strtmpl_append(& output, appended, gen_strtmpl_slots( + gen_strtmpl_slot("name", gen_txt("world")), + gen_strtmpl_slot("tail", gen_txt("!")) + ), + gen_txt( + "hello \n" + "" + ) + ); + expect(appended, gen_txt("wrapper multiline result")); + gen_Str got = gen_strbuilder_to_str(output); + expect(gen_str_starts_with(got, gen_txt("KEEP")), gen_txt("wrapper prefix kept")); + expect(gen_str_are_equal (got, gen_txt("KEEPhello world\nworld!")), gen_txt("wrapper multiline text")); + gen_strbuilder_free(& output); +} + +gen_internal void expect_template_wrapper_scope() +{ + gen_StrBuilder then_output = gen_strbuilder_make_reserve(gen_heap(), 1); + gen_StrBuilder else_output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& then_output, gen_txt("KEEP")), gen_txt("wrapper then prefix")); + expect(gen_strbuilder_append_str(& else_output, gen_txt("KEEP")), gen_txt("wrapper else prefix")); + + bool then_result = false; + bool else_result = false; + gen_s32 selector = 1; + gen_s32 after_branch = 0; + if (selector == 1) + gen_strtmpl_append(& then_output, then_result, gen_strtmpl_slots(gen_strtmpl_slot("arm", gen_txt("THEN"))), + gen_txt("") + ); + else + gen_strtmpl_append(& else_output, else_result, gen_strtmpl_slots(gen_strtmpl_slot("arm", gen_txt("ELSE"))), + gen_txt("") + ); + after_branch = 1; + expect(after_branch == 1, gen_txt("wrapper then continues")); + expect(then_result, gen_txt("wrapper then assigned")); + expect(else_result == false, gen_txt("wrapper else skipped")); + expect(gen_str_are_equal(gen_strbuilder_to_str(then_output), gen_txt("KEEPTHEN")), gen_txt("wrapper then text")); + expect(gen_str_are_equal(gen_strbuilder_to_str(else_output), gen_txt("KEEP")), gen_txt("wrapper else untouched")); + gen_strbuilder_free(& then_output); + gen_strbuilder_free(& else_output); + + then_output = gen_strbuilder_make_reserve(gen_heap(), 1); + else_output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& then_output, gen_txt("KEEP")), gen_txt("wrapper skipped then prefix")); + expect(gen_strbuilder_append_str(& else_output, gen_txt("KEEP")), gen_txt("wrapper taken else prefix")); + + then_result = false; + else_result = false; + selector = 0; + after_branch = 0; + if (selector == 1) gen_strtmpl_append(& then_output, then_result, gen_strtmpl_slots(gen_strtmpl_slot("arm", gen_txt("THEN"))), gen_txt("")); + else gen_strtmpl_append(& else_output, else_result, gen_strtmpl_slots(gen_strtmpl_slot("arm", gen_txt("ELSE"))), gen_txt("")); + after_branch = 1; + expect(after_branch == 1, gen_txt("wrapper else continues")); + expect(then_result == false, gen_txt("wrapper then skipped")); + expect(else_result, gen_txt("wrapper else assigned")); + expect(gen_str_are_equal(gen_strbuilder_to_str(then_output), gen_txt("KEEP")), gen_txt("wrapper then untouched")); + expect(gen_str_are_equal(gen_strbuilder_to_str(else_output), gen_txt("KEEPELSE")), gen_txt("wrapper else text")); + gen_strbuilder_free(& then_output); + gen_strbuilder_free(& else_output); +} + +gen_internal void expect_template_wrapper_once() { + gen_s32 value_reads = 0; + gen_s32 format_reads = 0; + gen_s32 output_reads = 0; + gen_s32 result_reads = 0; + gen_StrBuilder output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), gen_txt("wrapper once prefix")); + bool appended = false; + gen_strtmpl_append( + counted_template_output(& output, & output_reads), + counted_template_result(& appended, & result_reads)[0], + gen_strtmpl_slots( + gen_strtmpl_slot("k", counted_template_value(& value_reads)), + gen_strtmpl_slot("tail", gen_txt("Z")) + ), + counted_template_format(& format_reads) + ); + expect(appended, gen_txt("wrapper once result")); + expect(value_reads == 1, gen_txt("wrapper value once")); + expect(format_reads == 1, gen_txt("wrapper format once")); + expect(output_reads == 1, gen_txt("wrapper output once")); + expect(result_reads == 1, gen_txt("wrapper result once")); + expect(gen_str_are_equal(gen_strbuilder_to_str(output), gen_txt("KEEPONCE:ONCE")), gen_txt("wrapper once text")); + gen_strbuilder_free(& output); +} + +gen_internal void expect_template_wrapper_resize_fail() { + gen_s32 attempts = 0; + gen_AllocatorInfo alloc = {0}; + alloc.Proc = resize_fail_allocator_proc; + alloc.Data = & attempts; + gen_StrBuilder output = gen_strbuilder_make_reserve(alloc, 4); + expect(gen_strbuilder_capacity(output) == 4, gen_txt("wrapper resize capacity")); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), gen_txt("wrapper resize prefix")); + + bool appended = true; + gen_strtmpl_append(& output, appended, + gen_strtmpl_slots(gen_strtmpl_slot("blob", gen_txt("X"))), + gen_txt("") + ); + expect(appended == false, gen_txt("wrapper resize assigned false")); + expect(gen_str_are_equal(gen_strbuilder_to_str(output), gen_txt("KEEP")), gen_txt("wrapper resize keep")); + expect(attempts > 0, gen_txt("wrapper resize attempts")); + gen_strbuilder_free(& output); +} + +gen_internal void expect_template_slot_literal() +{ + gen_Str value = gen_txt("V"); + gen_StrTemplateSlot slot = gen_strtmpl_slot("k", value); + expect(gen_str_are_equal(slot.Key, gen_txt("k")), gen_txt("slot literal key slice")); + expect(slot.Key.Ptr[slot.Key.Len] == '\0', gen_txt("slot literal key terminator")); + expect(slot.Value.Ptr == value.Ptr, gen_txt("slot literal value pointer")); + expect(slot.Value.Len == value.Len, gen_txt("slot literal value length")); + + gen_Str assigned = gen_txt("W"); + slot = gen_strtmpl_slot("key", assigned); + expect(gen_str_are_equal(slot.Key, gen_txt("key")), gen_txt("slot assign key slice")); + expect(slot.Value.Ptr == assigned.Ptr, gen_txt("slot assign value pointer")); + expect(slot.Value.Len == assigned.Len, gen_txt("slot assign value length")); + expect_template_text(gen_txt("slot assign expansion"), gen_txt(""), & slot, 1, gen_txt("W")); + + gen_StrTemplateSlot row[2] = { + gen_strtmpl_slot("a", gen_txt("X")), + gen_strtmpl_slot("b", gen_txt("Y")) + }; + expect(gen_str_are_equal(row[0].Value, gen_txt("X")), gen_txt("slot array value slice")); + expect(gen_str_are_equal(row[1].Key, gen_txt("b")), gen_txt("slot array key slice")); + expect_template_text(gen_txt("slot array entry"), gen_txt(""), row, 2, gen_txt("YX")); + + gen_StrBuilder output = gen_strbuilder_make_reserve(gen_heap(), 1); + expect(gen_strbuilder_append_str(& output, gen_txt("KEEP")), gen_txt("slot wrapper prefix")); + bool appended = false; + gen_strtmpl_append(& output, appended, gen_strtmpl_slots( + gen_strtmpl_slot("name", gen_txt("world")), + gen_strtmpl_slot("tail", gen_txt("!")) + ), + gen_txt("") + ); + expect(appended, gen_txt("slot wrapper result")); + expect(gen_str_are_equal(gen_strbuilder_to_str(output), gen_txt("KEEPworld!")), gen_txt("slot wrapper text")); + gen_strbuilder_free(& output); + + gen_StrTemplateSlot long_key = gen_strtmpl_slot("operands[0]", gen_txt("eax")); + expect(gen_str_are_equal(long_key.Key, gen_txt("operands[0]")), gen_txt("slot literal key slice")); + expect(long_key.Key.Ptr[long_key.Key.Len] == '\0', gen_txt("slot literal key excludes nul")); + expect(gen_str_are_equal(long_key.Value, gen_txt("eax")), gen_txt("slot literal value slice")); + + gen_s32 value_reads = 0; + gen_StrTemplateSlot once = gen_strtmpl_slot("k", counted_template_value(& value_reads)); + expect(value_reads == 1, gen_txt("slot value once")); + expect(gen_str_are_equal(once.Value, gen_txt("ONCE")), gen_txt("slot value once text")); + expect(once.Key.Len == 1, gen_txt("slot value once key")); + + char nul_bytes[4] = { 'a', '\0', 'b', '!' }; + gen_Str nul_value = { nul_bytes, 3 }; + gen_StrTemplateSlot nul_slot = gen_strtmpl_slot("n", nul_value); + expect(nul_slot.Value.Ptr == nul_bytes, gen_txt("slot nul pointer")); + expect(nul_slot.Value.Len == 3, gen_txt("slot nul length")); + expect(nul_slot.Value.Ptr[1] == '\0', gen_txt("slot nul byte")); + char nul_expected[5] = { '[', 'a', '\0', 'b', ']' }; + gen_Str nul_expansion = { nul_expected, 5 }; + expect_template_text(gen_txt("slot nul expansion"), gen_txt("[]"), & nul_slot, 1, nul_expansion); + + gen_Str angle_value = gen_txt("ac"); + gen_StrTemplateSlot angle_slot = gen_strtmpl_slot("raw", angle_value); + expect(angle_slot.Value.Ptr == angle_value.Ptr, gen_txt("slot angle pointer")); + expect(angle_slot.Value.Len == angle_value.Len, gen_txt("slot angle length")); + expect_template_text(gen_txt("slot angle expansion"), gen_txt(""), & angle_slot, 1, gen_txt("ac")); + + gen_Str empty_value = { 0, 0 }; + gen_StrTemplateSlot empty_slot = gen_strtmpl_slot("e", empty_value); + expect(empty_slot.Value.Ptr == 0, gen_txt("slot null value pointer")); + expect(empty_slot.Value.Len == 0, gen_txt("slot null value length")); + expect_template_text(gen_txt("slot null value expansion"), gen_txt("AB"), & empty_slot, 1, gen_txt("AB")); + + gen_Str percent_value = gen_txt("%d:%S"); + gen_StrTemplateSlot percent_slot = gen_strtmpl_slot("fmt", percent_value); + expect(percent_slot.Value.Ptr == percent_value.Ptr, gen_txt("slot percent value pointer")); + expect(gen_str_are_equal(percent_slot.Value, gen_txt("%d:%S")), gen_txt("slot percent value text")); + expect_template_text(gen_txt("slot percent literal"), gen_txt("pre %d:%S "), & percent_slot, 1, gen_txt("pre %d:%S %d:%S")); +} + +gen_internal void expect_text_template(gen_ssize printf_maxlen) { + expect_template_shapes(); + expect_template_lookup(); + expect_template_crc_collision(); + expect_template_boundaries(); + expect_template_growth(printf_maxlen); + expect_template_resize_fail(printf_maxlen); + expect_template_slice(); + expect_template_wrapper(); + expect_template_wrapper_scope(); + expect_template_wrapper_once(); + expect_template_wrapper_resize_fail(); + expect_template_slot_literal(); +} diff --git a/test/c_library/test.c b/test/c_library/test.c index 54209ed..536c6a5 100644 --- a/test/c_library/test.c +++ b/test/c_library/test.c @@ -23,16 +23,13 @@ gen_internal void expect_variable(gen_CodeVar var, gen_Str label) { expect(var->Type == CT_Variable, label); expect(gen_str_contains(var->Name, gen_txt("baseline_message")), label); expect(var->ValueType != 0, label); - expect(var->Value != 0, label); + expect(var->Value != 0, label); if (var->Value != 0) { expect(gen_str_contains(var->Value->Content, gen_txt("GENCPP_BASELINE")), label); } } -gen_internal gen_Code_POD code_pod(gen_Code code) { - gen_Code_POD result = { (gen_AST*)code }; - return result; -} +gen_internal gen_Code_POD code_pod(gen_Code code) { gen_Code_POD result = { (gen_AST*)code }; return result; } gen_internal void expect_parse_info(void* result, ParseInfo* info, gen_Str label) { expect(result != 0, label); @@ -42,9 +39,14 @@ gen_internal void expect_parse_info(void* result, ParseInfo* info, gen_Str label expect(info->lexed.messages == 0 && info->messages == 0, label); } +#include "dependencies/strings.c" +#include "dependencies/simd.c" + int main() { gen_Context ctx = {0}; gen_init(&ctx); + expect_text_template(GEN_PRINTF_MAXLEN); + expect_simd_lanes(); gen_CodeTypename type = gen_def_type(name(char), .specifiers = gen_def_specifiers(2, Spec_Const, Spec_Ptr) ); gen_CodeVar upfront = gen_def_variable(type, name(baseline_message), diff --git a/test/cpp_library/dependencies/simd.cpp b/test/cpp_library/dependencies/simd.cpp new file mode 100644 index 0000000..88b12e3 --- /dev/null +++ b/test/cpp_library/dependencies/simd.cpp @@ -0,0 +1,91 @@ +internal void expect_simd_lanes() +{ +#if defined(GEN_SYSTEM_WINDOWS) && (defined(_M_X64) || defined(__x86_64__)) + using namespace gen; + u8 storage[32]; + mem_set(storage, 0x11, 32); + u8 pattern[16] = { + 0x00, 0x80, 0xFF, 0x01, + 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, + 0x0A, 0x0B, 0x0C, 0x0D + }; + mem_copy(storage + 1, pattern, 16); + u8x16 loaded = u8x16_load(storage + 1); + u8x16 again = u8x16_load(storage + 1); + u16 same = u8x16_mask(u8x16_equal(loaded, again)); + expect(same == 0xFFFF, txt("simd u8 equal load")); + u16 zero_mask = u8x16_mask(u8x16_equal(loaded, u8x16_splat(0x00))); + expect(zero_mask == 0x0001, txt("simd byte 0")); + u16 high_bit = u8x16_mask(u8x16_equal(loaded, u8x16_splat(0x80))); + expect(high_bit == 0x0002, txt("simd byte 0x80")); + u16 top = u8x16_mask(u8x16_equal(loaded, u8x16_splat(0xFF))); + expect(top == 0x0004, txt("simd byte 0xFF")); + + u8 high_lane[16]; + mem_set(high_lane, 0x22, 16); + high_lane[15] = 0xFF; + u8x16 high_loaded = u8x16_load(high_lane); + u16 lane15 = u8x16_mask(u8x16_equal(high_loaded, u8x16_splat(0xFF))); + expect(lane15 == 0x8000, txt("simd lane 15")); + expect(u32_mask_first_set(lane15) == 15, txt("simd first set lane 15")); + + u8 window[24]; + mem_set(window, 0xEE, 24); + window[1] = 0x00; + window[2] = 0x80; + window[3] = 0xFF; + window[4] = 0x11; + window[5] = 0x22; + window[6] = 0x33; + window[7] = 0x44; + window[8] = 0x55; + u8x16 loaded8 = u8x16_load8(window + 1); + u16 upper = u8x16_mask(u8x16_equal(loaded8, u8x16_splat(0x00))); + expect((upper & 0xFF00) == 0xFF00, txt("simd load8 upper zero")); + expect((upper & 0x0001) == 0x0001, txt("simd load8 low zero")); + u16 low_ff = u8x16_mask(u8x16_equal(loaded8, u8x16_splat(0xFF))); + expect(low_ff == 0x0004, txt("simd load8 low 0xFF")); + u16 low_80 = u8x16_mask(u8x16_equal(loaded8, u8x16_splat(0x80))); + expect(low_80 == 0x0002, txt("simd load8 low 0x80")); + u16 byte7 = u8x16_mask(u8x16_equal(loaded8, u8x16_splat(0x55))); + expect(byte7 == 0x0080, txt("simd load8 byte 7")); + u16 sentinel = u8x16_mask(u8x16_equal(loaded8, u8x16_splat(0xEE))); + expect(sentinel == 0, txt("simd load8 ignores tail")); + u8x16 loaded16 = u8x16_load(window + 1); + u16 tail = u8x16_mask(u8x16_equal(loaded16, u8x16_splat(0xEE))); + expect(tail == 0xFF00, txt("simd load16 sees load8 tail")); + + u32 hashes[4] = { 0x00000000u, 0x7FFFFFFFu, 0x80000000u, 0xFFFFFFFFu }; + u32x4 hash_loaded = u32x4_load(hashes); + u8 lane0 = u32x4_mask(u32x4_equal(hash_loaded, u32x4_splat(0x00000000u))); + u8 lane1 = u32x4_mask(u32x4_equal(hash_loaded, u32x4_splat(0x7FFFFFFFu))); + u8 lane2 = u32x4_mask(u32x4_equal(hash_loaded, u32x4_splat(0x80000000u))); + u8 lane3 = u32x4_mask(u32x4_equal(hash_loaded, u32x4_splat(0xFFFFFFFFu))); + expect(lane0 == 0x01, txt("simd u32 lane 0")); + expect(lane1 == 0x02, txt("simd u32 lane 1")); + expect(lane2 == 0x04, txt("simd u32 bit31 lane")); + expect(lane3 == 0x08, txt("simd u32 lane 3")); + expect((lane2 & 0xF0) == 0, txt("simd u32 mask low bits")); + + u32 all_values[4] = { 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u }; + u32x4 all_loaded = u32x4_load(all_values); + u8 all_mask = u32x4_mask(u32x4_equal(all_loaded, u32x4_splat(0x80000000u))); + expect(all_mask == 0x0F, txt("simd u32 all bit31")); + u8 none_mask = u32x4_mask(u32x4_equal(all_loaded, u32x4_splat(0x7FFFFFFFu))); + expect(none_mask == 0x00, txt("simd u32 none")); + + u8 hash_storage[20]; + mem_set(hash_storage, 0xA5, 20); + mem_copy(hash_storage + 1, hashes, 16); + u32x4 unaligned = u32x4_load(hash_storage + 1); + u8 unaligned_mask = u32x4_mask(u32x4_equal(unaligned, u32x4_splat(0x80000000u))); + expect(unaligned_mask == 0x04, txt("simd u32 unaligned bit31")); + + expect(u32_mask_first_set(0x1u) == 0, txt("simd first set bit 0")); + expect(u32_mask_first_set(0x14u) == 2, txt("simd first set lowest")); + expect(u32_mask_first_set(0x8000u) == 15, txt("simd first set bit 15")); + expect(u32_mask_first_set(0x80000000u) == 31, txt("simd first set bit 31")); + expect(u32_mask_first_set(0xFFFFFFFFu) == 0, txt("simd first set all bits")); +#endif +} diff --git a/test/cpp_library/dependencies/strings.cpp b/test/cpp_library/dependencies/strings.cpp new file mode 100644 index 0000000..2bb3cb8 --- /dev/null +++ b/test/cpp_library/dependencies/strings.cpp @@ -0,0 +1,651 @@ +internal gen::Str bounded_format(char* storage, gen::ssize storage_len, char const* bytes, gen::ssize len) +{ + gen::mem_set(storage, '!', storage_len); + if (len > 0) + gen::mem_copy(storage, bytes, len); + storage[storage_len - 1] = '\0'; + gen::Str result = { storage, len }; + return result; +} + +internal void expect_template_expansion(gen::Str label, gen::Str format, gen::StrTemplateSlot* slots, gen::ssize slot_count, gen::Str expansion) +{ + using namespace gen; + StrBuilder output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& output, txt("KEEP")), label); + Slice_StrTemplateSlot slice = { nullptr, 0 }; + if (slot_count != 0) { + slice.Ptr = slots; + slice.Len = slot_count; + } + bool appended = strbuilder_append_template(& output, slice, format); + expect(appended, label); + Str got = strbuilder_to_str(output); + expect(got.Len == 4 + expansion.Len, label); + expect(str_starts_with(got, txt("KEEP")), label); + if (got.Len >= 4 + expansion.Len) { + Str tail = { got.Ptr + 4, expansion.Len }; + expect(str_are_equal(tail, expansion), label); + } + StrBuilderHeader* header = strbuilder_get_header(output); + expect(header->Allocator.Proc == heap_allocator_proc, label); + strbuilder_free(& output); +} + +internal void expect_template_text(gen::Str label, gen::Str format, gen::StrTemplateSlot* slots, gen::ssize slot_count, gen::Str expansion) +{ + expect_template_expansion(label, format, slots, slot_count, expansion); +} + +internal void expect_literal_boundary(gen::ssize mark) { + using namespace gen; + char format_storage[64]; + char format_bytes[40]; + for (ssize idx = 0; idx < mark; ++ idx) + format_bytes[idx] = 'L'; + Str format = bounded_format(format_storage, 64, format_bytes, mark); + Str expansion = { format_bytes, mark }; + expect(format_storage[mark] == '!', txt("literal boundary")); + expect_template_expansion(txt("literal boundary"), format, nullptr, 0, expansion); +} + +internal void expect_open_boundary(gen::ssize mark) +{ + using namespace gen; + char format_storage[64]; + char format_bytes[48]; + char expansion_bytes[48]; + ssize format_len = 0; + for (ssize idx = 0; idx < mark; ++ idx) + format_bytes[format_len ++] = 'L'; + format_bytes[format_len ++] = '<'; + format_bytes[format_len ++] = 'k'; + format_bytes[format_len ++] = '>'; + format_bytes[format_len ++] = 'Z'; + Str format = bounded_format(format_storage, 64, format_bytes, format_len); + StrTemplateSlot slot = {}; + slot.Key = txt("k"); + slot.Value = txt("V"); + ssize expansion_len = 0; + for (ssize idx = 0; idx < mark; ++ idx) + expansion_bytes[expansion_len ++] = 'L'; + expansion_bytes[expansion_len ++] = 'V'; + expansion_bytes[expansion_len ++] = 'Z'; + Str expansion = { expansion_bytes, expansion_len }; + expect(format.Ptr[mark] == '<', txt("open boundary")); + expect(format_storage[format_len] == '!', txt("open boundary")); + expect_template_expansion(txt("open boundary"), format, & slot, 1, expansion); +} + +internal void expect_close_boundary(gen::ssize mark) { + using namespace gen; + char format_storage[64]; + char format_bytes[48]; + char key_storage[40]; + ssize key_len = mark - 1; + mem_set(key_storage, '!', 40); + for (ssize idx = 0; idx < key_len; ++ idx) + key_storage[idx] = 'k'; + key_storage[39] = '\0'; + Str key = { key_storage, key_len }; + ssize format_len = 0; + format_bytes[format_len ++] = '<'; + for (ssize idx = 0; idx < key_len; ++ idx) + format_bytes[format_len ++] = 'k'; + format_bytes[format_len ++] = '>'; + format_bytes[format_len ++] = 'Z'; + Str format = bounded_format(format_storage, 64, format_bytes, format_len); + StrTemplateSlot slot = {}; + slot.Key = key; + slot.Value = txt("V"); + expect(format.Ptr[mark] == '>', txt("close boundary")); + expect(key.Ptr[key.Len] == '!', txt("close boundary")); + expect(format_storage[format_len] == '!', txt("close boundary")); + expect_template_expansion(txt("close boundary"), format, & slot, 1, txt("VZ")); +} + +internal void expect_template_shapes() +{ + using namespace gen; + char literal_storage[8]; + char literal_bytes[6] = { 'a', 'b', 'c', 'D', 'E', 'F' }; + Str literal = bounded_format(literal_storage, 8, literal_bytes, 3); + expect(literal_storage[3] == '!', txt("literal sentinel")); + expect_template_expansion(txt("literal bounded"), literal, nullptr, 0, txt("abc")); + + char empty_storage[4]; + Str empty_format = bounded_format(empty_storage, 4, empty_storage, 0); + expect(empty_storage[0] == '!', txt("empty sentinel")); + expect_template_expansion(txt("empty format"), empty_format, nullptr, 0, txt("")); + + StrTemplateSlot name_slot = {}; + name_slot.Key = txt("name"); + name_slot.Value = txt("world"); + expect_template_text(txt("one slot"), txt("hello !"), & name_slot, 1, txt("hello world!")); + + StrTemplateSlot repeat_slot = {}; + repeat_slot.Key = txt("w"); + repeat_slot.Value = txt("v"); + expect_template_text(txt("repeated slot"), txt("--"), & repeat_slot, 1, txt("v-v-v")); + + StrTemplateSlot adjacent[2] = {}; + adjacent[0].Key = txt("a"); + adjacent[0].Value = txt("X"); + adjacent[1].Key = txt("b"); + adjacent[1].Value = txt("Y"); + expect_template_text(txt("adjacent slots"), txt(""), adjacent, 2, txt("XYX")); + + StrTemplateSlot raw_slots[3] = {}; + raw_slots[0].Key = txt("raw"); + raw_slots[0].Value = txt("ac"); + raw_slots[1].Key = txt("angles"); + raw_slots[1].Value = txt("<>"); + raw_slots[2].Key = txt("b"); + raw_slots[2].Value = txt("SHOULD_NOT"); + expect_template_text(txt("value angles literal"), txt(""), raw_slots, 3, txt("ac<>")); + + char empty_value_storage[2] = { '!', '!' }; + StrTemplateSlot empty_slot = {}; + empty_slot.Key = txt("e"); + empty_slot.Value = { empty_value_storage, 0 }; + expect_template_text(txt("empty value"), txt("AB"), & empty_slot, 1, txt("AB")); + + StrTemplateSlot empty_key = {}; + empty_key.Key = { nullptr, 0 }; + empty_key.Value = txt("Z"); + expect_template_text(txt("empty key"), txt("<>"), & empty_key, 1, txt("Z")); + + StrTemplateSlot null_value = {}; + null_value.Key = txt("e"); + null_value.Value = { nullptr, 0 }; + expect(null_value.Value.Ptr == nullptr, txt("null empty value pointer")); + expect(null_value.Value.Len == 0, txt("null empty value length")); + expect_template_text(txt("null empty value"), txt("AB"), & null_value, 1, txt("AB")); + + char nul_value_bytes[4] = { 'a', '\0', 'b', '!' }; + char nul_expected[5] = { '[', 'a', '\0', 'b', ']' }; + StrTemplateSlot nul_slot = {}; + nul_slot.Key = txt("n"); + nul_slot.Value = { nul_value_bytes, 3 }; + gen::Str nul_expansion = { nul_expected, 5 }; + expect_template_text(txt("embedded nul"), txt("[]"), & nul_slot, 1, nul_expansion); + + StrTemplateSlot duffle[2] = {}; + duffle[0].Key = txt("operands[0]"); + duffle[0].Value = txt("eax"); + duffle[1].Key = txt("mod_rm.bits"); + duffle[1].Value = txt("11"); + expect_template_text(txt("duffle keys"), txt("mov , ;"), duffle, 2, txt("mov eax, 11;")); +} + +internal void fill_lookup_slots(gen::StrTemplateSlot* slots, gen::ssize slot_count) { + gen::Str keys[9] = { + txt("k0"), txt("k1"), txt("k2"), txt("k3"), txt("k4"), + txt("k5"), txt("k6"), txt("k7"), txt("k8") + }; + gen::Str values[9] = { + txt("V0"), txt("V1"), txt("V2"), txt("V3"), txt("V4"), + txt("V5"), txt("V6"), txt("V7"), txt("V8") + }; + for (gen::ssize idx = 0; idx < slot_count; ++ idx) { + slots[idx].Key = keys[idx]; + slots[idx].Value = values[idx]; + } +} + +internal void expect_template_lookup() +{ + using namespace gen; + StrTemplateSlot slots[9] = {}; + expect_template_text(txt("lookup 0"), txt("none"), nullptr, 0, txt("none")); + + fill_lookup_slots(slots, 1); + expect_template_text(txt("lookup 1"), txt(""), slots, 1, txt("V0")); + + fill_lookup_slots(slots, 2); + expect_template_text(txt("lookup 2"), txt(""), slots, 2, txt("V1V0")); + + fill_lookup_slots(slots, 3); + expect_template_text(txt("lookup 3"), txt("--"), slots, 3, txt("V2-V0-V1")); + + fill_lookup_slots(slots, 4); + expect_template_text(txt("lookup 4"), txt(""), slots, 4, txt("V3V0V2V1")); + + fill_lookup_slots(slots, 5); + expect_template_text(txt("lookup 5 tail"), txt(""), slots, 5, txt("V4V0V3")); + + fill_lookup_slots(slots, 8); + expect_template_text(txt("lookup 8 lanes"), txt(""), slots, 8, txt("V7V4V3V0")); + + fill_lookup_slots(slots, 9); + expect_template_text(txt("lookup 9 tail"), txt(""), slots, 9, txt("V8V5V6V4")); + + StrTemplateSlot dup_small[3] = {}; + dup_small[0].Key = txt("dup"); dup_small[0].Value = txt("FIRST"); + dup_small[1].Key = txt("other"); dup_small[1].Value = txt("X"); + dup_small[2].Key = txt("dup"); dup_small[2].Value = txt("SECOND"); + expect_template_text(txt("duplicate first wins"), txt(""), dup_small, 3, txt("FIRSTX")); + + StrTemplateSlot dup_lanes[4] = {}; + dup_lanes[0].Key = txt("same"); dup_lanes[0].Value = txt("A"); + dup_lanes[1].Key = txt("same"); dup_lanes[1].Value = txt("B"); + dup_lanes[2].Key = txt("other"); dup_lanes[2].Value = txt("C"); + dup_lanes[3].Key = txt("same"); dup_lanes[3].Value = txt("D"); + expect_template_text(txt("duplicate lane first wins"), txt(""), dup_lanes, 4, txt("AC")); + + StrTemplateSlot dup_tail[5] = {}; + dup_tail[0].Key = txt("dup"); dup_tail[0].Value = txt("FIRST"); + dup_tail[1].Key = txt("k1"); dup_tail[1].Value = txt("V1"); + dup_tail[2].Key = txt("k2"); dup_tail[2].Value = txt("V2"); + dup_tail[3].Key = txt("k3"); dup_tail[3].Value = txt("V3"); + dup_tail[4].Key = txt("dup"); dup_tail[4].Value = txt("TAIL"); + expect_template_text(txt("duplicate tail first wins"), txt(""), dup_tail, 5, txt("FIRST")); +} + +namespace gen { +internal void expect_template_boundaries() { + ssize marks[12] = { 7, 8, 9, 15, 16, 17, 23, 24, 25, 31, 32, 33 }; + for (ssize idx = 0; idx < count_of(marks); ++ idx) { + expect_literal_boundary(marks[idx]); + expect_open_boundary(marks[idx]); + expect_close_boundary(marks[idx]); + } +} +} + +internal void expect_template_growth(gen::ssize printf_maxlen) +{ + using namespace gen; + ssize huge_len = printf_maxlen + 64; + char* huge = rcast(char*, alloc(heap(), huge_len + 1)); + expect(huge != nullptr, txt("growth alloc")); + if (huge == nullptr) return; + mem_set(huge, 'Q', huge_len); + huge[huge_len] = '!'; + + Str value = { huge, huge_len }; + StrTemplateSlot slot = {}; + slot.Key = txt("blob"); + slot.Value = value; + Str format = txt(""); + StrBuilder output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_capacity(output) == 1, txt("growth initial capacity")); + expect(strbuilder_append_str(& output, txt("KEEP")), txt("growth prefix")); + + Slice_StrTemplateSlot slice = { & slot, 1 }; + bool appended = strbuilder_append_template(& output, slice, format); + expect(appended, txt("growth append")); + + Str got = strbuilder_to_str(output); + expect(got.Len == 4 + huge_len, txt("growth length")); + expect(got.Len > printf_maxlen, txt("growth beyond printf max")); + expect(str_starts_with(got, txt("KEEP")), txt("growth keeps prefix")); + + ssize capacity = strbuilder_capacity(output); + expect(capacity >= got.Len, txt("growth capacity")); + + if (got.Len == 4 + huge_len && capacity >= got.Len) { + b32 payload_matches = true; + for (ssize idx = 0; idx < got.Len - 4; ++ idx) { + if (got.Ptr[4 + idx] != 'Q') payload_matches = false; + } + expect(payload_matches, txt("growth bytes")); + } + + StrBuilderHeader* header = strbuilder_get_header(output); + expect(header->Allocator.Proc == heap_allocator_proc, txt("growth allocator")); + expect(huge[huge_len] == '!', txt("growth sentinel")); + strbuilder_free(& output); + allocator_free(heap(), huge); +} + +internal void expect_template_crc_collision() +{ + using namespace gen; + Str plumless = txt("plumless"); + Str buckeroo = txt("buckeroo"); + u32 plum_hash = crc32(plumless.Ptr, plumless.Len); + u32 buck_hash = crc32(buckeroo.Ptr, buckeroo.Len); + expect(plum_hash == buck_hash, txt("crc collision equal")); + expect(plum_hash == 0x4ddb0c25, txt("crc collision value")); + expect(str_are_equal(plumless, buckeroo) == false, txt("crc collision keys differ")); + + StrTemplateSlot scalar[2] = {}; + scalar[0].Key = plumless; scalar[0].Value = txt("WRONG"); + scalar[1].Key = buckeroo; scalar[1].Value = txt("RIGHT"); + expect_template_text(txt("crc scalar 2"), txt(""), scalar, 2, txt("RIGHT")); + + StrTemplateSlot lanes[4] = {}; + lanes[0].Key = plumless; lanes[0].Value = txt("WRONG"); + lanes[1].Key = txt("k1"); lanes[1].Value = txt("V1"); + lanes[2].Key = buckeroo; lanes[2].Value = txt("RIGHT"); + lanes[3].Key = txt("k3"); lanes[3].Value = txt("V3"); + expect_template_text(txt("crc lane reject"), txt(""), lanes, 4, txt("RIGHT")); + + StrTemplateSlot tail[5] = {}; + tail[0].Key = plumless; tail[0].Value = txt("WRONG"); + tail[1].Key = txt("k1"); tail[1].Value = txt("V1"); + tail[2].Key = txt("k2"); tail[2].Value = txt("V2"); + tail[3].Key = txt("k3"); tail[3].Value = txt("V3"); + tail[4].Key = buckeroo; tail[4].Value = txt("TAIL"); + expect_template_text(txt("crc tail after false hit"), txt(""), tail, 5, txt("TAIL")); +} + +internal +void* resize_fail_allocator_proc(void* allocator_data, gen::AllocType type, gen::ssize size, gen::ssize alignment + , void* old_memory, gen::ssize old_size + , gen::u64 flags +){ + if (type == gen::EAllocation_RESIZE) { + gen::s32* attempts = rcast(gen::s32*, allocator_data); + attempts[0] = attempts[0] + 1; + return nullptr; + } + return gen::heap_allocator_proc(nullptr, type, size, alignment, old_memory, old_size, flags); +} + +internal void expect_template_resize_fail(gen::ssize printf_maxlen) +{ + using namespace gen; + ssize huge_len = printf_maxlen + 64; + char* huge = rcast(char*, alloc(heap(), huge_len + 1)); + expect(huge != nullptr, txt("resize fail alloc")); + if (huge == nullptr) return; + mem_set(huge, 'L', huge_len); + huge[huge_len] = '!'; + Str huge_text = { huge, huge_len }; + + s32 literal_attempts = 0; + AllocatorInfo literal_alloc = {}; + literal_alloc.Proc = resize_fail_allocator_proc; + literal_alloc.Data = & literal_attempts; + StrBuilder literal_output = strbuilder_make_reserve(literal_alloc, 4); + expect(strbuilder_append_str(& literal_output, txt("KEEP")), txt("resize fail literal prefix")); + + Slice_StrTemplateSlot literal_slots = { nullptr, 0 }; + bool literal_appended = strbuilder_append_template(& literal_output, literal_slots, huge_text); + expect(literal_appended == false, txt("resize fail literal")); + + Str literal_got = strbuilder_to_str(literal_output); + expect(literal_got.Len == 4, txt("resize fail literal length")); + expect(str_are_equal(literal_got, txt("KEEP")), txt("resize fail literal keep")); + expect(literal_attempts > 0, txt("resize fail literal attempts")); + strbuilder_free(& literal_output); + + s32 value_attempts = 0; + AllocatorInfo value_alloc = {}; + value_alloc.Proc = resize_fail_allocator_proc; + value_alloc.Data = & value_attempts; + + StrTemplateSlot slot = {}; + slot.Key = txt("blob"); + slot.Value = huge_text; + Str format = txt(""); + StrBuilder value_output = strbuilder_make_reserve(value_alloc, 4); + expect(strbuilder_append_str(& value_output, txt("KEEP")), txt("resize fail value prefix")); + + Slice_StrTemplateSlot value_slots = { & slot, 1 }; + bool value_appended = strbuilder_append_template(& value_output, value_slots, format); + expect(value_appended == false, txt("resize fail value")); + + Str value_got = strbuilder_to_str(value_output); + expect(value_got.Len == 4, txt("resize fail value length")); + expect(str_are_equal(value_got, txt("KEEP")), txt("resize fail value keep")); + expect(value_attempts > 0, txt("resize fail value attempts")); + strbuilder_free(& value_output); + allocator_free(heap(), huge); +} + +internal gen::Str counted_template_value (gen::s32* reads) { reads[0] = reads[0] + 1; return txt("ONCE"); } +internal gen::Str counted_template_format(gen::s32* reads) { reads[0] = reads[0] + 1; return txt(":"); } +internal gen::StrBuilder* counted_template_output(gen::StrBuilder* output, gen::s32* reads) { reads[0] = reads[0] + 1; return output; } +internal bool* counted_template_result(bool* result, gen::s32* reads) { reads[0] = reads[0] + 1; return result; } + +internal void expect_template_slice() { + using namespace gen; + StrTemplateSlot slots[2] = { + strtmpl_slot("a", txt("X")), + strtmpl_slot("b", txt("Y")) + }; + Slice_StrTemplateSlot slice = { slots, 2 }; + StrBuilder output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& output, txt("KEEP")), txt("slice prefix")); + + bool appended = strbuilder_append_template(& output, slice, txt("")); + expect(appended, txt("slice append")); + expect(str_are_equal(strbuilder_to_str(output), txt("KEEPYXY")), txt("slice text")); + expect(str_are_equal(slots[0].Key, txt("a")), txt("slice key")); + expect(str_are_equal(slots[0].Value, txt("X")), txt("slice value")); + strbuilder_free(& output); + + slice.Ptr[0].Value = txt("Z"); + StrBuilder mutated_output = strbuilder_make_reserve(heap(), 1); + bool mutated_appended = strbuilder_append_template(& mutated_output, slice, txt("")); + expect(mutated_appended, txt("slice mutate append")); + expect(str_are_equal(strbuilder_to_str(mutated_output), txt("YZY")), txt("slice mutate text")); + strbuilder_free(& mutated_output); + + Slice_StrTemplateSlot empty_slice = { nullptr, 0 }; + StrBuilder empty_output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& empty_output, txt("KEEP")), txt("zero slice prefix")); + bool empty_appended = strbuilder_append_template(& empty_output, empty_slice, txt("none")); + expect(empty_appended, txt("zero slice append")); + expect(str_are_equal(strbuilder_to_str(empty_output), txt("KEEPnone")), txt("zero slice text")); + expect(empty_slice.Ptr == nullptr, txt("zero slice pointer")); + expect(empty_slice.Len == 0, txt("zero slice length")); + strbuilder_free(& empty_output); +} + +internal void expect_template_wrapper() { + using namespace gen; + StrBuilder output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& output, txt("KEEP")), txt("wrapper prefix")); + + bool appended = false; + strtmpl_append(& output, appended + strtmpl_slots( + strtmpl_slot("name", txt("world")), + strtmpl_slot("tail", txt("!")) + ), + txt("(hello \n)") + ); + expect(appended, txt("wrapper multiline result")); + + Str got = strbuilder_to_str(output); + expect(str_starts_with(got, txt("KEEP")), txt("wrapper prefix kept")); + expect(str_are_equal (got, txt("KEEPhello world\nworld!")), txt("wrapper multiline text")); + strbuilder_free(& output); +} + +internal void expect_template_wrapper_scope() +{ + using namespace gen; + StrBuilder then_output = strbuilder_make_reserve(heap(), 1); + StrBuilder else_output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& then_output, txt("KEEP")), txt("wrapper then prefix")); + expect(strbuilder_append_str(& else_output, txt("KEEP")), txt("wrapper else prefix")); + bool then_result = false; + bool else_result = false; + s32 selector = 1; + s32 after_branch = 0; + if (selector == 1) strtmpl_append(& then_output, then_result, strtmpl_slots(strtmpl_slot("arm", txt("THEN"))), txt("")); + else strtmpl_append(& else_output, else_result, strtmpl_slots(strtmpl_slot("arm", txt("ELSE"))), txt("")); + + after_branch = 1; + expect(after_branch == 1, txt("wrapper then continues")); + expect(then_result, txt("wrapper then assigned")); + expect(else_result == false, txt("wrapper else skipped")); + expect(str_are_equal(strbuilder_to_str(then_output), txt("KEEPTHEN")), txt("wrapper then text")); + expect(str_are_equal(strbuilder_to_str(else_output), txt("KEEP")), txt("wrapper else untouched")); + strbuilder_free(& then_output); + strbuilder_free(& else_output); + + then_output = strbuilder_make_reserve(heap(), 1); + else_output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& then_output, txt("KEEP")), txt("wrapper skipped then prefix")); + expect(strbuilder_append_str(& else_output, txt("KEEP")), txt("wrapper taken else prefix")); + + then_result = false; + else_result = false; + selector = 0; + after_branch = 0; + if (selector == 1) strtmpl_append(& then_output, then_result, strtmpl_slots(strtmpl_slot("arm", txt("THEN"))), txt("")); + else strtmpl_append(& else_output, else_result, strtmpl_slots(strtmpl_slot("arm", txt("ELSE"))), txt("")); + + after_branch = 1; + expect(after_branch == 1, txt("wrapper else continues")); + expect(then_result == false, txt("wrapper then skipped")); + expect(else_result, txt("wrapper else assigned")); + expect(str_are_equal(strbuilder_to_str(then_output), txt("KEEP")), txt("wrapper then untouched")); + expect(str_are_equal(strbuilder_to_str(else_output), txt("KEEPELSE")), txt("wrapper else text")); + strbuilder_free(& then_output); + strbuilder_free(& else_output); +} + +internal void expect_template_wrapper_once() { + using namespace gen; + s32 value_reads = 0; + s32 format_reads = 0; + s32 output_reads = 0; + s32 result_reads = 0; + StrBuilder output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& output, txt("KEEP")), txt("wrapper once prefix")); + + bool appended = false; + strtmpl_append( + counted_template_output(& output, & output_reads), + counted_template_result(& appended, & result_reads)[0], + strtmpl_slots( + strtmpl_slot("k", counted_template_value(& value_reads)), + strtmpl_slot("tail", txt("Z")) + ), + counted_template_format(& format_reads) + ); + expect(appended, txt("wrapper once result")); + expect(value_reads == 1, txt("wrapper value once")); + expect(format_reads == 1, txt("wrapper format once")); + expect(output_reads == 1, txt("wrapper output once")); + expect(result_reads == 1, txt("wrapper result once")); + expect(str_are_equal(strbuilder_to_str(output), txt("KEEPONCE:ONCE")), txt("wrapper once text")); + strbuilder_free(& output); +} + +internal void expect_template_wrapper_resize_fail() { + using namespace gen; + s32 attempts = 0; + AllocatorInfo alloc = {}; + alloc.Proc = resize_fail_allocator_proc; + alloc.Data = & attempts; + + StrBuilder output = strbuilder_make_reserve(alloc, 4); + expect(strbuilder_capacity(output) == 4, txt("wrapper resize capacity")); + expect(strbuilder_append_str(& output, txt("KEEP")), txt("wrapper resize prefix")); + bool appended = true; + strtmpl_append( + & output, + appended, + strtmpl_slots(strtmpl_slot("blob", txt("X"))), + txt("") + ); + expect(appended == false, txt("wrapper resize assigned false")); + expect(str_are_equal(strbuilder_to_str(output), txt("KEEP")), txt("wrapper resize keep")); + expect(attempts > 0, txt("wrapper resize attempts")); + strbuilder_free(& output); +} + +internal void expect_template_slot_literal() +{ + using namespace gen; + Str value = txt("V"); + StrTemplateSlot slot = strtmpl_slot("k", value); + expect(str_are_equal(slot.Key, txt("k")), txt("slot literal key text")); + expect(slot.Key.Ptr[slot.Key.Len] == '\0', txt("slot literal key terminator")); + expect(slot.Value.Ptr == value.Ptr, txt("slot literal value pointer")); + expect(slot.Value.Len == value.Len, txt("slot literal value length")); + + Str assigned = txt("W"); + slot = strtmpl_slot("key", assigned); + expect(str_are_equal(slot.Key, txt("key")), txt("slot assign key")); + expect(slot.Value.Ptr == assigned.Ptr, txt("slot assign value pointer")); + expect(slot.Value.Len == assigned.Len, txt("slot assign value length")); + expect_template_text(txt("slot assign expansion"), txt(""), & slot, 1, txt("W")); + + StrTemplateSlot row[2] = { + strtmpl_slot("a", txt("X")), + strtmpl_slot("b", txt("Y")) + }; + expect(str_are_equal(row[0].Value, txt("X")), txt("slot array value")); + expect(str_are_equal(row[1].Key, txt("b")), txt("slot array key")); + expect_template_text(txt("slot array entry"), txt(""), row, 2, txt("YX")); + + StrBuilder output = strbuilder_make_reserve(heap(), 1); + expect(strbuilder_append_str(& output, txt("KEEP")), txt("slot wrapper prefix")); + bool appended = false; + strtmpl_append( + & output, + appended, + strtmpl_slots( + strtmpl_slot("name", txt("world")), + strtmpl_slot("tail", txt("!")) + ), + txt("") + ); + expect(appended, txt("slot wrapper result")); + expect(str_are_equal(strbuilder_to_str(output), txt("KEEPworld!")), txt("slot wrapper text")); + strbuilder_free(& output); + + StrTemplateSlot long_key = strtmpl_slot("operands[0]", txt("eax")); + expect(str_are_equal(long_key.Key, txt("operands[0]")), txt("slot literal key equal")); + expect(long_key.Key.Ptr[long_key.Key.Len] == '\0', txt("slot literal key excludes nul")); + expect(str_are_equal(long_key.Value, txt("eax")), txt("slot literal value")); + + s32 value_reads = 0; + StrTemplateSlot once = strtmpl_slot("k", counted_template_value(& value_reads)); + expect(value_reads == 1, txt("slot value once")); + expect(str_are_equal(once.Value, txt("ONCE")), txt("slot value once text")); + expect(once.Key.Len == 1, txt("slot value once key")); + + char nul_bytes[4] = { 'a', '\0', 'b', '!' }; + Str nul_value = { nul_bytes, 3 }; + StrTemplateSlot nul_slot = strtmpl_slot("n", nul_value); + expect(nul_slot.Value.Ptr == nul_bytes, txt("slot nul pointer")); + expect(nul_slot.Value.Len == 3, txt("slot nul length")); + expect(nul_slot.Value.Ptr[1] == '\0', txt("slot nul byte")); + + char nul_expected[5] = { '[', 'a', '\0', 'b', ']' }; + Str nul_expansion = { nul_expected, 5 }; + expect_template_text(txt("slot nul expansion"), txt("[]"), & nul_slot, 1, nul_expansion); + + Str angle_value = txt("ac"); + StrTemplateSlot angle_slot = strtmpl_slot("raw", angle_value); + expect(angle_slot.Value.Ptr == angle_value.Ptr, txt("slot angle pointer")); + expect(angle_slot.Value.Len == angle_value.Len, txt("slot angle length")); + expect_template_text(txt("slot angle expansion"), txt(""), & angle_slot, 1, txt("ac")); + + Str empty_value = { nullptr, 0 }; + StrTemplateSlot empty_slot = strtmpl_slot("e", empty_value); + expect(empty_slot.Value.Ptr == nullptr, txt("slot null value pointer")); + expect(empty_slot.Value.Len == 0, txt("slot null value length")); + expect_template_text(txt("slot null value expansion"), txt("AB"), & empty_slot, 1, txt("AB")); + + Str percent_value = txt("%d:%S"); + StrTemplateSlot percent_slot = strtmpl_slot("fmt", percent_value); + expect(percent_slot.Value.Ptr == percent_value.Ptr, txt("slot percent value pointer")); + expect(str_are_equal(percent_slot.Value, txt("%d:%S")), txt("slot percent value text")); + expect_template_text(txt("slot percent literal"), txt("pre %d:%S "), & percent_slot, 1, txt("pre %d:%S %d:%S")); +} + +internal void expect_text_template(gen::ssize printf_maxlen) { + expect_template_shapes(); + expect_template_lookup(); + expect_template_crc_collision(); + gen::expect_template_boundaries(); + expect_template_growth(printf_maxlen); + expect_template_resize_fail(printf_maxlen); + expect_template_slice(); + expect_template_wrapper(); + expect_template_wrapper_scope(); + expect_template_wrapper_once(); + expect_template_wrapper_resize_fail(); + expect_template_slot_literal(); +} diff --git a/test/cpp_library/test.cpp b/test/cpp_library/test.cpp index 2c78e86..0e3b501 100644 --- a/test/cpp_library/test.cpp +++ b/test/cpp_library/test.cpp @@ -38,10 +38,15 @@ internal void expect_parse_result(gen::Code result, gen::ParseInfo* info, gen::C expect(info->lexed.messages == nullptr && info->messages == nullptr, label); } +#include "dependencies/strings.cpp" +#include "dependencies/simd.cpp" + int main() { using namespace gen; Context ctx = {}; init(&ctx); + expect_text_template(GEN_PRINTF_MAXLEN); + expect_simd_lanes(); Opts_def_type type_opts = {}; type_opts.specifiers = def_specifiers(args(Spec_Const, Spec_Ptr));