Prototype: Template-based code emission#151
Conversation
Introduces a template rendering system for generating C++ code:
- Template files (.cpp.tmpl) with {{PLACEHOLDER}} syntax
- render() function for placeholder substitution
- Convenience functions for common patterns (for_range, for_each, function_def, struct_def, if_stmt)
- Template caching to avoid repeated file I/O
Templates for: for loops, function definitions, struct definitions, if statements
This is a prototype for exploring template-based code generation as
an alternative to inline string concatenation.
Refs: #150
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Code Review: Template-based Code Emission (PR #151)REVIEW:Summary: Well-structured prototype with clear separation of concerns, but has critical issues around error handling and path resolution that need addressing before production use. Critical Issues1. Silent Failure on Template Load (CRITICAL)Location: if (!file.is_open()) {
return ""; // Template not found
}Per project guidelines: "returning empty values instead of proper errors is usually not ok" When a template file is missing or unreadable, the code silently falls back to inline templates. In production, this could cause:
Fix: Return an 2. Hardcoded Relative Paths (CRITICAL)Location: std::string path = "codegen/templates/" + name + ".cpp.tmpl";
std::ifstream file(path);
if (!file.is_open()) {
// Try alternative path (when running from build directory)
path = "../codegen/templates/" + name + ".cpp.tmpl";
file.open(path);
}This path resolution is fragile and will break in:
Fix: Use a configurable path (environment variable, config file, or compile-time embed the templates as string literals as noted in the comment). Suggestions (Non-blocking)1. Template ValidationLocation: The 2. Cache Thread SafetyLocation: static std::unordered_map<std::string, std::string> template_cache;The static cache is not thread-safe. If codegen runs in parallel (e.g., multiple translation units), this could cause data races. 3. Missing Docstrings on Template FilesPer project guidelines, all code requires docstrings. The Comparison with Current Approach
Pros of This Approach
Cons of This Approach
Decision: REQUEST_CHANGESThe prototype demonstrates the approach well, but the critical issues around silent failure and path resolution would cause production problems. These should be addressed before this pattern is adopted. Recommendation for comparing with PR #152 (Builder pattern):
🤖 Review generated by Claude Code |
Code Review: Template-based Code EmissionPros of Template Approach
Cons of Template Approach
Comparison with Current Approach (fmt::format)
Comparison with Builder Pattern (PR #152)
RecommendationFor Bishop's use case (a C++ transpiler with many conditional constructs), I'd lean toward the builder pattern because:
However, templates could be valuable for user-facing features like:
Before MergingFix the critical issues:
🤖 Generated with Claude Code |
There was a problem hiding this comment.
Review: Template-based Code Emission Prototype
REVIEW:
Summary: Well-structured template system prototype with good documentation, but has critical issues around error handling and template path resolution that must be addressed before production use.
Critical Issues
1. Silent failure on template load (violates project guidelines)
Location: codegen/template_render.cpp:66-67
if (!file.is_open()) {
return ""; // Template not found
}Per CLAUDE.md: "returning empty values instead of proper errors is usually not ok"
The current implementation silently returns an empty string when templates fail to load. While the convenience functions fall back to inline templates, this masks real problems (missing templates, permission issues, wrong paths). This should throw an exception or return a std::optional / result type.
Suggested fix: Throw a descriptive error like throw std::runtime_error("Failed to load template: " + name) or use a result type.
2. Template path resolution is fragile
Location: codegen/template_render.cpp:57-64
std::string path = "codegen/templates/" + name + ".cpp.tmpl";
std::ifstream file(path);
if (!file.is_open()) {
// Try alternative path (when running from build directory)
path = "../codegen/templates/" + name + ".cpp.tmpl";
file.open(path);
}This hardcoded relative path approach is problematic:
- Breaks if the binary is run from a different directory
- Breaks after installation (templates won't be in
codegen/templates/relative to installed binary) - The comment acknowledges this: "In a production build, templates would be embedded as string literals"
For production, templates should be:
- Embedded at compile time (e.g., using
#includewith raw string literals or CMake'sconfigure_file) - Or located via a configurable path/environment variable
Suggestions (Non-blocking)
1. Unused placeholder validation
Consider validating that all {{PLACEHOLDER}} markers are substituted. Currently, leftover placeholders silently appear in output. Could add a post-render check.
2. Template caching uses static global
Location: codegen/template_render.cpp:18
static std::unordered_map<std::string, std::string> template_cache;This is thread-unsafe. If Bishop ever supports parallel compilation, this would be a data race. Consider using thread_local or protecting with a mutex.
3. The #include <regex> is unused
Location: codegen/template_render.cpp:13
The regex header is included but not used. The simple {{KEY}} substitution uses string operations instead.
Pros of Template Approach
- Separation of concerns - Template files are editable without recompilation
- Readability - Template files show the exact output structure clearly
- Fallback handling - Inline fallbacks ensure graceful degradation
- Good documentation - Docstrings are thorough and follow project guidelines
- Caching - Avoids repeated file I/O
Cons of Template Approach
- Runtime file dependency - Requires templates to exist at runtime (unless embedded)
- Debugging complexity - Errors in templates are harder to trace than inline code
- Path fragility - Current path resolution won't work in all execution contexts
- Overhead - File I/O and string replacement vs direct
fmt::formatcalls - Limited power - Simple substitution can't handle conditionals or loops within templates (the existing
if_stmtapproach handles this by using separate templates for if/if-else)
Comparison to Current Approach
The existing emit_*.cpp files use fmt::format directly:
string out = fmt::format("for (int {} = {}; {} < {}; {}++) {{\n", var, start, var, end, var);Current approach advantages:
- No runtime file dependencies
- Faster (no file I/O, caching overhead)
- Single source of truth (code is the template)
- Easier debugging (set breakpoint, see exact string building)
Template approach advantages:
- Templates are more readable for complex structures
- Non-engineers could potentially edit templates
- Changes to output format don't require recompilation (if templates are external)
Decision: COMMENT
This is a reasonable prototype for exploration purposes. The core template rendering logic is correct. However, the error handling and path resolution issues would need to be fixed before production use.
For a production decision between this and the Builder pattern (PR #152), I'd recommend:
- If templates will be embedded at compile time: Template approach could work well
- If templates need to be external files: Current implementation needs significant hardening
- If neither requirement exists: The current
fmt::formatapproach is simpler and has fewer moving parts
Looking forward to seeing PR #152 for comparison.
Code Review: Template-based Code Emission PrototypeREVIEWSummary: Clean, well-documented prototype that demonstrates the template-based approach. The implementation is solid for a prototype but has one notable issue that should be addressed before adoption. Critical Issues
Suggestions (Non-blocking)
Pros of Template Approach (for comparison with #152)✅ Readability: Templates look like actual C++ code - easy to see what will be generated Cons of Template Approach❌ File I/O dependency: External files must be discoverable at runtime Comparison with Current Approach (
|
|
I'm definitely not in to the loading a file thing |
|
Thanks for the feedback @chrishayen! That's a valid concern about the template file loading approach. The file I/O overhead and added complexity of managing external template files may not be worth the benefits. Current state of the prototypes:
Based on your feedback, it sounds like neither of these approaches fully address the core need. The templates require file loading, and the builders (while avoiding that) added their own complexity. Alternative suggestions:
Would you like me to explore the inline string templates approach in a new prototype, or would you prefer a different direction? |
Code ReviewSummary: Well-designed template system with good documentation, but has issues that should be addressed before merging. Critical Issues (Must Fix)1. Returning empty string instead of proper error on template load failure
2. Static global cache without thread safety
Suggestions (Non-blocking)
Decision: REQUEST_CHANGES The architecture and API design look good. Once the error handling and thread-safety issues are addressed, this is ready to merge. |
There was a problem hiding this comment.
Code Review
Summary: Solid prototype for template-based code emission with good fallback design.
What's Good
- Well-documented with docstrings throughout (per CLAUDE.md requirements)
- Sensible fallback inline templates if files can't be loaded - graceful degradation
- Clean separation between core rendering (
render,load_template,indent,join_body) and convenience functions - Template caching for performance
- CI passing
Suggestions (non-blocking)
-
Thread safety (for future integration): The static
template_cacheat line 18 is not thread-safe. Since tests run in parallel withstd::async, consider usingthread_localor mutex protection when integrating into production. The fallback templates mean this won't cause incorrect behavior, just potentially redundant file reads. -
Template contract: The
.cpp.tmplfiles show{{BODY}}with no leading whitespace, relying onjoin_body()to add indentation. This coupling is implicit - consider adding a comment documenting this contract.
Security Check
- No injection vulnerabilities - template substitution is straightforward string replacement
- No file path traversal risks - template names are hardcoded
- No sensitive data exposure
Verdict: Code is correct, well-documented, and ready for integration. No blocking issues found. ✅
|
Closing based on owner feedback - the file loading approach adds unwanted complexity. The current |
Summary
This PR introduces a template-based code emission system for the Bishop compiler codegen phase.
What's Added
Templates Included
Convenience Functions
```cpp
templates::for_range(var, start, end, body);
templates::for_each(var, collection, body);
templates::function_def(name, params, return_type, body);
templates::struct_def(name, fields);
templates::if_stmt(condition, then_body, else_body);
```
Example Usage
```cpp
std::string code = templates::for_range("i", "0", "n", {
"sum += i;"
});
// Output:
// for (int i = 0; i < n; i++) {
// sum += i;
// }
```
Test plan
Related
🤖 Generated with Claude Code