Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/doxygen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ jobs:
with:
submodules: recursive

- name: Install Graphviz
- name: Install Graphviz, gcc-14, g++-14
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends graphviz
sudo apt-get install -y --no-install-recommends graphviz gcc-14 g++-14

- name: Restore Doxygen cache
id: cache-doxygen
Expand All @@ -49,7 +49,7 @@ jobs:
tar -C "${{ github.workspace }}/.cache" -xf "${RUNNER_TEMP}/${archive}"

- name: Configure
run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF -DFE_BUILD_DOCS=ON -DDOXYGEN_EXECUTABLE=${{ env.DOXYGEN_CACHE_DIR }}/bin/doxygen
run: CC=gcc-14 CXX=g++-14 cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=OFF -DFE_BUILD_DOCS=ON -DDOXYGEN_EXECUTABLE=${{ env.DOXYGEN_CACHE_DIR }}/bin/doxygen

- name: Build
run: cmake --build ${{github.workspace}}/build -v --target docs
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ jobs:
with:
submodules: recursive

- name: Install Valgrind
- name: Install Valgrind, gcc-14, g++-14
run: |
sudo apt-get update
sudo apt-get install -y valgrind
sudo apt-get install -y valgrind gcc-14 g++-14

- name: Configure
run: cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DBUILD_TESTING=ON
run: CC=gcc-14 CXX=g++-14 cmake -S ${{github.workspace}} -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DBUILD_TESTING=ON

