Identify files by (device, inode), not by path, so that symlinks,
relative paths, case-insensitive paths, or other edge cases
do not result in double includes.
* 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
- FIX: `Label & const` was not actually doing the `& const` masking
(fixes#1446)
- ADD: `LOW(Label)` can be constant if `Label` is aligned to 8 or more bits
(resolves#1444)
- ADD: `!expr` can be constant 0 if `expr` has any non-zero bits
(resolves#1447)
- `LOW()` and `HIGH()` have their own RPN operator values
(resolves#1445)
The change to RPN values means that the object file version was incremented.
This also refactors unary operators and functions, combining their
evaluation similarly to binary ones.
`std::visit` is (arguably) cleaner code, but older versions of gcc
and clang (not very old; the ones packaged with Ubuntu 22.04 LTS)
compile them as tables of function pointers, instead of efficient
jump tables.
This is a work in progress: its performance is unacceptably slow,
and it is obviously not a complete refactoring:
- The parser's semantic functions are still written for C-style
strings, taking `.c_str()` pointers instead of `std::string`
references (and using their methods, `<algorithm>`s, etc).
- Quoted string literals from the lexer still use our `String`
struct, which wraps around a fixed-size char array.
- Symbol values, macro arguments, and so forth are still pointers
to C-style strings with unclear ownership semantics (i.e. we
still have "leaks as a feature").