From 9405768105501ffdddbb32e450918cd0278b2677 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Thu, 6 Nov 2025 13:15:19 -0500 Subject: [PATCH 1/7] First pass at splitting zngur.h out of generated.h --- benchmark/build.rs | 1 + examples/cxx_demo/.gitignore | 1 + examples/cxx_demo/build.rs | 7 +- examples/memory_management/.gitignore | 1 + examples/memory_management/Makefile | 6 +- examples/multiple_zngur_files/.gitignore | 1 + examples/multiple_zngur_files/Makefile | 10 +- examples/raw_pointer/.gitignore | 1 + examples/raw_pointer/Makefile | 6 +- examples/rayon/.gitignore | 1 + examples/rayon/Makefile | 6 +- examples/regression_test1/.gitignore | 1 + examples/regression_test1/Makefile | 6 +- examples/rustyline/.gitignore | 1 + examples/rustyline/Makefile | 6 +- examples/simple/.gitignore | 1 + examples/simple/Makefile | 6 +- examples/simple_import/.gitignore | 1 + examples/simple_import/Makefile | 6 +- examples/tutorial-wasm32/.gitignore | 1 + examples/tutorial-wasm32/Makefile | 3 +- examples/tutorial/.gitignore | 1 + examples/tutorial/Makefile | 6 +- examples/tutorial_cpp/build.rs | 1 + xtask/src/ci.rs | 10 +- zngur-cli/src/main.rs | 9 +- zngur-generator/src/cpp.rs | 63 +++- zngur-generator/src/lib.rs | 99 ++++-- zngur-generator/src/rust.rs | 77 +++-- zngur-generator/src/template.rs | 26 +- zngur-generator/templates/cpp_header.sptl | 363 +++----------------- zngur-generator/templates/zngur_header.sptl | 331 ++++++++++++++++++ zngur/src/lib.rs | 38 +- 33 files changed, 649 insertions(+), 448 deletions(-) create mode 100644 zngur-generator/templates/zngur_header.sptl diff --git a/benchmark/build.rs b/benchmark/build.rs index 1688a7c3..870536b2 100644 --- a/benchmark/build.rs +++ b/benchmark/build.rs @@ -20,6 +20,7 @@ fn main() { .with_cpp_file(out_dir.join("generated.cpp")) .with_h_file(out_dir.join("generated.h")) .with_rs_file(out_dir.join("generated.rs")) + .with_output_dir(out_dir.clone()) .generate(); let my_build = &mut cc::Build::new(); diff --git a/examples/cxx_demo/.gitignore b/examples/cxx_demo/.gitignore index 96943236..dc7e38a8 100644 --- a/examples/cxx_demo/.gitignore +++ b/examples/cxx_demo/.gitignore @@ -1,4 +1,5 @@ generated.h generated.rs generated.cpp +zngur.h history.txt diff --git a/examples/cxx_demo/build.rs b/examples/cxx_demo/build.rs index b5c6ca6d..3ede1578 100644 --- a/examples/cxx_demo/build.rs +++ b/examples/cxx_demo/build.rs @@ -15,10 +15,15 @@ fn main() { .with_cpp_file(crate_dir.join("generated.cpp")) .with_h_file(crate_dir.join("generated.h")) .with_rs_file(crate_dir.join("./src/generated.rs")) + .with_output_dir(crate_dir.clone()) .generate(); let my_build = &mut cc::Build::new(); - let my_build = my_build.cpp(true).compiler(&cxx).std("c++17"); + let my_build = my_build + .cpp(true) + .compiler(&cxx) + .include(&crate_dir) + .std("c++17"); let my_build = || my_build.clone(); my_build().file("generated.cpp").compile("zngur_generated"); diff --git a/examples/memory_management/.gitignore b/examples/memory_management/.gitignore index 404d3a43..f8bc9014 100644 --- a/examples/memory_management/.gitignore +++ b/examples/memory_management/.gitignore @@ -1,3 +1,4 @@ generated.h +zngur.h generated.rs generated.cpp diff --git a/examples/memory_management/Makefile b/examples/memory_management/Makefile index ba71b53d..3ca52ec0 100644 --- a/examples/memory_management/Makefile +++ b/examples/memory_management/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_memory_management.a - ${CXX} -std=c++11 -Werror main.cpp generated.cpp -g -L ../../target/release/ -l example_memory_management + ${CXX} -std=c++11 -Werror -I. main.cpp generated.cpp -g -L ../../target/release/ -l example_memory_management ../../target/release/libexample_memory_management.a: cargo build --release generated.h generated.cpp ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/memory_management/main.zng + cd ../../zngur-cli && cargo run g ../examples/memory_management/main.zng -o ../examples/memory_management .PHONY: ../../target/release/libexample_memory_management.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/multiple_zngur_files/.gitignore b/examples/multiple_zngur_files/.gitignore index 11662710..c12386ed 100644 --- a/examples/multiple_zngur_files/.gitignore +++ b/examples/multiple_zngur_files/.gitignore @@ -1,3 +1,4 @@ generated*.h +zngur.h generated*.rs generated*.cpp diff --git a/examples/multiple_zngur_files/Makefile b/examples/multiple_zngur_files/Makefile index 130a0be9..1a4c10ab 100644 --- a/examples/multiple_zngur_files/Makefile +++ b/examples/multiple_zngur_files/Makefile @@ -1,5 +1,5 @@ a.out: main.cpp generated1.h src/generated1.rs generated2.h src/generated2.rs src/lib.rs ../../target/release/libexample_multiple_zngur_files.a - ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_multiple_zngur_files + ${CXX} -std=c++11 -Werror -I. main.cpp -g -L ../../target/release/ -l example_multiple_zngur_files ../../target/release/libexample_multiple_zngur_files.a: cargo build --release @@ -8,15 +8,17 @@ generated1.h ./src/generated1.rs: main.zng cd ../../zngur-cli && cargo run g ../examples/multiple_zngur_files/main.zng \ --h-file=../examples/multiple_zngur_files/generated1.h \ --rs-file=../examples/multiple_zngur_files/src/generated1.rs \ - --cpp-namespace=m1 + --cpp-namespace=m1 \ + -o ../examples/multiple_zngur_files generated2.h ./src/generated2.rs: main.zng cd ../../zngur-cli && cargo run g ../examples/multiple_zngur_files/main.zng \ --h-file=../examples/multiple_zngur_files/generated2.h \ --rs-file=../examples/multiple_zngur_files/src/generated2.rs \ - --cpp-namespace=m2 + --cpp-namespace=m2 \ + -o ../examples/multiple_zngur_files .PHONY: ../../target/release/libexample_multiple_zngur_files.a generated1.h generated2.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/raw_pointer/.gitignore b/examples/raw_pointer/.gitignore index 404d3a43..3c0743c5 100644 --- a/examples/raw_pointer/.gitignore +++ b/examples/raw_pointer/.gitignore @@ -1,3 +1,4 @@ generated.h generated.rs generated.cpp +zngur.h diff --git a/examples/raw_pointer/Makefile b/examples/raw_pointer/Makefile index b34fd7af..a6f5af3a 100644 --- a/examples/raw_pointer/Makefile +++ b/examples/raw_pointer/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_raw_pointer.a - ${CXX} --std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_raw_pointer + ${CXX} --std=c++11 -Werror -I. main.cpp -g -L ../../target/release/ -l example_raw_pointer ../../target/release/libexample_raw_pointer.a: cargo build --release generated.h ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/raw_pointer/main.zng + cd ../../zngur-cli && cargo run g ../examples/raw_pointer/main.zng -o ../examples/raw_pointer .PHONY: ../../target/release/libexample_raw_pointer.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/rayon/.gitignore b/examples/rayon/.gitignore index 96943236..dc7e38a8 100644 --- a/examples/rayon/.gitignore +++ b/examples/rayon/.gitignore @@ -1,4 +1,5 @@ generated.h generated.rs generated.cpp +zngur.h history.txt diff --git a/examples/rayon/Makefile b/examples/rayon/Makefile index 1a94f4ff..b42ea800 100644 --- a/examples/rayon/Makefile +++ b/examples/rayon/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_rayon.a - ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_rayon + ${CXX} -std=c++11 -Werror -I. main.cpp -g -L ../../target/release/ -l example_rayon ../../target/release/libexample_rayon.a: cargo build --release generated.h ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/rayon/main.zng + cd ../../zngur-cli && cargo run g ../examples/rayon/main.zng -o ../examples/rayon .PHONY: ../../target/release/libexample_rayon.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/regression_test1/.gitignore b/examples/regression_test1/.gitignore index 404d3a43..f8bc9014 100644 --- a/examples/regression_test1/.gitignore +++ b/examples/regression_test1/.gitignore @@ -1,3 +1,4 @@ generated.h +zngur.h generated.rs generated.cpp diff --git a/examples/regression_test1/Makefile b/examples/regression_test1/Makefile index e229a936..3b68b717 100644 --- a/examples/regression_test1/Makefile +++ b/examples/regression_test1/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_regression_test1.a - ${CXX} -std=c++20 -Werror main.cpp -g -L ../../target/release/ -l example_regression_test1 + ${CXX} -std=c++20 -Werror -I. main.cpp -g -L ../../target/release/ -l example_regression_test1 ../../target/release/libexample_regression_test1.a: cargo build --release generated.h ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/regression_test1/main.zng + cd ../../zngur-cli && cargo run g ../examples/regression_test1/main.zng -o ../examples/regression_test1 .PHONY: ../../target/release/libexample_regression_test1.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/rustyline/.gitignore b/examples/rustyline/.gitignore index 660ff455..30e56685 100644 --- a/examples/rustyline/.gitignore +++ b/examples/rustyline/.gitignore @@ -1,3 +1,4 @@ generated.h generated.rs history.txt +zngur.h diff --git a/examples/rustyline/Makefile b/examples/rustyline/Makefile index 05214405..ec7549ee 100644 --- a/examples/rustyline/Makefile +++ b/examples/rustyline/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_rustyline.a - ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_rustyline + ${CXX} -std=c++11 -Werror -I. main.cpp -g -L ../../target/release/ -l example_rustyline ../../target/release/libexample_rustyline.a: cargo build --release generated.h ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/rustyline/main.zng + cd ../../zngur-cli && cargo run g ../examples/rustyline/main.zng -o ../examples/rustyline .PHONY: ../../target/release/libexample_rustyline.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/simple/.gitignore b/examples/simple/.gitignore index 404d3a43..3c0743c5 100644 --- a/examples/simple/.gitignore +++ b/examples/simple/.gitignore @@ -1,3 +1,4 @@ generated.h generated.rs generated.cpp +zngur.h diff --git a/examples/simple/Makefile b/examples/simple/Makefile index 886efdd1..5c2dd35e 100644 --- a/examples/simple/Makefile +++ b/examples/simple/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_simple.a - ${CXX} -std=c++11 -Werror main.cpp generated.cpp -g -L ../../target/release/ -l example_simple + ${CXX} -std=c++11 -Werror -I. main.cpp generated.cpp -g -L ../../target/release/ -l example_simple ../../target/release/libexample_simple.a: cargo build --release generated.h generated.cpp ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/simple/main.zng + cd ../../zngur-cli && cargo run g ../examples/simple/main.zng -o ../examples/simple .PHONY: ../../target/release/libexample_simple.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/simple_import/.gitignore b/examples/simple_import/.gitignore index 404d3a43..f8bc9014 100644 --- a/examples/simple_import/.gitignore +++ b/examples/simple_import/.gitignore @@ -1,3 +1,4 @@ generated.h +zngur.h generated.rs generated.cpp diff --git a/examples/simple_import/Makefile b/examples/simple_import/Makefile index 83b8e98f..a5f52df9 100644 --- a/examples/simple_import/Makefile +++ b/examples/simple_import/Makefile @@ -1,13 +1,13 @@ .PHONY: ../../target/release/libexample_simple_import.a a.out: main.cpp foo.cpp bar.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_simple_import.a - ${CXX} -std=c++11 main.cpp foo.cpp bar.cpp -g -L ../../target/release/ -l example_simple_import + ${CXX} -std=c++11 -I. main.cpp foo.cpp bar.cpp -g -L ../../target/release/ -l example_simple_import ../../target/release/libexample_simple_import.a: cargo build --release generated.h ./src/generated.rs: main.zng primitives.zng foo.zng bar.zng - cd ../../zngur-cli && cargo run g ../examples/simple_import/main.zng + cd ../../zngur-cli && cargo run g ../examples/simple_import/main.zng -o ../examples/simple_import clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/tutorial-wasm32/.gitignore b/examples/tutorial-wasm32/.gitignore index 4bd1d883..8c2d3bb3 100644 --- a/examples/tutorial-wasm32/.gitignore +++ b/examples/tutorial-wasm32/.gitignore @@ -1,4 +1,5 @@ generated.h +zngur.h generated.rs generated.cpp generated.o diff --git a/examples/tutorial-wasm32/Makefile b/examples/tutorial-wasm32/Makefile index 46395ef3..e70e4c7d 100644 --- a/examples/tutorial-wasm32/Makefile +++ b/examples/tutorial-wasm32/Makefile @@ -30,13 +30,14 @@ main.wasm: main.cpp generated.h generated.cpp \ $(CXX) $(WASIFLAGS) \ main.cpp generated.cpp \ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a \ + -I. \ -o $@ ./target/wasm32-wasip1/release/libexample_tutorial_wasm32.a: wasm32-wasip1-target generated.h ./src/generated.rs ./src/lib.rs cargo build --target=wasm32-wasip1 --release generated.h ./src/generated.rs generated.cpp: main32.zng - cargo run --release --manifest-path ../../zngur-cli/Cargo.toml g main32.zng + cargo run --release --manifest-path ../../zngur-cli/Cargo.toml g main32.zng -o . # Ensure wasm32-wasip1 target is installed wasm32-wasip1-target: diff --git a/examples/tutorial/.gitignore b/examples/tutorial/.gitignore index a99c55a5..0a956cb5 100644 --- a/examples/tutorial/.gitignore +++ b/examples/tutorial/.gitignore @@ -1,2 +1,3 @@ generated.h generated.rs +zngur.h diff --git a/examples/tutorial/Makefile b/examples/tutorial/Makefile index 0ac767b2..1c8f0e94 100644 --- a/examples/tutorial/Makefile +++ b/examples/tutorial/Makefile @@ -1,13 +1,13 @@ a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_tutorial.a - ${CXX} -std=c++11 -Werror main.cpp -g -L ../../target/release/ -l example_tutorial + ${CXX} -std=c++11 -Werror -I. main.cpp -g -L ../../target/release/ -l example_tutorial ../../target/release/libexample_tutorial.a: cargo build --release generated.h ./src/generated.rs: main.zng - cd ../../zngur-cli && cargo run g ../examples/tutorial/main.zng + cd ../../zngur-cli && cargo run g ../examples/tutorial/main.zng -o ../examples/tutorial .PHONY: ../../target/release/libexample_tutorial.a generated.h clean clean: - rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt + rm -f generated.h generated.cpp src/generated.rs zngur.h a.out actual_output.txt diff --git a/examples/tutorial_cpp/build.rs b/examples/tutorial_cpp/build.rs index b4f434fe..21175ae3 100644 --- a/examples/tutorial_cpp/build.rs +++ b/examples/tutorial_cpp/build.rs @@ -29,6 +29,7 @@ fn main() { .with_cpp_file(out_dir.join("generated.cpp")) .with_h_file(out_dir.join("generated.h")) .with_rs_file(out_dir.join("generated.rs")) + .with_output_dir(out_dir.clone()) .generate(); let my_build = &mut cc::Build::new(); diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 67b16ade..34cfa47c 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -23,12 +23,10 @@ fn check_examples(sh: &Shell, fix: bool) -> Result<()> { sh.change_dir(example); if CARGO_PROJECTS.contains(&example) { - // Clean generated files for Cargo projects - let _ = cmd!( - sh, - "rm -f generated.h generated.cpp src/generated.rs actual_output.txt" - ) - .run(); + // Clean generated files and cargo artifacts for Cargo projects + cmd!(sh, "cargo clean") + .run() + .with_context(|| format!("Cleaning example `{example}` failed"))?; cmd!(sh, "cargo build") .run() .with_context(|| format!("Building example `{example}` failed"))?; diff --git a/zngur-cli/src/main.rs b/zngur-cli/src/main.rs index 52c55a39..8184d1b7 100644 --- a/zngur-cli/src/main.rs +++ b/zngur-cli/src/main.rs @@ -11,6 +11,11 @@ enum Command { /// Path to the zng file path: PathBuf, + /// Directory where utility headers (like zngur.h) will be generated. + /// This directory should be added to your C++ include path. + #[arg(short = 'o', long)] + output_dir: PathBuf, + /// Path of the generated C++ file, if it is needed /// /// Default is {ZNG_FILE_PARENT}/generated.cpp @@ -52,6 +57,7 @@ fn main() { match cmd { Command::Generate { path, + output_dir, cpp_file, h_file, rs_file, @@ -65,7 +71,8 @@ fn main() { let mut zng = Zngur::from_zng_file(&path) .with_cpp_file(cpp_file) .with_h_file(h_file) - .with_rs_file(rs_file); + .with_rs_file(rs_file) + .with_output_dir(output_dir); if let Some(mangling_base) = mangling_base { zng = zng.with_mangling_base(&mangling_base); } diff --git a/zngur-generator/src/cpp.rs b/zngur-generator/src/cpp.rs index 74303961..7bcc0630 100644 --- a/zngur-generator/src/cpp.rs +++ b/zngur-generator/src/cpp.rs @@ -9,7 +9,7 @@ use zngur_def::{CppRef, CppValue, RustTrait, ZngurField, ZngurMethodReceiver}; use crate::{ ZngurWellknownTraitData, - template::{CppHeaderTemplate, CppSourceTemplate}, + template::{CppHeaderTemplate, CppSourceTemplate, ZngurHeaderTemplate}, }; use sailfish::Template; @@ -41,16 +41,30 @@ impl CppPath { self.0.split_last().unwrap().0 } - fn need_header(&self) -> bool { - self.0.first().map(|x| x.as_str()) == Some("rust") - && self.0 != ["rust", "Unit"] - && self.0 != ["rust", "Ref"] - && self.0 != ["rust", "RefMut"] + /// Returns whether this type needs a forward declaration in the generated header. + /// + /// Forward declarations are needed for user-defined types so they can be + /// referenced before their full definition. Returns false for: + /// - Primitive types (uint8_t, int32_t) - built-in to C++ + /// - Infrastructure types (rust::Ref, rust::RefMut, rust::Unit) - defined in zngur.h + fn needs_forward_declaration(&self) -> bool { + // Primitive types (like uint8_t, int32_t, etc.) have no namespace - just a single component + if self.0.len() == 1 { + return false; + } + + // Skip zngur utility types + if self.0 == ["rust", "Unit"] || self.0 == ["rust", "Ref"] || self.0 == ["rust", "RefMut"] { + return false; + } + + // For all other types with a namespace, generate forward declaration + true } - pub(crate) fn from_rust_path(path: &[String]) -> CppPath { + pub(crate) fn from_rust_path(path: &[String], namespace: &str) -> CppPath { CppPath( - iter::once("rust") + iter::once(namespace) .chain(path.iter().map(|x| x.as_str())) .map(cpp_handle_keyword) .map(|x| x.to_owned()) @@ -123,7 +137,7 @@ impl CppType { x.header_helper(state)?; } - if !self.path.need_header() { + if !self.path.needs_forward_declaration() { return Ok(()); } @@ -223,7 +237,12 @@ pub(crate) struct State { impl State { fn remove_no_except_in_panic(&mut self) { if self.panic_to_exception.is_some() { + // Remove noexcept when converting panics to exceptions + // Handle various cases: " noexcept ", " noexcept;", " noexcept{", etc. self.text = self.text.replace(" noexcept ", " "); + self.text = self.text.replace(" noexcept;", ";"); + self.text = self.text.replace(" noexcept{", "{"); + self.text = self.text.replace(" noexcept}", "}"); } } } @@ -350,10 +369,10 @@ pub struct CppFile { } impl CppFile { - fn emit_h_file(&self, state: &mut State) -> std::fmt::Result { + fn emit_h_file(&self, state: &mut State, cpp_namespace: &str) -> std::fmt::Result { let template = CppHeaderTemplate { + cpp_namespace: &cpp_namespace.to_owned(), panic_to_exception: &self.panic_to_exception, - additional_includes: &self.additional_includes, fn_deps: &self.fn_defs, type_defs: &self.type_defs, trait_defs: &self.trait_defs, @@ -364,6 +383,13 @@ impl CppFile { Ok(()) } + fn emit_zngur_h_file(&self) -> String { + let template = ZngurHeaderTemplate { + additional_includes: &self.additional_includes, + }; + template.render().unwrap() + } + fn emit_cpp_file(&self, state: &mut State, is_really_needed: &mut bool) -> std::fmt::Result { let template = CppSourceTemplate { header_file_name: &self.header_file_name, @@ -380,7 +406,7 @@ impl CppFile { Ok(()) } - pub fn render(self) -> (String, Option) { + pub fn render(self, cpp_namespace: &str) -> (String, String, Option) { let mut h_file = State { text: "".to_owned(), panic_to_exception: self.panic_to_exception.clone(), @@ -389,12 +415,21 @@ impl CppFile { text: "".to_owned(), panic_to_exception: self.panic_to_exception.clone(), }; - self.emit_h_file(&mut h_file).unwrap(); + let mut zngur_h_state = State { + text: self.emit_zngur_h_file(), + panic_to_exception: self.panic_to_exception.clone(), + }; + self.emit_h_file(&mut h_file, cpp_namespace).unwrap(); let mut is_cpp_needed = false; self.emit_cpp_file(&mut cpp_file, &mut is_cpp_needed) .unwrap(); h_file.remove_no_except_in_panic(); - (h_file.text, is_cpp_needed.then_some(cpp_file.text)) + zngur_h_state.remove_no_except_in_panic(); + ( + h_file.text, + zngur_h_state.text, + is_cpp_needed.then_some(cpp_file.text), + ) } } diff --git a/zngur-generator/src/lib.rs b/zngur-generator/src/lib.rs index 9a00463d..9757236e 100644 --- a/zngur-generator/src/lib.rs +++ b/zngur-generator/src/lib.rs @@ -23,6 +23,14 @@ pub use zngur_parser::ParsedZngFile; pub use zngur_def::*; +/// Generated files from zngur code generation +pub struct GeneratedFiles { + pub rust_file: String, + pub primary_header: String, // generated.h content + pub utility_headers: Vec<(String, String)>, // vec of (filename, content) pairs like ("zngur.h", content) + pub cpp_file: Option, +} + pub struct ZngurGenerator(pub ZngurSpec); impl ZngurGenerator { @@ -30,8 +38,9 @@ impl ZngurGenerator { ZngurGenerator(zng) } - pub fn render(self) -> (String, String, Option) { + pub fn render(self) -> GeneratedFiles { let mut zng = self.0; + let namespace = &zng.cpp_namespace; // Unit type is a bit special, and almost everyone needs it, so we add it ourself. zng.types.push(ZngurType { @@ -51,7 +60,12 @@ impl ZngurGenerator { cpp_file.trait_defs = zng .traits .iter() - .map(|(key, value)| (key.clone(), rust_file.add_builder_for_dyn_trait(value))) + .map(|(key, value)| { + ( + key.clone(), + rust_file.add_builder_for_dyn_trait(value, namespace), + ) + }) .collect(); if zng.convert_panic_to_exception.0 { cpp_file.panic_to_exception = Some(rust_file.enable_panic_to_exception()); @@ -84,8 +98,12 @@ impl ZngurGenerator { kind: ZngurMethodReceiver::Static, sig: CppFnSig { rust_link_name: rust_link_names.constructor, - inputs: constructor.inputs.iter().map(|x| x.1.into_cpp()).collect(), - output: ty.into_cpp(), + inputs: constructor + .inputs + .iter() + .map(|x| x.1.into_cpp(namespace)) + .collect(), + output: ty.into_cpp(namespace), }, }); cpp_methods.push(CppMethod { @@ -93,7 +111,7 @@ impl ZngurGenerator { kind: ZngurMethodReceiver::Ref(Mutability::Not), sig: CppFnSig { rust_link_name: rust_link_names.match_check, - inputs: vec![ty.into_cpp().into_ref()], + inputs: vec![ty.into_cpp(namespace).into_ref()], output: CppType::from("uint8_t"), }, }); @@ -104,8 +122,12 @@ impl ZngurGenerator { .constructor; constructors.push(CppFnSig { rust_link_name, - inputs: constructor.inputs.iter().map(|x| x.1.into_cpp()).collect(), - output: ty.into_cpp(), + inputs: constructor + .inputs + .iter() + .map(|x| x.1.into_cpp(namespace)) + .collect(), + output: ty.into_cpp(namespace), }); } } @@ -119,8 +141,8 @@ impl ZngurGenerator { let rust_link_name = rust_file.add_tuple_constructor(&fields); constructors.push(CppFnSig { rust_link_name, - inputs: fields.iter().map(|x| x.into_cpp()).collect(), - output: ty.into_cpp(), + inputs: fields.iter().map(|x| x.into_cpp(namespace)).collect(), + output: ty.into_cpp(namespace), }); } } @@ -137,7 +159,7 @@ impl ZngurGenerator { use_path, deref, } = method_details; - let (rusty_inputs, inputs) = real_inputs_of_method(&method, &ty); + let (rusty_inputs, inputs) = real_inputs_of_method(&method, &ty, namespace); let rust_link_name = rust_file.add_function( &format!( "<{}>::{}::<{}>", @@ -156,12 +178,12 @@ impl ZngurGenerator { sig: CppFnSig { rust_link_name, inputs, - output: method.output.into_cpp(), + output: method.output.into_cpp(namespace), }, }); } cpp_file.type_defs.push(CppTypeDefinition { - ty: ty.into_cpp(), + ty: ty.into_cpp(namespace), layout: rust_file.add_layout_policy_shim(&ty, ty_def.layout), constructors, fields, @@ -186,8 +208,11 @@ impl ZngurGenerator { e.insert(CppTraitDefinition::Fn { sig: CppFnSig { rust_link_name, - inputs: inputs.iter().map(|x| x.into_cpp()).collect(), - output: output.into_cpp(), + inputs: inputs + .iter() + .map(|x| x.into_cpp(namespace)) + .collect(), + output: output.into_cpp(namespace), }, }); } @@ -215,11 +240,15 @@ impl ZngurGenerator { None, ); cpp_file.fn_defs.push(CppFnDefinition { - name: CppPath::from_rust_path(&func.path.path), + name: CppPath::from_rust_path(&func.path.path, namespace), sig: CppFnSig { rust_link_name, - inputs: func.inputs.into_iter().map(|x| x.into_cpp()).collect(), - output: func.output.into_cpp(), + inputs: func + .inputs + .into_iter() + .map(|x| x.into_cpp(namespace)) + .collect(), + output: func.output.into_cpp(namespace), }, }); } @@ -230,8 +259,12 @@ impl ZngurGenerator { name: func.name.clone(), sig: CppFnSig { rust_link_name, - inputs: func.inputs.into_iter().map(|x| x.into_cpp()).collect(), - output: func.output.into_cpp(), + inputs: func + .inputs + .into_iter() + .map(|x| x.into_cpp(namespace)) + .collect(), + output: func.output.into_cpp(namespace), }, }); } @@ -242,32 +275,41 @@ impl ZngurGenerator { &impl_block.methods, ); cpp_file.exported_impls.push(CppExportedImplDefinition { - tr: impl_block.tr.map(|x| x.into_cpp()), - ty: impl_block.ty.into_cpp(), + tr: impl_block.tr.map(|x| x.into_cpp(namespace)), + ty: impl_block.ty.into_cpp(namespace), methods: impl_block .methods .iter() .zip(&rust_link_names) .map(|(method, link_name)| { - let (_, inputs) = real_inputs_of_method(method, &impl_block.ty); + let (_, inputs) = real_inputs_of_method(method, &impl_block.ty, namespace); ( cpp_handle_keyword(&method.name).to_owned(), CppFnSig { rust_link_name: link_name.clone(), inputs, - output: method.output.into_cpp(), + output: method.output.into_cpp(namespace), }, ) }) .collect(), }); } - let (h, cpp) = cpp_file.render(); - (rust_file.text, h, cpp) + let (h, zngur_h, cpp) = cpp_file.render(&zng.cpp_namespace); + GeneratedFiles { + rust_file: rust_file.text, + primary_header: h, + utility_headers: vec![("zngur.h".to_string(), zngur_h)], + cpp_file: cpp, + } } } -fn real_inputs_of_method(method: &ZngurMethod, ty: &RustType) -> (Vec, Vec) { +fn real_inputs_of_method( + method: &ZngurMethod, + ty: &RustType, + namespace: &str, +) -> (Vec, Vec) { let receiver_type = match method.receiver { ZngurMethodReceiver::Static => None, ZngurMethodReceiver::Ref(m) => Some(RustType::Ref(m, Box::new(ty.clone()))), @@ -277,6 +319,9 @@ fn real_inputs_of_method(method: &ZngurMethod, ty: &RustType) -> (Vec, .into_iter() .chain(method.inputs.clone()) .collect::>(); - let inputs = rusty_inputs.iter().map(|x| x.into_cpp()).collect_vec(); + let inputs = rusty_inputs + .iter() + .map(|x| x.into_cpp(namespace)) + .collect_vec(); (rusty_inputs, inputs) } diff --git a/zngur-generator/src/rust.rs b/zngur-generator/src/rust.rs index d0c99274..9901ff2d 100644 --- a/zngur-generator/src/rust.rs +++ b/zngur-generator/src/rust.rs @@ -13,42 +13,43 @@ use crate::{ use zngur_def::*; pub trait IntoCpp { - fn into_cpp(&self) -> CppType; + fn into_cpp(&self, namespace: &str) -> CppType; } impl IntoCpp for RustPathAndGenerics { - fn into_cpp(&self) -> CppType { + fn into_cpp(&self, namespace: &str) -> CppType { let RustPathAndGenerics { path, generics, named_generics, } = self; let named_generics = named_generics.iter().sorted_by_key(|x| &x.0).map(|x| &x.1); + CppType { - path: CppPath::from_rust_path(path), + path: CppPath::from_rust_path(path, namespace), generic_args: generics .iter() .chain(named_generics) - .map(|x| x.into_cpp()) + .map(|x| x.into_cpp(namespace)) .collect(), } } } impl IntoCpp for RustTrait { - fn into_cpp(&self) -> CppType { + fn into_cpp(&self, namespace: &str) -> CppType { match self { - RustTrait::Normal(pg) => pg.into_cpp(), + RustTrait::Normal(pg) => pg.into_cpp(namespace), RustTrait::Fn { name, inputs, output, } => CppType { - path: CppPath::from(&*format!("rust::{name}")), + path: CppPath::from(&*format!("{namespace}::{name}")), generic_args: inputs .iter() .chain(Some(&**output)) - .map(|x| x.into_cpp()) + .map(|x| x.into_cpp(namespace)) .collect(), }, } @@ -56,8 +57,8 @@ impl IntoCpp for RustTrait { } impl IntoCpp for RustType { - fn into_cpp(&self) -> CppType { - fn for_builtin(this: &RustType) -> Option { + fn into_cpp(&self, namespace: &str) -> CppType { + fn for_builtin(this: &RustType, namespace: &str) -> Option { match this { RustType::Primitive(s) => match s { PrimitiveRustType::Uint(s) => Some(CppType::from(&*format!("uint{s}_t"))), @@ -68,64 +69,74 @@ impl IntoCpp for RustType { PrimitiveRustType::Usize => Some(CppType::from("size_t")), PrimitiveRustType::Bool | PrimitiveRustType::Str => None, PrimitiveRustType::ZngurCppOpaqueOwnedObject => { + // ZngurCppOpaqueOwnedObject is always in rust:: namespace Some(CppType::from("rust::ZngurCppOpaqueOwnedObject")) } }, RustType::Raw(Mutability::Mut, t) => Some(CppType::from(&*format!( "{}*", - for_builtin(t)?.to_string().strip_prefix("::")? + for_builtin(t, namespace)?.to_string().strip_prefix("::")? ))), RustType::Raw(Mutability::Not, t) => Some(CppType::from(&*format!( "{} const*", - for_builtin(t)?.to_string().strip_prefix("::")? + for_builtin(t, namespace)?.to_string().strip_prefix("::")? ))), _ => None, } } - if let Some(builtin) = for_builtin(self) { + if let Some(builtin) = for_builtin(self, namespace) { return builtin; } match self { RustType::Primitive(s) => match s { + // Primitives are always in rust:: namespace PrimitiveRustType::Bool => CppType::from("rust::Bool"), PrimitiveRustType::Str => CppType::from("rust::Str"), _ => unreachable!(), }, RustType::Boxed(t) => CppType { + // Box is always in rust:: namespace path: CppPath::from("rust::Box"), - generic_args: vec![t.into_cpp()], + generic_args: vec![t.into_cpp(namespace)], }, RustType::Ref(m, t) => CppType { + // Ref/RefMut are always in rust:: namespace path: match m { Mutability::Mut => CppPath::from("rust::RefMut"), Mutability::Not => CppPath::from("rust::Ref"), }, - generic_args: vec![t.into_cpp()], + generic_args: vec![t.into_cpp(namespace)], }, RustType::Slice(s) => CppType { + // Slice is always in rust:: namespace path: CppPath::from("rust::Slice"), - generic_args: vec![s.into_cpp()], + generic_args: vec![s.into_cpp(namespace)], }, RustType::Raw(m, t) => CppType { + // Raw/RawMut are always in rust:: namespace path: match m { Mutability::Mut => CppPath::from("rust::RawMut"), Mutability::Not => CppPath::from("rust::Raw"), }, - generic_args: vec![t.into_cpp()], + generic_args: vec![t.into_cpp(namespace)], }, - RustType::Adt(pg) => pg.into_cpp(), + // User-defined ADTs use the custom namespace + RustType::Adt(pg) => pg.into_cpp(namespace), RustType::Tuple(v) => { if v.is_empty() { + // Unit is always in rust:: namespace return CppType::from("rust::Unit"); } CppType { + // Tuple is always in rust:: namespace path: CppPath::from("rust::Tuple"), - generic_args: v.into_iter().map(|x| x.into_cpp()).collect(), + generic_args: v.into_iter().map(|x| x.into_cpp(namespace)).collect(), } } RustType::Dyn(tr, marker_bounds) => { - let tr_as_cpp_type = tr.into_cpp(); + let tr_as_cpp_type = tr.into_cpp(namespace); CppType { + // Dyn is always in rust:: namespace path: CppPath::from("rust::Dyn"), generic_args: [tr_as_cpp_type] .into_iter() @@ -164,7 +175,7 @@ mod zngur_types { impl ZngurCppOpaqueOwnedObject { pub unsafe fn new( data: *mut u8, - destructor: extern "C" fn(*mut u8), + destructor: extern "C" fn(*mut u8), ) -> Self { Self { data, destructor } } @@ -289,7 +300,11 @@ impl RustFile { ); } - pub(crate) fn add_builder_for_dyn_trait(&mut self, tr: &ZngurTrait) -> CppTraitDefinition { + pub(crate) fn add_builder_for_dyn_trait( + &mut self, + tr: &ZngurTrait, + namespace: &str, + ) -> CppTraitDefinition { assert!(matches!(tr.tr, RustTrait::Normal { .. })); let mut method_mangled_name = vec![]; wln!(self, r#"unsafe extern "C" {{"#); @@ -311,7 +326,7 @@ impl RustFile { let link_name = self.add_builder_for_dyn_trait_owned(tr, &method_mangled_name); let link_name_ref = self.add_builder_for_dyn_trait_borrowed(tr, &method_mangled_name); CppTraitDefinition::Normal { - as_ty: tr.tr.into_cpp(), + as_ty: tr.tr.into_cpp(namespace), methods: tr .methods .clone() @@ -320,8 +335,12 @@ impl RustFile { .map(|(x, rust_link_name)| CppTraitMethod { name: x.name, rust_link_name, - inputs: x.inputs.into_iter().map(|x| x.into_cpp()).collect(), - output: x.output.into_cpp(), + inputs: x + .inputs + .into_iter() + .map(|x| x.into_cpp(namespace)) + .collect(), + output: x.output.into_cpp(namespace), }) .collect(), link_name, @@ -347,7 +366,7 @@ pub extern "C" fn {mangled_name}( destructor: extern "C" fn(*mut u8), o: *mut u8, ) {{ - struct Wrapper {{ + struct Wrapper {{ value: ZngurCppOpaqueOwnedObject, }} impl {trait_without_assocs} for Wrapper {{ @@ -378,7 +397,7 @@ pub extern "C" fn {mangled_name}( self, r#" }} - unsafe {{ + unsafe {{ let this = Wrapper {{ value: ZngurCppOpaqueOwnedObject::new(data, destructor), }}; @@ -439,7 +458,7 @@ pub extern "C" fn {mangled_name}( self, r#" }} - unsafe {{ + unsafe {{ let this = data as *mut Wrapper; let r: &dyn {trait_name} = &*this; std::ptr::write(o as *mut _, r) @@ -863,7 +882,7 @@ pub extern "C" fn {debug_print}(v: *mut u8) {{ pub fn {size_fn}() -> usize {{ ::std::mem::size_of::<{ty}>() }} - + #[allow(non_snake_case)] #[unsafe(no_mangle)] pub fn {alloc_fn}() -> *mut u8 {{ diff --git a/zngur-generator/src/template.rs b/zngur-generator/src/template.rs index 76e9c6f5..097a7e2f 100644 --- a/zngur-generator/src/template.rs +++ b/zngur-generator/src/template.rs @@ -46,18 +46,12 @@ macro_rules! splat { pub(crate) use splat; #[derive(Template)] -#[template(path = "cpp_header.sptl", escape = false)] -pub(crate) struct CppHeaderTemplate<'a> { - pub(crate) panic_to_exception: &'a Option, +#[template(path = "zngur_header.sptl", escape = false)] +pub(crate) struct ZngurHeaderTemplate<'a> { pub(crate) additional_includes: &'a String, - pub(crate) fn_deps: &'a Vec, - pub(crate) type_defs: &'a Vec, - pub(crate) trait_defs: &'a HashMap, - pub(crate) exported_impls: &'a Vec, - pub(crate) exported_fn_defs: &'a Vec, } -impl<'a> CppHeaderTemplate<'a> { +impl<'a> ZngurHeaderTemplate<'a> { // TODO: Docs - what do these represent? When will we change this list? fn builtin_types(&self) -> Vec { [8, 16, 32, 64] @@ -79,7 +73,21 @@ impl<'a> CppHeaderTemplate<'a> { ]) .collect() } +} + +#[derive(Template)] +#[template(path = "cpp_header.sptl", escape = false)] +pub(crate) struct CppHeaderTemplate<'a> { + pub(crate) cpp_namespace: &'a String, + pub(crate) panic_to_exception: &'a Option, + pub(crate) fn_deps: &'a Vec, + pub(crate) type_defs: &'a Vec, + pub(crate) trait_defs: &'a HashMap, + pub(crate) exported_impls: &'a Vec, + pub(crate) exported_fn_defs: &'a Vec, +} +impl<'a> CppHeaderTemplate<'a> { fn panic_handler(&self) -> String { if let Some(symbols) = &self.panic_to_exception { format!( diff --git a/zngur-generator/templates/cpp_header.sptl b/zngur-generator/templates/cpp_header.sptl index d789339e..7ab77c37 100644 --- a/zngur-generator/templates/cpp_header.sptl +++ b/zngur-generator/templates/cpp_header.sptl @@ -1,315 +1,24 @@ -#pragma once +#include -#include -#include -#include -#include -#include -#include -#include -#include - -<%- self.additional_includes %> - -<% if let Some(symbols) = &self.panic_to_exception { %> - namespace rust { - class Panic {}; - } - extern "C" { - uint8_t <%- symbols.detect_panic %>(); - void <%- symbols.take_panic %>(); - } +<% if self.cpp_namespace != "rust" { %> +// Forward declare custom namespace for template specializations +namespace <%- self.cpp_namespace %> {} <% } %> -#define zngur_dbg(x) (::rust::zngur_dbg_impl(__FILE__, __LINE__, #x, x)) - +<% if let Some(symbols) = &self.panic_to_exception { %> +#ifndef ZNGUR_RUST_PANIC_EXCEPTION_CLASS_DEFINED +#define ZNGUR_RUST_PANIC_EXCEPTION_CLASS_DEFINED namespace rust { - template - uint8_t* __zngur_internal_data_ptr(const T& t) noexcept ; - - template - void __zngur_internal_assume_init(T& t) noexcept ; - - template - void __zngur_internal_assume_deinit(T& t) noexcept ; - - template - inline size_t __zngur_internal_size_of() noexcept ; - - template - inline void __zngur_internal_move_to_rust(uint8_t* dst, T& t) noexcept { - memcpy(dst, ::rust::__zngur_internal_data_ptr(t), ::rust::__zngur_internal_size_of()); - ::rust::__zngur_internal_assume_deinit(t); - } - - template - inline T __zngur_internal_move_from_rust(uint8_t* src) noexcept { - T t; - ::rust::__zngur_internal_assume_init(t); - memcpy(::rust::__zngur_internal_data_ptr(t), src, ::rust::__zngur_internal_size_of()); - return t; - } - - template - inline void __zngur_internal_check_init(const T&) noexcept {} - - class ZngurCppOpaqueOwnedObject { - uint8_t* data; - void (*destructor)(uint8_t*); - - public: - template - inline static ZngurCppOpaqueOwnedObject build(Args&&... args) { - ZngurCppOpaqueOwnedObject o; - o.data = reinterpret_cast(new T(::std::forward(args)...)); - o.destructor = [](uint8_t* d) { - delete reinterpret_cast(d); - }; - return o; - } - - template - inline T& as_cpp() { return *reinterpret_cast(data); } - }; - - template - struct Ref; - - template - struct RefMut; - - template - struct FieldOwned { - inline operator T() const noexcept { return *::rust::Ref(*this); } - }; - - template - struct FieldRef { - inline operator T() const noexcept { return *::rust::Ref(*this); } - }; - - template - struct FieldRefMut { - inline operator T() const noexcept { return *::rust::Ref(*this); } - }; - - template - struct zngur_is_unsized : std::false_type {}; - struct zngur_fat_pointer { - uint8_t* data; - size_t metadata; - }; - template - struct Raw { - using DataType = typename std::conditional< - zngur_is_unsized::value, - zngur_fat_pointer, - uint8_t* - >::type; - DataType data; - Raw() {} - Raw(Ref value) { - memcpy(&data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); - } - Raw(RefMut value) { - memcpy(&data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); - } - Raw(DataType data) : data(data) { - } - Raw offset(ssize_t n) { - return Raw(data + n * __zngur_internal_size_of()); - } - Ref read_ref() { - Ref value; - memcpy(__zngur_internal_data_ptr>(value), &data, __zngur_internal_size_of>()); - __zngur_internal_assume_init>(value); - return value; - } - }; - template - struct RawMut { - using DataType = typename std::conditional< - zngur_is_unsized::value, - zngur_fat_pointer, - uint8_t* - >::type; - DataType data; - RawMut() {} - RawMut(RefMut value) { - memcpy(&data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); - } - RawMut(DataType data) : data(data) { - } - RawMut offset(ssize_t n) { - return RawMut(data + n * __zngur_internal_size_of()); - } - T read() { - T value; - memcpy(__zngur_internal_data_ptr(value), data, __zngur_internal_size_of()); - __zngur_internal_assume_init(value); - return value; - } - Ref read_ref() { - Ref value; - memcpy(__zngur_internal_data_ptr>(value), &data, __zngur_internal_size_of>()); - __zngur_internal_assume_init>(value); - return value; - } - RefMut read_mut() { - RefMut value; - memcpy(__zngur_internal_data_ptr>(value), &data, __zngur_internal_size_of>()); - __zngur_internal_assume_init>(value); - return value; - } - void write(T value) { - memcpy(data, __zngur_internal_data_ptr(value), __zngur_internal_size_of()); - __zngur_internal_assume_deinit(value); - } - }; - template - struct Tuple; - - using Unit = Tuple<>; - - template - struct ZngurPrettyPrinter; - - class Inherent; - - template - class Impl; - - template - T&& zngur_dbg_impl(const char* file_name, int line_number, const char* exp, T&& input) { - ::std::cerr << "[" << file_name << ":" << line_number << "] " << exp << " = "; - ZngurPrettyPrinter::type>::print(input); - return ::std::forward(input); - } - -<% for ty in self.builtin_types() { %> - <% let needs_endif = ty == "::size_t"; %> - <% if needs_endif { %> - #if defined(__APPLE__) || defined(__wasm__) - <% } %> - - template<> - inline uint8_t* __zngur_internal_data_ptr< <%- ty %> >(const <%- ty %>& t) noexcept { - return const_cast(reinterpret_cast(&t)); - } - - template<> - inline void __zngur_internal_assume_init< <%- ty %> >(<%- ty %>&) noexcept {} - template<> - inline void __zngur_internal_assume_deinit< <%- ty %> >(<%- ty %>&) noexcept {} - - template<> - inline size_t __zngur_internal_size_of< <%- ty %> >() noexcept { - return sizeof(<%- ty %>); - } - - template<> - inline uint8_t* __zngur_internal_data_ptr< <%- ty %>*>(<%- ty %>* const & t) noexcept { - return const_cast(reinterpret_cast(&t)); - } - - template<> - inline void __zngur_internal_assume_init< <%- ty %>*>(<%- ty %>*&) noexcept {} - template<> - inline void __zngur_internal_assume_deinit< <%- ty %>*>(<%- ty %>*&) noexcept {} - - template<> - inline uint8_t* __zngur_internal_data_ptr< <%- ty %> const*>(<%- ty %> const* const & t) noexcept { - return const_cast(reinterpret_cast(&t)); - } - - template<> - inline void __zngur_internal_assume_init< <%- ty %> const*>(<%- ty %> const*&) noexcept {} - template<> - inline void __zngur_internal_assume_deinit< <%- ty %> const*>(<%- ty %> const*&) noexcept {} - - template<> - struct Ref< <%- ty %> > { - Ref() { - data = 0; - } - Ref(const <%- ty %>& t) { - data = reinterpret_cast(__zngur_internal_data_ptr(t)); - } - - template - Ref(const FieldOwned< <%- ty %>, OFFSET >& f) { - data = reinterpret_cast(&f) + OFFSET; - } - - template - Ref(const FieldRef< <%- ty %>, OFFSET >& f) { - data = *reinterpret_cast(&f) + OFFSET; - } - - template - Ref(const FieldRefMut< <%- ty %>, OFFSET >& f) { - data = *reinterpret_cast(&f) + OFFSET; - } - - <%- ty %>& operator*() { - return *reinterpret_cast< <%- ty %>*>(data); - } - - private: - size_t data; - friend uint8_t* ::rust::__zngur_internal_data_ptr > >(const ::rust::Ref< <%- ty %> >& t) noexcept ; - friend ::rust::ZngurPrettyPrinter< Ref< <%- ty %> > >; - - }; - - template<> - struct RefMut< <%- ty %> > { - RefMut() { - data = 0; - } - - RefMut(<%- ty %>& t) { - data = reinterpret_cast(__zngur_internal_data_ptr(t)); - } - - template - RefMut(const FieldOwned< <%- ty %>, OFFSET >& f) { - data = reinterpret_cast(&f) + OFFSET; - } - - template - RefMut(const FieldRefMut< <%- ty %>, OFFSET >& f) { - data = *reinterpret_cast(&f) + OFFSET; - } - - <%- ty %>& operator*() { - return *reinterpret_cast< <%- ty %>*>(data); - } - private: - size_t data; - friend uint8_t* ::rust::__zngur_internal_data_ptr > >(const ::rust::RefMut< <%- ty %> >& t) noexcept ; - friend ::rust::ZngurPrettyPrinter< Ref< <%- ty %> > >; - }; - - <% let printable = ty.starts_with("int") || ty.starts_with("uint") || ty.starts_with("::size_t") || ty.starts_with("::double") || ty.starts_with("::float"); %> - <% if printable { %> - template<> - struct ZngurPrettyPrinter< <%- ty %> > { - static inline void print(<%- ty %> const& t) { - ::std::cerr << t << ::std::endl; - } - }; - <% } %> - - <% if needs_endif { %> - #endif - <% } %> + class Panic {}; +} +#endif -// end builtin types +extern "C" { + uint8_t <%- symbols.detect_panic %>(); + void <%- symbols.take_panic %>(); +} <% } %> -} // namespace rust - extern "C" { <% for f in self.fn_deps { %> void <%- f.sig.rust_link_name %> ( @@ -396,7 +105,7 @@ namespace rust { if td.wellknown_traits.contains(&ZngurWellknownTraitData::Unsized) { %> template<> struct zngur_is_unsized< <%- td.ty %> > : ::std::true_type {}; -<% } +<% } } %> } @@ -422,6 +131,7 @@ namespace rust { <% let is_unsized = td.wellknown_traits.contains(&ZngurWellknownTraitData::Unsized); %> <% let name = td.ty.path.name(); %> + <% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { template<> inline uint8_t* __zngur_internal_data_ptr< <%- td.ty %> >(const <%- td.ty %>& t) noexcept ; @@ -434,12 +144,11 @@ namespace rust { template<> inline size_t __zngur_internal_size_of< <%- td.ty %> >() noexcept ; } + <% } %> + <% if td.ty.path.0 != ["rust", "Unit"] { %> <%- td.ty.path.open_namespace() %> - <% if td.ty.path.0 == ["rust", "Unit"] { %> - template<> struct Tuple<> { ::std::array< ::uint8_t, 1> data; }; - <% } else { /* !unit */ %> <%- td.ty.specialization_decl() %> { <% match td.layout { CppLayoutPolicy::OnlyByRef => { %> public: @@ -543,7 +252,7 @@ namespace rust { <% match &td.from_trait { Some(RustTrait::Fn { inputs, output, .. }) => { %> static inline <%- name %> make_box( - ::std::function< <%- output.into_cpp() %> (<%- inputs.iter().map(|x| x.into_cpp()).join(", ") %>) > f + ::std::function< <%- output.into_cpp(self.cpp_namespace) %> (<%- inputs.iter().map(|x| x.into_cpp(self.cpp_namespace)).join(", ") %>) > f ); <% } Some(RustTrait::Normal{..}) => { %> template @@ -581,18 +290,17 @@ namespace rust { <% for field in &td.fields { %> [[no_unique_address]] ::rust::FieldOwned< - <%- field.ty.into_cpp() %>, + <%- field.ty.into_cpp(self.cpp_namespace) %>, <%- field.offset %> > <%- cpp_handle_field_name(&field.name) %>; <% } %> }; // <%- td.ty.specialization_decl() %> - // end !rust unit - <% } %> - <%- td.ty.path.close_namespace() %> + <% } %> +<% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { <% match &td.layout { CppLayoutPolicy::StackAllocated { size, align: _ } => { %> @@ -650,7 +358,9 @@ namespace rust { <% } %> } // namespace rust +<% } %> +<% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { template<> @@ -669,7 +379,7 @@ namespace rust { <% if !is_unsized && !matches!(td.layout, CppLayoutPolicy::OnlyByRef) { %> <% for field in &td.fields { %> [[no_unique_address]] ::rust::FieldRefMut< - <%- field.ty.into_cpp() %>, + <%- field.ty.into_cpp(self.cpp_namespace) %>, <%- field.offset %> > <%- cpp_handle_field_name(&field.name) %>; <% } %> @@ -694,10 +404,10 @@ namespace rust { <% } %> <% match &td.from_trait_ref { Some(RustTrait::Fn { inputs, output, .. }) => { %> - <% let as_std_function = format!("::std::function< {}({})>", output.into_cpp(), inputs.iter().map(|x| x.into_cpp()).join(", ")); %> + <% let as_std_function = format!("::std::function< {}({})>", output.into_cpp(self.cpp_namespace), inputs.iter().map(|x| x.into_cpp(self.cpp_namespace)).join(", ")); %> inline <%- td.ty.path.name() %>(<%- as_std_function %> f); <% } Some(tr @ RustTrait::Normal { .. }) => { %> - <% let tr = tr.into_cpp(); %> + <% let tr = tr.into_cpp(self.cpp_namespace); %> inline RefMut(<%- tr %>& arg); <% } None => { %> <% } @@ -746,12 +456,14 @@ namespace rust { } } // namespace rust +<% } %> // Ref specialization <% if td.ty.path.to_string() == "::rust::Str" { %> auto operator""_rs(const char* input, size_t len) -> ::rust::Ref<::rust::Str>; <% } %> +<% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { template<> @@ -773,7 +485,7 @@ namespace rust { <% for field in &td.fields { %> [[no_unique_address]] ::rust::FieldRef< - <%- field.ty.into_cpp() %>, + <%- field.ty.into_cpp(self.cpp_namespace) %>, <%- field.offset %> > <%- cpp_handle_field_name(&field.name) %>; <% } %> @@ -802,10 +514,10 @@ namespace rust { <% } %> <% match &td.from_trait_ref { Some(RustTrait::Fn { inputs, output, .. }) => { %> - <% let as_std_function = format!("::std::function< {}({})>", output.into_cpp(), inputs.iter().map(|x| x.into_cpp()).join(", ")); %> + <% let as_std_function = format!("::std::function< {}({})>", output.into_cpp(self.cpp_namespace), inputs.iter().map(|x| x.into_cpp(self.cpp_namespace)).join(", ")); %> inline <%- td.ty.path.name() %>(<%- as_std_function %> f); <% } Some(tr @ RustTrait::Normal { .. }) => { %> - <% let tr = tr.into_cpp(); %> + <% let tr = tr.into_cpp(self.cpp_namespace); %> inline Ref(<%- tr %>& arg); <% } None => { %> <% } @@ -837,6 +549,12 @@ namespace rust { <% } %> }; +} // namespace rust +<% } %> + +<% if td.ty.path.0 != ["rust", "Unit"] { %> +namespace rust { + <% for ref_kind in ["Ref", "Raw", "RawMut"] { %> template<> @@ -861,6 +579,7 @@ inline size_t __zngur_internal_size_of< <%- ref_kind %> < <%- td.ty %> > >() noe <% } %> } // namespace rust +<% } %> <% if td.ty.path.to_string() == "::rust::Str" { %> inline ::rust::Ref<::rust::Str> operator""_rs(const char* input, size_t len) { @@ -874,6 +593,7 @@ inline size_t __zngur_internal_size_of< <%- ref_kind %> < <%- td.ty %> > >() noe namespace rust { // Field specializations +<% if td.ty.path.0 != ["rust", "Unit"] { %> <% for field_kind in &["FieldOwned", "FieldRef", "FieldRefMut"] { %> template @@ -881,7 +601,7 @@ namespace rust { <% for field in &td.fields { %> [[no_unique_address]] <%- field_kind %>< - <%- field.ty.into_cpp() %>, + <%- field.ty.into_cpp(self.cpp_namespace) %>, OFFSET + <%- field.offset %> > <%- cpp_handle_field_name(&field.name) %>; <% } %> @@ -898,6 +618,7 @@ namespace rust { }; // struct <%- field_kind %>< <%- td.ty %>, OFFSET > <% } /* for field_kind in &[...] */ %> +<% } %> } // namespace rust diff --git a/zngur-generator/templates/zngur_header.sptl b/zngur-generator/templates/zngur_header.sptl new file mode 100644 index 00000000..b2db1182 --- /dev/null +++ b/zngur-generator/templates/zngur_header.sptl @@ -0,0 +1,331 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +<%- self.additional_includes %> + +#define zngur_dbg(x) (::rust::zngur_dbg_impl(__FILE__, __LINE__, #x, x)) + +namespace rust { + template + uint8_t* __zngur_internal_data_ptr(const T& t) noexcept ; + + template + void __zngur_internal_assume_init(T& t) noexcept ; + + template + void __zngur_internal_assume_deinit(T& t) noexcept ; + + template + inline size_t __zngur_internal_size_of() noexcept ; + + template + inline void __zngur_internal_move_to_rust(uint8_t* dst, T& t) noexcept { + memcpy(dst, ::rust::__zngur_internal_data_ptr(t), ::rust::__zngur_internal_size_of()); + ::rust::__zngur_internal_assume_deinit(t); + } + + template + inline T __zngur_internal_move_from_rust(uint8_t* src) noexcept { + T t; + ::rust::__zngur_internal_assume_init(t); + memcpy(::rust::__zngur_internal_data_ptr(t), src, ::rust::__zngur_internal_size_of()); + return t; + } + + template + inline void __zngur_internal_check_init(const T&) noexcept {} + + class ZngurCppOpaqueOwnedObject { + uint8_t* data; + void (*destructor)(uint8_t*); + + public: + template + inline static ZngurCppOpaqueOwnedObject build(Args&&... args) { + ZngurCppOpaqueOwnedObject o; + o.data = reinterpret_cast(new T(::std::forward(args)...)); + o.destructor = [](uint8_t* d) { + delete reinterpret_cast(d); + }; + return o; + } + + template + inline T& as_cpp() { return *reinterpret_cast(data); } + }; + + template + struct Ref; + + template + struct RefMut; + + template + struct FieldOwned { + inline operator T() const noexcept { return *::rust::Ref(*this); } + }; + + template + struct FieldRef { + inline operator T() const noexcept { return *::rust::Ref(*this); } + }; + + template + struct FieldRefMut { + inline operator T() const noexcept { return *::rust::Ref(*this); } + }; + + template + struct zngur_is_unsized : std::false_type {}; + struct zngur_fat_pointer { + uint8_t* data; + size_t metadata; + }; + template + struct Raw { + using DataType = typename std::conditional< + zngur_is_unsized::value, + zngur_fat_pointer, + uint8_t* + >::type; + DataType data; + Raw() {} + Raw(Ref value) { + memcpy(&data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); + } + Raw(RefMut value) { + memcpy(&data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); + } + Raw(DataType data) : data(data) { + } + Raw offset(ssize_t n) { + return Raw(data + n * __zngur_internal_size_of()); + } + Ref read_ref() { + Ref value; + memcpy(__zngur_internal_data_ptr>(value), &data, __zngur_internal_size_of>()); + __zngur_internal_assume_init>(value); + return value; + } + }; + template + struct RawMut { + using DataType = typename std::conditional< + zngur_is_unsized::value, + zngur_fat_pointer, + uint8_t* + >::type; + DataType data; + RawMut() {} + RawMut(RefMut value) { + memcpy(&data, __zngur_internal_data_ptr>(value), __zngur_internal_size_of>()); + } + RawMut(DataType data) : data(data) { + } + RawMut offset(ssize_t n) { + return RawMut(data + n * __zngur_internal_size_of()); + } + T read() { + T value; + memcpy(__zngur_internal_data_ptr(value), data, __zngur_internal_size_of()); + __zngur_internal_assume_init(value); + return value; + } + Ref read_ref() { + Ref value; + memcpy(__zngur_internal_data_ptr>(value), &data, __zngur_internal_size_of>()); + __zngur_internal_assume_init>(value); + return value; + } + RefMut read_mut() { + RefMut value; + memcpy(__zngur_internal_data_ptr>(value), &data, __zngur_internal_size_of>()); + __zngur_internal_assume_init>(value); + return value; + } + void write(T value) { + memcpy(data, __zngur_internal_data_ptr(value), __zngur_internal_size_of()); + __zngur_internal_assume_deinit(value); + } + }; + template + struct Tuple; + + // Unit type (empty tuple) + #ifndef ZNGUR_UNIT_TYPE_DEFINED + #define ZNGUR_UNIT_TYPE_DEFINED + template<> + struct Tuple<> { + ::std::array< ::uint8_t, 1> data; + }; + + using Unit = Tuple<>; + + // Internal function specializations for Unit + template<> + inline size_t __zngur_internal_size_of>() noexcept { + return 1; + } + + template<> + inline void __zngur_internal_check_init>(const Tuple<>&) noexcept {} + + template<> + inline void __zngur_internal_assume_init>(Tuple<>&) noexcept {} + + template<> + inline void __zngur_internal_assume_deinit>(Tuple<>&) noexcept {} + + template<> + inline uint8_t* __zngur_internal_data_ptr>(Tuple<> const & t) noexcept { + return const_cast(&t.data[0]); + } + + #endif // ZNGUR_UNIT_TYPE_DEFINED + + template + struct ZngurPrettyPrinter; + + class Inherent; + + template + class Impl; + + template + T&& zngur_dbg_impl(const char* file_name, int line_number, const char* exp, T&& input) { + ::std::cerr << "[" << file_name << ":" << line_number << "] " << exp << " = "; + ZngurPrettyPrinter::type>::print(input); + return ::std::forward(input); + } + +<% for ty in self.builtin_types() { %> + <% let needs_endif = ty == "::size_t"; %> + <% if needs_endif { %> + #if defined(__APPLE__) || defined(__wasm__) + <% } %> + + template<> + inline uint8_t* __zngur_internal_data_ptr< <%- ty %> >(const <%- ty %>& t) noexcept { + return const_cast(reinterpret_cast(&t)); + } + + template<> + inline void __zngur_internal_assume_init< <%- ty %> >(<%- ty %>&) noexcept {} + template<> + inline void __zngur_internal_assume_deinit< <%- ty %> >(<%- ty %>&) noexcept {} + + template<> + inline size_t __zngur_internal_size_of< <%- ty %> >() noexcept { + return sizeof(<%- ty %>); + } + + template<> + inline uint8_t* __zngur_internal_data_ptr< <%- ty %>*>(<%- ty %>* const & t) noexcept { + return const_cast(reinterpret_cast(&t)); + } + + template<> + inline void __zngur_internal_assume_init< <%- ty %>*>(<%- ty %>*&) noexcept {} + template<> + inline void __zngur_internal_assume_deinit< <%- ty %>*>(<%- ty %>*&) noexcept {} + + template<> + inline uint8_t* __zngur_internal_data_ptr< <%- ty %> const*>(<%- ty %> const* const & t) noexcept { + return const_cast(reinterpret_cast(&t)); + } + + template<> + inline void __zngur_internal_assume_init< <%- ty %> const*>(<%- ty %> const*&) noexcept {} + template<> + inline void __zngur_internal_assume_deinit< <%- ty %> const*>(<%- ty %> const*&) noexcept {} + + template<> + struct Ref< <%- ty %> > { + Ref() { + data = 0; + } + Ref(const <%- ty %>& t) { + data = reinterpret_cast(__zngur_internal_data_ptr(t)); + } + + template + Ref(const FieldOwned< <%- ty %>, OFFSET >& f) { + data = reinterpret_cast(&f) + OFFSET; + } + + template + Ref(const FieldRef< <%- ty %>, OFFSET >& f) { + data = *reinterpret_cast(&f) + OFFSET; + } + + template + Ref(const FieldRefMut< <%- ty %>, OFFSET >& f) { + data = *reinterpret_cast(&f) + OFFSET; + } + + <%- ty %>& operator*() { + return *reinterpret_cast< <%- ty %>*>(data); + } + + private: + size_t data; + friend uint8_t* ::rust::__zngur_internal_data_ptr > >(const ::rust::Ref< <%- ty %> >& t) noexcept ; + friend ::rust::ZngurPrettyPrinter< Ref< <%- ty %> > >; + + }; + + template<> + struct RefMut< <%- ty %> > { + RefMut() { + data = 0; + } + + RefMut(<%- ty %>& t) { + data = reinterpret_cast(__zngur_internal_data_ptr(t)); + } + + template + RefMut(const FieldOwned< <%- ty %>, OFFSET >& f) { + data = reinterpret_cast(&f) + OFFSET; + } + + template + RefMut(const FieldRefMut< <%- ty %>, OFFSET >& f) { + data = *reinterpret_cast(&f) + OFFSET; + } + + <%- ty %>& operator*() { + return *reinterpret_cast< <%- ty %>*>(data); + } + private: + size_t data; + friend uint8_t* ::rust::__zngur_internal_data_ptr > >(const ::rust::RefMut< <%- ty %> >& t) noexcept ; + friend ::rust::ZngurPrettyPrinter< Ref< <%- ty %> > >; + }; + + <% let printable = ty.starts_with("int") || ty.starts_with("uint") || ty.starts_with("::size_t") || ty.starts_with("::double") || ty.starts_with("::float"); %> + <% if printable { %> + template<> + struct ZngurPrettyPrinter< <%- ty %> > { + static inline void print(<%- ty %> const& t) { + ::std::cerr << t << ::std::endl; + } + }; + <% } %> + + <% if needs_endif { %> + #endif + <% } %> + +// end builtin types +<% } %> + +} // namespace rust diff --git a/zngur/src/lib.rs b/zngur/src/lib.rs index 3c647e09..3042749a 100644 --- a/zngur/src/lib.rs +++ b/zngur/src/lib.rs @@ -2,7 +2,7 @@ //! about the Zngur itself, see [the documentation](https://hkalbasi.github.io/zngur). use std::{ - fs::File, + fs::{self, File}, io::Write, path::{Path, PathBuf}, }; @@ -27,6 +27,7 @@ pub struct Zngur { h_file_path: Option, cpp_file_path: Option, rs_file_path: Option, + output_dir: Option, mangling_base: Option, cpp_namespace: Option, } @@ -38,6 +39,7 @@ impl Zngur { h_file_path: None, cpp_file_path: None, rs_file_path: None, + output_dir: None, mangling_base: None, cpp_namespace: None, } @@ -68,11 +70,17 @@ impl Zngur { self } + pub fn with_output_dir(mut self, path: impl AsRef) -> Self { + self.output_dir = Some(path.as_ref().to_owned()); + self + } + pub fn generate(self) { let mut file = ZngurGenerator::build_from_zng(ParsedZngFile::parse(self.zng_file)); let rs_file_path = self.rs_file_path.expect("No rs file path provided"); let h_file_path = self.h_file_path.expect("No h file path provided"); + let output_dir = self.output_dir.expect("No output directory provided. Use with_output_dir() to specify where utility headers should be generated."); file.0.cpp_include_header_name = h_file_path .file_name() @@ -91,25 +99,33 @@ impl Zngur { file.0.mangling_base = mangling_base; } - let cpp_namespace = file.0.cpp_namespace.clone(); + let generated = file.render(); + + // Create output directory for utility headers + fs::create_dir_all(&output_dir).unwrap(); - let (rust, mut h, mut cpp) = file.render(); + // Write utility headers to output directory + for (filename, content) in generated.utility_headers { + let utility_path = output_dir.join(&filename); + File::create(utility_path) + .unwrap() + .write_all(content.as_bytes()) + .unwrap(); + } - // TODO: Don't hard code namespace as "::rust" and remove this replace - h = h - .replace("rust::", &format!("{cpp_namespace}::")) - .replace("namespace rust", &format!("namespace {cpp_namespace}")); - cpp = cpp.map(|cpp| cpp.replace("rust::", &format!("{cpp_namespace}::"))); + // Print the absolute path to the output directory + let abs_output_dir = output_dir.canonicalize().unwrap(); + println!("Generated headers directory: {}", abs_output_dir.display()); File::create(rs_file_path) .unwrap() - .write_all(rust.as_bytes()) + .write_all(generated.rust_file.as_bytes()) .unwrap(); File::create(h_file_path) .unwrap() - .write_all(h.as_bytes()) + .write_all(generated.primary_header.as_bytes()) .unwrap(); - if let Some(cpp) = cpp { + if let Some(cpp) = generated.cpp_file { let cpp_file_path = self.cpp_file_path.expect("No cpp file path provided"); File::create(cpp_file_path) .unwrap() From d37446476e7c472421bea6136173f9afa1b9326d Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Thu, 6 Nov 2025 14:59:05 -0500 Subject: [PATCH 2/7] Remove special casing for rust::Unit and simplify handling for infrastructure types in general --- zngur-generator/src/cpp.rs | 57 +++++++++++++++++++-- zngur-generator/src/lib.rs | 13 +---- zngur-generator/templates/cpp_header.sptl | 38 ++------------ zngur-generator/templates/zngur_header.sptl | 6 +-- 4 files changed, 60 insertions(+), 54 deletions(-) diff --git a/zngur-generator/src/cpp.rs b/zngur-generator/src/cpp.rs index 7bcc0630..677503ed 100644 --- a/zngur-generator/src/cpp.rs +++ b/zngur-generator/src/cpp.rs @@ -41,24 +41,63 @@ impl CppPath { self.0.split_last().unwrap().0 } + /// Returns true if this is an infrastructure template type defined in zngur.h. + /// + /// These types have generic template definitions in zngur.h and never appear + /// in type_defs as they don't need per-type specialization in the generated header. + pub(crate) fn is_infrastructure_template(&self) -> bool { + matches!( + self.0 + .iter() + .map(|s| s.as_str()) + .collect::>() + .as_slice(), + ["rust", "Ref"] + | ["rust", "RefMut"] + | ["rust", "Raw"] + | ["rust", "RawMut"] + | ["rust", "FieldOwned"] + | ["rust", "FieldRef"] + | ["rust", "FieldRefMut"] + | ["rust", "ZngurCppOpaqueOwnedObject"] + | ["rust", "Tuple"] // generic Tuple, not Unit + ) + } + + /// Returns true if this type is rust::Unit (the empty tuple). + pub(crate) fn is_unit(&self) -> bool { + self.0.len() == 2 && self.0[0] == "rust" && self.0[1] == "Unit" + } + + /// Returns true if this type is rust::Bool. + pub(crate) fn is_bool(&self) -> bool { + self.0.len() == 2 && self.0[0] == "rust" && self.0[1] == "Bool" + } + + /// Returns true if this type is rust::Str. + pub(crate) fn is_str(&self) -> bool { + self.0.len() == 2 && self.0[0] == "rust" && self.0[1] == "Str" + } + /// Returns whether this type needs a forward declaration in the generated header. /// /// Forward declarations are needed for user-defined types so they can be /// referenced before their full definition. Returns false for: /// - Primitive types (uint8_t, int32_t) - built-in to C++ - /// - Infrastructure types (rust::Ref, rust::RefMut, rust::Unit) - defined in zngur.h + /// - Infrastructure types (Ref, RefMut, Raw, etc.) - defined in zngur.h as generic templates + /// - Unit type - fully defined in zngur.h as Tuple<> specialization fn needs_forward_declaration(&self) -> bool { // Primitive types (like uint8_t, int32_t, etc.) have no namespace - just a single component if self.0.len() == 1 { return false; } - // Skip zngur utility types - if self.0 == ["rust", "Unit"] || self.0 == ["rust", "Ref"] || self.0 == ["rust", "RefMut"] { + // Skip infrastructure types defined in zngur.h + if self.is_infrastructure_template() || self.is_unit() { return false; } - // For all other types with a namespace, generate forward declaration + // User types need forward declarations true } @@ -119,6 +158,16 @@ impl CppType { } } + /// Returns true if this type is rust::Bool. + pub(crate) fn is_bool(&self) -> bool { + self.path.is_bool() + } + + /// Returns true if this type is rust::Str. + pub(crate) fn is_str(&self) -> bool { + self.path.is_str() + } + pub(crate) fn specialization_decl(&self) -> String { if self.generic_args.is_empty() { format!("struct {}", self.path.name()) diff --git a/zngur-generator/src/lib.rs b/zngur-generator/src/lib.rs index 9757236e..6fa8fe96 100644 --- a/zngur-generator/src/lib.rs +++ b/zngur-generator/src/lib.rs @@ -39,20 +39,9 @@ impl ZngurGenerator { } pub fn render(self) -> GeneratedFiles { - let mut zng = self.0; + let zng = self.0; let namespace = &zng.cpp_namespace; - // Unit type is a bit special, and almost everyone needs it, so we add it ourself. - zng.types.push(ZngurType { - ty: RustType::UNIT, - layout: LayoutPolicy::ZERO_SIZED_TYPE, - wellknown_traits: vec![ZngurWellknownTrait::Copy], - methods: vec![], - constructors: vec![], - fields: vec![], - cpp_value: None, - cpp_ref: None, - }); let mut cpp_file = CppFile::default(); cpp_file.header_file_name = zng.cpp_include_header_name.clone(); cpp_file.additional_includes = zng.additional_includes.0; diff --git a/zngur-generator/templates/cpp_header.sptl b/zngur-generator/templates/cpp_header.sptl index 7ab77c37..b36a2ba3 100644 --- a/zngur-generator/templates/cpp_header.sptl +++ b/zngur-generator/templates/cpp_header.sptl @@ -1,18 +1,6 @@ #include -<% if self.cpp_namespace != "rust" { %> -// Forward declare custom namespace for template specializations -namespace <%- self.cpp_namespace %> {} -<% } %> - <% if let Some(symbols) = &self.panic_to_exception { %> -#ifndef ZNGUR_RUST_PANIC_EXCEPTION_CLASS_DEFINED -#define ZNGUR_RUST_PANIC_EXCEPTION_CLASS_DEFINED -namespace rust { - class Panic {}; -} -#endif - extern "C" { uint8_t <%- symbols.detect_panic %>(); void <%- symbols.take_panic %>(); @@ -131,7 +119,6 @@ namespace rust { <% let is_unsized = td.wellknown_traits.contains(&ZngurWellknownTraitData::Unsized); %> <% let name = td.ty.path.name(); %> - <% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { template<> inline uint8_t* __zngur_internal_data_ptr< <%- td.ty %> >(const <%- td.ty %>& t) noexcept ; @@ -144,9 +131,7 @@ namespace rust { template<> inline size_t __zngur_internal_size_of< <%- td.ty %> >() noexcept ; } - <% } %> - <% if td.ty.path.0 != ["rust", "Unit"] { %> <%- td.ty.path.open_namespace() %> <%- td.ty.specialization_decl() %> { @@ -170,7 +155,7 @@ namespace rust { friend void ::rust::__zngur_internal_assume_deinit< <%- td.ty %> >(<%- td.ty %>& t) noexcept ; friend ::rust::ZngurPrettyPrinter< <%- td.ty %> >; - <% if td.ty.path.to_string() == "::rust::Bool" { %> + <% if td.ty.is_bool() { %> public: operator bool() { return data[0]; @@ -298,9 +283,7 @@ namespace rust { }; // <%- td.ty.specialization_decl() %> <%- td.ty.path.close_namespace() %> - <% } %> -<% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { <% match &td.layout { CppLayoutPolicy::StackAllocated { size, align: _ } => { %> @@ -358,9 +341,7 @@ namespace rust { <% } %> } // namespace rust -<% } %> -<% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { template<> @@ -456,14 +437,12 @@ namespace rust { } } // namespace rust -<% } %> // Ref specialization -<% if td.ty.path.to_string() == "::rust::Str" { %> +<% if td.ty.is_str() { %> auto operator""_rs(const char* input, size_t len) -> ::rust::Ref<::rust::Str>; <% } %> -<% if td.ty.path.0 != ["rust", "Unit"] { %> namespace rust { template<> @@ -544,17 +523,11 @@ namespace rust { <% } %> <% } %> - <% if td.ty.path.to_string() == "::rust::Str" { %> + <% if td.ty.is_str() { %> friend auto ::operator""_rs(const char* input, size_t len) -> ::rust::Ref<::rust::Str>; <% } %> }; -} // namespace rust -<% } %> - -<% if td.ty.path.0 != ["rust", "Unit"] { %> -namespace rust { - <% for ref_kind in ["Ref", "Raw", "RawMut"] { %> template<> @@ -579,9 +552,8 @@ inline size_t __zngur_internal_size_of< <%- ref_kind %> < <%- td.ty %> > >() noe <% } %> } // namespace rust -<% } %> -<% if td.ty.path.to_string() == "::rust::Str" { %> +<% if td.ty.is_str() { %> inline ::rust::Ref<::rust::Str> operator""_rs(const char* input, size_t len) { ::rust::Ref<::rust::Str> o; o.data[0] = reinterpret_cast(input); @@ -593,7 +565,6 @@ inline size_t __zngur_internal_size_of< <%- ref_kind %> < <%- td.ty %> > >() noe namespace rust { // Field specializations -<% if td.ty.path.0 != ["rust", "Unit"] { %> <% for field_kind in &["FieldOwned", "FieldRef", "FieldRefMut"] { %> template @@ -618,7 +589,6 @@ namespace rust { }; // struct <%- field_kind %>< <%- td.ty %>, OFFSET > <% } /* for field_kind in &[...] */ %> -<% } %> } // namespace rust diff --git a/zngur-generator/templates/zngur_header.sptl b/zngur-generator/templates/zngur_header.sptl index b2db1182..b706706f 100644 --- a/zngur-generator/templates/zngur_header.sptl +++ b/zngur-generator/templates/zngur_header.sptl @@ -14,6 +14,8 @@ #define zngur_dbg(x) (::rust::zngur_dbg_impl(__FILE__, __LINE__, #x, x)) namespace rust { + class Panic {}; + template uint8_t* __zngur_internal_data_ptr(const T& t) noexcept ; @@ -160,8 +162,6 @@ namespace rust { struct Tuple; // Unit type (empty tuple) - #ifndef ZNGUR_UNIT_TYPE_DEFINED - #define ZNGUR_UNIT_TYPE_DEFINED template<> struct Tuple<> { ::std::array< ::uint8_t, 1> data; @@ -189,8 +189,6 @@ namespace rust { return const_cast(&t.data[0]); } - #endif // ZNGUR_UNIT_TYPE_DEFINED - template struct ZngurPrettyPrinter; From d22d05533e777f5cf5b1a271431d62c6e6362fef Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Tue, 18 Nov 2025 12:15:09 -0500 Subject: [PATCH 3/7] Fix sed expressions that remove PIDs from 'thread panicked' messages to also remove '' --- xtask/src/ci.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 5528ef57..6c5f532f 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -31,7 +31,7 @@ fn check_examples(sh: &Shell, fix: bool) -> Result<()> { .run() .with_context(|| format!("Building example `{example}` failed"))?; let bash_cmd = format!( - "../../target/debug/example-{example} 2>&1 | sed -e s/thread.*\\(.*\\)/thread/g > actual_output.txt" + "../../target/debug/example-{example} 2>&1 | sed 's/thread .* panicked/thread panicked/g' > actual_output.txt" ); cmd!(sh, "bash -c {bash_cmd}") .run() @@ -46,7 +46,7 @@ fn check_examples(sh: &Shell, fix: bool) -> Result<()> { .with_context(|| format!("Building example `{example}` failed"))?; cmd!( sh, - "bash -c './a.out 2>&1 | sed -e s/thread.*\\(.*\\)/thread/g > actual_output.txt'" + "bash -c './a.out 2>&1 | sed s/thread.*panicked/thread\\ panicked/g > actual_output.txt'" ) .run() .with_context(|| format!("Running example `{example}` failed"))?; From 3a4d5cf63d3ad74886474fa77f0e3a496ae0b2b8 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 1 Dec 2025 14:22:53 -0500 Subject: [PATCH 4/7] Add --single-header for legacy header generation --- Cargo.lock | 4 ++ examples/simple_single_header/.gitignore | 3 + examples/simple_single_header/Cargo.toml | 14 ++++ examples/simple_single_header/Makefile | 13 ++++ examples/simple_single_header/README.md | 34 +++++++++ .../simple_single_header/expected_output.txt | 18 +++++ examples/simple_single_header/main.cpp | 72 +++++++++++++++++++ examples/simple_single_header/main.zng | 61 ++++++++++++++++ examples/simple_single_header/src/lib.rs | 2 + zngur-cli/src/main.rs | 20 +++++- zngur/src/lib.rs | 69 +++++++++++++----- 11 files changed, 291 insertions(+), 19 deletions(-) create mode 100644 examples/simple_single_header/.gitignore create mode 100644 examples/simple_single_header/Cargo.toml create mode 100644 examples/simple_single_header/Makefile create mode 100644 examples/simple_single_header/README.md create mode 100644 examples/simple_single_header/expected_output.txt create mode 100644 examples/simple_single_header/main.cpp create mode 100644 examples/simple_single_header/main.zng create mode 100644 examples/simple_single_header/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a814b2b9..c243ac18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,6 +412,10 @@ version = "0.7.0" name = "example-simple-import" version = "0.7.0" +[[package]] +name = "example-simple_single_header" +version = "0.7.0" + [[package]] name = "example-tutorial" version = "0.7.0" diff --git a/examples/simple_single_header/.gitignore b/examples/simple_single_header/.gitignore new file mode 100644 index 00000000..404d3a43 --- /dev/null +++ b/examples/simple_single_header/.gitignore @@ -0,0 +1,3 @@ +generated.h +generated.rs +generated.cpp diff --git a/examples/simple_single_header/Cargo.toml b/examples/simple_single_header/Cargo.toml new file mode 100644 index 00000000..1bbf0127 --- /dev/null +++ b/examples/simple_single_header/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "example-simple_single_header" +version = "0.7.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish = false + +[lib] +crate-type = ["staticlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/examples/simple_single_header/Makefile b/examples/simple_single_header/Makefile new file mode 100644 index 00000000..86e1d90b --- /dev/null +++ b/examples/simple_single_header/Makefile @@ -0,0 +1,13 @@ +a.out: main.cpp generated.h src/generated.rs src/lib.rs ../../target/release/libexample_simple_single_header.a + ${CXX} -std=c++11 -Werror main.cpp generated.cpp -g -L ../../target/release/ -l example_simple_single_header + +../../target/release/libexample_simple_single_header.a: + cargo build --release + +generated.h generated.cpp ./src/generated.rs: main.zng + cd ../../zngur-cli && cargo run g ../examples/simple_single_header/main.zng --single-header + +.PHONY: ../../target/release/libexample_simple_single_header.a generated.h clean + +clean: + rm -f generated.h generated.cpp src/generated.rs a.out actual_output.txt diff --git a/examples/simple_single_header/README.md b/examples/simple_single_header/README.md new file mode 100644 index 00000000..6a8b6047 --- /dev/null +++ b/examples/simple_single_header/README.md @@ -0,0 +1,34 @@ +# Simple Single Header Example + +This example demonstrates the `--single-header` flag, which generates a single `generated.h` file instead of splitting into `generated.h` and `zngur.h`. This emulates the old behavior of zngur before the header split. + +## Differences from the regular simple example + +- Uses `--single-header` flag in the Makefile +- Does not require `-o` flag (output directory) since utility headers are merged into the main header +- Does not need `-I.` flag for compilation since there's no separate `zngur.h` to include +- The `.gitignore` doesn't include `zngur.h` since it's not generated + +## Building + +```bash +make +``` + +## Running + +```bash +./a.out +``` + +## When to use single-header mode + +Use `--single-header` when: +- You want simpler build configuration (no need to manage include paths for utility headers) +- You're migrating from an older version of zngur +- You have a simple project that doesn't need the modularity of split headers + +Use split headers (default) when: +- You have multiple zngur-generated libraries that can share the same `zngur.h` +- You want faster incremental compilation (changes to your types don't require recompiling the infrastructure) +- You want cleaner separation between infrastructure and user code diff --git a/examples/simple_single_header/expected_output.txt b/examples/simple_single_header/expected_output.txt new file mode 100644 index 00000000..a03e40d4 --- /dev/null +++ b/examples/simple_single_header/expected_output.txt @@ -0,0 +1,18 @@ +17 +s[2] = 7 + +thread panicked at examples/simple_single_header/src/generated.rs:192:39: +called `Option::unwrap()` on a `None` value +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +s[4] = Rust panic happened +hello 2 2 +hello 5 7 +hello 7 14 +hello 3 17 +34 17 +vector iterator has been destructed +[main.cpp:71] t = [ + 10, + 20, + 60, +] diff --git a/examples/simple_single_header/main.cpp b/examples/simple_single_header/main.cpp new file mode 100644 index 00000000..7bee9611 --- /dev/null +++ b/examples/simple_single_header/main.cpp @@ -0,0 +1,72 @@ +#include +#include + +#include "./generated.h" + +// Rust values are available in the `::rust` namespace from their absolute path +// in Rust +template using Vec = rust::std::vec::Vec; +template using Option = rust::std::option::Option; +template using BoxDyn = rust::Box>; + +// You can implement Rust traits for your classes +template +class VectorIterator : public rust::std::iter::Iterator { + std::vector vec; + size_t pos; + +public: + VectorIterator(std::vector &&v) : vec(v), pos(0) {} + ~VectorIterator() { + std::cout << "vector iterator has been destructed" << std::endl; + } + + Option next() override { + if (pos >= vec.size()) { + return Option::None(); + } + T value = vec[pos++]; + // You can construct Rust enum with fields in C++ + return Option::Some(value); + } +}; + +int main() { + // You can call Rust functions that return things by value, and store that + // value in your stack. + auto s = Vec::new_(); + s.push(2); + Vec::push(s, 5); + s.push(7); + Vec::push(s, 3); + // You can call Rust functions just like normal Rust. + std::cout << s.clone().into_iter().sum() << std::endl; + // You can catch Rust panics as C++ exceptions + try { + std::cout << "s[2] = " << *s.get(2).unwrap() << std::endl; + std::cout << "s[4] = " << *s.get(4).unwrap() << std::endl; + } catch (rust::Panic e) { + std::cout << "Rust panic happened" << std::endl; + } + int state = 0; + // You can convert a C++ lambda into a `Box` and friends. + auto f = BoxDyn>::make_box([&](int32_t x) { + state += x; + std::cout << "hello " << x << " " << state << "\n"; + return x * 2; + }); + // And pass it to Rust functions that accept closures. + auto x = s.into_iter().map(std::move(f)).sum(); + std::cout << x << " " << state << "\n"; + std::vector vec{10, 20, 60}; + // You can convert a C++ type that implements `Trait` to a `Box`. + // `make_box` is similar to the `make_unique`, it takes constructor arguments + // and construct it inside the `Box` (instead of `unique_ptr`). + auto vec_as_iter = BoxDyn>::make_box< + VectorIterator>(std::move(vec)); + // Then use it like a normal Rust value. + auto t = vec_as_iter.collect(); + // Some utilities are also provided. For example, `zngur_dbg` is the + // equivalent of `dbg!` macro. + zngur_dbg(t); +} diff --git a/examples/simple_single_header/main.zng b/examples/simple_single_header/main.zng new file mode 100644 index 00000000..09a33dcf --- /dev/null +++ b/examples/simple_single_header/main.zng @@ -0,0 +1,61 @@ +#convert_panic_to_exception + +type Box i32> { + #layout(size = 16, align = 8); +} + +mod ::std { + type option::Option { + #layout(size = 8, align = 4); + wellknown_traits(Copy); + + constructor None; + constructor Some(i32); + + fn unwrap(self) -> i32; + } + + type option::Option<&i32> { + #layout(size = 8, align = 8); + wellknown_traits(Copy); + + fn unwrap(self) -> &i32; + } + + type iter::Map<::std::vec::IntoIter, Box i32>> { + #layout(size = 48, align = 8); + + fn sum(self) -> i32; + } + + mod vec { + type IntoIter { + #layout(size = 32, align = 8); + + fn sum(self) -> i32; + fn map i32>>(self, Box i32>) + -> ::std::iter::Map<::std::vec::IntoIter, Box i32>>; + } + + type Vec { + #layout(size = 24, align = 8); + wellknown_traits(Debug); + + fn new() -> Vec; + fn push(&mut self, i32); + fn clone(&self) -> Vec; + fn get(&self, usize) -> ::std::option::Option<&i32> deref [i32]; + fn into_iter(self) -> ::std::vec::IntoIter; + } + } + + trait iter::Iterator:: { + fn next(&mut self) -> ::std::option::Option; + } +} + +type Box> { + #layout(size = 16, align = 8); + + fn collect<::std::vec::Vec>(self) -> ::std::vec::Vec; +} diff --git a/examples/simple_single_header/src/lib.rs b/examples/simple_single_header/src/lib.rs new file mode 100644 index 00000000..76cfca89 --- /dev/null +++ b/examples/simple_single_header/src/lib.rs @@ -0,0 +1,2 @@ +#[rustfmt::skip] +mod generated; diff --git a/zngur-cli/src/main.rs b/zngur-cli/src/main.rs index 8184d1b7..2829a6e4 100644 --- a/zngur-cli/src/main.rs +++ b/zngur-cli/src/main.rs @@ -13,8 +13,9 @@ enum Command { /// Directory where utility headers (like zngur.h) will be generated. /// This directory should be added to your C++ include path. + /// Not required when using --single-header. #[arg(short = 'o', long)] - output_dir: PathBuf, + output_dir: Option, /// Path of the generated C++ file, if it is needed /// @@ -49,6 +50,11 @@ enum Command { /// Default is "rust" #[arg(long)] cpp_namespace: Option, + + /// Generate a single header file instead of splitting into generated.h and zngur.h. + /// This emulates the old behavior of zngur. + #[arg(long)] + single_header: bool, }, } @@ -63,16 +69,26 @@ fn main() { rs_file, mangling_base, cpp_namespace, + single_header, } => { let pp = path.parent().unwrap(); let cpp_file = cpp_file.unwrap_or_else(|| pp.join("generated.cpp")); let h_file = h_file.unwrap_or_else(|| pp.join("generated.h")); let rs_file = rs_file.unwrap_or_else(|| pp.join("src/generated.rs")); + let mut zng = Zngur::from_zng_file(&path) .with_cpp_file(cpp_file) .with_h_file(h_file) .with_rs_file(rs_file) - .with_output_dir(output_dir); + .with_single_header(single_header); + + if let Some(output_dir) = output_dir { + zng = zng.with_output_dir(output_dir); + } else if !single_header { + eprintln!("Error: --output-dir is required when not using --single-header"); + std::process::exit(1); + } + if let Some(mangling_base) = mangling_base { zng = zng.with_mangling_base(&mangling_base); } diff --git a/zngur/src/lib.rs b/zngur/src/lib.rs index 3042749a..d5de48ea 100644 --- a/zngur/src/lib.rs +++ b/zngur/src/lib.rs @@ -30,6 +30,7 @@ pub struct Zngur { output_dir: Option, mangling_base: Option, cpp_namespace: Option, + single_header: bool, } impl Zngur { @@ -42,6 +43,7 @@ impl Zngur { output_dir: None, mangling_base: None, cpp_namespace: None, + single_header: false, } } @@ -75,12 +77,16 @@ impl Zngur { self } + pub fn with_single_header(mut self, single_header: bool) -> Self { + self.single_header = single_header; + self + } + pub fn generate(self) { let mut file = ZngurGenerator::build_from_zng(ParsedZngFile::parse(self.zng_file)); let rs_file_path = self.rs_file_path.expect("No rs file path provided"); let h_file_path = self.h_file_path.expect("No h file path provided"); - let output_dir = self.output_dir.expect("No output directory provided. Use with_output_dir() to specify where utility headers should be generated."); file.0.cpp_include_header_name = h_file_path .file_name() @@ -101,30 +107,59 @@ impl Zngur { let generated = file.render(); - // Create output directory for utility headers - fs::create_dir_all(&output_dir).unwrap(); - - // Write utility headers to output directory - for (filename, content) in generated.utility_headers { - let utility_path = output_dir.join(&filename); - File::create(utility_path) + if self.single_header { + // Single header mode: merge utility headers into primary header + let mut merged_header = String::new(); + + // Add utility headers first + for (_, content) in generated.utility_headers { + merged_header.push_str(&content); + merged_header.push('\n'); + } + + // Then add the primary header content, but skip the #include line + let primary_without_include = generated.primary_header + .lines() + .filter(|line| !line.contains("#include ")) + .collect::>() + .join("\n"); + merged_header.push_str(&primary_without_include); + + File::create(&h_file_path) + .unwrap() + .write_all(merged_header.as_bytes()) + .unwrap(); + } else { + // Split header mode: write utility headers to output directory + let output_dir = self.output_dir.expect("No output directory provided. Use with_output_dir() to specify where utility headers should be generated."); + + // Create output directory for utility headers + fs::create_dir_all(&output_dir).unwrap(); + + // Write utility headers to output directory + for (filename, content) in generated.utility_headers { + let utility_path = output_dir.join(&filename); + File::create(utility_path) + .unwrap() + .write_all(content.as_bytes()) + .unwrap(); + } + + // Print the absolute path to the output directory + let abs_output_dir = output_dir.canonicalize().unwrap(); + println!("Generated headers directory: {}", abs_output_dir.display()); + + File::create(&h_file_path) .unwrap() - .write_all(content.as_bytes()) + .write_all(generated.primary_header.as_bytes()) .unwrap(); } - // Print the absolute path to the output directory - let abs_output_dir = output_dir.canonicalize().unwrap(); - println!("Generated headers directory: {}", abs_output_dir.display()); - File::create(rs_file_path) .unwrap() .write_all(generated.rust_file.as_bytes()) .unwrap(); - File::create(h_file_path) - .unwrap() - .write_all(generated.primary_header.as_bytes()) - .unwrap(); + if let Some(cpp) = generated.cpp_file { let cpp_file_path = self.cpp_file_path.expect("No cpp file path provided"); File::create(cpp_file_path) From f144eb3176c4d91a1da9ae7d54fa3c0bb5dd72c7 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 1 Dec 2025 14:29:22 -0500 Subject: [PATCH 5/7] Update book with generated code overview --- book/src/SUMMARY.md | 1 + book/src/generated_code.md | 891 +++++++++++++++++++++++++++++++++++++ 2 files changed, 892 insertions(+) create mode 100644 book/src/generated_code.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 95a2e308..eaaff413 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -11,6 +11,7 @@ - [Types with special support](./call_rust_from_cpp/special_types.md) - [Panic and exceptions](./call_rust_from_cpp/panic_and_exceptions.md) - [Raw pointers](./call_rust_from_cpp/raw_pointers.md) + - [Generated code overview](./generated_code.md) - [Calling C++ from Rust](./call_cpp_from_rust/index.md) - [Calling C++ free functions](./call_cpp_from_rust/function.md) - [Writing `impl` blocks for Rust types in C++](./call_cpp_from_rust/rust_impl.md) diff --git a/book/src/generated_code.md b/book/src/generated_code.md new file mode 100644 index 00000000..4bf7e8c2 --- /dev/null +++ b/book/src/generated_code.md @@ -0,0 +1,891 @@ +# Generated Code Structure + +Zngur generates both Rust and C++ files from a `.zng` interface definition file. There are two generation modes for the C++ side: + +## Generation Modes + +### Split-Header Mode (Default) + +By default, Zngur generates one Rust file and three C++ files: + +**Rust:** +1. **`generated.rs`** - Rust bridge code and FFI declarations + +**C++:** +1. **`zngur.h`** - Foundation header with core utilities +2. **`generated.h`** - Main header with Rust type wrappers and inline functions +3. **`generated.cpp`** - Implementation file with method bodies and C++ bridge code + +This mode provides better modularity and allows `zngur.h` to be shared across multiple independent Zngur-generated libraries. + +**Usage:** +```bash +zngur g main.zng -o output_dir +``` + +The `-o` flag specifies where `zngur.h` should be generated. You must add this directory to your C++ include path (e.g., with `-I` flag). + +### Single-Header Mode + +For backward compatibility and simpler build setups, you can use the `--single-header` flag to merge `zngur.h` into `generated.h`: + +**Usage:** +```bash +zngur g main.zng --single-header +``` + +This generates one Rust file and two C++ files: + +**Rust:** +1. **`generated.rs`** - Rust bridge code and FFI declarations + +**C++:** +1. **`generated.h`** - Contains both foundation utilities and type wrappers +2. **`generated.cpp`** - Implementation file + +This mode emulates the old behavior of Zngur before the header split and is useful for simple projects or when migrating from older versions. + +## File Structure + +The rest of this chapter describes the generated files. For C++, split-header mode is described; in single-header mode, the content of `zngur.h` is merged directly into `generated.h`. + +## 1. `generated.rs` - Rust Bridge Code + +**Purpose**: Contains Rust-side FFI bridge code that enables C++ to call Rust functions and implement Rust traits. + +This file should be included in your Rust project with `mod generated;`. + +### Opaque Type Definitions + +The file begins with helper types for representing C++ objects in Rust: + +```rust +pub struct ZngurCppOpaqueOwnedObject { + data: *mut u8, + destructor: extern "C" fn(*mut u8), +} + +pub struct ZngurCppOpaqueBorrowedObject(()); +``` + +- `ZngurCppOpaqueOwnedObject` wraps a C++ object with ownership, calling the destructor when dropped +- `ZngurCppOpaqueBorrowedObject` represents a borrowed C++ object (zero-sized marker type) + +### Static Assertions + +For each Rust type declared in the `.zng` file, generates compile-time size and alignment checks: + +```rust +const _: [(); SIZE] = [(); ::std::mem::size_of::()]; +const _: [(); ALIGN] = [(); ::std::mem::align_of::()]; +``` + +These ensure that the sizes specified in `#layout(size=X, align=Y)` match the actual Rust type sizes. +If there's a mismatch, compilation fails with a clear error. + +For Copy types, an additional assertion verifies the `Copy` trait: + +```rust +const _: () = { + const fn static_assert_is_copy() {} + static_assert_is_copy::(); +}; +``` + +### Function Bridge Implementations + +For each Rust function and method declared in the `.zng` file, generates an `extern "C"` wrapper: + +```rust +#[no_mangle] +pub extern "C" fn _zngur_mangled_name(i0: *mut u8, i1: *mut u8, o: *mut u8) { + unsafe { + let arg0 = std::ptr::read(i0 as *mut ArgType0); + let arg1 = std::ptr::read(i1 as *mut ArgType1); + let result = rust_function(arg0, arg1); + std::ptr::write(o as *mut _, result); + } +} +``` + +These functions: +- Use mangled names to avoid symbol conflicts +- Accept arguments and return value as raw `*mut u8` pointers +- Use `ptr::read` to move values from C++ to Rust +- Use `ptr::write` to move the result back to C++ +- Are marked `#[no_mangle]` so C++ can link to them + +### Drop in Place Functions + +For non-Copy types, generates destructor bridge functions: + +```rust +#[no_mangle] +pub extern "C" fn _zngur_Type_drop_in_place(v: *mut u8) { + unsafe { + ::std::ptr::drop_in_place(v as *mut Type); + } +} +``` + +These allow C++ destructors to properly drop Rust values by calling Rust's `drop_in_place`. + +### Trait Object Builders + +For `Box` types declared in `extern "C++"` blocks, generates trait implementations that wrap C++ objects: + +```rust +#[no_mangle] +pub extern "C" fn _zngur_build_trait_object( + data: *mut u8, + destructor: extern "C" fn(*mut u8), + o: *mut u8, +) { + struct Wrapper { + value: ZngurCppOpaqueOwnedObject, + } + + impl Trait for Wrapper { + fn method(&mut self, arg: ArgType) -> ReturnType { + unsafe { + let data = self.value.ptr(); + // Call extern "C" function that forwards to C++ method + _zngur_trait_method_call(data, &arg as *const _ as *mut u8, ...); + ... + } + } + } + + unsafe { + let wrapper = Wrapper { + value: ZngurCppOpaqueOwnedObject::new(data, destructor), + }; + let trait_object: Box = Box::new(wrapper); + std::ptr::write(o as *mut _, trait_object); + } +} +``` + +This enables C++ classes to implement Rust traits and be used as `Box` in Rust. + +### Closure Builders + +For `Box` types, generates functions that wrap C++ lambdas: + +```rust +#[no_mangle] +pub extern "C" fn _zngur_build_closure( + data: *mut u8, + destructor: extern "C" fn(*mut u8), + call: extern "C" fn(data: *mut u8, args..., o: *mut u8), + o: *mut u8, +) { + let cpp_object = unsafe { ZngurCppOpaqueOwnedObject::new(data, destructor) }; + let closure: Box Output> = Box::new(move |args...| unsafe { + let data = cpp_object.ptr(); + // Marshal arguments and call C++ function pointer + call(data, ...); + ... + }); + unsafe { std::ptr::write(o as *mut _, closure) } +} +``` + +This allows C++ `std::function` and lambdas to be converted to Rust closures. + +### Panic Handling (Optional) + +If `#convert_panic_to_exception` is enabled in the `.zng` file, generates panic detection functions: + +```rust +thread_local! { + pub static PANIC_PAYLOAD: ::std::cell::Cell> = + ::std::cell::Cell::new(None); +} + +#[no_mangle] +pub fn _zngur_detect_panic() -> u8 { + PANIC_PAYLOAD.with(|p| { + if p.get().is_some() { 1 } else { 0 } + }) +} + +#[no_mangle] +pub fn _zngur_take_panic() { + PANIC_PAYLOAD.with(|p| p.take()); +} +``` + +These functions allow C++ to detect when a Rust panic occurred and convert it to a C++ exception. + +### Exported Function Declarations + +For functions declared in `extern "C++"` blocks, generates `extern "C"` declarations: + +```rust +extern "C" { + fn _zngur_cpp_function(args..., o: *mut u8); +} +``` + +These are implemented on the C++ side and called from Rust. + +## 2. `zngur.h` - C++ Foundation Header + +**Purpose**: Provides the foundational infrastructure needed by all generated C++ code. +This is a reusable header that doesn't depend on the specific `.zng` file. + +### Standard Includes + +The header includes necessary C++ standard library headers: +``, ``, ``, ``, ``, ``, ``, `` + +It also includes any user-specified additional includes via `#cpp_additional_includes` in the `.zng` file. These additional includes are placed in `zngur.h` (split-header mode) or at the top of the merged `generated.h` (single-header mode). + +### Debug Macro + +```cpp +#define zngur_dbg(x) (::rust::zngur_dbg_impl(__FILE__, __LINE__, #x, x)) +``` + +This provides a C++ equivalent of Rust's `dbg!` macro for inspecting values during debugging. + +### Core Templates and Utilities + +All core functionality lives in the `rust` namespace: + +#### Exception Type + +```cpp +class Panic {}; +``` + +Used for catching Rust panics as C++ exceptions (when `#convert_panic_to_exception` is enabled). + +#### Internal Data Management Templates + +These template functions manage the lifecycle and memory of Rust values in C++: + +- `__zngur_internal_data_ptr()` - Gets pointer to the underlying data buffer +- `__zngur_internal_assume_init()` - Marks a value as initialized (sets drop flag) +- `__zngur_internal_assume_deinit()` - Marks a value as moved/uninitialized (clears drop flag) +- `__zngur_internal_size_of()` - Gets the size of a type +- `__zngur_internal_check_init()` - Validates initialization state +- `__zngur_internal_move_to_rust()` - Moves a C++ value to Rust +- `__zngur_internal_move_from_rust()` - Moves a Rust value to C++ + +#### ZngurCppOpaqueOwnedObject + +A type-erased owned pointer for C++ objects that need to be called from Rust. +This is essentially a type-erased `unique_ptr`: + +```cpp +class ZngurCppOpaqueOwnedObject { + uint8_t* data; + void (*destructor)(uint8_t*); + +public: + template + inline static ZngurCppOpaqueOwnedObject build(Args&&... args); + + template + inline T& as_cpp(); +}; +``` + +#### Reference Types + +Templates for representing Rust references in C++: + +- `template struct Ref` - Immutable reference (`&T`) +- `template struct RefMut` - Mutable reference (`&mut T`) +- `template struct Raw` - Raw pointer (`*const T`) +- `template struct RawMut` - Mutable raw pointer (`*mut T`) + +#### Field Access Types + +Templates for accessing fields of Rust types: + +- `template struct FieldOwned` - Access to an owned field +- `template struct FieldRef` - Shared reference to a field +- `template struct FieldRefMut` - Mutable reference to a field + +These enable zero-cost field access by encoding the field offset as a template parameter. + +#### Trait Support + +```cpp +class Inherent; + +template +class Impl; +``` + +The `Impl` template is specialized for each C++ trait implementation written in `extern "C++"` blocks. + +#### Unit Type + +```cpp +template<> struct Tuple<> { ... }; +using Unit = Tuple<>; +``` + +Represents Rust's unit type `()`. + +#### Builtin Type Specializations + +Complete specializations of all internal templates for primitive types: +`int8_t`, `uint8_t`, `int16_t`, `uint16_t`, `int32_t`, `uint32_t`, `int64_t`, `uint64_t`, +`float`, `double`, `size_t`, and pointer types. + +These specializations provide `Ref` and `RefMut` wrappers and integrate with the +internal template system. + +## 3. `generated.h` - C++ Main Generated Header + +**Purpose**: Contains C++ wrappers for all Rust types, functions, and traits declared in the `.zng` file. + +### Header Structure + +#### Includes zngur.h (Split-Header Mode Only) + +In split-header mode, `generated.h` includes the foundation header: + +```cpp +#include +``` + +In single-header mode, this include is omitted and the content of `zngur.h` is merged directly into `generated.h`. + +#### Panic Detection Functions + +If `#convert_panic_to_exception` is enabled in the `.zng` file, generates functions for +detecting and handling Rust panics: + +```cpp +extern "C" { + uint8_t detect_panic(); + void take_panic(); +} +``` + +#### Extern "C" Function Declarations + +For every Rust function and method declared in `.zng`, generates an `extern "C"` declaration +with a mangled name: + +```cpp +extern "C" { + void __zngur_[mangled_name](uint8_t* i0, uint8_t* i1, ..., uint8_t* o) noexcept; +} +``` + +These declarations correspond to the functions generated in the Rust code and provide the +bridge between C++ and Rust. + +### Per-Type Code Generation + +For each `type` declared in the `.zng` file, Zngur generates comprehensive C++ wrapper code: + +#### A. Forward Declarations + +Opens appropriate C++ namespaces matching Rust's module structure: + +```cpp +namespace rust { +namespace std { +namespace vec { + template class Vec; +}}} +``` + +#### B. Trait Definitions + +For each Rust trait exposed in the `.zng` file, generates an abstract base class: + +```cpp +namespace rust::std::iter { + template + class Iterator { + public: + virtual ~Iterator() {}; + virtual Option next() = 0; + }; +} +``` + +C++ classes can inherit from these to implement Rust traits. + +#### C. Main Type Definition + +For each type, generates a C++ class or struct with the following components: + +##### 1. Private Data Storage + +The storage strategy depends on the layout policy specified in the `.zng` file: + +**Stack-allocated types** (`#layout(size=X, align=Y)`): + +```cpp +alignas(Y) mutable ::std::array data; +``` + +The `mutable` keyword allows modifying `const` objects (equivalent to Rust's `UnsafeCell`), +which is necessary for interior mutability. + +**Heap-allocated types** (custom allocators): + +```cpp +uint8_t* data; // Allocated via Rust-provided allocator functions +``` + +**Only-by-ref types** (`#only_by_ref`): + +```cpp +// No data storage - these types can only be used via references +``` + +##### 2. Drop Flag (for non-Copy types) + +```cpp +bool drop_flag; // Tracks whether destructor should run +``` + +This prevents double-free and tracks move semantics, similar to how the Rust compiler +generates drop flags. + +##### 3. Special Member Functions + +**For Copy types** (`wellknown_traits(Copy)`): + +```cpp +Type(); // Default constructor +~Type(); // Destructor +Type(const Type&); // Copy constructor +Type& operator=(const Type&); // Copy assignment +Type(Type&&); // Move constructor +Type& operator=(Type&&); // Move assignment +``` + +Copy types use `memcpy` for both copy and move operations. + +**For non-Copy types**: + +```cpp +Type() : drop_flag(false) { } // Default constructor + +~Type() { // Destructor with drop flag check + if (drop_flag) { + rust_drop_in_place(&data[0]); + } +} + +Type(const Type&) = delete; // No copy allowed +Type& operator=(const Type&) = delete; + +Type(Type&&); // Move transfers drop flag +Type& operator=(Type&&); +``` + +The move operations transfer the `drop_flag` from the source to the destination, +ensuring only one object owns the Rust value at a time. + +##### 4. Constructors + +For each `constructor` declaration in the `.zng` file: + +```cpp +Type(arg1_type arg1, arg2_type arg2, ...); +``` + +These constructors call the corresponding Rust functions to initialize the object. + +##### 5. Static and Member Methods + +For each function and method declared, the header contains declarations: + +```cpp +static ReturnType method_name(Args...) noexcept; // Static method +ReturnType method_name(Args...) [const] noexcept; // Member method +``` + +The implementations are placed in `generated.cpp` to reduce header bloat and compilation times. + +Methods can have different receiver types: + +- Static methods have no receiver +- `&self` methods are `const` member functions +- `&mut self` methods are non-const member functions +- `self` methods consume the object (move) + +##### 6. Field Access + +For each field exposed in the `.zng` file: + +```cpp +[[no_unique_address]] ::rust::FieldOwned field_name; +``` + +The `[[no_unique_address]]` attribute ensures these zero-sized types don't increase +the size of the containing class. The `OFFSET` template parameter encodes the field's +byte offset for direct memory access. + +##### 7. make_box for Trait Objects + +For `Box` types, generates factory functions: + +```cpp +// For normal traits +template +static inline Type make_box(Args&&... args); + +// For Fn traits +static inline Type make_box(::std::function f); +``` + +These allow C++ classes and lambdas to be converted into Rust trait objects. + +##### 8. C++ Opaque Type Support + +If `#cpp_value` is specified for a type: + +```cpp +inline CppType& cpp() { + return (*accessor_fn(&data[0])).as_cpp(); +} +``` + +This provides access to the underlying C++ object for types that wrap C++ values. + +#### D. Reference Type Specializations + +For each type, generates `Ref` and `RefMut` template specializations: + +```cpp +namespace rust { + template<> + struct Ref { + private: + size_t data; // Or array for unsized types + + public: + Ref(); + Ref(const Type& t); + template + Ref(const FieldOwned& f); + // ... method forwarding + }; + + template<> + struct RefMut { + // Similar to Ref, but allows mutation + }; +} +``` + +**Thin vs Fat Pointers**: Sized types use a single `size_t` for the pointer, while +unsized types (`?Sized`) use `array` to store both the data pointer and +metadata (length for slices, vtable for trait objects). + +#### E. Field Type Specializations + +For each type, generates specializations of `FieldOwned`, `FieldRef`, and `FieldRefMut` +with nested field access: + +```cpp +template +struct FieldOwned { + // Nested field accessors + FieldOwned nested_field; + + // Method forwarding + ReturnType method(Args...) const noexcept; +}; +``` + +This enables chained field access like `obj.field1.field2.field3`. + +#### F. Debug Support + +For types with `wellknown_traits(Debug)`, generates pretty printer specializations: + +```cpp +template<> +struct ZngurPrettyPrinter { + static inline void print(Type const& t) { + rust_debug_print(&t.data[0]); + } +}; +``` + +Plus corresponding specializations for `Ref`, `RefMut`, and all field types. + +### Function Implementations + +Function implementations are split between the header and source files: + +#### Inline in Header + +**Free functions** (non-member functions) remain inline in the header: + +```cpp +inline ReturnType function_name(ArgType1 i0, ArgType2 i1) noexcept { + ReturnType o{}; + ::rust::__zngur_internal_assume_deinit(i0); + ::rust::__zngur_internal_assume_deinit(i1); + __zngur_mangled_name( + ::rust::__zngur_internal_data_ptr(i0), + ::rust::__zngur_internal_data_ptr(i1), + ::rust::__zngur_internal_data_ptr(o) + ); + // Handle panics if enabled + if (detect_panic()) { + take_panic(); + throw rust::Panic(); + } + ::rust::__zngur_internal_assume_init(o); + return o; +} +``` + +**Template functions** (like `make_box`) and **forwarding methods** (for `Ref`/`RefMut`/`Field*` types) also remain inline for correctness and performance. + +#### Moved to generated.cpp + +Static and non-static method implementations are moved to `generated.cpp` to reduce header bloat and improve compilation times. See the next section for details. + +### Exported Functions and Impl Blocks + +For `extern "C++"` declarations (C++ code called from Rust): + +```cpp +namespace rust::exported_functions { + ReturnType function_name(Args...); +} + +namespace rust { + template<> + class Impl { + public: + static ReturnType method_name(Args...); + }; +} +``` + +These are forward declarations; the implementations must be provided by user C++ code. + +## 4. `generated.cpp` - C++ Implementation File + +**Purpose**: Contains implementations of method wrappers and C++ → Rust bridge functions. + +### Header Include + +```cpp +#include "generated.h" +``` + +### Constructor Implementations + +For each constructor declared in the `.zng` file: + +```cpp +Type::Type(ArgType1 i0, ArgType2 i1) noexcept { + ::rust::__zngur_internal_assume_init(*this); + __zngur_constructor_mangled_name( + ::rust::__zngur_internal_data_ptr(i0), + ::rust::__zngur_internal_data_ptr(i1), + ::rust::__zngur_internal_data_ptr(*this) + ); + ::rust::__zngur_internal_assume_deinit(i0); + ::rust::__zngur_internal_assume_deinit(i1); +} +``` + +### Method Implementations + +For each static and non-static method: + +```cpp +ReturnType Type::method_name(ArgType1 i0, ArgType2 i1) noexcept { + ReturnType o{}; + ::rust::__zngur_internal_assume_deinit(i0); + ::rust::__zngur_internal_assume_deinit(i1); + __zngur_method_mangled_name( + ::rust::__zngur_internal_data_ptr(i0), + ::rust::__zngur_internal_data_ptr(i1), + ::rust::__zngur_internal_data_ptr(o) + ); + // Handle panics if enabled + if (detect_panic()) { + take_panic(); + throw rust::Panic(); + } + ::rust::__zngur_internal_assume_init(o); + return o; +} +``` + +These implementations are moved out of the header to reduce compilation time and header bloat. + +### make_box Implementations for Fn Traits + +For `Box` types, the non-template `make_box` implementations are also in the `.cpp` file: + +```cpp +Type Type::make_box(::std::function f) { + auto data = new ::std::function(f); + Type o; + ::rust::__zngur_internal_assume_init(o); + __zngur_box_new_mangled_name( + reinterpret_cast(data), + [](uint8_t *d) { delete reinterpret_cast<::std::function*>(d); }, + [](uint8_t *d, uint8_t* i0, ..., uint8_t* o) { + auto dd = reinterpret_cast<::std::function*>(d); + Output oo = (*dd)(::rust::__zngur_internal_move_from_rust(i0), ...); + ::rust::__zngur_internal_move_to_rust(o, oo); + }, + ::rust::__zngur_internal_data_ptr(o) + ); + return o; +} +``` + +Template `make_box` functions for normal traits remain in the header. + +### Extern "C" Function Definitions + +The `.cpp` file also contains `extern "C"` function definitions that Rust can call. + +#### 1. Trait Method Implementations + +For each C++ trait implementation, generates glue functions that translate between +the C ABI and C++ member functions: + +```cpp +extern "C" { + void __zngur_trait_method(uint8_t* data, uint8_t* i0, ..., uint8_t* o) { + // Cast opaque pointer to trait type + TraitType* data_typed = reinterpret_cast(data); + + // Move arguments from Rust + OutputType oo = data_typed->method_name( + ::rust::__zngur_internal_move_from_rust(i0), + ... + ); + + // Move result back to Rust + ::rust::__zngur_internal_move_to_rust(o, oo); + } +} +``` + +#### 2. Exported Function Implementations + +For each `extern "C++"` free function: + +```cpp +extern "C" { + void __zngur_mangled_name(uint8_t* i0, ..., uint8_t* o) { + OutputType oo = ::rust::exported_functions::function_name( + ::rust::__zngur_internal_move_from_rust(i0), + ... + ); + ::rust::__zngur_internal_move_to_rust(o, oo); + } +} +``` + +#### 3. Impl Block Implementations + +For each `extern "C++"` impl block method: + +```cpp +extern "C" { + void __zngur_mangled_name(uint8_t* i0, ..., uint8_t* o) { + OutputType oo = ::rust::Impl::method_name( + ::rust::__zngur_internal_move_from_rust(i0), + ... + ); + ::rust::__zngur_internal_move_to_rust(o, oo); + } +} +``` + +## Key Design Principles + +Understanding these design principles helps explain why the generated code looks the way it does: + +### 1. ABI Safety + +All Rust↔C++ communication goes through `extern "C"` functions that pass `uint8_t*` pointers. +This avoids Rust's undefined ABI by using only the stable C ABI. The actual types are +reconstructed on each side using `ptr::read` and `ptr::write` in Rust, and `memcpy` +in C++. + +### 2. Move Semantics + +Zngur simulates Rust's move semantics in C++ using `memcpy` plus a `drop_flag`. +When a C++ object is "moved", the data is copied and the source's drop flag is cleared, +preventing the destructor from running. This matches Rust's behavior where moves are +bitwise copies that leave the source uninitialized. + +### 3. Memory Safety + +The drop flag mechanism prevents double-free errors. The initialization check functions +catch use-after-move bugs in debug builds, providing similar safety to Rust's borrow checker +(though only at runtime in C++). + +### 4. Zero-Cost Abstractions + +References are implemented as wrapped pointers with no additional overhead. +Field access uses zero-sized types with offset template parameters, compiling down to +direct memory access with no runtime cost. Owned values use inline storage when possible +to avoid heap allocation. + +### 5. Type Erasure + +C++ objects become `ZngurCppOpaqueOwnedObject` when passed to Rust, using type erasure +to work around Rust's inability to store C++ objects by value. Traits are implemented +via vtable-like function pointers, enabling dynamic dispatch from Rust to C++ implementations. + +### 6. Namespace Mapping + +Rust's module hierarchy maps directly to C++ namespaces under the `::rust::` prefix. +For example, `::std::vec::Vec` in Rust becomes `::rust::std::vec::Vec` in C++. +This creates a clear and predictable naming scheme. + +## Summary + +Zngur's generated files work together to enable seamless bidirectional interop: + +### Rust Side (Both Modes) +- **`generated.rs`** contains FFI bridge functions, trait wrappers, and compile-time assertions + +### Split-Header Mode (Default) +**C++:** +- **`zngur.h`** provides reusable infrastructure (templates, utilities, base types) +- **`generated.h`** defines C++ wrappers, with type declarations and inline functions +- **`generated.cpp`** contains method implementations and C++ → Rust call bridges + +The separation between foundation and application-specific code reduces compilation time, enables header reuse across multiple libraries, and keeps template functions inline for performance. + +### Single-Header Mode (`--single-header`) +**C++:** +- **`generated.h`** contains both infrastructure and type wrappers (merged) +- **`generated.cpp`** contains method implementations and C++ → Rust call bridges + +This simpler mode is useful for small projects or backward compatibility. + +Together, these files enable: + +- **Rust → C++**: Using Rust types naturally in C++ with proper move/copy semantics +- **Rust → C++**: Calling Rust functions from C++ as if they were native C++ functions +- **Rust → C++**: Field access, method calls, and pattern matching on Rust types from C++ +- **C++ → Rust**: Implementing Rust traits in C++ classes +- **C++ → Rust**: Passing C++ objects to Rust as trait objects +- **C++ → Rust**: Converting C++ lambdas to Rust closures + +The generated code maintains type safety, manages memory correctly across the language +boundary, and achieves zero-cost abstractions wherever possible. The Rust bridge code +(`generated.rs`) handles the unsafe FFI operations, exposing a safe Rust API, while the +C++ code provides ergonomic wrappers that feel natural in each language. From a47d59911eed1d145d4e0cada252a08406e8a540 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 8 Dec 2025 10:25:59 -0500 Subject: [PATCH 6/7] Format md --- book/src/generated_code.md | 12 ++++++++++++ examples/simple_single_header/README.md | 2 ++ 2 files changed, 14 insertions(+) diff --git a/book/src/generated_code.md b/book/src/generated_code.md index 4bf7e8c2..9a406c1d 100644 --- a/book/src/generated_code.md +++ b/book/src/generated_code.md @@ -9,9 +9,11 @@ Zngur generates both Rust and C++ files from a `.zng` interface definition file. By default, Zngur generates one Rust file and three C++ files: **Rust:** + 1. **`generated.rs`** - Rust bridge code and FFI declarations **C++:** + 1. **`zngur.h`** - Foundation header with core utilities 2. **`generated.h`** - Main header with Rust type wrappers and inline functions 3. **`generated.cpp`** - Implementation file with method bodies and C++ bridge code @@ -19,6 +21,7 @@ By default, Zngur generates one Rust file and three C++ files: This mode provides better modularity and allows `zngur.h` to be shared across multiple independent Zngur-generated libraries. **Usage:** + ```bash zngur g main.zng -o output_dir ``` @@ -30,6 +33,7 @@ The `-o` flag specifies where `zngur.h` should be generated. You must add this d For backward compatibility and simpler build setups, you can use the `--single-header` flag to merge `zngur.h` into `generated.h`: **Usage:** + ```bash zngur g main.zng --single-header ``` @@ -37,9 +41,11 @@ zngur g main.zng --single-header This generates one Rust file and two C++ files: **Rust:** + 1. **`generated.rs`** - Rust bridge code and FFI declarations **C++:** + 1. **`generated.h`** - Contains both foundation utilities and type wrappers 2. **`generated.cpp`** - Implementation file @@ -109,6 +115,7 @@ pub extern "C" fn _zngur_mangled_name(i0: *mut u8, i1: *mut u8, o: *mut u8) { ``` These functions: + - Use mangled names to avoid symbol conflicts - Accept arguments and return value as raw `*mut u8` pointers - Use `ptr::read` to move values from C++ to Rust @@ -859,10 +866,13 @@ This creates a clear and predictable naming scheme. Zngur's generated files work together to enable seamless bidirectional interop: ### Rust Side (Both Modes) + - **`generated.rs`** contains FFI bridge functions, trait wrappers, and compile-time assertions ### Split-Header Mode (Default) + **C++:** + - **`zngur.h`** provides reusable infrastructure (templates, utilities, base types) - **`generated.h`** defines C++ wrappers, with type declarations and inline functions - **`generated.cpp`** contains method implementations and C++ → Rust call bridges @@ -870,7 +880,9 @@ Zngur's generated files work together to enable seamless bidirectional interop: The separation between foundation and application-specific code reduces compilation time, enables header reuse across multiple libraries, and keeps template functions inline for performance. ### Single-Header Mode (`--single-header`) + **C++:** + - **`generated.h`** contains both infrastructure and type wrappers (merged) - **`generated.cpp`** contains method implementations and C++ → Rust call bridges diff --git a/examples/simple_single_header/README.md b/examples/simple_single_header/README.md index 6a8b6047..ecc690bd 100644 --- a/examples/simple_single_header/README.md +++ b/examples/simple_single_header/README.md @@ -24,11 +24,13 @@ make ## When to use single-header mode Use `--single-header` when: + - You want simpler build configuration (no need to manage include paths for utility headers) - You're migrating from an older version of zngur - You have a simple project that doesn't need the modularity of split headers Use split headers (default) when: + - You have multiple zngur-generated libraries that can share the same `zngur.h` - You want faster incremental compilation (changes to your types don't require recompiling the infrastructure) - You want cleaner separation between infrastructure and user code From f4a82fb92a09c9049e4053dd1bfebecc90b4fee5 Mon Sep 17 00:00:00 2001 From: Nick DeMarco Date: Mon, 8 Dec 2025 11:21:21 -0500 Subject: [PATCH 7/7] Format .rs --- zngur/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zngur/src/lib.rs b/zngur/src/lib.rs index d5de48ea..18cb08fc 100644 --- a/zngur/src/lib.rs +++ b/zngur/src/lib.rs @@ -118,7 +118,8 @@ impl Zngur { } // Then add the primary header content, but skip the #include line - let primary_without_include = generated.primary_header + let primary_without_include = generated + .primary_header .lines() .filter(|line| !line.contains("#include ")) .collect::>()