A C++20 units and physical-quantities conversion library with:
- Strongly typed quantities at compile time (
Quantity<Unit>) - Runtime quantities (
VtlQuantity) - A Ruby DSL to define physical quantities, units, and conversions
- Strong DSL validation (duplicates, missing references, conversion coverage)
- Automatic C++ code generation and Doxygen documentation
- Project Status
- Repository Layout
- Requirements
- Quick Start
- Consuming the Library
- Build Targets
- Running Tests
- Running Fuzz (Bounded)
- Running Utilities
- Examples
- Defining New Units and Conversions
- DSL Validation Rules
- Documentation (Doxygen)
- CI Workflows
- Exception Style Guide
- Troubleshooting
- License
The project is currently organized around the DSL pipeline:
- Source of truth for units DSL:
lib/unit-defs.rb+lib/unit-defs/*.rb - Generated C++ headers:
<build-dir>/generated/units/*.H - Core runtime API:
include/uconv.H - Unit tests (GoogleTest +
ctest):Tests/ - CLI utilities (manual checks/tools):
utils/
Generated headers are build artifacts and are not versioned in Git.
include/: public headers (uconv.H, helper headers)lib/: core implementation and DSL definitionsbin/gen-units: Ruby generator + DSL validatorTests/: automated unit testsutils/: manual CLI utilitiesExamples/: usage examplesdocs/: human-facing technical documentation (included in Doxygen)cmake/Doxyfile.in: Doxygen configuration template
- C++20 compiler (
g++orclang++) - CMake >= 3.10
- Ruby (to run
bin/gen-units) Aleph-wheaders + librarynlohmann/jsoninclude path- GoogleTest (for
Tests/)
- Doxygen + Graphviz (for docs)
You can provide dependencies as environment variables or CMake variables.
Using environment variables:
export ALEPHW=/path/to/Aleph-w
export JSON=/path/to/jsonOr pass them explicitly to CMake:
cmake -S . -B cmake-build-debug -DALEPHW=/path/to/Aleph-w -DJSON_PATH=/path/to/jsoncmake -S . -B cmake-build-debug -DALEPHW=/path/to/Aleph-w -DJSON_PATH=/path/to/jsoncmake --build cmake-build-debug -jThis build automatically regenerates <build-dir>/generated/units/*.H from the DSL when needed.
Link against target uconv and include:
#include <uconv-list.H>uconv-list.H includes generated all_units.H automatically.
When you link uconv, CMake exports include paths for both:
include/<build-dir>/generated/units/
Minimal consumer example:
add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE uconv)#include <uconv-list.H>
int main() {
const auto& _units = init_units();
(void)_units;
Quantity<meter> d(10.0);
Quantity<foot> f = d;
(void)f;
return 0;
}You must add both include paths explicitly:
-I/path/to/uconv/include-I/path/to/uconv/<build-dir>/generated/units
- DSL source of truth:
lib/unit-defs.rb,lib/unit-defs/*.rb - Generated API docs:
cmake-build-debug/docs/doxygen/html/index.html - In Doxygen, search/open page
Units Catalog(auto-generated from DSL) - Generated headers:
cmake-build-debug/generated/units/*.H
Common targets:
- Default build:
cmake --build cmake-build-debug - Unit tests binaries: included via
Tests/ - Utilities binaries: included via
utils/ - Examples binaries: included via
Examples/ - Docs site:
cmake --build cmake-build-debug --target docs
CMake options:
-DENABLE_SANITIZERS=ON|OFF(defaultON)-DBUILD_DOCS=ON|OFF(defaultON)
Automated unit tests are in Tests/ and are registered with ctest.
Run all:
ctest --test-dir cmake-build-debug --output-on-failureIf ASAN leak detection interferes in your environment, use:
ASAN_OPTIONS=detect_leaks=0 ctest --test-dir cmake-build-debug --output-on-failureThe fuzz target is simple in fuzz/.
Important: libFuzzer runs indefinitely by default. Use bounded flags:
./cmake-build-debug/fuzz/simple -runs=20000 -max_total_time=20In Clang builds, ctest also registers a bounded smoke test:
ctest --test-dir cmake-build-debug -R fuzz_simple_smoke --output-on-failureManual utilities are built under utils/:
util-convert: convert scalar values between unitsutil-vector-convert: convert value vectorsutil-all-units: validate/range-check by physical quantityutil-unit-table: inspect registered units table
Example:
./cmake-build-debug/utils/util-convert --helpExamples are built under Examples/:
basic_conversiondynamic_unitsphysics
Example:
./cmake-build-debug/Examples/basic_conversion- Root DSL entrypoint:
lib/unit-defs.rb - Per-domain DSL files:
lib/unit-defs/*.rb
If you add a new DSL file, import it in lib/unit-defs.rb.
physical_quantity :Name do ... endunit :UnitName do ... endconversion :FromUnit, :ToUnit do |v| "...#{v}..." end
Required metadata:
- Physical quantity:
symbol,latex,description - Unit:
symbol,latex,description,quantity,range - Conversion:
from,to, formula block
ruby bin/gen-units lib/unit-defs.rb <build-dir>/generated/unitscmake --build cmake-build-debug -j
ctest --test-dir cmake-build-debug --output-on-failurebin/gen-units enforces structural checks before generating C++:
- Missing fields (
symbol,latex,description,quantity,range, formula) - Invalid ranges (
min > max, non-numeric range) - Duplicates (quantity names/symbols, unit names/symbols, conversion pairs)
- Unknown references (unit -> quantity, conversion endpoints)
- Cross-quantity conversions (forbidden)
- Conversion coverage (directed full coverage within each quantity family)
- Explicit self-conversions (forbidden)
If validation fails, generation aborts with a detailed error list.
cmake --build cmake-build-debug --target docsOutput:
- HTML index:
cmake-build-debug/docs/doxygen/html/index.html - Warnings log:
cmake-build-debug/docs/doxygen/warnings.log
- Full public API in
include/uconv.H - Generated units headers (
<build-dir>/generated/units/*.H) - DSL source files (
lib/unit-defs.rb,lib/unit-defs/*.rb) - Markdown docs in
docs/
When you define new units or conversions in the DSL:
bin/gen-unitsregenerates headers with Doxygen blocks per quantity/unit/conversion.docstarget runs Doxygen over generated headers and DSL docs.
So new DSL definitions appear in Doxygen automatically after:
cmake --build cmake-build-debug --target docsCurrent workflows (.github/workflows/):
ci.yml: matrix build/test + sanitizers (gccandclang, Debug) + docs warnings gatedocs.yml: build and publish Doxygen site to GitHub Pagesfull_analysis.yml: scheduled deeper analysis (build/test/cppcheck/docs)- All workflows clone
Aleph-w, buildlibAleph.a, and exportALEPHW_DIRbefore configuringuconv.
All new exception throws must use macro-based style.
- For standard exceptions (
std::domain_error,std::range_error, etc.): useah-errors.Hmacros (for exampleah_domain_error_if(...),ah_range_error_unless(...)). - For uconv/Aleph custom exceptions not present in
ah-errors.H: useinclude/ah-uconv-errors.Hmacros (for exampleah_unit_not_found_error_if(...),ah_unit_conversion_not_found_error_if(...)).
Rules:
- Prefer conditional macros (
*_if,*_unless) instead of manualif (...) throw .... - Build error messages with stream syntax (
<<) directly on the macro. - Avoid direct
throw ...in library/runtime code unless there is no macro coverage yet.
Set ALEPHW or pass -DALEPHW=....
Set JSON env var or pass -DJSON_PATH=....
Run directly to see validation errors:
ruby bin/gen-units lib/unit-defs.rb <build-dir>/generated/unitsInstall Doxygen and reconfigure:
sudo apt-get install doxygen graphviz
cmake -S . -B cmake-build-debuguconv is licensed under GNU GPL v3.
See LICENSE.