[TOC]
FE is a header-only C++20 toolkit for building handwritten compiler and interpreter frontends.
Rather than generating lexers or parsers for you, FE focuses on the infrastructure that every frontend needs anyway: source locations, diagnostics, interning, parsing support, and efficient memory management. The goal is simple: keep handwritten frontends lightweight, explicit, and pleasant to maintain.
FE is a good fit if you want to build:
- a small programming language or DSL,
- a hand-written recursive-descent parser,
- a lexer with precise UTF-8-aware source tracking,
- a frontend with high-quality diagnostics,
- a prototype compiler or interpreter that should stay easy to evolve.
It is especially useful when you want the flexibility of handwritten code without repeatedly rebuilding the same frontend infrastructure from scratch.
Handwritten frontends are often the right choice when you want full control over syntax, diagnostics, recovery, and architecture. FE embraces that style.
It provides a compact set of reusable, well-integrated components:
fe::Arenafor fast arena allocation and arena-backed ownership.fe::Symandfe::SymPoolfor string interning and cheap identifier comparison.fe::Driverfor diagnostics and shared frontend state.fe::Posandfe::Locfor source positions and source spans.fe::Lexer<K, S>for UTF-8-aware lexing with lookahead and token text accumulation.fe::Parser<Tok, Tag, K, S>for recursive-descent-style parsing with token lookahead and span tracking.- Optional
FE_ABSLsupport for Abseil hash containers.
FE does not try to hide frontend construction behind a generator. Instead, it gives you sharp, reusable tools so you can build exactly the frontend you want.
For a complete end-to-end example, see Let, a small toy language built on FE..
The easiest way to get going is through Let.
You can either:
- 📦 create a new repository from the Let template, or
- 🍴 fork Let directly.
That gives you a concrete, working example of how FE is intended to be used in practice.
Add FE as a subdirectory and link the fe interface target:
add_subdirectory(external/fe)
target_link_libraries(my_compiler PRIVATE fe)If you want Abseil-backed hash containers, enable FE_ABSL before adding the subdirectory:
set(FE_ABSL ON)
add_subdirectory(external/fe)
target_link_libraries(my_compiler PRIVATE fe)Because FE is header-only, you can also vendor include/fe/ directly into your project.
If you want Abseil support in that setup, compile with:
-DFE_ABSLA typical FE-based frontend looks roughly like this:
- Define a token type exposing
tag()andloc(). - Implement your lexer by deriving from
fe::Lexer<K, S>. - Implement your parser by deriving from
fe::Parser<Tok, Tag, K, S>. - Use
fe::Driverto centralize diagnostics and shared state. - Thread
fe::Locthrough tokens and AST nodes for precise error reporting. - Use
fe::Arenaand symbol interning where allocation cost and identifier handling matter.
If you want a concrete model to copy from, start with tests/lexer.cpp.
To configure, build, and run the test suite:
cmake -S . -B build -DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failureTo run one discovered test:
ctest --test-dir build -R '^Lexer$' --output-on-failureTo run a doctest case directly:
./build/bin/fe-test --test-case=LexerTo build the documentation:
cmake -S . -B build -DFE_BUILD_DOCS=ON
cmake --build build --target docsThis requires Doxygen and Graphviz (dot).
A few projects that use or reflect the same frontend philosophy:
- Let - a small demo language built on FE.
- MimIR - an intermediate representation project by the author.
- GraphTool - a DOT-language tool using FE-style frontend infrastructure.
- SQL - a small SQL parser.
FE is licensed under the MIT License.