Skip to the content.

Common Patterns

Recipes for frequent xccmeta tasks. Copy-paste starting points for typical workflows.

Pattern: Basic Enum-to-String Generator

#include <xccmeta.hpp>
#include <iostream>

int main() {
  // 1. Parse
  xccmeta::compile_args args = xccmeta::compile_args::modern_cxx();
  xccmeta::parser parser;
  auto ast = parser.parse(source_code, args);

  if (!ast) {
    std::cerr << "Parse failed\n";
    return 1;
  }

  // 2. Find tagged enums
  auto enums = ast->find_descendants([](auto& n) {
    return n->get_kind() == xccmeta::node::kind::enum_decl
        && n->has_tag("reflect");
  });

  // 3. Generate
  xccmeta::generator gen("enum_strings.hpp");
  gen.out("#pragma once");
  gen.out("#include <string>");
  gen.out("");

  for (auto& e : enums) {
    gen.out("inline std::string to_string(" + e->get_qualified_name() + " val) {");
    gen.indent();
    gen.out("switch (val) {");
    gen.indent();

    for (auto& constant : e->get_enum_constants()) {
      auto name = constant->get_qualified_name();
      gen.out("case " + name + ": return \"" + constant->get_name() + "\";");
    }

    gen.unindent();
    gen.out("}");
    gen.out("return \"<unknown>\";");
    gen.unindent();
    gen.out("}");
    gen.out("");
  }

  return gen.done() ? 0 : 1;
}

Pattern: Multi-File Type Collection

#include <xccmeta.hpp>

int main() {
  // 1. Import files
  xccmeta::importer headers("include/**/*.hpp");

  // 2. Setup filter
  xccmeta::filter::config cfg;
  cfg.allowed_kinds = {
    xccmeta::node::kind::struct_decl,
    xccmeta::node::kind::class_decl
  };
  cfg.grab_tag_names = {"serialize"};

  xccmeta::filter types(cfg);

  // 3. Parse all files, collect types
  xccmeta::compile_args args = xccmeta::compile_args::modern_cxx();
  args.add_include_path("include");

  xccmeta::parser parser;
  for (auto& file : headers.get_files()) {
    auto content = file.read();
    auto ast = parser.parse(content, args);

    if (ast) {
      for (auto& child : ast->get_children()) {
        types.add(child);
      }
    }
  }

  types.clean(); // Remove non-matching nodes

  // 4. Generate from deduplicated types
  // ... use types.get_types()
}

Pattern: Field Iteration with Type Checks

for (auto& field : struct_node->get_fields()) {
  // Skip private fields
  if (field->get_access() != xccmeta::access_specifier::public_) {
    continue;
  }

  auto type = field->get_type();
  auto name = field->get_name();

  // Handle different type categories
  if (type.is_array()) {
    auto elem_type = type.get_array_element_type();
    auto count = type.get_array_size();

    if (count == -1) {
      gen.warn("Unsized array: " + name, field);
      continue;
    }

    gen.out("// Array: " + name + "[" + std::to_string(count) + "]");

  } else if (type.is_pointer()) {
    auto pointee = type.get_pointee_type();
    gen.out("// Pointer: " + name + " -> " + pointee);

  } else if (type.is_builtin()) {
    gen.out("// Builtin: " + name + " (" + type.get_spelling() + ")");

  } else {
    // Custom type
    gen.out("// Custom: " + name + " (" + type.get_canonical() + ")");
  }
}

Pattern: Method Filtering

// Find all public non-deleted constructors
auto ctors = class_node->find_children([](auto& n) {
  return n->get_kind() == xccmeta::node::kind::constructor_decl
      && n->get_access() == xccmeta::access_specifier::public_
      && !n->is_deleted()
      && !n->is_defaulted();
});

// Find all const methods
auto const_methods = class_node->find_children([](auto& n) {
  return n->get_kind() == xccmeta::node::kind::method_decl
      && n->is_const_method();
});

// Find virtual methods
auto virtual_methods = class_node->find_children([](auto& n) {
  return n->get_kind() == xccmeta::node::kind::method_decl
      && n->is_virtual();
});

Pattern: Tag-Based Configuration

for (auto& type : types) {
  // Check for exclusion tag
  if (type->has_tag("no_codegen")) {
    continue;
  }

  // Get optional config from tag
  bool binary_mode = false;
  if (auto tag = type->find_tag("serialize")) {
    auto& args = tag->get_args();
    if (!args.empty() && args[0] == "binary") {
      binary_mode = true;
    }
  }

  // Use tag arguments for generation
  if (binary_mode) {
    generate_binary_serializer(type);
  } else {
    generate_text_serializer(type);
  }
}

Pattern: Handling Inheritance

