Skip to content

Commit ecbbf55

Browse files
authored
Polish (#21)
Really sanding down the rough edges this time around. This release includes significant ergonomic improvements to the high-level API. ### Added - New Signature Keywords: Added keywords for common C and C++ types to improve signature readability and portability. - Added `size_t` and `ssize_t` as platform-dependent abstract types. - Added `char8_t`, `char16_t`, and `char32_t` as aliases for `uint8`, `uint16`, and `uint32` for better C++ interoperability. - Cookbook Examples: Extracted all recipes from the cookbook documentation into a comprehensive suite of standalone, compilable example programs located in the `eg/cookbook/` directory. - Advanced C++ Recipes: Added new, advanced cookbook recipes demonstrating direct, wrapper-free interoperability with core C++ features: - Calling C++ virtual functions by emulating v-table dispatch. - Bridging C-side stateful callbacks with C++ objects that expect `std::function` or similar callable objects. ### Changed - Improved C++ Interoperability Recipes: Refined the C++ recipes to focus on direct interaction with C++ ABIs (mangled names, v-tables) rather than relying on C-style wrappers, showcasing more advanced use cases. - Improved `wchar_t` Guidance: Added a dedicated cookbook recipe explaining the best-practice for handling `wchar_t` and other semantic string types via the Type Registry, ensuring signatures are unambiguous and introspectable. - Enhanced High-Level API for Registered Types. The primary creation functions (`infix_forward_create`, `infix_forward_create_unbound`, `infix_reverse_create_callback`, and `infix_reverse_create_closure`) can now directly accept a registered named type as a signature. ```c // The high-level API now understands the "@name" syntax directly. // Assume the registry already has "@Adder_add_fn = (*{ val: int }, int) -> int;" infix_reverse_create_callback(&ctx, "@Adder_add_fn", (void*)Adder_add, reg); ``` - The `infix_read_global` and `infix_write_global` functions now take an additional `infix_registry_t*` argument to support reading and writing global variables that are defined by a named type (e.g., `@MyStruct`). ### Fixed - Fixed a critical parsing bug in `infix_register_types` that occurred when defining a function pointer type alias (e.g., `@MyFunc = (...) -> ...;`). The preliminary parser for finding definition boundaries would incorrectly interpret the `>` in the `->` token as a closing delimiter, corrupting its internal nesting level calculation. This resulted in an `INFIX_CODE_UNEXPECTED_TOKEN` error and prevented the registration of function pointer types. The parser is now context-aware and correctly handles the `->` token, allowing for the clean and correct registration of function pointer aliases as intended.
1 parent 649f1df commit ecbbf55

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4847
-1783
lines changed

CHANGELOG.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,37 @@ All notable changes to this project will (I hope) be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [Unreleased]
8+
## [0.1.1] - 2025-11-01
99

10-
### Here we go again...
10+
Really sanding down the rough edges this time around. This release includes significant ergonomic improvements to the high-level API.
11+
12+
### Added
13+
14+
- New Signature Keywords: Added keywords for common C and C++ types to improve signature readability and portability.
15+
- Added `size_t` and `ssize_t` as platform-dependent abstract types.
16+
- Added `char8_t`, `char16_t`, and `char32_t` as aliases for `uint8`, `uint16`, and `uint32` for better C++ interoperability.
17+
- Cookbook Examples: Extracted all recipes from the cookbook documentation into a comprehensive suite of standalone, compilable example programs located in the `eg/cookbook/` directory.
18+
- Advanced C++ Recipes: Added new, advanced cookbook recipes demonstrating direct, wrapper-free interoperability with core C++ features:
19+
- Calling C++ virtual functions by emulating v-table dispatch.
20+
- Bridging C-side stateful callbacks with C++ objects that expect `std::function` or similar callable objects.
21+
22+
### Changed
23+
24+
- Improved C++ Interoperability Recipes: Refined the C++ recipes to focus on direct interaction with C++ ABIs (mangled names, v-tables) rather than relying on C-style wrappers, showcasing more advanced use cases.
25+
- Improved `wchar_t` Guidance: Added a dedicated cookbook recipe explaining the best-practice for handling `wchar_t` and other semantic string types via the Type Registry, ensuring signatures are unambiguous and introspectable.
26+
- Enhanced High-Level API for Registered Types. The primary creation functions (`infix_forward_create`, `infix_forward_create_unbound`, `infix_reverse_create_callback`, and `infix_reverse_create_closure`) can now directly accept a registered named type as a signature.
27+
28+
```c
29+
// The high-level API now understands the "@Name" syntax directly.
30+
// Assume the registry already has "@Adder_add_fn = (*{ val: int }, int) -> int;"
31+
infix_reverse_create_callback(&ctx, "@Adder_add_fn", (void*)Adder_add, reg);
32+
```
33+
34+
- The `infix_read_global` and `infix_write_global` functions now take an additional `infix_registry_t*` argument to support reading and writing global variables that are defined by a named type (e.g., `@MyStruct`).
35+
36+
### Fixed
37+
38+
- Fixed a critical parsing bug in `infix_register_types` that occurred when defining a function pointer type alias (e.g., `@MyFunc = (...) -> ...;`). The preliminary parser for finding definition boundaries would incorrectly interpret the `>` in the `->` token as a closing delimiter, corrupting its internal nesting level calculation. This resulted in an `INFIX_CODE_UNEXPECTED_TOKEN` error and prevented the registration of function pointer types. The parser is now context-aware and correctly handles the `->` token, allowing for the clean and correct registration of function pointer aliases as intended.
1139

1240
## [0.1.0] - 2025-10-27
1341

README.md

Lines changed: 84 additions & 244 deletions
Large diffs are not rendered by default.

build.pl

Lines changed: 117 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
# Argument Parsing
1818
my %opts;
19-
GetOptions( \%opts, 'cc|compiler=s', 'cflags=s', 'h|help', 'codecov=s', 'abi=s', 'verbose|v' );
19+
GetOptions( \%opts, 'cc|compiler=s', 'cflags=s', 'h|help', 'codecov=s', 'abi=s', 'verbose|v', 'examples' );
2020
show_help() if $opts{help};
2121
my $command = lc( shift @ARGV || 'build' );
2222
my @test_names = @ARGV;
@@ -35,6 +35,7 @@
3535
File::Spec->catdir( $FindBin::Bin, 't/include' )
3636
],
3737
lib_dir => 'build_lib',
38+
bin_dir => 'bin',
3839
lib_name => 'infix',
3940
coverage_dir => 'coverage'
4041
);
@@ -107,50 +108,61 @@
107108
my $obj_suffix = ( $config{compiler} eq 'msvc' ) ? '.obj' : '.o';
108109
if ( $config{compiler} eq 'msvc' ) {
109110
die 'Warning: MSVC environment not detected. Build may fail. Please run from a VS dev prompt.' unless $ENV{VCINSTALLDIR};
110-
$config{cc} = 'cl';
111+
$config{cc} = 'cl';
112+
$config{cxx} = 'cl';
111113
my @include_flags = map { '-I' . File::Spec->catfile($_) } @{ $config{include_dirs} };
112-
$config{cflags} = [ @base_cflags, '-std:c17', '-experimental:c11atomics', '-W3', '-GS', '-MD', @include_flags ];
113-
$config{ldflags} = ['-link'];
114+
$config{cflags} = [ @base_cflags, '-std:c11', '-experimental:c11atomics', '-W3', '-GS', '-MD', @include_flags ];
115+
$config{cxxflags} = [ @base_cflags, '-std:c++11', '-EHsc', '-W3', '-GS', '-MD', @include_flags ];
116+
$config{ldflags} = ['-link'];
114117
if ( $is_coverage_build || $command eq 'test' ) {
115-
push @{ $config{cflags} }, '-Zi';
116-
push @{ $config{ldflags} }, '-DEBUG';
118+
push @{ $config{cflags} }, '-Zi';
119+
push @{ $config{cxxflags} }, '-Zi';
120+
push @{ $config{ldflags} }, '-DEBUG';
117121
}
118122
if ( $command ne 'build' ) {
119-
push @{ $config{cflags} }, '-O2';
123+
push @{ $config{cflags} }, '-O2';
124+
push @{ $config{cxxflags} }, '-O2';
120125
}
121126
}
122127
else { # GCC or Clang
123-
$config{cc} = $config{compiler};
128+
$config{cc} = $config{compiler};
129+
$config{cxx} = ( $config{compiler} eq 'clang' ) ? 'clang++' : 'g++';
124130
my @include_flags = map { "-I" . File::Spec->catfile($_) } @{ $config{include_dirs} };
125-
$config{cflags} = [ @base_cflags, '-std=c17', '-Wall', '-Wextra', '-g', '-O2', '-pthread', @include_flags ];
126-
$config{ldflags} = [];
131+
$config{cflags} = [ @base_cflags, '-std=c11', '-Wall', '-Wextra', '-g', '-O2', @include_flags ];
132+
$config{cxxflags} = [ @base_cflags, '-std=c++11', '-Wall', '-Wextra', '-g', '-O2', @include_flags ];
133+
$config{ldflags} = [];
127134
if ( $config{compiler} eq 'clang' && $config{arch} eq 'arm64' && $host_arch_raw !~ /arm64|aarch64|evbarm/ && !$opts{abi} ) {
128135
print "ARM64 cross-compilation detected for clang. Adding --target flag.\n";
129136
my $target_triple = $config{is_windows} ? 'aarch64-pc-windows-msvc' : 'aarch64-linux-gnu';
130-
push @{ $config{cflags} }, "--target=$target_triple";
131-
push @{ $config{ldflags} }, "--target=$target_triple";
137+
push @{ $config{cflags} }, "--target=$target_triple";
138+
push @{ $config{cxxflags} }, "--target=$target_triple";
139+
push @{ $config{ldflags} }, "--target=$target_triple";
132140
}
133141
if ($is_coverage_build) {
134142
if ( $config{compiler} eq 'clang' ) {
135-
push @{ $config{cflags} }, '-fprofile-instr-generate', '-fcoverage-mapping';
136-
push @{ $config{ldflags} }, '-fprofile-instr-generate', '-fcoverage-mapping';
143+
push @{ $config{cflags} }, '-fprofile-instr-generate', '-fcoverage-mapping';
144+
push @{ $config{cxxflags} }, '-fprofile-instr-generate', '-fcoverage-mapping';
145+
push @{ $config{ldflags} }, '-fprofile-instr-generate', '-fcoverage-mapping';
137146
}
138147
else { # gcc
139-
push @{ $config{cflags} }, '--coverage';
140-
push @{ $config{ldflags} }, '--coverage';
148+
push @{ $config{cflags} }, '--coverage';
149+
push @{ $config{cxxflags} }, '--coverage';
150+
push @{ $config{ldflags} }, '--coverage';
141151
}
142152
}
143-
if ( !( $config{is_windows} && $config{compiler} eq 'clang' ) ) {
144-
push @{ $config{ldflags} }, '-lm';
145-
}
146153
if ( !$config{is_windows} ) {
147-
push @{ $config{ldflags} }, '-pthread';
154+
push @{ $config{cflags} }, '-pthread';
155+
push @{ $config{cxxflags} }, '-pthread';
156+
push @{ $config{ldflags} }, '-pthread';
148157
my $lrt_flag = check_for_lrt( \%config );
149158
push @{ $config{ldflags} }, $lrt_flag if $lrt_flag;
150159
}
151160
if ( $^O eq 'openbsd' ) {
152161
push @{ $config{ldflags} }, '-Wl,-w';
153162
}
163+
if ( !( $config{is_windows} && $config{compiler} eq 'clang' ) ) {
164+
push @{ $config{ldflags} }, '-lm';
165+
}
154166
}
155167
push @{ $config{cflags} }, $opts{cflags} if $opts{cflags};
156168

