WIP: Array generation test. Still need gen.cpp impl.

This commit is contained in:
Edward R. Gonzalez 2023-04-02 12:35:14 -04:00
parent 340f466f24
commit b4b518f005
10 changed files with 566 additions and 104 deletions

View File

@ -9,15 +9,16 @@ Version 1 will have c and a subset of c++ features available to it.
A metaprogram is built to generate files before the main program is built. We'll term runtime for this program as `gen_time`. The metaprogram's core implementation are within `gen.hpp` and `gen.cpp` in the project directory.
`gen.cpp` \`s `main()` will expect to call one `gen_main()` which the user will have to define once for their program. There they will dictate everything that should be generated.
`gen.cpp` \`s `main()` is defined as `gen_main()` which the user will have to define once for their program. There they will dictate everything that should be generated.
In order to keep the locality of this code within the same files the following pattern may be used:
Within `program.cpp` :
```cpp
#ifdef gen_time
#include "gen.hpp"
#ifdef gen_time
...
u32 gen_main()
@ -37,19 +38,31 @@ This is ofc entirely optional and the metaprogram can be quite separated from th
## Building
To fill in.
An example of building is provided in the test directory.
There are two meson build files the one within test is the program's build specification.
The other one in the gen directory within test is the metaprogram's build specification.
Both of them build the same source file: `test.cpp`. The only differences between them is that gen need a different relative path to the include directories and defines the macro definition: `gen_time`.
This method is setup where all the metaprogram's code are the within the same files as the regular program's code.
If in your use case, decide to have exclusive separation or partial separation of the metaprogam's code from the program's code files then your build configuration would need to change to reflect that (specifically the sources).
## Why
Macros in c/c++ are usually painful to debug, and templates can be unless your on a monsterous IDE (and even then fail often).
Unfortunately most programming langauges opt the approach of internally processing the generated code immediately within the AST or not expose it to the user in a nice way to even introspect as a text file. Stage metaprogramming doesn't have this problem, since its entire purpose is to create those generated files.
Templates also have a heavy cost to compile-times due to their recursive nature of expansion if complex code is getting generated, or if heavy type checking system is used (assertsion require expansion, etc).
This is technically what the macro preprocessor does in a basic form, however naturally its easier to deal with for more complex generation.
Unfortunately most programming langauges opt the approach of internally processing the generated code immediately within the AST or not expose it to the user in a nice way to even introspect as a text file.
The drawback naturally is generation functions at face value harder to grasp than something following a template pattern (for simple generation). This drawback becomes less valid the more complex the code generation becomes.
Stage metaprogramming doesn't have this problem, since its entire purpose is to create those generated files that the final program will reference instead.
Thus a rule of thumb is if its a simple define you can get away with just the preprocessor, or if the templates being used don't break the debugger, this is most likely not neded.
This is technically what the macro preprocessor does in a basic form, however a proper metaprogram for generation is easier to deal with for more complex generation.
The drawback naturally is generation functions, at face value, are harder to grasp than something following a template pattern (for simple generation). This drawback becomes less valid the more complex the code generation becomes.
Thus a rule of thumb is if its a simple definition you can get away with just the preprocessor `#define`, or if the templates being used don't break the debugger or your compile times, this is most likely not neded.
However, if the code being generated becomes complex, or from a datatable or database, this will be easier to deal with.

View File

@ -57,6 +57,7 @@
#define scast( Type_, Value_ ) static_cast< Type_ >( Value_ )
#define rcast( Type_, Value_ ) reinterpret_cast< Type_ >( Value_ )
#define pcast( Type_, Value_ ) ( * (Type_*)( & Value_ ) )
#define txt( Value_ ) ZPL_STRINGIFY_EX( Value_ )
#define do_once() \
do \
@ -69,6 +70,19 @@ do \
} \
while(0) \
#define do_once_start \
do \
{ \
static \
bool Done = false; \
if ( Done ) \
break; \
Done = true;
#define do_once_end \
} \
while(0);
using Line = char*;
using Array_Line = array( Line );

View File