void process_class(const xccmeta::node_ptr& cls) {
  // Get base classes
  auto bases = cls->get_bases();

  if (!bases.empty()) {
    gen.out("// Inherits from:");
    for (auto& base : bases) {
      auto access = xccmeta::access_specifier_to_string(base->get_access());
      auto name = base->get_name();

      std::string mods;
      if (base->is_virtual_base()) {
        mods = "virtual ";
      }

      gen.out("//   " + mods + access + " " + name);
    }
  }

  // Process own fields (not inherited)
  for (auto& field : cls->get_fields()) {
    // ...
  }
}

Pattern: Error-Resilient Generator

int main() {
  try {
    xccmeta::importer imp("src/**/*.hpp");

    if (imp.get_files().empty()) {
      std::cerr << "No files matched pattern\n";
      return 1;
    }

    xccmeta::parser parser;
    xccmeta::compile_args args = xccmeta::compile_args::modern_cxx();

    std::vector<xccmeta::node_ptr> all_asts;

    for (auto& file : imp.get_files()) {
      std::cout << "Parsing " << file.get_path() << "...\n";

      auto content = file.read();
      auto ast = parser.parse(content, args);

      if (ast) {
        all_asts.push_back(ast);
      } else {
        std::cerr << "Warning: Failed to parse " << file.get_path() << "\n";
        // Continue with other files
      }
    }

    if (all_asts.empty()) {
      std::cerr << "No files parsed successfully\n";
      return 1;
    }

    // Generate from successfully parsed ASTs
    // ...

  } catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << "\n";
    return 1;
  }

  return 0;
}

Pattern: Compile Argument Customization

// Platform-specific parsing
xccmeta::compile_args args;
args.set_standard(xccmeta::language_standard::cxx20);

#ifdef _WIN32
  args.define("PLATFORM_WINDOWS")
      .add_include_path("C:/Program Files/SDK/include");
#else
  args.define("PLATFORM_LINUX")
      .add_include_path("/usr/local/include");
#endif

// Debug vs Release
#ifndef NDEBUG
  args.define("DEBUG_BUILD");
#endif

// Custom macros
args.define("PROJECT_VERSION", "1.2.3")
    .define("FEATURE_ENABLED", "1");

auto ast = parser.parse(source, args);

Pattern: Incremental Code Generation

// Generate header
{
  xccmeta::generator gen("types.generated.hpp");
  gen.out("#pragma once");

  for (auto& type : types) {
    gen.out("struct " + type->get_name() + "_Meta;");
  }
}

// Generate implementation
{
  xccmeta::generator gen("types.generated.cpp");
  gen.out("#include \"types.generated.hpp\"");
  gen.out("#include \"original_types.hpp\"");
  gen.out("");

  for (auto& type : types) {
    gen.out("struct " + type->get_name() + "_Meta {");
    gen.indent();
    // Implementation
    gen.unindent();
    gen.out("};");
  }
}

Pattern: Location-Based Diagnostics

for (auto& node : nodes) {
  auto loc = node->get_location();

  if (!validate(node)) {
    std::cerr << loc.to_string() << ": error: validation failed for "
              << node->get_name() << "\n";
  }

  if (needs_warning(node)) {
    gen.warn("Consider using std::optional", node);
  }
}

Pattern: Parent Tag Inheritance

/// @namespace_config(version=2)
namespace app {
  /// @reflect
  struct Data {
    int value;
  };
}

// Query parent tags
auto struct_node = /* Data struct */;
auto parent_tags = struct_node->get_parent_tags();

for (auto& tag : parent_tags) {
  if (tag.get_name() == "namespace_config") {
    auto version = tag.get_args()[0]; // "version=2"
    // Use namespace-level config
  }
}

Anti-Patterns

Don’t preprocess before parsing:

// WRONG
auto preprocessed = xccmeta::preprocessor(file, args).get_preprocessed_content()[0];
auto ast = parser.parse(preprocessed, args);

// CORRECT
auto ast = parser.parse(file.read(), args);

Don’t assume type size is available:

// WRONG
auto size = field->get_type().get_size_bytes();
gen.out("char buffer[" + std::to_string(size) + "];");

// CORRECT
auto size = field->get_type().get_size_bytes();
if (size != -1) {
  gen.out("char buffer[" + std::to_string(size) + "];");
} else {
  gen.warn("Type size unavailable", field);
}

Don’t modify AST after parsing:

// WRONG - no public setters
node->set_name("new_name"); // Compile error

// CORRECT - use metadata as-is
auto name = node->get_name();

Don’t parse file paths directly:

// WRONG
auto ast = parser.parse("input.hpp", args);

// CORRECT
xccmeta::file f("input.hpp");
auto ast = parser.parse(f.read(), args);