- name: Build
run: cmake --build ${{github.workspace}}/build -v
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ target_sources(fe
include/fe/utf8.h
)
target_include_directories(fe INTERFACE $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
target_compile_features(fe INTERFACE cxx_std_20)
target_compile_features(fe INTERFACE cxx_std_23)

if(MSVC)
target_compile_definitions(fe INTERFACE _CTYPE_DISABLE_MACROS)
Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
[![Doxygen](https://img.shields.io/github/actions/workflow/status/leissa/fe/doxygen.yml?style=flat-square&logo=gitbook&logoColor=white&label=doxygen&branch=main)](https://github.com/leissa/fe/actions/workflows/doxygen.yml)
[![Documentation](https://img.shields.io/badge/docs-main-green?style=flat-square&logo=gitbook&logoColor=white)](https://leissa.github.io/fe)

[![C++20](https://img.shields.io/badge/C%2B%2B-20-blue?style=flat-square&logo=cplusplus)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization)
[![License](https://img.shields.io/github/license/leissa/fe?style=flat-square&color=yellowgreen&logo=opensourceinitiative&logoColor=white)](https://github.com/leissa/fe/blob/main/LICENSE.TXT)
[![C++23](https://img.shields.io/badge/C%2B%2B-23-blue?style=flat-square&logo=cplusplus)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization)
[![License: MIT](https://img.shields.io/github/license/leissa/fe?style=flat-square&color=yellowgreen&logo=opensourceinitiative&logoColor=white)](https://github.com/leissa/fe/blob/main/LICENSE.TXT)

[TOC]

**FE** is a header-only C++20 toolkit for building handwritten compiler and interpreter frontends.
**FE** is a header-only C++23 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.
Expand Down
64 changes: 22 additions & 42 deletions include/fe/enum.h
Original file line number Diff line number Diff line change
@@ -1,62 +1,42 @@
#pragma once

#include <compare>

#include <type_traits>

namespace fe {

/// @name is_enum
///@{
template<typename T>
struct is_bit_enum : std::false_type {};
template<class T>
inline constexpr bool is_bit_enum_v = is_bit_enum<T>::value;
template<class E>
concept BitEnum = std::is_enum_v<E> && is_bit_enum_v<E>;
///@}

/// @name Bit operations for enum classs
/// Provides all kind of bit and comparison operators for an `enum class` @p E.
/// Note that the bit operators return @p E's underlying type and not the original `enum` @p E.
/// This is because the result may not be a valid `enum` value.
/// For the same reason, it doesn't make sense to declare operators such as `&=`.
/// Use like this:
/// ```
/// using fe::operator&;
/// using fe::operator|;
/// using fe::operator^;
/// using fe::operator<=>;
/// using fe::operator==;
/// using fe::operator!=;
///
/// enum class MyEnum : unsigned {
/// A = 1 << 0,
/// B = 1 << 1,
/// C = 1 << 2,
/// };
///
/// template<> struct fe::is_bit_enum<MyEnum> : std::true_type { };
/// template<> struct fe::is_bit_enum<MyEnum> : std::true_type {};
/// ```
///@{
// clang-format off
template <BitEnum E> constexpr auto operator&(E x, E y) { return std::underlying_type_t<E>(x) & std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr auto operator&(std::underlying_type_t<E> x, E y) { return x & std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr auto operator&( E x, std::underlying_type_t<E> y) { return std::underlying_type_t<E>(x) & y ; }
template <BitEnum E> constexpr auto operator|( E x, E y) { return std::underlying_type_t<E>(x) | std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr auto operator|(std::underlying_type_t<E> x, E y) { return x | std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr auto operator|( E x, std::underlying_type_t<E> y) { return std::underlying_type_t<E>(x) | y ; }
template <BitEnum E> constexpr auto operator^( E x, E y) { return std::underlying_type_t<E>(x) ^ std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr auto operator^(std::underlying_type_t<E> x, E y) { return x ^ std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr auto operator^( E x, std::underlying_type_t<E> y) { return std::underlying_type_t<E>(x) ^ y ; }
template <BitEnum E> constexpr std::strong_ordering operator<=>(std::underlying_type_t<E> x, E y) { return x <=> std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr std::strong_ordering operator<=>(E x, std::underlying_type_t<E> y) { return std::underlying_type_t<E>(x) <=> y; }
template <BitEnum E> constexpr bool operator==(std::underlying_type_t<E> x, E y) { return x == std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr bool operator!=(std::underlying_type_t<E> x, E y) { return x != std::underlying_type_t<E>(y); }
template <BitEnum E> constexpr bool operator==(E x, std::underlying_type_t<E> y) { return std::underlying_type_t<E>(x) == y; }
template <BitEnum E> constexpr bool operator!=(E x, std::underlying_type_t<E> y) { return std::underlying_type_t<E>(x) != y; }
// clang-format on
template<typename T>
struct is_bit_enum : std::false_type {};

///@}
template<typename E>
concept BitEnum = std::is_enum_v<E> && is_bit_enum<E>::value;

template<fe::BitEnum E> constexpr auto to_underlying(E e) noexcept { return static_cast<std::underlying_type_t<E>>(e); }

} // namespace fe

// clang-format off
template<fe::BitEnum E> constexpr E operator|(E a, E b) noexcept { return static_cast<E>(fe::to_underlying(a) | fe::to_underlying(b)); }
template<fe::BitEnum E> constexpr E operator&(E a, E b) noexcept { return static_cast<E>(fe::to_underlying(a) & fe::to_underlying(b)); }
template<fe::BitEnum E> constexpr E operator^(E a, E b) noexcept { return static_cast<E>(fe::to_underlying(a) ^ fe::to_underlying(b)); }
template<fe::BitEnum E> constexpr E operator~(E a) noexcept { return static_cast<E>(~fe::to_underlying(a)); }
template<fe::BitEnum E> constexpr E& operator|=(E& a, E b) noexcept { return a = (a | b); }
template<fe::BitEnum E> constexpr E& operator&=(E& a, E b) noexcept { return a = (a & b); }
template<fe::BitEnum E> constexpr E& operator^=(E& a, E b) noexcept { return a = (a ^ b); }

namespace fe {
template<fe::BitEnum E> constexpr bool has_flag(E value, E flag) noexcept { return (value & flag) == flag; }
} // namespace fe

// clang-format on
176 changes: 135 additions & 41 deletions include/fe/format.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,50 @@
#pragma once

#include <concepts>

#include <format>
#include <functional>
#include <iostream>
#include <ostream>
#include <print>
#include <ranges>
Comment thread
leissa marked this conversation as resolved.
#include <sstream>
#include <string_view>
#include <utility>

#include "fe/loc.h"
#include "fe/utf8.h"

namespace fe {

/// Wrap a callable `f(std::ostream&) -> std::ostream&` so it streams via `operator<<` and `std::format`.
/// Useful for inline ad-hoc formatting:
/// ```
/// auto greet = fe::StreamFn{[](std::ostream& os) { os << "hi"; }};
/// std::cout << greet;
/// std::format("{}", greet);
/// ```
template<class F>
requires std::invocable<const F&, std::ostream&>
class StreamFn {
public:
constexpr StreamFn(F f)
: f_(std::move(f)) {}

friend std::ostream& operator<<(std::ostream& os, StreamFn const& s) {
if constexpr (std::same_as<std::invoke_result_t<F const&, std::ostream&>, std::ostream&>)
return std::invoke(s.f_, os);
else
return std::invoke(s.f_, os), os;
}

private:
F f_;
};

template<class F>
StreamFn(F) -> StreamFn<F>;

/// Make types that support ostream operators available for `std::format`.
/// Use like this:
/// ```
Expand All @@ -15,90 +53,129 @@ namespace fe {
/// @sa [Stack Overflow](https://stackoverflow.com/a/75738462).
template<class Char>
struct basic_ostream_formatter : std::formatter<std::basic_string_view<Char>, Char> {
template<class T, class O>
O format(const T& value, std::basic_format_context<O, Char>& ctx) const {
template<class T, class FormatContext>
auto format(T const& value, FormatContext& ctx) const {
std::basic_stringstream<Char> ss;
ss << value;
#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 170000
return std::formatter<std::basic_string_view<Char>, Char>::format(ss.str(), ctx);
#else
return std::formatter<std::basic_string_view<Char>, Char>::format(ss.view(), ctx);
#endif
}
};

using ostream_formatter = basic_ostream_formatter<char>;

/// @name out/outln/err/errln
/// Print to `std::cout`/`std::cerr` via `std::format`; the `*ln` variants conclude with `std::endl`.
///@{
// clang-format off
template<class... Args> void err (std::format_string<Args...> fmt, Args&&... args) { std::cerr << std::format(fmt, std::forward<Args>(args)...); }
template<class... Args> void out (std::format_string<Args...> fmt, Args&&... args) { std::cout << std::format(fmt, std::forward<Args>(args)...); }
template<class... Args> void errln(std::format_string<Args...> fmt, Args&&... args) { std::cerr << std::format(fmt, std::forward<Args>(args)...) << std::endl; }
template<class... Args> void outln(std::format_string<Args...> fmt, Args&&... args) { std::cout << std::format(fmt, std::forward<Args>(args)...) << std::endl; }
// clang-format on

/// Keeps track of indentation level during output
class Tab {
public:
/// @name Construction
///@{
Tab(const Tab&) = default;
Tab(std::string_view tab = {"\t"}, size_t indent = 0)
Tab(std::string_view tab = {"\t"}, int indent = 0)
: tab_(tab)
, indent_(indent) {}
, indent_(indent) {
assert(indent >= 0);
}

/// @name Getters
///@{
constexpr size_t indent() const noexcept { return indent_; }
constexpr std::string_view tab() const noexcept { return tab_; }
static Tab spaces() { return Tab(std::string_view(" ")); }
///@}

/// @name Setters
/// @name Getters
///@{
Tab& operator=(size_t indent) {
indent_ = indent;
return *this;
}
Tab& operator=(std::string tab) {
tab_ = tab;
return *this;
}
constexpr int indent() const noexcept { return indent_; }
constexpr std::string_view tab() const noexcept { return tab_; }
///@}

// clang-format off
/// @name Creates a new Tab
///@{
[[nodiscard]] constexpr Tab operator++(int) noexcept { return {tab_, indent_++}; }
[[nodiscard]] constexpr Tab operator--(int) noexcept { assert(indent_ > 0); return {tab_, indent_--}; }
[[nodiscard]] constexpr Tab operator+(size_t indent) const noexcept { return {tab_, indent_ + indent}; }
[[nodiscard]] constexpr Tab operator-(size_t indent) const noexcept { assert(indent_ > 0); return {tab_, indent_ - indent}; }
///
[[nodiscard]] Tab operator+(int indent) const noexcept { assert(indent >= 0); return {tab_, indent_ + indent}; }
[[nodiscard]] Tab operator-(int indent) const noexcept { assert(indent >= 0 && indent_ >= indent); return {tab_, indent_ - indent}; }
///@}

/// @name Modifies this Tab
///@{
constexpr Tab& operator++() noexcept { ++indent_; return *this; }
constexpr Tab& operator--() noexcept { assert(indent_ > 0); --indent_; return *this; }
constexpr Tab& operator+=(size_t indent) noexcept { indent_ += indent; return *this; }
constexpr Tab& operator-=(size_t indent) noexcept { assert(indent_ > 0); indent_ -= indent; return *this; }
constexpr Tab& operator=(size_t indent) noexcept { indent_ = indent; return *this; }
constexpr Tab& operator=(std::string_view tab) noexcept { tab_ = tab; return *this; }
constexpr Tab& operator+=(int indent) noexcept { assert(indent >= 0); indent_ += indent; return *this; }
constexpr Tab& operator-=(int indent) noexcept { assert(indent >= 0 && indent_ >= indent); indent_ -= indent; return *this; }
///@}
// clang-format on

friend std::ostream& operator<<(std::ostream& os, Tab tab) {
for (size_t i = 0; i != tab.indent_; ++i)
for (int i = 0; i != tab.indent_; ++i)
os << tab.tab_;
return os;
}

private:
std::string_view tab_;
size_t indent_ = 0;
int indent_ = 0;
};

template<class T, class CharT = char>
concept Formattable
= requires(std::basic_format_context<std::back_insert_iterator<std::basic_string<CharT>>, CharT>& ctx, T const& v) {
std::formatter<std::remove_cvref_t<T>, CharT>{}.format(v, ctx);
};

/// Join elements of @p range with @p sep.
/// Use as a `std::format` or `operator<<` argument: `std::format("{}", fe::Join(v, ", "))`.
template<std::ranges::input_range R>
requires Formattable<std::remove_cvref_t<std::ranges::range_reference_t<std::views::all_t<R>>>>
class Join {
public:
using View = std::views::all_t<R>;

Join(R&& range, std::string_view sep = ", ")
: range_(std::views::all(std::forward<R>(range)))
, sep_(sep) {}
Comment thread
leissa marked this conversation as resolved.

const auto& range() const { return range_; }
std::string_view sep() const { return sep_; }

friend std::ostream& operator<<(std::ostream& os, const Join& j) {
for (std::string_view sep{}; const auto& elem : j.range_) {
os << sep << elem;
sep = j.sep_;
}
return os;
}

private:
View range_;
std::string_view sep_;
};

template<class R>
Join(R&&, std::string_view = ", ") -> Join<R>;

} // namespace fe

#ifndef DOXYGEN
template<class F>
struct std::formatter<fe::StreamFn<F>> : fe::ostream_formatter {};

template<class R>
struct std::formatter<fe::Join<R>> {
using elem_t = std::remove_cvref_t<std::ranges::range_reference_t<std::views::all_t<R>>>;
std::formatter<elem_t> elem_fmt;

constexpr auto parse(std::format_parse_context& ctx) { return elem_fmt.parse(ctx); }

template<class FormatContext>
auto format(const fe::Join<R>& j, FormatContext& ctx) const {
auto out = ctx.out();
for (std::string_view sep = {}; const auto& elem : j.range()) {
out = std::ranges::copy(sep, out).out;
ctx.advance_to(out);
out = elem_fmt.format(elem, ctx);
Comment on lines +169 to +171
ctx.advance_to(out);
sep = j.sep();
}
return out;
}
};

// clang-format off
template<> struct std::formatter<fe::Pos> : fe::ostream_formatter {};
template<> struct std::formatter<fe::Loc> : fe::ostream_formatter {};
Expand All @@ -107,3 +184,20 @@ template<> struct std::formatter<fe::Tab> : fe::ostream_formatter {};
template<> struct std::formatter<fe::utf8::Char32> : fe::ostream_formatter {};
// clang-format on
#endif

#ifdef NDEBUG
# define assertf(condition, ...) \
do { \
(void)sizeof(condition); \
__VA_OPT__((void)sizeof((__VA_ARGS__));) \
} while (false)
#else
# define assertf(condition, ...) \
do { \
if (!(condition)) { \
std::print(std::cerr, "{}:{}: assertion `{}` failed", __FILE__, __LINE__, #condition); \
std::println(std::cerr __VA_OPT__(, ": " __VA_ARGS__)); \
fe::breakpoint(); \
} \
} while (false)
#endif
Loading
Loading