today i'd like to show you one of the most cursed things i've ever made. but first, some context:
this is a tagged union in Hare:
(int | str | rune | void)
you can match on it like this:
let x: (int | str | rune | void) = f();
match (x) {
case let i: int =>
return i + 1;
case let s: str =>
defer free(s);
if (s != "") {
fmt::println(s)!;
};
return len(s): int;
case void => void; // no-op
case rune => abort("unreachable");
};
internally, the compiler assigns every type a unique 32-bit ID; this is used as the tag for the tagged union. so the internal representation of a tagged union looks something like this:
struct {
tag: u32,
union {
i: int,
s: str,
r: rune,
v: void,
},
}
you can also match on pointers to tagged unions:
let x = match (&x) {
case let i: *int =>
*i += 1;
yield i;
case void =>
yield null;
case let s: *str =>
defer free(*s);
fmt::println(*s)!;
return len(*s): int;
case *rune => abort("unreachable");
};
the match cases can also refer to tagged union subsets:
let x = match (&x) {
case let tu: *(int | rune) =>
yield tu;
case *(str | void) =>
return;
};
but this creates a problem with our internal representation: the new subset tagged union would be represented like this:
struct {
tag: u32,
union {
i: int,
r: rune,
},
}
previously, the union had an alignment of 8, so 4 bytes of padding were added after the tag. but now, it only has an alignment of 4, so there's no padding bytes! this means that it's not possible to get a pointer to a subset of the tagged union with this representation.
to solve this, hare's tagged unions actually look a little different. the data always goes in the first aligned offset after the tag. so in the case of (int | str), the int data would go at offset 4, whereas the str data would go at offset 8. they no longer exactly overlap, but this allows tagged union subsets to have the same representation as their supersets, allowing for pointer matching.
this works great within hare, but it makes interop with C considerably more difficult. here's what (int | str | rune | void) looks like in C:
union {
uint32_t _tag;
struct {
uint32_t _tag;
int _value;
} _int;
struct {
uint32_t _tag;
hare_str _value;
} _str;
struct {
uint32_t _tag;
hare_rune _value;
} _rune;
struct {
uint32_t _tag;
struct hare_zero_sized_type _value;
} _void;
}
(for now don't worry about struct hare_zero_sized_type; it's an implementation detail which will help out later with the atrocities i'm about to commit)
(also note that, for the thing i'm about to do to work, the names of the structs actually need to be of the form _ID, where ID is the ID of the type. but i'm trying to keep things somewhat approachable for now)
in our persuit to allow matching on pointers to tagged union subsets, we've made tagged unions look absolutely disgusting in C. they're now much more difficult to work with, to the point that a reasonable programmer wouldn't be blamed for just wanting to avoid touching this structure at all.
and like, sure, maybe we could introduce some macros to aid with selecting a field, so the user doesn't need to type ._value everytime. ...maybe. but like, does that really meaningfully improve the situation?
and like, the biggest upside to hare's tagged unions is safety. that's, like, the whole reason it's valuable to have them as a first-class language feature. with C, we reap none of those benefits. we have a difficult to work with structure with no upsides.
ok i think that's enough background information. i'm gonna cut to the chase now. in my efforts to make interop between hare and C as seamless as possible, i've created something truly awful.
i ported hare's match construct to C, using the preprocessor:
auto x = f(); // (int | str | void)
// match with value:
HARE_MATCH (x) {
HARE_CASE(i, HARE_TAG_int):
int val = i + 1;
printf("%d\n", val);
HARE_CASE(s, HARE_TAG_str):
const char *val = HARE_STR_DATA(s);
printf("%.*s\n", (int)s.len, val);
HARE_CASE(_, HARE_TAG_void):
// no-op
}
// match with pointer:
int old;
HARE_MATCH (x) {
HARE_CASE(*i, HARE_TAG_int):
old = *i;
++*i;
HARE_DEFAULT:
return;
}
assert(old + 1 == HARE_TYPE_ASSERT(x, HARE_TAG_int));
this allows matching on tagged unions, either by value or by pointer. each case has its own scope. there's no implicit fallthrough; each case is breaked from when it ends. for consistency with C's switch, break still breaks out of the HARE_MATCH (this is one place where the macro intentionally deviates from hare's behavior).
rudimentary compile-time checking is performed on the matching expression, as well as on each case. that is, the macro verifies to the best of its ability that the matching expression is a tagged union, and that each case is a HARE_TAG_* constant which correctly describes a direct member of that tagged union. it will also error if you have a duplicate case.
each HARE_CASE can create a binding, or _ can be used to not create a binding. for zero-sized types (such as HARE_TAG_void), the binding must be _, and it can't be a pointer (i.e. it can't be *_). this is enforced by the macro at compile time.
and this all works in standard C11 and C++11 (and later), with no extensions required (except that __typeof__ is required prior to C23). on gcc and clang, it compiles with no warnings (albeit it uses compiler-specific pragmas to silence specific warnings).
there are a few caveats:
- the matching expression must be an lvalue.
- in C versions prior to C2y (and C++ versions prior to C++17),
continuedoesn't work within a match case. there's unfortunately nothing i can do about this. - you can't match subsets. yeah, i know i had that whole thing at the beginning about how you can do that in hare. that was just to explain why the internal representation is the way it is. please understand that it's a miracle that this macro works at all.
i'll show you the full header at the end, but it's nearly 1000 lines of preprocessor and template bullshit, most of which isn't relevant, so i'll start by just showing you just the definition of HARE_MATCH (with comments removed):
#define HARE_MATCH(...) \
internal_HARE_DIAGNOSTIC_PUSH_ \
internal_HARE_DIAGNOSTIC_IGNORE_UNREACHABLE_ \
internal_HARE_MATCH_HEADER_(__VA_ARGS__) \
if (true) \
internal_HARE_DIAGNOSTIC_POP_
the internal_HARE_DIAGNOSTIC_* macros expand to compiler-specific pragmas to silence warnings; they're not really important.
internal_HARE_MATCH_HEADER_ is defined like this:
#if __STDC_VERSION__ > 202311L || __cplusplus >= 201703L
#define internal_HARE_MATCH_HEADER_(...) \
switch (auto _ = &(__VA_ARGS__); \
internal_HARE_MATCH_SWITCH_VALUE_(__VA_ARGS__))
#else
#define internal_HARE_MATCH_HEADER_(...) \
switch (0) default: \
for (internal_HARE_TYPEOF_(__VA_ARGS__) *_ = &(__VA_ARGS__); _; _ = NULL) \
switch (internal_HARE_MATCH_SWITCH_VALUE_(__VA_ARGS__))
#endif
to start, we declare a local binding _ as a pointer to the thing being matched on. we do this because each case gets its own binding, but the expression should only be evaluated once.
if possible, _ is declared in the switch header itself. if that feature isn't available, the only option is to surround the switch with a for-loop that only executes once. this is why continue doesn't work in certain older versions. that for-loop is surrounded by another switch statement, because C++ allows switch statements and for-loops to be labelled. we want to allow HARE_MATCH to be labelled, but we don't want to allow continuing to that label.
internal_HARE_MATCH_SWITCH_VALUE_ just expands to the tagged union's tag, but with additional compile-time checks included in the expression to ensure that the value is really a tagged union (+ a hopefully readable error message if it isn't).
what's with the if (true) in HARE_MATCH? here's where things get kinda cool: we want each HARE_CASE to get its own scope (implicit compound statement), and to implicitly break at the end (no fallthrough). how do we accomplish that?
notice that the user surrounds the HARE_MATCH body with curly braces. this mimicks hare's match syntax (as well as C's switch syntax if you're not doing something cursed with switch), but we can use those curly braces to our advantage. we can't start a compound statement in the macro itself, because we'd have no way to close it. however, the user starts their own compound statement. so we can use an if-else-if chain to have multiple compound statements in the switch body. note that the body itself isn't executed; it only jumps to the selected case within the body.
so: we start off with an if statement, which opens a compund statement which will end up being empty. each HARE_CASE closes the previous compound statement, and opens a new one. the compound statement for the last case is closed by the user. this gives us the implicit compound statements we want, and neatly separates each case into its own scope.
the definition for HARE_CASE is gonna look really complicated, but there's comments to walk you through it:
#define HARE_CASE(LET, TAG) internal_HARE_CASE_1_(__LINE__, LET, TAG)
#define internal_HARE_CASE_1_(LINE, LET, TAG) \
internal_HARE_CASE_2_(LINE, LET, TAG)
#define internal_HARE_CASE_2_(LINE, LET, TAG) \
/* see the big-ass comment in HARE_MATCH for a description of what the
* hell is going on here */ \
} else if (true) { \
internal_HARE_ASSERT_TAG_CONST_(TAG, "HARE_CASE"); \
case TAG:; \
/* the user may use _ as the binding name if they intend to not use it.
* _ is also used in the initializer of the binding, so the initializer
* must be evaluated before the new binding is inserted into the scope.
* this necessitates a temporary variable */ \
internal_HARE_TYPEOF_(_->_##TAG._value) *_hare_##LINE = \
&_->_##TAG._value; \
/* LET can optionally be prefixed by *, in which case the binding is a
* pointer, rather than a copied value. this means that we can't use LET
* to refer to the binding, since if it's a pointer then it'll be
* dereferenced. this is an issue when trying to take its type. the
* solution i came up with is to create a function type with a parameter
* named LET; this parameter will have the same type as the binding LET.
* so then we can have cases for whether the function's parameter is a
* pointer or not */ \
internal_HARE_TYPEOF_(_->_##TAG._value) LET = *internal_HARE_IF_TYPE_( \
void (*)(internal_HARE_TYPEOF_(*_hare_##LINE) LET), \
void (*)(internal_HARE_TYPEOF_(*_hare_##LINE)), _hare_##LINE, \
void (*)(internal_HARE_TYPEOF_(_hare_##LINE)), &_hare_##LINE); \
/* if the binding is _, then don't warn if it's unused. it should warn
* otherwise though */ \
(void)_; \
{ \
/* some checks related to bindings named _ */ \
struct _ { int _; } *_; (void)_; \
internal_HARE_STATIC_ASSERT_( \
!internal_HARE_TYPE_EQ_(LET, struct _), \
"Can't have pointer to no binding (*_)"); \
internal_HARE_STATIC_ASSERT_( \
internal_HARE_TYPE_EQ_(LET, struct _) \
|| internal_HARE_TYPE_EQ_(LET, struct _ *) \
|| !internal_HARE_TYPE_EQ_(_hare_##LINE, \
struct hare_zero_sized_type *), \
"Can't make binding for zero-sized type; use _ instead"); \
} \
goto _hare_##TAG##LINE; _hare_##TAG##LINE
there's two layers of macro indirection here, because the current line is used to generate a unique internal binding and label name. __COUNTER__ is unfortunately non-standard, so we need to use __LINE__. two layers of indirection are needed to force __LINE__ to expand prior to its use as a string pasting (##) operand.
there's some internal_HARE_ macros which perform compile-time checks. a few other notable ones are internal_HARE_TYPEOF_, which expands to typeof(X) in C or std::remove_reference<decltype(X)>::type in C++. internal_HARE_IF_TYPE_ expands to _Generic in C, or template fuckery in C++. internal_HARE_TYPE_EQ_ expands to _Generic in C, or std::is_same<std::decay<decltype(A)>::type, std::remove_cv<std::remove_reference<B>::type>::type>::value in C++.
the last line creates a label so the user can suffix it with a colon. a goto statement precedes it to avoid an unused label warning.
also notice that struct hare_zero_sized_type is used to disallow bindings and pointers to zero-sized types. that part of the macro is kinda convoluted, but it works!
finally, there's HARE_DEFAULT, which is defined like this:
#define HARE_DEFAULT } else if (true) { default
all of these macros are part of a header i created alongside a program called hareconv, which creates C headers from Hare declarations, and vice versa. i haven't touched it in a while and it doesn't compile in the latest version of Hare, and i kinda wanna rewrite it because i think i could do a much better job since i know so much more about C than i did when i first started, so yeah, eventually this header will be bundled with hareconv, but for now i'll paste it in its entirety if you want to look through it yourself (note that it's not very useful without hareconv-generated declarations though):
the full header
def HA2C_TEMPLATE = `// Generated by hareconv
#ifndef ${name}_H_
#define ${name}_H_
#ifndef HARE_HELPERS_DEFINED
#define HARE_HELPERS_DEFINED
/**
* The following helper macros, types, and templates are defined:
*
* hare_done
* hare_nomem
* hare_rune
* hare_c64
* hare_c128
* hare_off
* hare_ssize
* HARE_SLICE(T)
* hare_slice<T> (C++ only)
* hare_str
* HARE_CONST_STR(S)
* HARE_STR_DATA(S)
* HARE_MATCH (TU) { ... }
* HARE_CASE(LET, TAG)
* HARE_DEFAULT
* HARE_TAGGED_CAST(TU, TAG)
* HARE_TYPE_ASSERT(TU, TAG) (only defined if <assert.h>/<cassert> is available)
* HARE_TYPE_TEST(TU, TAG)
*
* Each of the above is documented below, alongside its definition.
*
* Additionally, a HARE_TAG_* macro constant is defined for every builtin
* primitive type.
*
* Inclusion of a hareconv-generated header will also #include the following
* headers:
*
* <stddef.h> (or <cstddef>)
* <stdint.h> (or <cstdint>)
* <stdalign.h> (C11-C17 only)
* <stdbool.h> (C11-C17 only)
* <sys/types.h> (POSIX only)
* <type_traits> (C++ only)
* <complex> (hosted C++ only)
* <assert.h> (or <cassert>) (hosted C/C++ only)
*
* Additionally, <stdarg.h> (or <cstdarg>) will be included if any declaration
* depends on va_list, and <wchar.h> (or <cwchar>) will be included if any
* declaration depends on wint_t (types::c::wint).
*/
#ifdef __cplusplus
#include <cstddef>
#include <cstdint>
#include <type_traits>
static_assert(__cplusplus >= 201103L,
"hareconv-generated header depends on at least C11 or C++11");
#define internal_HARE_STATIC_ASSERT_(...) static_assert(__VA_ARGS__)
#define internal_HARE_STATIC_ASSERT_EXPR_(...) \
(void)[&]{ static_assert(__VA_ARGS__); }
#if __cplusplus >= 202002L
#define internal_HARE_STATIC_ASSERT_TYPE_(MSG, TYPE, ...) \
decltype([&]() -> TYPE \
{ static_assert(__VA_ARGS__, MSG); return {}; }())
#else
template<bool COND, typename T> struct internal_hare_static_assert_type_ {
static_assert(COND, "Static assertion failed (see macro expansion backtrace for reason)");
using type = T;
};
#define internal_HARE_STATIC_ASSERT_TYPE_(MSG, TYPE, ...) \
internal_hare_static_assert_type_<__VA_ARGS__, TYPE>::type
#endif
#define internal_HARE_TYPEOF_(...) \
std::remove_reference<decltype(__VA_ARGS__)>::type
#define internal_HARE_TYPE_EQ_(A, B) \
std::is_same<std::decay<decltype(A)>::type, \
std::remove_cv<std::remove_reference<B>::type>::type>::value
#define internal_HARE_ALWAYS_INLINE_ inline
#ifdef __has_attribute
#if __has_attribute(__always_inline__)
#undef internal_HARE_ALWAYS_INLINE_
#define internal_HARE_ALWAYS_INLINE_ __attribute__((__always_inline__)) inline
#endif
#endif
#ifdef __has_cpp_attribute
#if __has_cpp_attribute(__gnu__::__always_inline__)
#undef internal_HARE_ALWAYS_INLINE_
#define internal_HARE_ALWAYS_INLINE_ [[__gnu__::__always_inline__]] inline
#endif
#endif
template<bool SAME, typename T1, typename T2>
struct internal_hare_if_same_ {
internal_HARE_ALWAYS_INLINE_ static constexpr T1 _(T1 _1, T2 _2) {
return _1;
}
};
template<typename T1, typename T2>
struct internal_hare_if_same_<false, T1, T2> {
internal_HARE_ALWAYS_INLINE_ static constexpr T2 _(T1 _1, T2 _2) {
return _2;
}
};
#undef internal_HARE_ALWAYS_INLINE_
template<typename COND, typename CASE1, typename T1, typename CASE2, typename T2>
struct internal_hare_if_type_ {
static_assert(std::is_same<COND, CASE1>::value
|| std::is_same<COND, CASE2>::value,
"Condition isn't compatible with either case");
static_assert(!std::is_same<COND, CASE1>::value
|| !std::is_same<COND, CASE2>::value,
"Condition is compatible with more than one case");
using _ = internal_hare_if_same_<std::is_same<COND, CASE1>::value, T1, T2>;
};
#define internal_HARE_IF_TYPE_(COND, CASE1, VAL1, CASE2, VAL2) \
internal_hare_if_type_<COND, CASE1, decltype(VAL1), \
CASE2, decltype(VAL2)>::_::_(VAL1, VAL2)
#define HARE_NORETURN [[__noreturn__]]
#define HARE_PACKED [[__gnu__::__packed__]]
#define HARE_THREAD_LOCAL thread_local
#ifdef __has_cpp_attribute
#if __has_cpp_attribute(__gnu__::__nonnull__)
#define HARE_NONNULL(...) [[__gnu__::__nonnull__(__VA_ARGS__)]]
#endif
#if __has_cpp_attribute(__gnu__::__returns_nonnull__)
#define HARE_RETURNS_NONNULL [[__gnu__::__returns_nonnull__]]
#endif
#endif
#ifdef __has_include
#if __has_include(<cassert>)
#define internal_HARE_HAS_ASSERT_
#endif
#if __has_include(<complex>)
#define internal_HARE_HAS_COMPLEX_
#endif
#endif
#if __STDC_HOSTED__ || defined(internal_HARE_HAS_ASSERT_)
#include <cassert> // required by HARE_TYPE_ASSERT
#undef internal_HARE_HAS_ASSERT_
#endif
#if __STDC_HOSTED__ || defined(internal_HARE_HAS_COMPLEX_)
#include <complex>
template<typename T> using internal_hare_complex_ = std::complex<T>;
#undef internal_HARE_HAS_COMPLEX_
#else
// this isn't exactly compatible with std::complex, but it's close enough
template<typename T> class internal_hare_complex_ {
public:
using value_type = T;
constexpr internal_hare_complex_(const T &real = T(), const T &imag = T())
: _{ real, imag } {}
constexpr internal_hare_complex_(const internal_hare_complex_ &)
= default;
constexpr T real() {
return this._[0];
}
void real(T _) {
this._[0] = _;
}
constexpr T imag() {
return this._[1];
}
void imag(T _) {
this._[1] = _;
}
internal_hare_complex_ &operator=(const T &_) {
this._[0] = _;
return *this;
}
internal_hare_complex_ &operator=(const internal_hare_complex_ &)
= default;
template<typename T>
internal_hare_complex_ &operator=(const internal_hare_complex_<T> &_) {
this._[0] = _.real();
this._[1] = _.imag();
return *this;
}
internal_hare_complex_ &operator+=(const T &_) {
this._[0] += _;
return *this;
}
template<typename T>
internal_hare_complex_ &operator+=(const internal_hare_complex_<T> &_) {
this._[0] += _.real();
this._[1] += _.imag();
return *this;
}
internal_hare_complex_ &operator-=(const T &_) {
this._[0] -= _;
return *this;
}
template<typename T>
internal_hare_complex_ &operator-=(const internal_hare_complex_<T> &_) {
this._[0] -= _.real();
this._[1] -= _.imag();
return *this;
}
internal_hare_complex_ &operator*=(const T &_) {
this._[0] *= _;
this._[1] *= _;
return *this;
}
template<typename T>
internal_hare_complex_ &operator*=(const internal_hare_complex_<T> &_) {
const T real = this._[0] * _.real() - this._[1] * _.imag();
this._[1] = this._[1] * _.real() + this._[0] * _.imag();
this._[0] = real;
return *this;
}
internal_hare_complex_ &operator/=(const T &_) {
this._[0] /= _;
this._[1] /= _;
return *this;
}
template<typename T>
internal_hare_complex_ &operator/=(const internal_hare_complex_<T> &_) {
const T d = _.real() * _.real() + _.imag() * _.imag();
const T real = this._[0] * _.real() + this._[1] * _.imag();
this._[1] = (this._[1] * _.real() - this._[0] * _.imag()) / d;
this._[0] = real / d;
return *this;
}
private:
T _[2];
};
template<typename T> inline internal_hare_complex_<T>
operator+(const internal_hare_complex_<T> &_1, const T &_2) {
internal_hare_complex_<T> _ = _1;
_ += _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator+(const T &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _2;
_ += _1;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator+(const internal_hare_complex_<T> &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _1;
_ += _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator-(const internal_hare_complex_<T> &_1, const T &_2) {
internal_hare_complex_<T> _ = _1;
_ -= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator-(const T &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = -_2;
_ += _1;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator-(const internal_hare_complex_<T> &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _1;
_ -= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator*(const internal_hare_complex_<T> &_1, const T &_2) {
internal_hare_complex_<T> _ = _1;
_ *= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator*(const T &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _2;
_ *= _1;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator*(const internal_hare_complex_<T> &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _1;
_ *= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator/(const internal_hare_complex_<T> &_1, const T &_2) {
internal_hare_complex_<T> _ = _1;
_ /= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator/(const T &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _1;
_ /= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator/(const internal_hare_complex_<T> &_1, const internal_hare_complex_<T> &_2) {
internal_hare_complex_<T> _ = _1;
_ /= _2;
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator+(const internal_hare_complex_<T> &_) {
return _;
}
template<typename T> inline internal_hare_complex_<T>
operator-(const internal_hare_complex_<T> &_) {
return internal_hare_complex_<T>(-_.real(), _.imag());
}
template<typename T> inline bool
operator==(const internal_hare_complex_<T> &_1, const T &_2) {
return _1.real() == _2 && _1.imag() == T();
}
template<typename T> inline bool
operator==(const T &_1, const internal_hare_complex_<T> &_2) {
return _2.real() == _1 && _2.imag() == T();
}
template<typename T> inline bool
operator==(const internal_hare_complex_<T> &_1, const internal_hare_complex_<T> &_2) {
return _1.real() == _2.real() && _1.imag() == _2.imag();
}
template<typename T> inline bool
operator!=(const internal_hare_complex_<T> &_1, const T &_2) {
return _1.real() != _2 || _1.imag() != T();
}
template<typename T> inline bool
operator!=(const T &_1, const internal_hare_complex_<T> &_2) {
return _2.real() != _1 || _2.imag() != T();
}
template<typename T> inline bool
operator!=(const internal_hare_complex_<T> &_1, const internal_hare_complex_<T> &_2) {
return _1.real() != _2.real() || _1.imag() != _2.imag();
}
#endif
/**
* A complex number containing a real component and an imaginary component,
* represented as two single-precision floating point numbers (with equivalent
* representation to math::complex::c64).
*/
using hare_c64 = internal_hare_complex_<float>;
/**
* A complex number containing a real component and an imaginary component,
* represented as two double-precision floating point numbers (with equivalent
* representation to math::complex::c128).
*/
using hare_c128 = internal_hare_complex_<double>;
/**
* The signed variant of size_t (equivalent to ssize_t on POSIX).
*/
using hare_ssize = std::make_signed<std::size_t>::type;
/**
* Macro which constructs a Hare-compatible slice type with a given member type.
* Its fields are data, len, and cap.
*
* In C++, this is a wrapper around the template hare_slice. This macro is
* provided for compatibility with C.
*
* Note: in C, two HARE_SLICEs with the same member type will NOT be compatible.
* You'll likely want to typedef the HARE_SLICE. This isn't an issue in C++.
*/
#define HARE_SLICE(...) hare_slice<__VA_ARGS__>
/**
* A Hare-compatible slice type.
*
* This template is C++ specific; C code should instead use the HARE_SLICE
* macro.
*/
template<typename T> struct hare_slice {
T *data;
std::size_t len, cap;
using const_reference = const T &;
using difference_type = std::ptrdiff_t;
using reference = T &;
using size_type = std::size_t;
// TODO: iterator, const_iterator
constexpr hare_slice() : data(nullptr), len(0), cap(0) {}
// TODO: hare_str-specific initializers
T *begin() {
return this.data;
}
T *end() {
return &this.data[this.len];
}
const T *cbegin() {
return this.data;
}
const T *cend() {
return &this.data[this.len];
}
// TODO: rbegin and rend?
size_type size() {
return this.len;
}
bool empty() {
return this.len == 0;
}
// TODO: operators
};
/**
* A type with equivalent representation to Hare's str type. Its fields are
* data, len, and cap.
*/
using hare_str = hare_slice<std::uint8_t>;
/**
* Converts a string literal into a hare_str. The string literal must either be
* unprefixed or u8-prefixed. The result is a constant expression.
*/
#define HARE_CONST_STR(S) internal_HARE_CONST_STR_(S)
#define internal_HARE_CONST_STR_(S) \
internal_HARE_STATIC_ASSERT_TYPE_("HARE_CONST_STR argument should be a \"string literal\"", \
hare_str, \
(#S[0] == '"' || (#S[0] == 'u' && #S[1] == '8' && #S[2] == '"')) \
&& #S[sizeof(#S) - 2] == '"') \
{ (std::uint8_t *)(S), sizeof(S) - 1, sizeof(S) }
#else
#include <stddef.h>
#include <stdint.h>
_Static_assert(__STDC_VERSION__ >= 201112L,
"hareconv-generated header depends on at least C11 or C++11");
#if __STDC_VERSION__ >= 202311L
#define HARE_NORETURN [[__noreturn__]]
#define HARE_PACKED [[__gnu__::__packed__]]
#define HARE_THREAD_LOCAL thread_local
#define internal_HARE_STATIC_ASSERT_(...) static_assert(__VA_ARGS__)
#define internal_HARE_TYPEOF_(...) typeof(__VA_ARGS__)
#if __has_c_attribute(__gnu__::__nonnull__)
#define HARE_NONNULL(...) [[__gnu__::__nonnull__(__VA_ARGS__)]]
#endif
#if __has_c_attribute(__gnu__::__returns_nonnull__)
#define HARE_RETURNS_NONNULL [[__gnu__::__returns_nonnull__]]
#endif
#else
#include <stdalign.h>
#include <stdbool.h>
#define HARE_NORETURN _Noreturn
#define HARE_PACKED __attribute__((__packed__))
#define HARE_THREAD_LOCAL _Thread_local
#define internal_HARE_STATIC_ASSERT_(...) _Static_assert(__VA_ARGS__)
#define internal_HARE_TYPEOF_(...) __typeof__(__VA_ARGS__)
#endif
#define internal_HARE_STATIC_ASSERT_EXPR_(...) \
((void)sizeof(struct { int _; \
internal_HARE_STATIC_ASSERT_(__VA_ARGS__); }))
#define internal_HARE_STATIC_ASSERT_TYPE_(MSG, TYPE, ...) \
internal_HARE_TYPEOF_(*(struct { \
internal_HARE_TYPEOF_(TYPE) *_; \
internal_HARE_STATIC_ASSERT_(__VA_ARGS__, MSG); \
}){ 0 }._)
#define internal_HARE_TYPE_EQ_(A, B) _Generic((A), B: 1, default: 0)
#define internal_HARE_IF_TYPE_(COND, CASE1, VAL1, CASE2, VAL2) \
_Generic((COND){ 0 }, CASE1: VAL1, CASE2: VAL2)
#ifdef __has_include
#if __has_include(<assert.h>)
#define internal_HARE_HAS_ASSERT_
#endif
#endif
#if __STDC_HOSTED__ || defined(interal_HARE_HAS_ASSERT_)
#include <assert.h> // required by HARE_TYPE_ASSERT
#undef internal_HARE_HAS_ASSERT_
#endif
#ifndef __STDC_NO_COMPLEX__
/**
* A complex number containing a real component and an imaginary component,
* represented as two single-precision floating point numbers (with equivalent
* representation to math::complex::c64).
*/
typedef float _Complex hare_c64;
/**
* A complex number containing a real component and an imaginary component,
* represented as two double-precision floating point numbers (with equivalent
* representation to math::complex::c128).
*/
typedef double _Complex hare_c128;
#else
/**
* A complex number containing a real component and an imaginary component,
* represented as two single-precision floating point numbers (with equivalent
* representation to math::complex::c64).
*/
typedef struct { float real, imag; } hare_c64;
/**
* A complex number containing a real component and an imaginary component,
* represented as two double-precision floating point numbers (with equivalent
* representation to math::complex::c128).
*/
typedef struct { double real, imag; } hare_c128;
#endif
#if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
#include <sys/types.h>
/**
* The signed variant of size_t (equivalent to ssize_t on POSIX).
*/
typedef ssize_t hare_ssize;
#else
/**
* The signed variant of size_t (equivalent to ssize_t on POSIX).
*/
typedef internal_HARE_TYPEOF_(_Generic((size_t)0,
unsigned short: (short)0,
unsigned int: 0,
unsigned long: 0l,
unsigned long long: 0ll)
) hare_ssize;
#endif
/**
* Macro which constructs a Hare-compatible slice type with a given member type.
* Its fields are data, len, and cap.
*
* In C++, this is a wrapper around the template hare_slice. This macro is
* provided for compatibility with C.
*
* Note: in C, two HARE_SLICEs with the same member type will NOT be compatible.
* You'll likely want to typedef the HARE_SLICE. This isn't an issue in C++.
*/
#define HARE_SLICE(...) \
struct { internal_HARE_TYPEOF_(__VA_ARGS__) *data; size_t len, cap; }
/**
* A type with equivalent representation to Hare's str type. Its fields are
* data, len, and cap.
*/
typedef HARE_SLICE(uint8_t) hare_str;
/**
* Converts a string literal into a hare_str. The string literal must either be
* unprefixed or u8-prefixed. The result is a constant expression.
*/
#define HARE_CONST_STR(S) \
(internal_HARE_STATIC_ASSERT_TYPE_("HARE_CONST_STR argument should be a \"string literal\"", \
hare_str, \
/* type must be unqualified char [] for unprefixed string literal, or
* unqualified char8_t [] for u8-prefixed string literal */ \
internal_HARE_TYPE_EQ_(&(S), char (*)[sizeof(S)]) \
|| internal_HARE_TYPE_EQ_(S, uint8_t (*)[sizeof(S)]))) \
{ (uint8_t *)(S), sizeof(S) - 1, sizeof(S) }
#endif
#if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
#include <sys/types.h>
/**
* A type with equivalent representation to io::off.
*/
typedef off_t hare_off;
#else
/**
* A type with equivalent representation to io::off.
*/
typedef long hare_off;
#endif
#ifdef __has_attribute
#if !defined(HARE_NONNULL) && __has_attribute(__nonnull__)
#define HARE_NONNULL(...) __attribute__((__nonnull__(__VA_ARGS__)))
#endif
#if !defined(HARE_RETURNS_NONNULL) && __has_attribute(__returns_nonnull__)
#define HARE_RETURNS_NONNULL __attribute__((__returns_nonnull__))
#endif
#endif
#ifndef HARE_NONNULL
#define HARE_NONNULL(...)
#endif
#ifndef HARE_RETURNS_NONNULL
#define HARE_RETURNS_NONNULL
#endif
/**
* Converts a hare_str into a const char *. It is your responsibility to ensure
* that the hare_str is nul-terminated, or that you're taking the string's
* length into consideration.
*
* Essentially, this macro just casts the data field to const char *. But using
* this macro means you don't need to write a pointer cast yourself, so it's
* safer and less error-prone. This macro also provides some additional type
* checking, which the compiler may not otherwise catch if you did the cast
* yourself.
*/
#define HARE_STR_DATA(S) \
((internal_HARE_STATIC_ASSERT_TYPE_("HARE_STR_DATA argument should be a hare_str", \
const char *, \
internal_HARE_TYPE_EQ_(S, hare_str))) \
(S).data)
#define internal_HARE_ASSERT_TAG_CONST_(TAG, MACRO_NAME) \
internal_HARE_STATIC_ASSERT_EXPR_( \
internal_HARE_TYPE_EQ_((internal_HARE_TYPEOF_(TAG) *)0, uint32_t *), \
MACRO_NAME "'s TAG argument should be a HARE_TAG_* constant")
#if defined(__clang__)
#define internal_HARE_DIAGNOSTIC_PUSH_ _Pragma("clang diagnostic push")
#define internal_HARE_DIAGNOSTIC_IGNORE_UNREACHABLE_ _Pragma("clang diagnostic ignored \"-Wunreachable-code\"")
#define internal_HARE_DIAGNOSTIC_POP_ _Pragma("clang diagnostic pop")
#elif defined(__GNUC__)
#define internal_HARE_DIAGNOSTIC_PUSH_ _Pragma("GCC diagnostic push")
#define internal_HARE_DIAGNOSTIC_IGNORE_UNREACHABLE_ _Pragma("GCC diagnostic ignored \"-Wswitch-unreachable\"")
#define internal_HARE_DIAGNOSTIC_POP_ _Pragma("GCC diagnostic pop")
#else
#define internal_HARE_DIAGNOSTIC_PUSH_
#define internal_HARE_DIAGNOSTIC_IGNORE_UNREACHABLE_
#define internal_HARE_DIAGNOSTIC_POP_
#endif
// TODO: what's the deal with array members?
/**
* A match construct. This allows matching on tagged unions (and pointers
* thereof), with similar syntax and semantics as Hare's match expression. Note
* that this can only be used with tagged unions; not with nullable pointers
* like in Hare.
*
* The matching expression must be an lvalue.
*
* Compound braces around the body are required. Each case begins with HARE_CASE
* or HARE_DEFAULT. Each case has its own scope. The match statement is breaked
* from when the case ends, i.e. there's no implicit fallthrough.
*
* Each non-default case matches one member of the tagged union. It isn't
* possible to match tagged union subsets, nor is it possible to match on nested
* tags. There's currently no way to specify multiple tags in the same case. The
* best workaround there is to just use goto. It sucks, I know. You need to
* understand that this macro is a giant hack and it's a miracle that it works
* at all.
*
* Rudimentary compile-time type checking is performed on the matching
* expression, as well as on each case. That is, this macro verifies to the best
* of its ability that the matching expression is a tagged union, and that each
* case is a HARE_TAG_* constant which correctly describes a direct member of
* that tagged union. It will also error if you have a duplicate case.
*
* The break statement can be used to break from the match statement. If
* supported by the language (C2y or C++), the match statement can also be
* labelled.
*
* Note that, prior to C2y or C++17, unlabelled continue statements do NOT work
* within HARE_MATCH. As far as I know this is impossible to work around, which
* kinda sucks. If you're stuck with an older C version, use goto instead, or
* you can use labelled continue in older C++ versions.
*
* auto x = f(); // (int | str | void)
*
* // Match with value:
* HARE_MATCH (x) {
* HARE_CASE(i, HARE_TAG_int):
* int val = i + 1;
* printf("%d\n", val);
* HARE_CASE(s, HARE_TAG_str):
* const char *val = HARE_STR_DATA(s);
* printf("%.*s\n", (int)s.len, val);
* HARE_CASE(_, HARE_TAG_void):
* // no-op
* }
*
* // Match with pointer:
* int old;
* HARE_MATCH (x) {
* HARE_CASE(*i, HARE_TAG_int):
* old = *i;
* ++*i;
* HARE_DEFAULT:
* return;
* }
* assert(old + 1 == HARE_TYPE_ASSERT(x, HARE_TAG_int));
*/
#define HARE_MATCH(...) \
internal_HARE_DIAGNOSTIC_PUSH_ \
internal_HARE_DIAGNOSTIC_IGNORE_UNREACHABLE_ \
internal_HARE_MATCH_HEADER_(__VA_ARGS__) \
/* ok, so, HARE_MATCH is designed to mimic the syntax and semantics of
* hare's match construct as closely as possible. this means that
* there's no need to break from each case (there's no fallthrough), and
* each case gets its own implicit compound statement (and thus its own
* scope). one notable deviation from hare is that HARE_MATCH can be
* breaked from, just like switch statements. finally, braces are
* required, just like in hare. we can use the brace requirement to our
* advantage: we can't start a compound statement within the macro
* itself, because then there's no way to close it. this goes both for
* HARE_MATCH and HARE_CASE. but the user will start and end a compound
* statement. so we can use an if-else-if chain to have multiple
* compound statements in the switch body. note that the body itself
* isn't executed; it only jumps to the selected case within the body.
* so: we start off with an if statement, which opens a compound
* statement which will end up being empty. each case *closes* the
* previous compound statement, and opens a new one. the compound
* statement for the last case is closed by the user. this gives the
* implicit compound statements we want, and neatly separates each case
* into its own scope, without fucking with break */ \
if (true) \
internal_HARE_DIAGNOSTIC_POP_
// we need to create a binding for the expression being matched on, since each
// case creates a binding for it but it should only be evaluated once. a
// compound statement can't be used since there's no way to close it, so the
// only other option is to declare it in the switch header, or in a for loop
// header. introducing a for loop makes unlabelled continue statements stop
// working, which fuckin sucks, but declarations in switch headers are only
// supported as of c2y or c++17, so for older versions there's no way around it,
// sadly. but anyway yeah that's why this macro exists
#if __STDC_VERSION__ > 202311L || __cplusplus >= 201703L
#define internal_HARE_MATCH_HEADER_(...) \
switch (auto _ = &(__VA_ARGS__); \
internal_HARE_MATCH_SWITCH_VALUE_(__VA_ARGS__))
#else
// in c++, switch statements can be labelled. HARE_MATCH can also be labelled,
// but we don't want to allow continuing to this label, as would be allowed with
// a loop, so we start with dummy switch statement, so that's what ends up being
// labelled rather than the loop inside of it
#define internal_HARE_MATCH_HEADER_(...) \
switch (0) default: \
for (internal_HARE_TYPEOF_(__VA_ARGS__) *_ = &(__VA_ARGS__); _; _ = NULL) \
switch (internal_HARE_MATCH_SWITCH_VALUE_(__VA_ARGS__))
#endif
// switch on the tag, and also perform some type checks
#define internal_HARE_MATCH_SWITCH_VALUE_(...) \
(internal_HARE_STATIC_ASSERT_EXPR_( \
offsetof(internal_HARE_TYPEOF_(__VA_ARGS__), _tag) == 0 \
&& internal_HARE_TYPE_EQ_(_->_tag, uint32_t), \
"HARE_MATCH argument should be a tagged union"), \
_->_tag)
/**
* A case in a match statement; refer to HARE_MATCH. LET is the name of the
* binding to create for this case, or _ to not create any binding. It's copied
* by value by default, but the identifier can be optionally prefixed with *, in
* which case it's a pointer instead. TAG must be a HARE_TAG_* constant.
*/
#define HARE_CASE(LET, TAG) internal_HARE_CASE_1_(__LINE__, LET, TAG)
#define internal_HARE_CASE_1_(LINE, LET, TAG) \
internal_HARE_CASE_2_(LINE, LET, TAG)
#define internal_HARE_CASE_2_(LINE, LET, TAG) \
/* see the big-ass comment in HARE_MATCH for a description of what the
* hell is going on here */ \
} else if (true) { \
internal_HARE_ASSERT_TAG_CONST_(TAG, "HARE_CASE"); \
case TAG:; \
/* the user may use _ as the binding name if they intend to not use it.
* _ is also used in the initializer of the binding, so the initializer
* must be evaluated before the new binding is inserted into the scope.
* this necessitates a temporary variable */ \
internal_HARE_TYPEOF_(_->_##TAG._value) *_hare_##LINE = \
&_->_##TAG._value; \
/* LET can optionally be prefixed by *, in which case the binding is a
* pointer, rather than a copied value. this means that we can't use LET
* to refer to the binding, since if it's a pointer then it'll be
* dereferenced. this is an issue when trying to take its type. the
* solution i came up with is to create a function type with a parameter
* named LET; this parameter will have the same type as the binding LET.
* so then we can have cases for whether the function's parameter is a
* pointer or not */ \
internal_HARE_TYPEOF_(_->_##TAG._value) LET = *internal_HARE_IF_TYPE_( \
void (*)(internal_HARE_TYPEOF_(*_hare_##LINE) LET), \
void (*)(internal_HARE_TYPEOF_(*_hare_##LINE)), _hare_##LINE, \
void (*)(internal_HARE_TYPEOF_(_hare_##LINE)), &_hare_##LINE); \
/* if the binding is _, then don't warn if it's unused. it should warn
* otherwise though */ \
(void)_; \
{ \
/* some checks related to bindings named _ */ \
struct _ { int _; } *_; (void)_; \
internal_HARE_STATIC_ASSERT_( \
!internal_HARE_TYPE_EQ_(LET, struct _), \
"Can't have pointer to no binding (*_)"); \
internal_HARE_STATIC_ASSERT_( \
internal_HARE_TYPE_EQ_(LET, struct _) \
|| internal_HARE_TYPE_EQ_(LET, struct _ *) \
|| !internal_HARE_TYPE_EQ_(_hare_##LINE, \
struct hare_zero_sized_type *), \
"Can't make binding for zero-sized type; use _ instead"); \
} \
goto _hare_##TAG##LINE; _hare_##TAG##LINE
/**
* A "default" catch-all case in a match statement; refer to HARE_MATCH.
*/
#define HARE_DEFAULT } else if (true) { default
/**
* Retrieves the value in TU of the tagged union member described by TAG, which
* must be a HARE_TAG_* constant. This behaves like Hare's cast operator, except
* it's subject to the same limitations as HARE_MATCH (i.e. no subsets and no
* nested subtypes).
*/
#define HARE_TAGGED_CAST(TU, TAG) internal_HARE_TAGGED_CAST_1_(TU, TAG)
#define internal_HARE_TAGGED_CAST_1_(TU, TAG) \
internal_HARE_TAGGED_CAST_2_(TU, TAG)
#define internal_HARE_TAGGED_CAST_2_(TU, TAG) \
(internal_HARE_ASSERT_TAG_CONST_(TAG, "HARE_TAGGED_CAST"), \
(TU)._##TAG._value)
#ifdef assert
/**
* Similar to HARE_TAGGED_CAST, but it also asserts that the specified TAG
* matches the tagged union's actual tag, like the 'as' operator in Hare.
*
* XXX: the assertion is currently a no-op if NDEBUG is defined; i'd like to
* change this to ignore NDEBUG
*/
#define HARE_TYPE_ASSERT(TU, TAG) internal_HARE_TYPE_ASSERT_1_(TU, TAG)
#define internal_HARE_TYPE_ASSERT_1_(TU, TAG) \
internal_HARE_TYPE_ASSERT_2_(TU, TAG)
#define internal_HARE_TYPE_ASSERT_2_(TU, TAG) \
(internal_HARE_ASSERT_TAG_CONST_(TAG, "HARE_TYPE_ASSERT"), \
(void)assert((TU)._tag == TAG), (TU)._##TAG._value)
#endif
/**
* Checks if the tag of the tagged union TU is TAG, which must be a HARE_TAG_*
* constant. A compile-time error is emitted if TAG doesn't describe a valid
* member of the tagged union, subject to the same limitations as HARE_MATCH
* (i.e. no subsets and no nested subtypes). This is analogous to Hare's 'is'
* operator.
*/
#define HARE_TYPE_TEST(TU, TAG) internal_HARE_TYPE_TEST_1_(TU, TAG)
#define internal_HARE_TYPE_TEST_1_(TU, TAG) \
internal_HARE_TYPE_TEST_2_(TU, TAG)
#define internal_HARE_TYPE_TEST_2_(TU, TAG) \
(internal_HARE_ASSERT_TAG_CONST_(TAG, "HARE_TYPE_TEST"), \
/* error out if tag isn't a member of the tagged union */ \
(void)(TU)._##TAG._value, \
(TU)._tag == TAG)
struct hare_zero_sized_type {
uint32_t _;
};
/**
* A stand-in for Hare's done type.
*/
typedef void hare_done;
/**
* A stand-in for Hare's nomem type.
*/
typedef void hare_nomem;
/**
* A type with equivalent representation to Hare's rune type.
*/
typedef uint32_t hare_rune;
#define HARE_TAG_bool UINT32_C($tag_bool)
#define HARE_TAG_done UINT32_C($tag_done)
#define HARE_TAG_f32 UINT32_C($tag_f32)
#define HARE_TAG_f64 UINT32_C($tag_f64)
#define HARE_TAG_i16 UINT32_C($tag_i16)
#define HARE_TAG_i32 UINT32_C($tag_i32)
#define HARE_TAG_i64 UINT32_C($tag_i64)
#define HARE_TAG_i8 UINT32_C($tag_i8)
#define HARE_TAG_int UINT32_C($tag_int)
#define HARE_TAG_never UINT32_C($tag_never)
#define HARE_TAG_nomem UINT32_C($tag_nomem)
#define HARE_TAG_null UINT32_C($tag_null)
#define HARE_TAG_opaque UINT32_C($tag_opaque)
#define HARE_TAG_rune UINT32_C($tag_rune)
#define HARE_TAG_size UINT32_C($tag_size)
#define HARE_TAG_str UINT32_C($tag_str)
#define HARE_TAG_u16 UINT32_C($tag_u16)
#define HARE_TAG_u32 UINT32_C($tag_u32)
#define HARE_TAG_u64 UINT32_C($tag_u64)
#define HARE_TAG_u8 UINT32_C($tag_u8)
#define HARE_TAG_uint UINT32_C($tag_uint)
#define HARE_TAG_uintptr UINT32_C($tag_uintptr)
#define HARE_TAG_valist UINT32_C($tag_valist)
#define HARE_TAG_void UINT32_C(0)
#endif
`;