diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..70d031f --- /dev/null +++ b/Readme.md @@ -0,0 +1,59 @@ +# gencpp + +An attempt at simple staged metaprogramming for c/c++. + +This project is not minimum feature complete yet. +Version 1 will have a most of c and a subset of c++ features available to it. + +## How it works + +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. + +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" + +... + +u32 gen_main() +{ + ... +} +#endif + + +#ifndef gen_time +#include "program.gen.cpp" + + // Regular runtime dependent on the generated code here. +#endif +``` +This is ofc entirely optional and the metaprogram can be quite separated from the runtime in file organization. + +## Building + +To fill in. + +## 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. + +This is technically what the macro preprocessor does in a basic form, however naturally its easier to deal with for more complex generation. + +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. + +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. + +However, if the code being generated becomes complex, or from a datatable or database, this will be easier to deal with. + +# TODO: + +* Need problably a better name, I found a few repos with this same one... +* Actually get to version 1. diff --git a/project/Bloat.cpp b/project/Bloat.cpp index e023ac9..b59d5af 100644 --- a/project/Bloat.cpp +++ b/project/Bloat.cpp @@ -1,5 +1,5 @@ #define BLOAT_IMPL -#include "Bloat.refactored.hpp" +#include "Bloat.hpp" namespace Global { diff --git a/project/Bloat.hpp b/project/Bloat.hpp index dba5783..802e2d6 100644 --- a/project/Bloat.hpp +++ b/project/Bloat.hpp @@ -84,7 +84,7 @@ namespace Global namespace Memory { - ct uw Initial_Reserve = megabytes(2); + ct uw Initial_Reserve = megabytes(10); extern arena Global_Arena; #define g_allocator arena_allocator( & Memory::Global_Arena) diff --git a/project/gen.cpp b/project/gen.cpp index 9107491..f903b88 100644 --- a/project/gen.cpp +++ b/project/gen.cpp @@ -1,9 +1,16 @@ #include "Bloat.hpp" +// #define gen_time #include "gen.hpp" #ifdef gen_time namespace gen { + ct Code make() + { + return { Code::Invalid, nullptr, nullptr, { nullptr } }; + } + + #if 0 static Code Unused() { static Code value = { @@ -13,13 +20,16 @@ namespace gen return value; } + #endif Code decl_type( char const* name, Code specifiers, Code type ) { Code - result; + result = make(); result.Type = Code::Decl_Type; result.Name = string_make( g_allocator, name ); + + array_init( result.Entries, g_allocator ); result.add( specifiers ); result.add( type ); @@ -33,11 +43,11 @@ namespace gen ) { Code - result; + result = make(); result.Type = Code::Decl_Function; result.Name = string_make( g_allocator, name ); - array_init( result.Content, g_allocator ); + array_init( result.Entries, g_allocator ); if ( specifiers ) result.add( specifiers ); @@ -55,20 +65,28 @@ namespace gen if (num <= 0) fatal("TT::make_paramters: num is %d", num); - Code result; + Code + result = make(); + result.Type = Code::Parameters; va_list va; va_start(va, num); result.Name = string_make( g_allocator, va_arg(va, char const*) ); - result.add( va_arg(va, Code) ); - while( num -= 2, num % 2 ) + array_init( result.Entries, g_allocator ); + + Code type = va_arg(va, Code); + result.add( type ); + + while( num -= 2, num && num % 2 == 0 ) { Code - param; + param = make(); param.Name = string_make( g_allocator, va_arg(va, char const*) ); - param.add( va_arg(va, Code) ); + + type = va_arg(va, Code); + param.add( type ); result.add(param); } @@ -89,12 +107,12 @@ namespace gen va_end(va); Code - code; - code.Name = string_make( g_allocator, fmt ); - code.Type = Code::Untyped; - code.Content = string_make( g_allocator, buf ); + result = make(); + result.Name = string_make( g_allocator, fmt ); + result.Type = Code::Untyped; + result.Content = string_make( g_allocator, buf ); - return code; + return result; } Code make_function( char const* name @@ -104,7 +122,7 @@ namespace gen , Code body ) { Code - result; + result = make(); result.Name = string_make( g_allocator, name ); result.Type = Code::Function; @@ -123,37 +141,51 @@ namespace gen return result; } - Code make_specifier( u32 num, ... ) + Code make_specifiers( u32 num, ... ) { if ( num <= 0 ) fatal("gen::make_specifier: num cannot be zero."); Code - result; + result = make(); + result.Type = Code::Specifiers; + result.Content = string_make( g_allocator, "" ); va_list va; va_start(va, num); do { - Specifier type = va_arg(va, Specifier); + Specifier type = (Specifier)va_arg(va, int); switch ( type ) { case Alignas: - result.Content = string_sprintf_buf( g_allocator, "%s(%d)", specifier_str(type), va_arg(va, u32) ); + result.Content = string_append_fmt( result.Content, "%s(%d)", specifier_str(type), va_arg(va, u32) ); break; default: - result.Content = string_make( g_allocator, specifier_str(type) ); + const char* str = specifier_str(type); + + result.Content = string_append_fmt( result.Content, "%s", str ); break; } } - while ( num-- ); + while ( --num, num ); va_end(va); return result; } + Code make_type( char const* name ) + { + Code + result = make(); + result.Name = string_make( g_allocator, name ); + result.Type = Code::Typename; + + return result; + } + string Code::to_string() { string result = string_make( g_allocator, "" ); @@ -173,12 +205,13 @@ namespace gen case Decl_Type: if ( Entries[0].Type == Specifiers ) - result = string_append_fmt("%s\n", Entries[0].to_string()); + result = string_append_fmt( result, "%s\n", Entries[0].to_string()); result = string_append_fmt( result, "%s %s;\n", Entries[1].to_string(), Name ); break; case Decl_Function: + { u32 index = 0; u32 left = array_count( Entries ); @@ -207,16 +240,22 @@ namespace gen } result = string_appendc( result, ");\n" ); + } break; - case Parameters: - result = string_append_fmt( result, "%s %s", Entries[0], Name ); + case Parameters: + { + result = string_append_fmt( result, "%s %s", Entries[0].to_string(), Name ); u32 index = 1; u32 left = array_count( Entries ) - 1; while ( left-- ) - result = string_append_fmt( result, ", %s %s", Entries[index].Entries[0], Entries[index].Name ); + result = string_append_fmt( result, ", %s %s" + , Entries[index].Entries[0].to_string() + , Entries[index].Name + ); + } break; case Struct: @@ -224,31 +263,36 @@ namespace gen break; case Function: + { u32 index = 0; u32 left = array_count( Entries ); if ( left <= 0 ) fatal( "Code::to_string - Name: %s Type: %s, expected definition", Name, Type ); - while ( Entries[index].Type == EType::Specifiers ) + if ( Entries[index].Type == Specifiers ) { - result = string_append_fmt( result, "%s ", Entries[index] ); + result = string_append_fmt( result, "%s\n", Entries[index].to_string() ); index++; + left--; } if ( left <= 0 ) fatal( "Code::to_string - Name: %s Type: %s, expected return type", Name, Type ); - result = string_append_fmt( result, "\n%s %s(", Entries[index], Name ); + result = string_append_fmt( result, "\n%s %s(", Entries[index].to_string(), Name ); index++; + left--; - while ( left && Entries[index].Type == Parameters ) + if ( left && Entries[index].Type == Parameters ) { - result = string_append_fmt( result, "%s, ", Entries[index] ); + result = string_append_fmt( result, "%s, ", Entries[index].to_string() ); index++; + left--; } - result = string_append_fmt( result, ")\n{\n%s\n}", Entries[index] ); + result = string_append_fmt( result, ")\n{\n%s\n}", Entries[index].to_string() ); + } break; case Specifiers: @@ -260,18 +304,41 @@ namespace gen break; case Typename: + result = string_append_fmt( result, "%s", Name ); break; } return result; } + + void Builder::print( Code code ) + { + Buffer = string_append( Buffer, code.to_string() ); + } + + bool Builder::open( char const* path ) + { + file_error error = file_open_mode( & File, ZPL_FILE_MODE_WRITE, path ); + + if ( error != ZPL_FILE_ERROR_NONE ) + { + fatal( "gen::File::open - Could not open file: %s", path); + return false; + } + + Buffer = string_make( g_allocator, "" ); + + return true; + } + + void Builder::write() + { + bool result = file_write( & File, Buffer, string_length(Buffer) ); + + if ( result == false ) + fatal("gen::File::write - Failed to write to file: %s", file_name( & File ) ); + + file_close( & File ); + } } - - -int main() -{ - gen_main(); - - return 0; -} -#endif gen_time +#endif diff --git a/project/gen.hpp b/project/gen.hpp index 172c3d2..2b4791b 100644 --- a/project/gen.hpp +++ b/project/gen.hpp @@ -1,8 +1,8 @@ #pragma once -#ifdef gen_time #include "Bloat.hpp" +#ifdef gen_time namespace gen { ct sw ColumnLimit = 256; @@ -119,7 +119,7 @@ namespace gen forceinline operator bool() { - return Type == Invalid; + return Type != Invalid; } operator char const*() @@ -153,13 +153,13 @@ namespace gen #endif #pragma endregion Member API - #define Using_Code_POD \ - Code::EType Type; \ - string Name; \ - string Comment; \ - union { \ - array(Code) Entries; \ - string Content; \ + #define Using_Code_POD \ + Code::EType Type; \ + string Name; \ + string Comment; \ + union { \ + array(Code) Entries; \ + string Content; \ }; Using_Code_POD; @@ -180,8 +180,8 @@ namespace gen , Code ret_type ); - Code make_parameters( u32 num, ... ); - + Code make_parameters( s32 num, ... ); + Code make_fmt( char const* fmt, ... ); Code make_function( char const* name @@ -197,20 +197,22 @@ namespace gen // Code make_template( Code subject, u32 num_dependents, ... ); - // Code make_type( char const* name ); + Code make_type( char const* name ); // Code make_using( char const* name, char const* type ); - struct File + struct Builder { - zpl_file file; - string Content; + zpl_file File; + string Buffer; - s32 print( Code ); + void print( Code ); - bool open( char const* Path ); + bool open( char const* path ); void write(); }; } + +#define gen_main main #endif diff --git a/scripts/build.ci.ps1 b/scripts/build.ci.ps1 index 0044fd7..fe404fd 100644 --- a/scripts/build.ci.ps1 +++ b/scripts/build.ci.ps1 @@ -14,16 +14,16 @@ foreach ( $arg in $args ) } -if ($false) -{ -#region Regular Build -write-host "Building project`n" - $path_root = git rev-parse --show-toplevel $path_build = Join-Path $path_root build $path_scripts = Join-Path $path_root scripts +if ($false) +{ +#region Regular Build +write-host "Building project`n" + if ( -not( Test-Path $path_build ) ) { $args_meson = @() @@ -87,7 +87,13 @@ Pop-Location $args_ninja += $path_gen_build Push-Location $path_root - ninja $args_ninja + & ninja $args_ninja + Pop-Location + + $gencpp = Join-Path $path_gen_build gencpp.exe + + Push-location $path_gen + & $gencpp Pop-Location diff --git a/scripts/clean.ps1 b/scripts/clean.ps1 index c51d476..5d6fe4e 100644 --- a/scripts/clean.ps1 +++ b/scripts/clean.ps1 @@ -1,7 +1,9 @@ $path_root = git rev-parse --show-toplevel $path_build = Join-Path $path_root build $path_test = Join-Path $path_root test +$path_gen = Join-Path $path_test gen $path_test_build = Join-Path $path_test build +$path_gen_build = Join-Path $path_gen build if ( Test-Path $path_build ) { @@ -13,6 +15,11 @@ if ( Test-Path $path_test_build ) Remove-Item $path_test_build -Recurse } +if ( Test-Path $path_gen_build ) +{ + Remove-Item $path_gen_build -Recurse +} + # [string[]] $include = '*.h', '*.hpp', '*.cpp' # [string[]] $exclude = diff --git a/test/array.gen.manual.hpp.txt b/test/array.gen.manual.hpp.txt deleted file mode 100644 index b022090..0000000 --- a/test/array.gen.manual.hpp.txt +++ /dev/null @@ -1,13 +0,0 @@ -// Handwritten generated code: - - -#pragma region gen_array - - - - -#pragma endregion gen_array - - - -#define Array( Type_ ) Array_##Type_ \ No newline at end of file diff --git a/test/array.hpp.txt b/test/array.hpp.txt deleted file mode 100644 index 9d85d76..0000000 --- a/test/array.hpp.txt +++ /dev/null @@ -1,164 +0,0 @@ -#include "gen.hpp" -#include "bloat.hpp" - -#if tt - #define gen_array( Type_ ) gen__array( #Type_ ) - Code* gen__array( char const* type ) - { - Code codegen; - - Code - data; - data.add( "%type* data" ); - - Code header; - header.define_struct( "Header", - "uw Num;" - "uw Capacity;" - "allocator Allocator;" - ); - - Code grow_formula; - grow_formula.define_function( "grow_formula", "static", "forceinline", - "return 2 * value * 8" - ); - - codegen.define_struct( "Array", - data - , header - , grow_formula - ); - - codegen.define - - R"( - %type* data; - - struct Header - { - uw Num; - uw Capacity; - allocator Allocator; - }; - - static forceinline - sw grow_formula ( sw value ) - { - return 2 * value * 8; - } - - static - bool init ( %Array& array, allocator mem_handler ) - { - return init_reserve( array, mem_handler, grow_formula(0) ); - } - - static - bool init_reserve( %Array& array, allocator mem_handler, uw capacity ) - { - Header* - header = nullptr; - header = rcast( Header*, alloc( mem_handler, size_of( Header ) + sizeof(type) * capacity )); - - if (header == nullptr) - return false; - - header->Num = 0; - header->Capacity = capacity; - header->Allocator = mem_handler; - - array.data = rcast( %type*, header + 1 ); - - return true; - } - - void append( %type value ) - { - - } - - type back() - { - Header& header = get_header(); - return data[ header.Num - 1 ]; - } - - void clear() - { - get_header().Num = 0; - } - - void fill( uw begin, uw end, type value ) - { - Header& header = get_header(); - - if ( begin < 0 || end >= header.Num ) - { - fatal( "Range out of bounds" ); - } - } - - void free() - { - Header& header = get_header(); - ::free( header.Allocator, & get_header() ); - } - - bool grow( uw min_capacity ) - { - Header& header = get_header(); - - uw new_capacity = grow_formula( header.Capacity ); - - if ( new_capacity < min_capacity ) - new_capacity = min_capacity; - - return set_capacity( new_capacity ); - } - - forceinline - Header& get_header() - { - return vcast( Header, data - 1 ); - } - - void pop() - { - - } - - void reserve() - { - - } - - void resize() - { - - } - - bool set_capacity( uw capacity ) - { - Header& header = get_header(); - - if ( capacity == header.Capacity ) - { - - } - } - )" - , type - ); - }; - #endif - - void tt_run() - { - gen_array( u32 ); - } -#endif tt - - -#if !tt - #include "array.gen.manual.hpp" -#endif diff --git a/test/gen/math.gen.hpp b/test/gen/math.gen.hpp new file mode 100644 index 0000000..e69de29 diff --git a/test/gen/meson.build b/test/gen/meson.build index 75c3aef..4be9050 100644 --- a/test/gen/meson.build +++ b/test/gen/meson.build @@ -4,15 +4,14 @@ project( 'test', 'c', 'cpp', default_options : ['buildtype=debug'] ) includes = include_directories( [ - '../test' - '../project' - , '../thirdparty' + '../../project', + '../../thirdparty' ]) # get_sources = files('./get_sources.ps1') # sources = files(run_command('powershell', get_sources, check: true).stdout().strip().split('\n')) -sources = [ 'test.cpp' ] +sources = [ '../test.cpp' ] if get_option('buildtype').startswith('debug') @@ -20,6 +19,6 @@ if get_option('buildtype').startswith('debug') endif - add_project_arguments('-Dgen_time', langauge : ['c', 'cpp']) + add_project_arguments('-Dgen_time', language : ['c', 'cpp']) -executable( '', sources, include_directories : includes ) +executable( 'gencpp', sources, include_directories : includes ) diff --git a/test/math.hpp b/test/math.hpp index 72f57b8..682793b 100644 --- a/test/math.hpp +++ b/test/math.hpp @@ -1,9 +1,9 @@ #pragma once -#ifdef gen_time - #include "Bloat.hpp" - #include "gen.hpp" +#include "Bloat.hpp" +#include "gen.hpp" +#ifdef gen_time using namespace gen; /* @@ -15,8 +15,8 @@ return value * value; } */ - #define gen_square( Type_ ) gen__squre( #Type_ ) - Code gen__squre( char const* type ) + #define gen_square( Type_ ) gen__square( #Type_ ) + Code gen__square( char const* type ) { Code integral_type = make_type( type ); @@ -41,17 +41,17 @@ u32 gen_math() { Code fadd_u8 = gen_square( u8 ); - Code fadd_u16 = gen_square( u16 ); - Code fadd_u32 = gen_square( u32 ); - Code fadd_u64 = gen_square( u64 ); + // Code fadd_u16 = gen_square( u16 ); + // Code fadd_u32 = gen_square( u32 ); + // Code fadd_u64 = gen_square( u64 ); - File + Builder mathgen; mathgen.open( "math.gen.hpp" ); mathgen.print( fadd_u8 ); - mathgen.print( fadd_u16 ); - mathgen.print( fadd_u32 ); - mathgen.print( fadd_u64 ); + // mathgen.print( fadd_u16 ); + // mathgen.print( fadd_u32 ); + // mathgen.print( fadd_u64 ); mathgen.write(); return 0; } diff --git a/test/test.cpp b/test/test.cpp index d620cd1..c9a9ff9 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1,17 +1,28 @@ +#include "Bloat.cpp" #include "math.hpp" #ifdef gen_time -u32 gen_main() -{ - return gen_math(); -} - #include "gen.cpp" + +int gen_main() +{ + Memory::setup(); + + zpl_printf("\nPress any key after attaching to process\n"); + getchar(); + + int result = gen_math(); + + Memory::cleanup(); + return result; +} #endif #ifndef gen_time +#include "math.hpp" + int main() { u32 result = square(5);