diff --git a/codegen/template_render.cpp b/codegen/template_render.cpp new file mode 100644 index 0000000..59a3a6d --- /dev/null +++ b/codegen/template_render.cpp @@ -0,0 +1,245 @@ +/** + * @file template_render.cpp + * @brief Implementation of template-based code emission. + * + * This module provides template loading and rendering functionality + * for generating C++ code. Templates use {{PLACEHOLDER}} syntax. + */ + +#include "template_render.hpp" +#include +#include +#include + +namespace codegen { +namespace templates { + +// Cache for loaded templates to avoid repeated file I/O +static std::unordered_map template_cache; + +/** + * @brief Renders a template string by substituting placeholders. + */ +std::string render(const std::string& template_str, + const std::unordered_map& subs) { + std::string result = template_str; + + for (const auto& [key, value] : subs) { + std::string placeholder = "{{" + key + "}}"; + size_t pos = 0; + + while ((pos = result.find(placeholder, pos)) != std::string::npos) { + result.replace(pos, placeholder.length(), value); + pos += value.length(); + } + } + + return result; +} + +/** + * @brief Loads a template file from the templates directory. + * + * Uses a cache to avoid repeated file reads. Templates are expected + * to be in the codegen/templates/ directory. + */ +std::string load_template(const std::string& name) { + // Check cache first + auto it = template_cache.find(name); + + if (it != template_cache.end()) { + return it->second; + } + + // Try to load from file + // Note: In a production build, templates would be embedded as string literals + // or the path would be configurable. For this prototype, we use a relative path. + 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); + } + + if (!file.is_open()) { + return ""; // Template not found + } + + std::stringstream buffer; + buffer << file.rdbuf(); + std::string content = buffer.str(); + + // Cache the template + template_cache[name] = content; + + return content; +} + +/** + * @brief Indents each line of text by the specified number of tabs. + */ +std::string indent(const std::string& text, int tabs) { + if (text.empty() || tabs <= 0) { + return text; + } + + std::string prefix(tabs, '\t'); + std::string result; + std::istringstream stream(text); + std::string line; + bool first = true; + + while (std::getline(stream, line)) { + if (!first) { + result += "\n"; + } + + if (!line.empty()) { + result += prefix + line; + } + + first = false; + } + + return result; +} + +/** + * @brief Joins statement strings into a body with proper indentation. + */ +std::string join_body(const std::vector& statements, int tabs) { + std::string result; + std::string prefix(tabs, '\t'); + + for (size_t i = 0; i < statements.size(); ++i) { + result += prefix + statements[i]; + + if (i + 1 < statements.size()) { + result += "\n"; + } + } + + return result; +} + +// ============================================================================ +// Convenience functions using templates +// ============================================================================ + +/** + * @brief Renders a for-range loop using the for_range template. + */ +std::string for_range(const std::string& var, + const std::string& start, + const std::string& end, + const std::vector& body) { + std::string tmpl = load_template("for_range"); + + if (tmpl.empty()) { + // Fallback to inline template if file not found + tmpl = "for (int {{VAR}} = {{START}}; {{VAR}} < {{END}}; {{VAR}}++) {\n{{BODY}}\n}"; + } + + return render(tmpl, { + {"VAR", var}, + {"START", start}, + {"END", end}, + {"BODY", join_body(body, 1)} + }); +} + +/** + * @brief Renders a for-each loop using the for_each template. + */ +std::string for_each(const std::string& var, + const std::string& collection, + const std::vector& body) { + std::string tmpl = load_template("for_each"); + + if (tmpl.empty()) { + tmpl = "for (auto& {{VAR}} : {{COLLECTION}}) {\n{{BODY}}\n}"; + } + + return render(tmpl, { + {"VAR", var}, + {"COLLECTION", collection}, + {"BODY", join_body(body, 1)} + }); +} + +/** + * @brief Renders a function definition using the function_def template. + */ +std::string function_def(const std::string& name, + const std::string& params, + const std::string& return_type, + const std::vector& body) { + std::string tmpl = load_template("function_def"); + + if (tmpl.empty()) { + tmpl = "{{RETURN_TYPE}} {{NAME}}({{PARAMS}}) {\n{{BODY}}\n}\n"; + } + + std::string rt = return_type.empty() ? "void" : return_type; + + return render(tmpl, { + {"NAME", name}, + {"PARAMS", params}, + {"RETURN_TYPE", rt}, + {"BODY", join_body(body, 1)} + }); +} + +/** + * @brief Renders a struct definition using the struct_def template. + */ +std::string struct_def(const std::string& name, + const std::vector& fields) { + std::string tmpl = load_template("struct_def"); + + if (tmpl.empty()) { + tmpl = "struct {{NAME}} {\n{{FIELDS}}};\n"; + } + + return render(tmpl, { + {"NAME", name}, + {"FIELDS", join_body(fields, 1)} + }); +} + +/** + * @brief Renders an if statement using the if_stmt or if_else_stmt template. + */ +std::string if_stmt(const std::string& condition, + const std::vector& then_body, + const std::vector& else_body) { + if (else_body.empty()) { + std::string tmpl = load_template("if_stmt"); + + if (tmpl.empty()) { + tmpl = "if ({{CONDITION}}) {\n{{THEN_BODY}}\n}"; + } + + return render(tmpl, { + {"CONDITION", condition}, + {"THEN_BODY", join_body(then_body, 1)} + }); + } + + std::string tmpl = load_template("if_else_stmt"); + + if (tmpl.empty()) { + tmpl = "if ({{CONDITION}}) {\n{{THEN_BODY}}\n} else {\n{{ELSE_BODY}}\n}"; + } + + return render(tmpl, { + {"CONDITION", condition}, + {"THEN_BODY", join_body(then_body, 1)}, + {"ELSE_BODY", join_body(else_body, 1)} + }); +} + +} // namespace templates +} // namespace codegen diff --git a/codegen/template_render.hpp b/codegen/template_render.hpp new file mode 100644 index 0000000..ddee585 --- /dev/null +++ b/codegen/template_render.hpp @@ -0,0 +1,140 @@ +/** + * @file template_render.hpp + * @brief Template-based code emission for the Bishop code generator. + * + * This file provides template loading and rendering functionality for + * generating C++ code from .cpp.tmpl template files. Templates use + * {{PLACEHOLDER}} syntax for variable substitution. + */ + +#ifndef CODEGEN_TEMPLATE_RENDER_HPP +#define CODEGEN_TEMPLATE_RENDER_HPP + +#include +#include +#include + +namespace codegen { +namespace templates { + +/** + * @brief Renders a template string with the given substitutions. + * + * Replaces all occurrences of {{KEY}} with the corresponding value + * from the substitutions map. + * + * @param template_str The template string with {{PLACEHOLDER}} markers + * @param subs Map of placeholder names to replacement values + * @return The rendered string with all placeholders substituted + * + * @example + * render("Hello {{NAME}}!", {{"NAME", "World"}}) + * // Returns: "Hello World!" + */ +std::string render(const std::string& template_str, + const std::unordered_map& subs); + +/** + * @brief Loads a template from the templates directory. + * + * Templates are stored in codegen/templates/ with .cpp.tmpl extension. + * + * @param name Template name (without path or extension) + * @return The template content, or empty string if not found + * + * @example + * load_template("for_range") + * // Loads: codegen/templates/for_range.cpp.tmpl + */ +std::string load_template(const std::string& name); + +/** + * @brief Indents each line of a string by a given number of tabs. + * + * Used to properly indent body content within templates. + * + * @param text The text to indent + * @param tabs Number of tab characters to prepend to each line + * @return The indented text + */ +std::string indent(const std::string& text, int tabs = 1); + +/** + * @brief Joins a vector of strings into a body with proper indentation. + * + * @param statements Vector of statement strings + * @param tabs Indentation level + * @return Joined and indented string + */ +std::string join_body(const std::vector& statements, int tabs = 1); + +// ============================================================================ +// Convenience functions for common patterns +// ============================================================================ + +/** + * @brief Renders a for-range loop using template. + * + * @param var Loop variable name + * @param start Range start expression + * @param end Range end expression + * @param body Vector of body statements + * @return Rendered for-range loop + */ +std::string for_range(const std::string& var, + const std::string& start, + const std::string& end, + const std::vector& body); + +/** + * @brief Renders a for-each loop using template. + * + * @param var Loop variable name + * @param collection Collection expression + * @param body Vector of body statements + * @return Rendered for-each loop + */ +std::string for_each(const std::string& var, + const std::string& collection, + const std::vector& body); + +/** + * @brief Renders a function definition using template. + * + * @param name Function name + * @param params Parameter list string + * @param return_type Return type string + * @param body Vector of body statements + * @return Rendered function definition + */ +std::string function_def(const std::string& name, + const std::string& params, + const std::string& return_type, + const std::vector& body); + +/** + * @brief Renders a struct definition using template. + * + * @param name Struct name + * @param fields Vector of field declarations (already formatted) + * @return Rendered struct definition + */ +std::string struct_def(const std::string& name, + const std::vector& fields); + +/** + * @brief Renders an if statement using template. + * + * @param condition Condition expression + * @param then_body Vector of then-body statements + * @param else_body Vector of else-body statements (empty for no else) + * @return Rendered if statement + */ +std::string if_stmt(const std::string& condition, + const std::vector& then_body, + const std::vector& else_body = {}); + +} // namespace templates +} // namespace codegen + +#endif // CODEGEN_TEMPLATE_RENDER_HPP diff --git a/codegen/templates/for_each.cpp.tmpl b/codegen/templates/for_each.cpp.tmpl new file mode 100644 index 0000000..3868a7f --- /dev/null +++ b/codegen/templates/for_each.cpp.tmpl @@ -0,0 +1,3 @@ +for (auto& {{VAR}} : {{COLLECTION}}) { +{{BODY}} +} diff --git a/codegen/templates/for_range.cpp.tmpl b/codegen/templates/for_range.cpp.tmpl new file mode 100644 index 0000000..ce92206 --- /dev/null +++ b/codegen/templates/for_range.cpp.tmpl @@ -0,0 +1,3 @@ +for (int {{VAR}} = {{START}}; {{VAR}} < {{END}}; {{VAR}}++) { +{{BODY}} +} diff --git a/codegen/templates/function_def.cpp.tmpl b/codegen/templates/function_def.cpp.tmpl new file mode 100644 index 0000000..09170d7 --- /dev/null +++ b/codegen/templates/function_def.cpp.tmpl @@ -0,0 +1,3 @@ +{{RETURN_TYPE}} {{NAME}}({{PARAMS}}) { +{{BODY}} +} diff --git a/codegen/templates/if_else_stmt.cpp.tmpl b/codegen/templates/if_else_stmt.cpp.tmpl new file mode 100644 index 0000000..4557035 --- /dev/null +++ b/codegen/templates/if_else_stmt.cpp.tmpl @@ -0,0 +1,5 @@ +if ({{CONDITION}}) { +{{THEN_BODY}} +} else { +{{ELSE_BODY}} +} diff --git a/codegen/templates/if_stmt.cpp.tmpl b/codegen/templates/if_stmt.cpp.tmpl new file mode 100644 index 0000000..ff6da55 --- /dev/null +++ b/codegen/templates/if_stmt.cpp.tmpl @@ -0,0 +1,3 @@ +if ({{CONDITION}}) { +{{THEN_BODY}} +} diff --git a/codegen/templates/struct_def.cpp.tmpl b/codegen/templates/struct_def.cpp.tmpl new file mode 100644 index 0000000..aeba8db --- /dev/null +++ b/codegen/templates/struct_def.cpp.tmpl @@ -0,0 +1,3 @@ +struct {{NAME}} { +{{FIELDS}} +};