@@ -166,6 +178,13 @@
166178
push @{ $config{cflags} }, '-DINFIX_DEBUG_ENABLED=1' if $opts{verbose};
167179
my $lib_path = create_static_library( \%config, $obj_suffix );
168180
print "\nStatic library '$lib_path' built successfully.\n";
181+
if ( $opts{examples} ) {
182+
build_examples( \%config, $obj_suffix, $lib_path );
183+
}
184+
}
185+
elsif ( $command eq 'examples' ) {
186+
my $lib_path = create_static_library( \%config, $obj_suffix );
187+
build_examples( \%config, $obj_suffix, $lib_path );
169188
}
170189
elsif ( $command eq 'test' || $command eq 'coverage' ) {
171190
push @{ $config{cflags} }, '-DDBLTAP_ENABLE=1';
@@ -225,6 +244,7 @@
225244
sub clean {
226245
return;
227246
rmtree( $config{lib_dir}, { verbose => 0 } );
247+
rmtree( $config{bin_dir}, { verbose => 0 } );
228248
rmtree( $config{coverage_dir}, { verbose => 0 } );
229249
rmtree( 'build_tools', { verbose => 0 } );
230250
my @artifacts;
@@ -249,7 +269,8 @@ sub show_help {
249269
Usage: ./build.pl [command] [options] [test_names...]
250270
251271
Commands:
252-
build Builds the core static library.
272+
build Builds the core static library. Use --examples to also build examples.
273+
examples Builds all cookbook examples.
253274
test Builds and runs specified tests (or all) individually.
254275
Test names are partial paths, e.g., '001_primitives'.
255276
coverage Generates a unified code coverage report by running all tests.
@@ -267,6 +288,7 @@ sub show_help {
267288
--abi=<s> Force a specific ABI for code generation. Overrides auto-detection.
268289
Supported: windows_x64, sysv_x64, aapcs64
269290
--codecov=<s> Specify a Codecov token to upload coverage results (or use CODECOV_TOKEN env var).
291+
--examples Build all cookbook examples (used with 'build' command).
270292
-v, --verbose Enable verbose debug output from the library by compiling with -DINFIX_DEBUG_ENABLED=1.
271293
-h, --help Show this help message.
272294
END_HELP
@@ -333,7 +355,7 @@ sub create_static_library_from_objects {
333355
my @cmd;
334356
if ($use_msvc_style_linker) {
335357
my $archiver = ( $config->{compiler} eq 'msvc' ) ? 'lib' : 'llvm-lib';
336-
my $out_flag = ( $archiver eq 'lib' ) ? '-OUT:' : '/OUT:';
358+
my $out_flag = ( $archiver eq 'lib' ) ? '-OUT:' : '-OUT:';
337359
@cmd = ( $archiver, $out_flag . $lib_path, @$obj_files_ref );
338360
}
339361
else {
@@ -386,7 +408,7 @@ sub compile_and_run_tests {
386408
if ( $config{compiler} eq 'msvc' ) {
387409

388410
# For MSVC, /arch:AVX512 is the most inclusive flag.
389-
push @local_cflags, '/arch:AVX512';
411+
push @local_cflags, '-arch:AVX512';
390412
}
391413
else {
392414
# For GCC/Clang, explicitly enable all vector extensions we want to test.
@@ -832,3 +854,75 @@ sub run_fuzz_test {
832854
}
833855
return 0;
834856
}
857+
858+
sub build_examples {
859+
my ( $config, $obj_suffix, $lib_path ) = @_;
860+
print "\nBuilding cookbook examples...\n";
861+
make_path( $config->{bin_dir} );
862+
my $eg_dir = File::Spec->catdir( 'eg', 'cookbook' );
863+
my $libs_dir = File::Spec->catdir( $eg_dir, 'libs' );
864+
865+
# Build Helper Shared Libraries
866+
my %libs;
867+
my $shared_lib_flags = $config->{is_windows} ? ( $config->{compiler} eq 'msvc' ? ['-LD'] : ['-shared'] ) : [ '-shared', '-fPIC' ];
868+
869+
# C++ libs
870+
$libs{myclass} = compile_shared_lib( $config, 'myclass', File::Spec->catfile( $libs_dir, 'MyClass.cpp' ), $config->{cxx}, $shared_lib_flags );
871+
$libs{box} = compile_shared_lib( $config, 'box', File::Spec->catfile( $libs_dir, 'Box.cpp' ), $config->{cxx}, $shared_lib_flags );
872+
$libs{shapes} = compile_shared_lib( $config, 'shapes', File::Spec->catfile( $libs_dir, 'shapes.cpp' ), $config->{cxx}, $shared_lib_flags );
873+
$libs{eventmanager}
874+
= compile_shared_lib( $config, 'eventmanager', File::Spec->catfile( $libs_dir, 'EventManager.cpp' ), $config->{cxx}, $shared_lib_flags );
875+
876+
# C libs
877+
$libs{globals} = compile_shared_lib( $config, 'globals', File::Spec->catfile( $libs_dir, 'libglobals.c' ), $config->{cc}, $shared_lib_flags );
878+
$libs{libB} = compile_shared_lib( $config, 'libB', File::Spec->catfile( $libs_dir, 'libB.c' ), $config->{cc}, $shared_lib_flags );
879+
my @libA_deps = $config->{is_windows} ? ( "-L" . $config->{bin_dir}, "-lB" ) : ( $libs{libB} );
880+
$libs{libA} = compile_shared_lib( $config, 'libA', File::Spec->catfile( $libs_dir, 'libA.c' ), $config->{cc}, $shared_lib_flags, \@libA_deps );
881+
882+
# Build Example Executables
883+
opendir my $dh, $eg_dir or die "Can't open $eg_dir: $!";
884+
while ( my $file = readdir $dh ) {
885+
next unless $file =~ /\.(c|cpp)$/;
886+
my $source_path = File::Spec->catfile( $eg_dir, $file );
887+
my $exe_name = basename($file);
888+
$exe_name =~ s/\.(c|cpp)$//;
889+
my $exe_path = File::Spec->catfile( $config->{bin_dir}, $exe_name . ( $config->{is_windows} ? '.exe' : '' ) );
890+
my $is_cpp = ( $file =~ /\.cpp$/ );
891+
my @local_cflags = $is_cpp ? @{ $config->{cxxflags} } : @{ $config->{cflags} };
892+
push @local_cflags, "-I$libs_dir";
893+
my @local_ldflags = @{ $config->{ldflags} };
894+
my $compiler = $is_cpp ? $config->{cxx} : $config->{cc};
895+
my $base_name = basename($file);
896+
my $link_name = $base_name;
897+
$link_name =~ s/^Ch\d+_Rec\d+_//;
898+
$link_name =~ s/\.c(pp)?$//;
899+
900+
if ( $link_name =~ /CppMangledNames|CppTemplates|VirtualFunctions|CppCallbacks|GlobalVariables|LibraryDependencies/ ) {
901+
push @local_ldflags, "-L" . $config->{bin_dir};
902+
}
903+
if ( $link_name =~ /CppMangledNames/ ) { push @local_ldflags, "-lmyclass"; }
904+
if ( $link_name =~ /CppTemplates/ ) { push @local_ldflags, "-lbox"; }
905+
if ( $link_name =~ /GlobalVariables/ ) { push @local_ldflags, "-lglobals"; }
906+
if ( $link_name =~ /LibraryDependencies/ ) { push @local_ldflags, "-llibA"; }
907+
if ( $link_name =~ /VirtualFunctions/ ) { push @local_ldflags, "-lshapes"; }
908+
if ( $link_name =~ /CppCallbacks/ ) { push @local_ldflags, "-leventmanager"; }
909+
if ( $link_name =~ /SystemLibraries/ && $config->{is_windows} ) {
910+
push @local_ldflags, '-luser32';
911+
}
912+
my @cmd = ( $compiler, @local_cflags, '-o', $exe_path, $source_path, $lib_path, @local_ldflags );
913+
run_command(@cmd);
914+
}
915+
closedir $dh;
916+
}
917+
918+
sub compile_shared_lib {
919+
my ( $config, $name, $source, $compiler, $flags, $deps ) = @_;
920+
my $is_cpp = ( $source =~ /\.cpp$/ );
921+
my $lib_prefix = $config->{is_windows} ? '' : 'lib';
922+
my $output_path = $config->{bin_dir} . '/' . $lib_prefix . $name . '.' . $Config{so};
923+
my @local_cflags = grep { $_ ne '-O2' } $is_cpp ? @{ $config->{cxxflags} } : @{ $config->{cflags} };
924+
my @cmd = ( $compiler, @local_cflags, @$flags, '-o', $output_path, $source );
925+
push @cmd, @$deps if $deps;
926+
run_command(@cmd);
927+
return $output_path;
928+
}

0 commit comments

Comments
 (0)