Skip to content

jodyhagins/Atlas

Atlas

CI codecov License: MIT C++20

"I got tired of writing the same boilerplate strong type wrappers over and over, so I wrote a tool to generate them. Then I spent 10x more time perfecting the tool than I would've spent just writing the types. This is the way."

A C++ code generator that turns terse descriptions into strongly typed wrappers. Because execute(int64_t bid_price, int64_t bid_qty, int64_t ask_price, int64_t ask_qty) is a serious bug and changing it to execute(Price bid_price, Quantity bid_qty, Price ask_price, Quantity ask_qty) only buys you just enough time to have a production issue in time to tank next quarter's bonus too.

NOTE This is also an AI playground. I extracted the strong type generation code from a much larger libclang tool, and used it as the kernel for this project. I then used AI for almost everything else. Most of the code in the initial commit, and almost all of the documentation and commits since were AI generated (with some human oversight). I've been playing with different techniques and tools, and this project has been one of the results of that experimentation.

Quick Start

The easiest way to use Atlas in your CMake project:

include(FetchContent)
FetchContent_Declare(Atlas
    GIT_REPOSITORY https://github.com/jodyhagins/Atlas.git
    GIT_TAG v1.0.0)  # Or use 'main' for latest
FetchContent_MakeAvailable(Atlas)

# Generate a strong type with minimal syntax
atlas_add_type(UserId int "<=>" TARGET my_library)

That's it! The helper function automatically:

  • Generates the header file in your source directory
  • Adds it to your target's sources
  • Sets up proper build dependencies

More Helper Functions

# Generate multiple types from a file
add_atlas_strong_types_from_file(
    INPUT types.atlas
    OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/Types.hpp
    TARGET my_library)

# Generate cross-type interactions (e.g., Price * Quantity -> Total)
add_atlas_interactions_inline(
    OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/Interactions.hpp
    TARGET my_library
    CONTENT [[
include "Price.hpp"
include "Quantity.hpp"
include "Total.hpp"

namespace=commerce

Price * Quantity -> Total
Price * int <-> Price
]])

See docs/AtlasHelpers.md for complete reference of all helper functions, or docs/cmake-integration.md for advanced integration patterns.

Installation

Build:

cmake --build --preset debug

Run tests:

ctest --preset debug

Available presets: debug, release, debug-gcc, release-gcc, and ASan variants (debug-clang, release-clang). See cmake --list-presets for the full list.

Install from a release build:

cmake --build --preset release
cmake --install .build/release-clang --prefix /usr/local

Run code coverage:

cmake --build build/coverage --target coverage
# See docs/COVERAGE.md for details

Requirements: C++20 compiler (GCC ≥11, Clang ≥15), CMake ≥3.20, and the crushing realization that strong types actually matter.

The tool requires a C++20 compiler, but generates code that should be usable back to C++11.

Usage

Strong Types

Generate a single type:

atlas --kind=struct --namespace=geom --name=Length \
      --description="double; +, -, ==, !=" \
      --output=Length.hpp

# With named constants
atlas --kind=struct --namespace=geom --name=Distance \
      --description="double; +, -, ==, <=>" \
      --constants="zero:0.0; max:1000.0" \
      --output=Distance.hpp

Generate multiple types from a file (batch processing your type safety):

atlas --input=types.atlas --output=strong_types.hpp

types.atlas:

# Optional file-level configuration
guard_prefix=MY_TYPES

# Define reusable profiles
profile=NUMERIC; +, -, *, /, <=>

[type]
kind=struct
namespace=math
name=Distance
description=strong int; {NUMERIC}
default_value=0
constants=zero:0; max:1000

[struct util::Counter]
description=int; ++, --, out

Cross-Type Interactions

Make types play nicely with each other (like all humans do):

atlas --input=interactions.atlas --interactions=true --output=ops.hpp

interactions.atlas:

include "price.hpp"
include "quantity.hpp"

namespace=commerce

Price * Quantity -> Total
Price * int <-> Price        # Symmetric (because it's is commutative)

namespace=physics
concept=Numeric
enable_if=std::is_arithmetic_v<Numeric>

Distance * Numeric <-> Distance
Distance / Time -> Velocity

Description Language

Syntax: [strong] <type>; <options...> (the strong keyword is optional)

Common options (because we're all about that opt-in life):

  • Arithmetic: +, -, *, /, ++, -- — when your type needs to do math
  • Comparison: ==, !=, <=>, <, > — for when sorting matters. <=> works in C++11+ via automatic fallback!
  • Stream: in, outstd::cout compatibility sold separately
  • Callable: (), (&) — yes, your strong type can be a functor. Why? Because C++.
  • Pointer-like: @, ->, &of — make it look like a pointer without the segfaults
  • Containers: [], iterable — for those fancy data structures
  • File-level: auto_hash, auto_ostream, auto_format — enable std::hash, operator<<, std::formatter for all types
  • Constants: Named constants with constants=name:value; name2:value2 — static members like scoped enums
  • Profiles: Reusable feature bundles with profile=NAME; features... and {NAME} references
  • Special: bool, no-constexpr — the "it's complicated" options

Constrained Types

Enforce invariants at construction time and after operations. Invalid states become unrepresentable:

  • positive — Value must be > 0 (prices, speeds)
  • non_negative — Value must be >= 0 (distances, counts)
  • non_zero — Value must be != 0 (denominators, divisors)
  • bounded<Min,Max> — Value must be in [Min, Max] closed interval (percentages, volume levels)
  • bounded_range<Min,Max> — Value must be in [Min, Max) half-open interval (array indices)
  • non_empty — Container/string must not be empty (usernames, required fields)
  • non_null — Pointer must not be null (required handles, non-null pointers)

Quick example:

[struct audio::Volume]
description=int; bounded<0,100>, +, -, <=>, saturating

[struct finance::Price]
description=double; positive, +, -, *, /

[struct auth::Username]
description=std::string; non_empty, ==

Generated types validate at construction and after operations:

Volume v{150};        // Throws atlas::ConstraintError
Volume a{50}, b{60};
auto c = a + b;       // Throws - result 110 exceeds max of 100

Username u{""};       // Throws - empty string not allowed
Username u;           // Won't compile - default ctor deleted for non_empty types

Constraints work with all arithmetic modes (checked, saturating, wrapping) and provide compile-time checking for constexpr values.

See the Constrained Types section in the description language docs for complete details.

Full reference (for the masochists): docs/description-language.md

Library API

Embed Atlas in your own build tools:

#include "StrongTypeGenerator.hpp"

wjh::atlas::StrongTypeDescription desc{
    .kind = "struct",
    .type_namespace = "geom",
    .type_name = "Length",
    .description = "double; +, -",
    .constants = {{"zero", "0.0"}, {"max", "1000.0"}}
};
StrongTypeGenerator generator;
std::string header = generator(desc);
#include "InteractionGenerator.hpp"

wjh::atlas::InteractionFileDescription desc;
desc.includes = {"\"price.hpp\""};
desc.interactions.push_back({
    .op_symbol = "*",
    .lhs_type = "Price",
    .rhs_type = "Quantity",
    .result_type = "Total",
    .interaction_namespace = "commerce"
});
std::string header = wjh::atlas::generate_interactions(desc);

Documentation

Contributing

Contributions welcome! See docs/CONTRIBUTING.md for guidelines and docs/CODE_OF_CONDUCT.md to learn how to be a decent human while coding.

License

MIT License. See LICENSE for the legal stuff. TL;DR: Do whatever you want, just don't blame me.

About

C++ code generator for strong types

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors