Article by Ayman Alheraki on January 11 2026 10:37 AM
github : https://github.com/ForgeLang/LearnSeries
In building a modern interpreter in C++20/23, using a robust and modular build system is essential. The complexity of a language project increases rapidly with the addition of lexical analysis, parsing, semantic checking, runtime evaluation, testing, and tools. This complexity must be managed with a clean build configuration that supports:
Multiple submodules or libraries
Dependency isolation
Modern C++ standards
Easy testing and documentation integration
This section explains how to structure a multi-component interpreter project using CMake, leveraging the latest standards and practices introduced over the past five years, including C++20 modules, target-based builds, toolchain abstraction, and cross-platform compatibility.
CMake has become the standard for large-scale C++ projects due to its flexibility, IDE integration, cross-platform support, and compatibility with modern build systems like Ninja, MSBuild, and Make. It supports:
Out-of-source builds
Fine-grained control over dependencies and targets
Build type separation (Debug, Release, RelWithDebInfo)
Test integration with CTest or external frameworks (Catch2, doctest)
Native support for C++20/23 features, including modules and std::format
Assuming a project directory named ForgeLang, the structure would be:
x
/ForgeLang├── CMakeLists.txt├── /cmake → CMake helper modules├── /src│ ├── /core│ ├── /lexer│ ├── /parser│ ├── /semantics│ ├── /runtime│ ├── /stdlib│ └── /main├── /tests├── /examples└── /build → Out-of-source build directoryCMakeLists.txt (Top-Level Configuration)x
cmake_minimum_required(VERSION 3.25)project(ForgeLang VERSION 0.1 LANGUAGES CXX)
# Require C++23set(CMAKE_CXX_STANDARD 23)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Use folders in IDEs like Visual Studioset_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Modules pathlist(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# Enable warnings and optionsinclude(cmake/CompilerWarnings.cmake)include(cmake/EnableSanitizers.cmake)
# Add source directoriesadd_subdirectory(src/core)add_subdirectory(src/lexer)add_subdirectory(src/parser)add_subdirectory(src/semantics)add_subdirectory(src/runtime)add_subdirectory(src/main)
# Add testsenable_testing()add_subdirectory(tests)This configuration enforces modern standards, organizes the project cleanly, and integrates useful features (warnings, sanitizers, tests).
src/lexer/CMakeLists.txt)x
add_library(ForgeLexer STATIC Token.cpp Lexer.cpp)
target_include_directories(ForgeLexer PUBLIC ${CMAKE_SOURCE_DIR}/src)target_link_libraries(ForgeLexer PUBLIC ForgeCore)target_compile_features(ForgeLexer PUBLIC cxx_std_23)
set_target_properties(ForgeLexer PROPERTIES FOLDER "ForgeLang/Lexer")Each component is built as a static library (or module, where supported). This improves compilation time and enforces separation of concerns.
src/core/CMakeLists.txt)x
add_library(ForgeCore STATIC SourceManager.cpp ErrorReporter.cpp Utility.cpp)
target_include_directories(ForgeCore PUBLIC ${CMAKE_SOURCE_DIR}/src)target_compile_features(ForgeCore PUBLIC cxx_std_23)
# Optional sanitizers and warningstarget_link_options(ForgeCore PRIVATE ${SANITIZER_FLAGS})The core library provides foundational types and services for all modules. All other components (lexer, parser, runtime) depend on it.
src/main/CMakeLists.txt)x
add_executable(ForgeLang main.cpp)
target_link_libraries(ForgeLang PRIVATE ForgeCore ForgeLexer ForgeParser ForgeSemantics ForgeRuntime)
target_compile_features(ForgeLang PUBLIC cxx_std_23)set_target_properties(ForgeLang PROPERTIES FOLDER "ForgeLang/Main")This is the main interpreter binary. Additional REPL or debugging tools can be added as separate executables under /tools or /cli.
If your compiler supports it (Clang >= 16, MSVC >= 19.34, GCC >= 13):
x
target_sources(ForgeParser PRIVATE FILE_SET cxx_modules TYPE CXX_MODULES BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} FILES Parser.ixx)Modules allow replacing header files, reduce compile time, and eliminate transitive dependencies. This is especially useful for high-level interpreter APIs.
tests/CMakeLists.txt)x
add_executable(ForgeTests LexerTests.cpp ParserTests.cpp)
target_link_libraries(ForgeTests PRIVATE ForgeCore ForgeLexer ForgeParser)
add_test(NAME LexerTests COMMAND ForgeTests)Use modern test frameworks that support C++20, such as Catch2 v3, with test discovery integration for IDEs and CI.
Assuming you're using the terminal and Ninja:
x
mkdir build && cd buildcmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ..cmake --build .ctest --output-on-failureThis setup supports fast iteration, cross-platform builds, and integration with tools like Clang-Tidy, Valgrind, or sanitizers.
CMake integrates with major IDEs:
Visual Studio 2022 supports C++23 and modules
CLion auto-detects compile_commands.json
VSCode with CMake Tools and C++ extensions provides excellent workflow
You can configure per-target compiler flags, debugging info, or even cross-compilation profiles using CMake presets.
Using CMake effectively for a multi-component interpreter project enables structured development, fast builds, and extensibility. By dividing components into standalone modules and libraries, leveraging C++20/23 features, and enforcing modern build practices, the interpreter can evolve cleanly from prototype to production.
This project structure reflects modern systems programming expectations and aligns well with the goals of the language we are creating—clarity, safety, and modularity.