@ -1,15 +1,21 @@
#include "Bloat.hpp"
#include "gen.hpp"
#define gen_time
#ifdef gen_time
namespace gen
{
void init()
{
}
ct Code make()
{
return { Code::Invalid, nullptr, nullptr, { nullptr } };
}
Code decl_type( char const* name, Code specifiers, Code type )
Code decl_type( char const* name, Code type, Code specifiers )
{
Code
result = make();
@ -47,7 +53,7 @@ namespace gen
return result;
}
Code make_parameters( s32 num, ... )
Code def_parameters( s32 num, ... )
{
if (num <= 0)
fatal("TT::make_paramters: num is %d", num);
@ -85,30 +91,12 @@ namespace gen
return result;
}
Code make_fmt(char const* fmt, ...)
{
local_persist thread_local
char buf[ZPL_PRINTF_MAXLEN] = { 0 };
va_list va;
va_start(va, fmt);
zpl_snprintf_va(buf, ZPL_PRINTF_MAXLEN, fmt, va);
va_end(va);
Code
result = make();
result.Name = string_make( g_allocator, fmt );
result.Type = Code::Untyped;
result.Content = string_make( g_allocator, buf );
return result;
}
Code make_function( char const* name
Code def_function( char const* name
, Code specifiers
, Code params
, Code ret_type
, Code body )
, Code body
)
{
Code
result = make();
@ -130,7 +118,22 @@ namespace gen
return result;
}
Code make_specifiers( u32 num, ... )
Code def_function_body( u32 num, ... )
{
}
Code def_namespace( char const* name, Code body )
{
}
Code def_namespace_body( u32 num, ... )
{
}
Code def_specifiers( u32 num, ... )
{
if ( num <= 0 )
fatal("gen::make_specifier: num cannot be zero.");
@ -165,7 +168,22 @@ namespace gen
return result;
}
Code make_type( char const* name )
Code def_struct( char const* name, Code body, Code parent, Code specifiers )
{
}
Code def_struct_body( u32 num, ... )
{
}
Code def_variable( char const* name, Code type, Code value, Code specifiers )
{
}
Code def_type( char const* name )
{
Code
result = make();
@ -175,6 +193,33 @@ namespace gen
return result;
}
Code untyped_fmt(char const* fmt, ...)
{
local_persist thread_local
char buf[ZPL_PRINTF_MAXLEN] = { 0 };
va_list va;
va_start(va, fmt);
zpl_snprintf_va(buf, ZPL_PRINTF_MAXLEN, fmt, va);
va_end(va);
Code
result = make();
result.Name = string_make( g_allocator, fmt );
result.Type = Code::Untyped;
result.Content = string_make( g_allocator, buf );
return result;
}
Code token_fmt( char const* fmt, ... )
{
}
string Code::to_string()
{
string result = string_make( g_allocator, "" );
@ -232,6 +277,9 @@ namespace gen
}
break;
case Function_Body:
break;
case Parameters:
{
result = string_append_fmt( result, "%s %s", Entries[0].to_string(), Name );
@ -247,10 +295,6 @@ namespace gen
}
break;
case Struct:
fatal("NOT SUPPORTED YET");
break;
case Function:
{
u32 index = 0;
@ -288,8 +332,16 @@ namespace gen
result = string_append_fmt( result, "%s", Content );
break;
case Struct:
fatal("NOT SUPPORTED YET");
break;
case Struct_Body:
fatal("NOT SUPPORTED YET");
break;
case Variable:
// result = string_append_fmt( result, "%s", )
fatal("NOT SUPPORTED YET");
break;
case Typename:
@ -300,6 +352,8 @@ namespace gen
return result;
}
void Builder::print( Code code )
{
Buffer = string_append_fmt( Buffer, "%s\n\n", code.to_string() );

View File

@ -20,6 +20,7 @@ namespace gen
API_Export, // Vendor specific way to dynamic export
External_Linkage, // extern
Internal_Linkage, // static (within unit file)
Static_Member, // static (within sturct/class)
Local_Persist, // static (within function)
Thread_Local, // thread_local
@ -62,7 +63,9 @@ namespace gen
Decl_Function,
Parameters, // Used with functions.
Struct,
Struct_Body,
Function,
Function_Body,
Specifiers,
Variable,
Typename,
@ -172,7 +175,11 @@ namespace gen
Using_Code_POD;
};
Code decl_type( char const* name, Code specifiers, Code type);
constexpr Code UnusedCode = { Code::Unused, nullptr, nullptr, { nullptr } };
void init();
Code decl_type( char const* name, Code type, Code specifiers = UnusedCode );
Code decl_fn( char const* name
, Code specifiers
@ -180,26 +187,33 @@ namespace gen
, Code ret_type
);
Code make_parameters( s32 num, ... );
Code make_fmt( char const* fmt, ... );
Code def_parameters( s32 num, ... );
Code make_function( char const* name
Code def_function( char const* name
, Code specifiers
, Code params
, Code ret_type
, Code body
);
Code def_function_body( u32 num, ... );
Code make_specifiers( u32 num , ... );
Code def_namespace( char const* name, Code body );
Code def_namespace_body( u32 num, ... );
// Code make_variable( char const* name, char const* type );
Code def_specifiers( u32 num , ... );
// Code make_template( Code subject, u32 num_dependents, ... );
Code def_struct( char const* name, Code body, Code parent = UnusedCode, Code specifiers = UnusedCode );
Code def_struct_body( u32 num, ... );
Code make_type( char const* name );
Code def_variable( char const* name, Code type, Code value = UnusedCode, Code specifiers = UnusedCode );
// Code make_using( char const* name, char const* type );
Code def_type( char const* name );
Code def_using( char const* name, Code type );
Code untyped_fmt( char const* fmt, ... );
Code token_fmt( char const* fmt, ... );
struct Builder

3
test/Array.cpp Normal file
View File

@ -0,0 +1,3 @@
#include Array.cpp

376
test/Array.hpp Normal file
View File

@ -0,0 +1,376 @@
/*
This is based of the array container implementation in the zpl.h library.
*/
#pragma once
#include "Bloat.hpp"
#include "gen.hpp"
#ifdef gen_time
using namespace gen;
Code gen__array_base()
{
// Make these global consts to be accessed anywhere...
Code t_sw = def_type( txt(sw) );
Code t_uw = def_type( txt(uw) );
Code t_allocator = def_type( txt(allocator) );
Code header;
{
Code num = def_variable( "Num", t_uw );
Code capacity = def_variable( "Capacity", t_uw );
Code allocator_var = def_variable( "Allocator", t_allocator );
Code header_body = def_struct_body( 3, num, capacity, allocator_var );
header = def_struct( "ArrayHeader", header_body );
}
Code grow_formula;
{
Code spec = def_specifiers(1, Specifier::Inline);
Code params = def_parameters(1, "value", t_uw );
Code body = untyped_fmt( "\t""return 2 * value * 8;" );
grow_formula = def_function( "grow_formula", spec, params, t_sw, body );
}
Code base_body = def_struct_body(2, header, grow_formula);
Code base = def_struct( "ArrayBase", base_body );
return base;
}
#define gen_array( Type_ ) gen__array( #Type_, sizeof(Type_), a_base )
Code gen__array( char const* type_str, s32 type_size, Code parent )
{
// Make these global consts to be accessed anywhere...
Code t_uw = def_type( txt(uw) );
Code t_sw = def_type( txt(sw) );
Code t_bool = def_type( txt(bool) );
Code t_allocator = def_type( txt(allocator) );
Code t_void = def_type( txt(void) );
Code v_nullptr = untyped_fmt( "nullptr" );
Code spec_ct = def_specifiers(1, Specifier::Constexpr );
Code spec_inline = def_specifiers(1, Specifier::Inline );
Code type = def_type( type_str );
Code ptr_type = def_type( string_sprintf_buf( g_allocator, "%s*", type_str ) );
Code ref_type = def_type( string_sprintf_buf( g_allocator, "%s&", type_str ) );
string name = string_sprintf_buf( g_allocator, "Array_%s", type_str );
// From ArrayBase
Code t_header = def_type( "Header" );
Code ptr_header = def_type( "Header*" );
Code ref_header = def_type( "Header&" );
Code array_def;
{
Code using_type = def_using( "type", type );
Code data = def_variable( "Data", ptr_type );
Code init;
{
Code params = def_parameters( 1, "mem_hanlder", t_allocator );
Code body = untyped_fmt( "\t""return init_reserve( mem_handler, grow_formula(0) );" );
init = def_function( "init", UnusedCode, params, t_bool, body );
}
Code init_reserve;
{
Code params = def_parameters( 2, "mem_handler", ref_type, "capacity", t_sw );
Code body;
{
Code header_value = untyped_fmt(
"rcast( Header*, alloc( mem_handler, sizeof( Header ) + sizeof(type) + capacity ))"
);
Code header = def_variable( "", ptr_header, header_value );
Code null_check = untyped_fmt(
"\t" "if (header == nullptr)"
"\n\t\t" "return false;"
);
Code header_init = untyped_fmt(
"\n\t" "header->Num = 0;"
"\n\t" "header->Capacity = capacity;"
"\n\t" "header->Allocator = mem_handler;"
);
Code assign_data = untyped_fmt(
"\t" "Data = rcast( %s, header + 1 );", ptr_type
);
Code ret_true = untyped_fmt( "\t""return true" );
body = def_function_body( 5
, header
, null_check
, header_init
, assign_data
, ret_true
);
}
init_reserve = def_function( "init_reserve", UnusedCode, params, t_bool, body );
}
Code free;
{
Code body = untyped_fmt(
"\t" "Header& header = get_header();"
"\n\t" "::free( header.Allocator, & get_header() );"
);
free = def_function( "free", UnusedCode, UnusedCode, t_void, body );
}
Code append;
{
Code params = def_parameters( 1, "value", type );
Code body;
{
Code header = def_variable( "", ref_header, untyped_fmt( "get_header()") );
Code check_cap = untyped_fmt(
"\t" "if ( header.Capacity < header.Num + 1 )"
"\n\t\t" "if ( ! grow(0) )"
"\n\t\t\t" "return false;"
);
Code assign = untyped_fmt(
"\t" "Data[ header.Num ] = value;"
"\t\n" "header.Num++;"
"\n"
"\n\t" "return true;"
);
body = def_function_body( 3, header, check_cap, assign );
}
append = def_function( "append", UnusedCode, params, t_void, body );
}
Code back;
{
Code body = untyped_fmt(
"\t" "Header& header = get_header();"
"\n\t" "return data[ header.Num - 1 ];"
);
back = def_function( "back", UnusedCode, UnusedCode, type, body );
}
Code clear;
{
Code body = untyped_fmt( "\t""get_header().Num = 0;" );
clear = def_function( "clear", UnusedCode, UnusedCode, t_void, body );
}
Code fill;
{
Code params = def_parameters( 3, "begin", t_uw, "end", t_uw, "value", type );
Code body;
{
Code header = def_variable( "", ref_header, untyped_fmt( "get_header()") );
Code check = untyped_fmt(
"\t" "if ( begin < 0 || end >= header.Num )"
"\n\t\t" "fatal( \"Range out of bounds\" );"
);
Code iter = untyped_fmt(
"\t" "for ( sw index = begin; index < end; index++ )"
"\n\t\t" "Data[index] = vallue;"
);
body = def_function_body( 3, header, check, iter );
}
fill = def_function( "fill", UnusedCode, params, t_void, body );
}
Code get_header;
{
Code body = untyped_fmt( "\t""return pcast( Header, Data - 1 );" );
get_header = def_function( "get_header", spec_inline, UnusedCode, ref_header, body );
}
Code grow;
{
Code param = def_parameters( 1, "min_capacity", t_uw );
Code body;
{
Code header = def_variable( "header", ref_header, untyped_fmt("get_header") );
Code new_capacity = def_variable( "new_capacity", t_uw, untyped_fmt("grow_formula( header.Capacity )") );
Code check_n_set = untyped_fmt(
"\t" "if ( new_capacity < min_capacity )"
"\n\t\t" "new_capacity = min_capacity;"
);
Code ret = untyped_fmt( "\t" "return set_capacity( new_capacity );" );
body = def_function_body( 4, header, new_capacity, check_n_set, ret );
}
grow = def_function( "grow", UnusedCode, param, t_bool, body );
}
Code pop;
{
Code body;
{
Code header = def_variable( "header", ref_header, untyped_fmt("get_header()") );
Code assertion = untyped_fmt( "\t" "assert( header.Num > 0 );" );
Code decrement = untyped_fmt( "\t" "header.Num--; " );
body = def_function_body( 3, header, assertion, decrement );
}
pop = def_function( "pop", UnusedCode, UnusedCode, t_void, body );
}
Code reserve;
{
Code params = def_parameters( 1, "new_capacity", t_uw );
Code body;
{
Code header = def_variable( "header", ref_header, untyped_fmt("get_header()") );
Code check_n_set = untyped_fmt(
"\t" "if ( header.Capacity < new_capacity )"
"\n\t\t" "return set_capacity( new_capacity );"
);
Code ret = untyped_fmt( "\t" "return true" );
body = def_function_body( 3, header, check_n_set, ret );
}
reserve = def_function( "reserve", UnusedCode, params, t_bool, body );
}
Code resize;
{
Code param = def_parameters( 1, "new_num", t_uw );
Code body;
{
Code header = def_variable( "header", ref_header, untyped_fmt("get_header()") );
Code check_n_grow = untyped_fmt(
"\t" "if ( header.Capacity < new_num )"
"\n\t\t" "if ( ! grow( new_num) )"
"\n\t\t\t" "return false;"
);
Code set_n_ret = untyped_fmt(
"\t" "header.Count = new_num;"
"\n\t" "return true;"
);
body = def_function_body( 3, header, check_n_grow, set_n_ret );
}
resize = def_function( "resize", UnusedCode, param, t_bool, body );
}
Code set_capacity;
{
Code param = def_parameters( 1, "capacity", t_uw );
Code body;
{
Code header = def_variable( "header", ref_header, untyped_fmt("get_header()") );
Code checks = untyped_fmt(
"\t" "if ( capacity == header.Capacity )"
"\n\t\t" "return true;"
"\n"
"\n\t" "if ( capacity < header.Num )"
"\n\t\t" "header.Num = capacity;"
);
Code size = def_variable( "size", t_uw, untyped_fmt("sizeof(Header) + sizeof(type) * capacity"));
Code new_header = def_variable( "new_header", ptr_header, untyped_fmt("rcast( Header*, alloc( header.Allocator, size ));"));
Code check_n_move = untyped_fmt(
"\t""if ( new_header == nullptr )"
"\n\t\t""return false;"
"\n"
"\n\t""memmove( new_header, & header, sizeof(Header) + sizeof(type) * header.Num );"
);
Code set_free_ret = untyped_fmt(
"\t" "new_header->Allocator = header.Allocator;"
"\n\t" "new_header->Num = header.Num;"
"\n\t" "new_header->Capacity = header.Capacity;"
"\n"
"\n\t" "zpl_free( header );"
"\n"
"\n\t" "*Data = new_header + 1;"
"\n"
"\n\t" "return true;"
);
body = def_function_body( 6, header, checks, size, new_header, check_n_move, set_free_ret );
}
set_capacity = def_function( "set_capacity", UnusedCode, param, t_bool, body );
}
Code body = def_struct_body( 15
, using_type
, data
, init
, init_reserve
, append
, back
, clear
, fill
, free
, get_header
, grow
, pop
, reserve
, resize
, set_capacity
);
array_def = def_struct( name, body, parent );
}
return array_def;
}
u32 gen_array_file()
{
Code a_base = gen__array_base();
Code a_u32 = gen_array( u32 );
Code a_cstr = gen_array( char const* );
Builder
arraygen;
arraygen.open( "Array.gen.hpp" );
arraygen.print( a_u32 );
arraygen.print( a_cstr );
arraygen.write();
return 0;
}
#endif
#ifndef gen_time
# include "Array.gen.hpp"
# define array( Type_ ) array_##Type_
#endif

View File

@ -1,24 +0,0 @@
inline
u8 square(u8 value)
{
return value * value;
}
inline
u16 square(u16 value)
{
return value * value;
}
inline
u32 square(u32 value)
{
return value * value;
}
inline
u64 square(u64 value)
{
return value * value;
}

View File

@ -12,7 +12,7 @@
What it should generate:
inline
type square_#type( type value )
type square( type value )
{
return value * value;
}
@ -20,19 +20,18 @@
#define gen_square( Type_ ) gen__square( #Type_ )
Code gen__square( char const* type )
{
Code integral_type = make_type( type );
Code integral_type = def_type( type );
string name = string_sprintf( g_allocator, (char*)sprintf_buf, ZPL_PRINTF_MAXLEN, "square", type );
Code specifiers = make_specifiers( 1, Specifier::Inline );
Code params = make_parameters( 1, "value", integral_type );
Code ret_stmt = make_fmt( "\treturn value * value;" );
string name = string_sprintf( g_allocator, (char*)sprintf_buf, ZPL_PRINTF_MAXLEN, "square", type );
Code result = make_function( name,
specifiers,
params,
integral_type,
ret_stmt
);
Code result;
{
Code params = def_parameters( 1, "value", integral_type );
Code specifiers = def_specifiers( 1, Specifier::Inline );
Code ret_stmt = untyped_fmt( "\treturn value * value;" );
result = def_function( name, specifiers, params, integral_type, ret_stmt );
}
if ( ! result )
fatal( "Failed to generate square function for: %s", type );
@ -60,7 +59,7 @@
#endif
#ifndef gen_time
#include "math.gen.hpp"
#undef square
# include "math.gen.hpp"
# undef square
#endif

View File

@ -12,6 +12,8 @@ int gen_main()
zpl_printf("\nPress any key after attaching to process\n");
getchar();
gen::init()
int result = gen_math();
Memory::cleanup();

49
thirdparty/zpl.h vendored
View File

@ -3948,24 +3948,28 @@ License:
} \
} while (0)
ZPL_IMPL_INLINE b8 zpl__array_set_capacity(void **a_array, sw capacity) {
array_header *h = ZPL_ARRAY_HEADER(*a_array);
if (capacity == h->capacity) return true;
if (capacity < h->count) h->count = capacity;
sw size = size_of(array_header) + h->elem_size * capacity;
array_header *nh = zpl_cast(array_header *) alloc(h->allocator, size);
if (!nh) return false;
zpl_memmove(nh, h, size_of(array_header) + h->elem_size * h->count);
nh->allocator = h->allocator;
nh->elem_size = h->elem_size;
nh->count = h->count;
nh->capacity = capacity;
free(h->allocator, h);
*a_array = nh + 1;
return true;
}
#define array_set_capacity(x, capacity) zpl__array_set_capacity(zpl_cast(void **) & (x), (capacity))
ZPL_IMPL_INLINE b8 zpl__array_set_capacity( void** a_array, sw capacity )
{
array_header* h = ZPL_ARRAY_HEADER( *a_array );
if ( capacity == h->capacity )
return true;
if ( capacity < h->count )
h->count = capacity;
sw size = size_of( array_header ) + h->elem_size * capacity;
array_header* nh = zpl_cast( array_header* ) alloc( h->allocator, size );
if ( ! nh )
return false;
zpl_memmove( nh, h, size_of( array_header ) + h->elem_size * h->count );
nh->allocator = h->allocator;
nh->elem_size = h->elem_size;
nh->count = h->count;
nh->capacity = capacity;
free( h->allocator, h );
*a_array = nh + 1;
return true;
}
#define array_set_capacity( x, capacity ) zpl__array_set_capacity( zpl_cast( void** ) & ( x ), ( capacity ) )
ZPL_IMPL_INLINE b8 zpl__array_grow(void **x, sw min_capacity) {
sw new_capacity = ZPL_ARRAY_GROW_FORMULA(array_capacity(*x));
@ -3982,7 +3986,14 @@ License:
return true;
}
#define array_append(x, item) (zpl__array_append_helper(zpl_cast(void **) & (x)) && (((x)[array_count(x)++] = (item)), true))
#define array_append(x, item) \
( \
zpl__array_append_helper(zpl_cast(void **) & (x)) \
&& ( \
((x)[array_count(x)++] = (item)) \
, true \
) \
)
ZPL_IMPL_INLINE b8 zpl__array_append_at_helper(void **x, sw ind) {
if (ind >= array_count(*x)) ind = array_count(*x) - 1;