Use a custom generic tagged union Either instead of std::variant for efficiency (#1476)

* Implement custom generic tagged union `Either`

This should be more efficient than `std::variant`, while still
keeping runtime safety as it `assert`s when `get`ting values.

* Use `Either` for RPN expressions

* Use `Either` for file stack node data

* Use `Either` for `File` buffer

* Use `Either` for `STRFMT` args

* Use `Either` for RGBLINK symbol values

* Support an equivalent of `std::monostate` for `Either`

* Use `Either` for lexer tokens

* Use `Either` for symbol values

* Use `Either` for lexer mmap/buffer state
This commit is contained in:
Sylvie
2024-08-20 15:19:11 -04:00
committed by GitHub
parent 7d98b9a900
commit 57c3d74b9e
17 changed files with 303 additions and 174 deletions

View File

@@ -10,8 +10,8 @@
#include <streambuf>
#include <string.h>
#include <string>
#include <variant>
#include "either.hpp"
#include "helpers.hpp" // assume
#include "platform.hpp"
@@ -19,7 +19,7 @@
class File {
// Construct a `std::streambuf *` by default, since it's probably lighter than a `filebuf`.
std::variant<std::streambuf *, std::filebuf> _file;
Either<std::streambuf *, std::filebuf> _file;
public:
File() {}
@@ -31,7 +31,8 @@ public:
*/
File *open(std::string const &path, std::ios_base::openmode mode) {
if (path != "-") {
return _file.emplace<std::filebuf>().open(path, mode) ? this : nullptr;
_file.emplace<std::filebuf>();
return _file.get<std::filebuf>().open(path, mode) ? this : nullptr;
} else if (mode & std::ios_base::in) {
assume(!(mode & std::ios_base::out));
_file.emplace<std::streambuf *>(std::cin.rdbuf());
@@ -49,8 +50,8 @@ public:
return this;
}
std::streambuf &operator*() {
auto *file = std::get_if<std::filebuf>(&_file);
return file ? *file : *std::get<std::streambuf *>(_file);
return _file.holds<std::filebuf>() ? _file.get<std::filebuf>()
: *_file.get<std::streambuf *>();
}
std::streambuf const &operator*() const {
// The non-`const` version does not perform any modifications, so it's okay.
@@ -63,22 +64,23 @@ public:
}
File *close() {
if (auto *file = std::get_if<std::filebuf>(&_file); file) {
if (_file.holds<std::filebuf>()) {
// This is called by the destructor, and an explicit `close` shouldn't close twice.
std::filebuf fileBuf = std::move(_file.get<std::filebuf>());
_file.emplace<std::streambuf *>(nullptr);
if (file->close() != nullptr) {
if (fileBuf.close() != nullptr) {
return this;
}
} else if (std::get<std::streambuf *>(_file) != nullptr) {
} else if (_file.get<std::streambuf *>() != nullptr) {
return this;
}
return nullptr;
}
char const *c_str(std::string const &path) const {
return std::holds_alternative<std::filebuf>(_file) ? path.c_str()
: std::get<std::streambuf *>(_file) == std::cin.rdbuf() ? "<stdin>"
: "<stdout>";
return _file.holds<std::filebuf>() ? path.c_str()
: _file.get<std::streambuf *>() == std::cin.rdbuf() ? "<stdin>"
: "<stdout>";
}
};