mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Compare commits
52 Commits
v1.0.0-rc1
...
v1.0.0-rc2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
268b586c9d | ||
|
|
85d3b5df58 | ||
|
|
eea277ae9c | ||
|
|
538395b92c | ||
|
|
3108fb5297 | ||
|
|
f99591bf6f | ||
|
|
0297da4d4c | ||
|
|
96b953fe51 | ||
|
|
0670c03bc2 | ||
|
|
09ef5b7e06 | ||
|
|
d5bb462f25 | ||
|
|
b0727e9779 | ||
|
|
ca4b890273 | ||
|
|
96a75500d3 | ||
|
|
634fd853d1 | ||
|
|
c8d22d8744 | ||
|
|
3ece61b103 | ||
|
|
a82fd17529 | ||
|
|
e7f5ab3f55 | ||
|
|
595c87b2f8 | ||
|
|
c49a7d1e2f | ||
|
|
d8aff148bb | ||
|
|
e31bcabbaa | ||
|
|
67741ab428 | ||
|
|
e0a6199f83 | ||
|
|
6cffd991f7 | ||
|
|
1fdeb34e50 | ||
|
|
9f16881d64 | ||
|
|
223b3d1921 | ||
|
|
65d408eb5d | ||
|
|
3677ab2ebf | ||
|
|
d404621e0d | ||
|
|
c8a088f281 | ||
|
|
94ed28acf8 | ||
|
|
00b5077b2a | ||
|
|
024b33b63a | ||
|
|
dcc10cebc3 | ||
|
|
1fc9ba86c4 | ||
|
|
e569e0c200 | ||
|
|
f7fb3af615 | ||
|
|
1dfc1d3231 | ||
|
|
891e6f98df | ||
|
|
bdc885bd69 | ||
|
|
5b67dc94b6 | ||
|
|
4f702a4be8 | ||
|
|
c5d437ab3c | ||
|
|
c5c2800f17 | ||
|
|
c798500563 | ||
|
|
590d113e94 | ||
|
|
ee1db0a582 | ||
|
|
5701d747d4 | ||
|
|
2110aaca20 |
@@ -2,7 +2,7 @@
|
|||||||
root = true
|
root = true
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = tab
|
indent_size = tab
|
||||||
tab_width = 8
|
tab_width = 4
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|||||||
261
ARCHITECTURE.md
261
ARCHITECTURE.md
@@ -1,17 +1,35 @@
|
|||||||
# RGBDS Architecture
|
# RGBDS Architecture
|
||||||
|
|
||||||
|
The RGBDS package consists of four programs: RGBASM, RGBLINK, RGBFIX, and RGBGFX.
|
||||||
|
|
||||||
|
- RGBASM is the assembler. It takes assembly code as input, and produces an RGB object file as output (and optionally a state file, logging the final state of variables and constants).
|
||||||
|
- RGBLINK is the linker. It takes object files as input, and produces a ROM file as output (and optionally a symbol and/or map file, logging where the assembly declarations got placed in the ROM).
|
||||||
|
- RGBFIX is the checksum/header fixer. It takes a ROM file as input, and outputs the same ROM file (or modifies it in-place) with the cartridge header's checksum and other metadata fixed for consistency.
|
||||||
|
- RGBGFX is the graphics converter. It takes a PNG image file as input, and outputs the tile data, palettes, tilemap, attribute map, and/or palette map in formats that the Game Boy can use.
|
||||||
|
|
||||||
|
In the simplest case, a single pipeline can turn an assembly file into a ROM:
|
||||||
|
|
||||||
|
```console
|
||||||
|
(rgbasm -o - - | rgblink -o - - | rgbfix -v -p 0) < game.asm > game.gb
|
||||||
|
```
|
||||||
|
|
||||||
|
This document describes how these four programs are structured. It goes over each source code file, noting which data is *global* (and thus scoped in all files), *owned* by that file (i.e. that is where the data's memory is managed, via [RAII](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization)) or *referenced* by that file (i.e. there are non-owning pointers to some data, and care must be taken to not dereference those pointers after the data's owner has moved or deleted the data).
|
||||||
|
|
||||||
|
We assume that the programs are single-threaded; data structures and operations may not be thread-safe.
|
||||||
|
|
||||||
## Folder Organization
|
## Folder Organization
|
||||||
|
|
||||||
The RGBDS source code file structure is as follows:
|
The RGBDS source code file structure is as follows:
|
||||||
|
|
||||||
```
|
```
|
||||||
.
|
rgbds/
|
||||||
├── .github/
|
├── .github/
|
||||||
│ ├── scripts/
|
│ ├── scripts/
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── contrib/
|
├── contrib/
|
||||||
|
│ ├── bash_compl/
|
||||||
│ ├── zsh_compl/
|
│ ├── zsh_compl/
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
│ └── ...
|
│ └── ...
|
||||||
@@ -31,39 +49,226 @@ The RGBDS source code file structure is as follows:
|
|||||||
│ ├── link/
|
│ ├── link/
|
||||||
│ │ └── ...
|
│ │ └── ...
|
||||||
│ ├── CMakeLists.txt
|
│ ├── CMakeLists.txt
|
||||||
|
│ ├── bison.sh
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── test/
|
├── test/
|
||||||
│ ├── ...
|
│ ├── fetch-test-deps.sh
|
||||||
│ └── run-tests.sh
|
│ ├── run-tests.sh
|
||||||
|
│ └── ...
|
||||||
├── .clang-format
|
├── .clang-format
|
||||||
|
├── .clang-tidy
|
||||||
├── CMakeLists.txt
|
├── CMakeLists.txt
|
||||||
├── compile_flags.txt
|
├── compile_flags.txt
|
||||||
├── Dockerfile
|
├── Dockerfile
|
||||||
└── Makefile
|
└── Makefile
|
||||||
```
|
```
|
||||||
|
|
||||||
- `.github/` - files and scripts related to the integration of the RGBDS codebase with
|
- **`.github/`:**
|
||||||
GitHub.
|
Files related to the integration of the RGBDS codebase with GitHub features.
|
||||||
* `scripts/` - scripts used by workflow files.
|
* **`scripts/`:**
|
||||||
* `workflows/` - CI workflow description files.
|
Scripts used by GitHub Actions workflow files.
|
||||||
- `contrib/` - scripts and other resources which may be useful to users and developers of
|
* **`workflows/`:**
|
||||||
RGBDS.
|
GitHub Actions CI workflow description files. Used for automated testing, deployment, etc.
|
||||||
* `zsh_compl` - contains tab completion scripts for use with zsh. Put them somewhere in
|
- **`contrib/`:**
|
||||||
your `fpath`, and they should auto-load.
|
Scripts and other resources which may be useful to RGBDS users and developers.
|
||||||
* `bash_compl` - contains tab completion scripts for use with bash. Run them with `source`
|
* **`bash_compl/`:**
|
||||||
somewhere in your `.bashrc`, and they should load every time you open a shell.
|
Tab completion scripts for use with `bash`. Run them with `source` somewhere in your `.bashrc`, and they should auto-load when you open a shell.
|
||||||
- `include/` - header files for the respective source files in `src`.
|
* **`zsh_compl/`:**
|
||||||
- `man/` - manual pages.
|
Tab completion scripts for use with `zsh`. Put them somewhere in your `fpath`, and they should auto-load when you open a shell.
|
||||||
- `src/` - source code of RGBDS.
|
- **`include/`:**
|
||||||
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
|
Header files for the respective source files in `src`.
|
||||||
(RGBASM's code is in `src/asm/`, for example). `src/extern/` contains code imported from
|
- **`man/`:**
|
||||||
external sources.
|
Manual pages to be read with `man`, written in the [`mandoc`](https://mandoc.bsd.lv) dialect.
|
||||||
- `test/` - testing framework used to verify that changes to the code don't break or
|
- **`src/`:**
|
||||||
modify the behavior of RGBDS.
|
Source code of RGBDS.
|
||||||
- `.clang-format` - code style for automated C++ formatting with
|
* **`asm/`:**
|
||||||
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
Source code of RGBASM.
|
||||||
- `CMakeLists.txt` - defines how to build RGBDS with CMake.
|
* **`extern/`:**
|
||||||
- `compile_flags.txt` - compiler flags for C++ static analysis with
|
Source code copied from external sources.
|
||||||
[`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/).
|
* **`fix/`:**
|
||||||
- `Dockerfile` - defines how to build RGBDS with Docker.
|
Source code of RGBFIX.
|
||||||
- `Makefile` - defines how to build RGBDS with `make`.
|
* **`gfx/`:**
|
||||||
|
Source code of RGBGFX.
|
||||||
|
* **`link/`:**
|
||||||
|
Source code of RGBLINK.
|
||||||
|
* **`CMakeLists.txt`:**
|
||||||
|
Defines how to build individual RGBDS programs with CMake, including the source files that each program depends on.
|
||||||
|
* **`bison.sh`:**
|
||||||
|
Script used to run the Bison parser generator with the latest flags that the user's version supports.
|
||||||
|
- **`test/`:**
|
||||||
|
Testing framework used to verify that changes to the code don't break or modify the behavior of RGBDS.
|
||||||
|
* **`fetch-test-deps.sh`:**
|
||||||
|
Script used to fetch dependencies for building external repositories. `fetch-test-deps.sh --help` describes its options.
|
||||||
|
* **`run-tests.sh`:**
|
||||||
|
Script used to run tests, including internal test cases and external repositories. `run-tests.sh --help` describes its options.
|
||||||
|
- **`.clang-format`:**
|
||||||
|
Code style for automated C++ formatting with [`clang-format`](https://clang.llvm.org/docs/ClangFormat.html) (for which we define the shortcut `make format`).
|
||||||
|
- **`.clang-tidy`:**
|
||||||
|
Configuration for C++ static analysis with [`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/) (for which we define the shortcut `make tidy`).
|
||||||
|
- **`CMakeLists.txt`:**
|
||||||
|
Defines how to build RGBDS with CMake.
|
||||||
|
- **`compile_flags.txt`:**
|
||||||
|
Compiler flags for `clang-tidy`.
|
||||||
|
- **`Dockerfile`:**
|
||||||
|
Defines how to build RGBDS with Docker (which we do in CI to provide a [container image](https://github.com/gbdev/rgbds/pkgs/container/rgbds)).
|
||||||
|
- **`Makefile`:**
|
||||||
|
Defines how to build RGBDS with `make`, including the source files that each program depends on.
|
||||||
|
|
||||||
|
## RGBDS
|
||||||
|
|
||||||
|
These files in the `src/` directory are shared across multiple programs: often all four (RGBASM, RGBLINK, RGBFIX, and RGBGFX), sometimes only RGBASM and RGBLINK.
|
||||||
|
|
||||||
|
- **`backtrace.cpp`:**
|
||||||
|
Generic printing of location backtraces for RGBASM and RGBLINK. Allows configuring backtrace styles with a command-line flag (conventionally `-B/--backtrace`). Renders warnings in yellow, errors in red, and locations in cyan.
|
||||||
|
- **`diagnostics.cpp`:**
|
||||||
|
Generic warning/error diagnostic support for all programs. Allows command-line flags (conventionally `-W`) to have `no-`, `error=`, or `no-error=` prefixes, and `=` level suffixes; allows "meta" flags to affect groups of individual flags; and counts how many total errors there have been. Every program has its own `warning.cpp` file that uses this.
|
||||||
|
- **`linkdefs.cpp`:**
|
||||||
|
Constants, data, and functions related to RGBDS object files, which are used for RGBASM output and RGBLINK input.
|
||||||
|
This file defines two *global* variables, `sectionTypeInfo` (metadata about each section type) and `sectionModNames` (names of section modifiers, for error reporting). RGBLINK may change some values in `sectionTypeInfo` depending on its command-line options (this only affects RGBLINK; `sectionTypeInfo` is immutable in RGBASM).
|
||||||
|
- **`opmath.cpp`:**
|
||||||
|
Functions for mathematical operations in RGBASM and RGBLINK that aren't trivially equivalent to built-in C++ ones, such as division and modulo with well-defined results for negative values.
|
||||||
|
- **`style.cpp`:**
|
||||||
|
Generic printing of cross-platform colored or bold text. Obeys the [`FORCE_COLOR`](https://force-color.org/) and [`NO_COLOR`](https://no-color.org/) environment variables, and allows configuring with a command-line flag (conventionally `--color`).
|
||||||
|
- **`usage.cpp`:**
|
||||||
|
Generic printing of usage information. Renders headings in green, flags in cyan, and URLs in blue. Every program has its own `main.cpp` file that uses this.
|
||||||
|
- **`util.cpp`:**
|
||||||
|
Utility functions applicable to most programs, mostly dealing with text strings, such as locale-independent character checks.
|
||||||
|
- **`verbosity.cpp`:**
|
||||||
|
Generic printing of messages conditionally at different verbosity levels. Allows configuring with a command-line flag (conventionally `-v/--verbose`).
|
||||||
|
- **`version.cpp`:**
|
||||||
|
RGBDS version number and string for all the programs.
|
||||||
|
|
||||||
|
## External
|
||||||
|
|
||||||
|
These files have been copied ("vendored") from external authors and adapted for use with RGBDS. Both of our vendored dependencies use the same MIT license as RGBDS.
|
||||||
|
|
||||||
|
- **`getopt.cpp`:**
|
||||||
|
Functions for parsing command-line options, including conventional single-dash and double-dash options.
|
||||||
|
This file defines some *global* `musl_opt*` variables, including `musl_optarg` (the argument given after an option flag) and `musl_optind` (the index of the next option in `argv`). Copied from [musl libc](https://musl.libc.org/).
|
||||||
|
- **`utf8decoder.cpp`:**
|
||||||
|
Function for decoding UTF-8 bytes into Unicode code points. Copied from [Björn Höhrmann](https://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
|
||||||
|
|
||||||
|
## RGBASM
|
||||||
|
|
||||||
|
- **`actions.cpp`:**
|
||||||
|
Actions taken by the assembly language parser, to avoid large amounts of code going in the parser.y file.
|
||||||
|
- **`charmap.cpp`:**
|
||||||
|
Functions and data related to charmaps.
|
||||||
|
This file *owns* the `Charmap`s in its `charmaps` collection. It also maintains a static `currentCharmap` pointer, and a `charmapStack` stack of pointers to `Charmap`s within `charmaps` (which is affected by `PUSHC` and `POPC` directives).
|
||||||
|
- **`fixpoint.cpp`:**
|
||||||
|
Functions for fixed-point math, with configurable [Q*m*.*n*](https://en.wikipedia.org/wiki/Q_(number_format)) precision.
|
||||||
|
- **`format.cpp`:**
|
||||||
|
`FormatSpec` methods for parsing and applying format specs, as used by `{interpolations}` and `STRFMT`.
|
||||||
|
- **`fstack.cpp`:**
|
||||||
|
Functions and data related to "fstack" nodes (the contents of top-level or `INCLUDE`d files, macro expansions, or `REPT`/`FOR` loop iterations) and their "contexts" (metadata that is only relevant while a node's content is being lexed and parsed).
|
||||||
|
This file *owns* the `Context`s in its `contextStack` collection. Each of those `Context`s *owns* its `LexerState`, and *refers* to its `FileStackNode`, `uniqueIDStr`, and `macroArgs`. Each `FileStackNode` also *references* its `parent`.
|
||||||
|
- **`lexer.cpp`:**
|
||||||
|
Functions and data related to [lexing](https://en.wikipedia.org/wiki/Lexical_analysis) assembly source code into tokens, which can then be parsed.
|
||||||
|
This file maintains static `lexerState` and `lexerStateEOL` pointers to `LexerState`s from the `Context`s in `fstack.cpp`.
|
||||||
|
Each `LexerState` *owns* its `content` and its `expansions`' content. Each `Expansion` (the contents of an `{interpolation}` or macro argument) in turn *owns* its `contents`.
|
||||||
|
The lexer and parser are interdependent: when the parser reaches certain tokens, it changes the lexer's mode, which affects how characters get lexed into tokens. For example, when the parser reaches a macro name, it changes the lexer to "raw" mode, which lexes the rest of the line as a sequence of string arguments to the macro.
|
||||||
|
- **`macro.cpp`:**
|
||||||
|
`MacroArgs` methods related to macro arguments. Each `MacroArgs` *references* its arguments' contents.
|
||||||
|
- **`main.cpp`:**
|
||||||
|
The `main` function for running RGBASM, including the initial handling of command-line options.
|
||||||
|
This file defines a *global* `options` variable with the parsed CLI options.
|
||||||
|
- **`opt.cpp`:**
|
||||||
|
Functions for parsing options specified by `OPT` or by certain command-line options.
|
||||||
|
This file *owns* the `OptStackEntry`s in its `stack` collection (which is affected by `PUSHO` and `POPO` directives).
|
||||||
|
- **`output.cpp`:**
|
||||||
|
Functions and data related to outputting object files (with `-o/--output`) and state files (with `-s/--state`).
|
||||||
|
This file *owns* its `assertions` (created by `ASSERT` and `STATIC_ASSERT` directives). Every assertion gets output in the object file.
|
||||||
|
This file also *references* some `fileStackNodes`, and maintains static pointers to `Symbol`s in `objectSymbols`. Only the "registered" symbols and fstack nodes get output in the object file. The `fileStackNodes` and `objectSymbols` collections keep track of which nodes and symbols have been registered for output.
|
||||||
|
- **`parser.y`:**
|
||||||
|
Grammar for the RGBASM assembly language, which Bison preprocesses into a [LALR(1) parser](https://en.wikipedia.org/wiki/LALR_parser).
|
||||||
|
The Bison-generated parser calls `yylex` (defined in `lexer.cpp`) to get the next token, and calls `yywrap` (defined in `fstack.cpp`) when the current context is out of tokens and returns `EOF`.
|
||||||
|
- **`rpn.cpp`:**
|
||||||
|
`Expression` methods and data related to "[RPN](https://en.wikipedia.org/wiki/Reverse_Polish_notation)" expressions. When a numeric expression is parsed, if its value cannot be calculated at assembly time, it is built up into a buffer of RPN-encoded operations to do so at link time by RGBLINK. The valid RPN operations are defined in [man/rgbds.5](man/rgbds.5).
|
||||||
|
- **`section.cpp`:**
|
||||||
|
Functions and data related to `SECTION`s.
|
||||||
|
This file *owns* the `Section`s in its `sections` collection. It also maintains various static pointers to those sections, including the `currentSection`, `currentLoadSection`, and `sectionStack` (which is affected by `PUSHS` and `POPS` directives). (Note that sections cannot be deleted.)
|
||||||
|
- **`symbol.cpp`:**
|
||||||
|
Functions and data related to symbols (labels, constants, variables, string constants, macros, etc).
|
||||||
|
This file *owns* the `Symbol`s in its `symbols` collection, and the various built-in ones outside that collection (`PCSymbol` for "`@`", `NARGSymbol` for "`_NARG`", etc). It also maintains a static `purgedSymbols` collection to remember which symbol names have been `PURGE`d from `symbols`, for error reporting purposes.
|
||||||
|
- **`warning.cpp`:**
|
||||||
|
Functions and data for warning and error output.
|
||||||
|
This file defines a *global* `warnings` variable using the `diagnostics.cpp` code for RGBASM-specific warning flags.
|
||||||
|
|
||||||
|
## RGBFIX
|
||||||
|
|
||||||
|
- **`fix.cpp`:**
|
||||||
|
Functions for fixing the ROM header.
|
||||||
|
- **`main.cpp`:**
|
||||||
|
The `main` function for running RGBFIX, including the initial handling of command-line options.
|
||||||
|
This file defines a *global* `options` variable with the parsed CLI options.
|
||||||
|
- **`mbc.cpp`:**
|
||||||
|
Functions and data related to [MBCs](https://gbdev.io/pandocs/MBCs.html), including the names of known MBC values.
|
||||||
|
- **`warning.cpp`:**
|
||||||
|
Functions and data for warning and error output.
|
||||||
|
This file defines a *global* `warnings` variable using the `diagnostics.cpp` code for RGBFIX-specific warning flags.
|
||||||
|
|
||||||
|
## RGBGFX
|
||||||
|
|
||||||
|
- **`color_set.cpp`:**
|
||||||
|
`ColorSet` methods for creating and comparing sets of colors. A color set includes the unique colors used by a single tile, and these sets are then packed into palettes.
|
||||||
|
- **`main.cpp`:**
|
||||||
|
The `main` function for running RGBGFX, including the initial handling of command-line options.
|
||||||
|
This file defines a *global* `options` variable with the parsed CLI options.
|
||||||
|
- **`pal_packing.cpp`:**
|
||||||
|
Functions for packing color sets into palettes. This is done with an ["overload-and-remove" heuristic](https://arxiv.org/abs/1605.00558) for a pagination algorithm.
|
||||||
|
- **`pal_sorting.cpp`:**
|
||||||
|
Functions for sorting colors within palettes, which works differently for grayscale, RGB, or indexed-color palettes.
|
||||||
|
- **`pal_spec.cpp`:**
|
||||||
|
Functions for parsing various formats of palette specifications (from `-c/--colors`).
|
||||||
|
- **`png.cpp`:**
|
||||||
|
`Png` methods for reading PNG image files, standardizing them to 8-bit RGBA pixels while also reading their indexed palette if there is one.
|
||||||
|
- **`process.cpp`:**
|
||||||
|
Functions related to generating and outputting files (tile data, palettes, tilemap, attribute map, and/or palette map).
|
||||||
|
- **`reverse.cpp`:**
|
||||||
|
Functions related to reverse-generating RGBGFX outputs into a PNG file (for `-r/--reverse`).
|
||||||
|
- **`rgba.cpp`:**
|
||||||
|
`Rgba` methods related to RGBA colors and their 8-bit or 5-bit representations.
|
||||||
|
- **`warning.cpp`:**
|
||||||
|
Functions and data for warning and error output.
|
||||||
|
This file defines a *global* `warnings` variable using the `diagnostics.cpp` code for RGBGFX-specific warning flags.
|
||||||
|
|
||||||
|
## RGBLINK
|
||||||
|
|
||||||
|
- **`assign.cpp`:**
|
||||||
|
Functions and data for assigning `SECTION`s to specific banks and addresses.
|
||||||
|
This file *owns* the `memory` table of free space: each section type is associated with a list of each bank's free address ranges, which are allocated to sections using a [first-fit decreasing](https://en.wikipedia.org/wiki/Bin_packing_problem#First-fit_algorithm) bin-packing algorithm.
|
||||||
|
- **`fstack.cpp`:**
|
||||||
|
Functions related to "fstack" nodes (the contents of top-level or `INCLUDE`d files, macro expansions, or `REPT`/`FOR` loop iterations) read from the object files. At link time, these nodes are only needed for printing of location backtraces.
|
||||||
|
- **`layout.cpp`:**
|
||||||
|
Actions taken by the linker script parser, to avoid large amounts of code going in the script.y file.
|
||||||
|
This file maintains some static data about the current bank and address layout, which get checked and updated for consistency as the linker script is parsed.
|
||||||
|
- **`lexer.cpp`:**
|
||||||
|
Functions and data related to [lexing](https://en.wikipedia.org/wiki/Lexical_analysis) linker script files into tokens, which can then be parsed.
|
||||||
|
This file *owns* the `LexerStackEntry`s in its `lexerStack` collection. Each of those `LexerStackEntry`s *owns* its `file`. The stack is updated as linker scripts can `INCLUDE` other linker script pieces.
|
||||||
|
The linker script lexer is simpler than the RGBASM one, and does not have modes.
|
||||||
|
- **`main.cpp`:**
|
||||||
|
The `main` function for running RGBLINK, including the initial handling of command-line options.
|
||||||
|
This file defines a *global* `options` variable with the parsed CLI options.
|
||||||
|
- **`object.cpp`:**
|
||||||
|
Functions and data for reading object files generated by RGBASM.
|
||||||
|
This file *owns* the `Symbol`s in its `symbolLists` collection, and the `FileStackNode`s in its `nodes` collection.
|
||||||
|
- **`output.cpp`:**
|
||||||
|
Functions and data related to outputting ROM files (with `-o/--output`), symbol files (with `-n/--sym`), and map files (with `-m/--map`).
|
||||||
|
This file *references* some `Symbol`s and `Section`s, in collections that keep them sorted by address and name, which allows the symbol and map output to be in order.
|
||||||
|
- **`patch.cpp`:**
|
||||||
|
Functions and data related to "[RPN](https://en.wikipedia.org/wiki/Reverse_Polish_notation)" expression patches read from the object files, including the ones for `ASSERT` conditions. After sections have been assigned specific locations, the RPN patches can have their values calculated and applied to the ROM. The valid RPN operations are defined in [man/rgbds.5](man/rgbds.5).
|
||||||
|
This file *owns* the `Assertion`s in its `assertions` collection, and the `RPNStackEntry`s in its `rpnStack` collection.
|
||||||
|
- **`script.y`:**
|
||||||
|
Grammar for the linker script language, which Bison preprocesses into a [LALR(1) parser](https://en.wikipedia.org/wiki/LALR_parser).
|
||||||
|
The Bison-generated parser calls `yylex` (defined in `lexer.cpp`) to get the next token, and calls `yywrap` (also defined in `lexer.cpp`) when the current context is out of tokens and returns `EOF`.
|
||||||
|
- **`sdas_obj.cpp`:**
|
||||||
|
Functions and data for reading object files generated by [GBDK with SDCC](https://gbdk.org/). RGBLINK support for these object files is incomplete.
|
||||||
|
- **`section.cpp`:**
|
||||||
|
Functions and data related to `SECTION`s read from the object files.
|
||||||
|
This file *owns* the `Section`s in its `sections` collection.
|
||||||
|
- **`symbol.cpp`:**
|
||||||
|
Functions and data related to symbols read from the object files.
|
||||||
|
This file *references* the `Symbol`s in its `symbols` and `localSymbols` collections, which allow accessing symbols by name.
|
||||||
|
- **`warning.cpp`:**
|
||||||
|
Functions and data for warning and error output.
|
||||||
|
This file defines a *global* `warnings` variable using the `diagnostics.cpp` code for RGBLINK-specific warning flags.
|
||||||
|
|||||||
130
CONTRIBUTING.md
130
CONTRIBUTING.md
@@ -51,8 +51,8 @@ but doesn't know that there's someone working on it.
|
|||||||
|
|
||||||
Note that you must contribute all your changes under the MIT License. If you are
|
Note that you must contribute all your changes under the MIT License. If you are
|
||||||
just modifying a file, you don't need to do anything (maybe update the copyright
|
just modifying a file, you don't need to do anything (maybe update the copyright
|
||||||
years). If you are adding new files, you need to use the `SPDX-License-Identifier: MIT`
|
years). If you are adding new files, you need to use the
|
||||||
header.
|
`SPDX-License-Identifier: MIT` header.
|
||||||
|
|
||||||
1. Fork this repository.
|
1. Fork this repository.
|
||||||
2. Checkout the `master` branch.
|
2. Checkout the `master` branch.
|
||||||
@@ -63,71 +63,117 @@ header.
|
|||||||
new warning (but it may be possible to remove some warning checks if it makes
|
new warning (but it may be possible to remove some warning checks if it makes
|
||||||
the code much easier).
|
the code much easier).
|
||||||
5. Test your changes by running `./run-tests.sh` in the `test` directory.
|
5. Test your changes by running `./run-tests.sh` in the `test` directory.
|
||||||
(You must run `./fetch-test-deps.sh` first; if you forget to, the test suite will fail and remind you mid-way.)
|
(You must run `./fetch-test-deps.sh` first; if you forget to, the test suite
|
||||||
|
will fail and remind you mid-way.)
|
||||||
5. Format your changes according to `clang-format`, which will reformat the
|
5. Format your changes according to `clang-format`, which will reformat the
|
||||||
coding style according to our standards defined in `.clang-format`.
|
coding style according to our standards defined in `.clang-format`.
|
||||||
6. Create a pull request against the branch `master`.
|
6. Create a pull request against the branch `master`.
|
||||||
7. Be prepared to get some comments about your code and to modify it. Tip: Use
|
7. Check the results of the GitHub Actions CI jobs for your pull request. The
|
||||||
|
"Code format checking" and "Regression testing" jobs should all succeed.
|
||||||
|
The "Diff completeness check" and "Static analysis" jobs should be manually
|
||||||
|
checked, as they may output warnings which do not count as failure errors.
|
||||||
|
The "Code coverage report" provides an
|
||||||
|
[LCOV](https://github.com/linux-test-project/lcov)-generated report which
|
||||||
|
can be downloaded and checked to see if your new code has full test coverage.
|
||||||
|
8. Be prepared to get some comments about your code and to modify it. Tip: Use
|
||||||
`git rebase -i origin/master` to modify chains of commits.
|
`git rebase -i origin/master` to modify chains of commits.
|
||||||
|
|
||||||
## Adding a test
|
## Adding a test
|
||||||
|
|
||||||
The test suite is a little ad-hoc, so the way tests work is different for each program being tested.
|
The test suite is a little ad-hoc, so the way tests work is different for each
|
||||||
|
program being tested.
|
||||||
|
|
||||||
Feel free to modify how the test scripts work, if the thing you want to test doesn't fit the existing scheme(s).
|
Feel free to modify how the test scripts work, if the thing you want to test
|
||||||
|
doesn't fit the existing scheme(s).
|
||||||
|
|
||||||
### RGBASM
|
### RGBASM
|
||||||
|
|
||||||
|
There are two kinds of test.
|
||||||
|
|
||||||
|
#### Simple tests
|
||||||
|
|
||||||
Each `.asm` file corresponds to one test.
|
Each `.asm` file corresponds to one test.
|
||||||
RGBASM will be invoked on the `.asm` file with all warnings enabled.
|
RGBASM will be invoked on the `.asm` file with all warnings enabled.
|
||||||
|
|
||||||
If a `.out` file exists, RGBASM's output (`print`, `println`, etc.) must match its contents.
|
If a `.flags` file exists, its first line contains flags to pass to RGBASM.
|
||||||
If a `.err` file exists, RGBASM's error output (`warn`, errors, etc.) must match its contents.
|
(There may be more lines, which will be ignored; they can serve as comments to
|
||||||
|
explain what the test is about.)
|
||||||
|
|
||||||
If a `.out.bin` file exists, the object file will be linked, and the generated ROM truncated to the length of the `.out.bin` file.
|
If a `.out` file exists, RGBASM's output (`print`, `println`, etc.) must match
|
||||||
|
its contents.
|
||||||
|
If a `.err` file exists, RGBASM's error output (`warn`, errors, etc.) must match
|
||||||
|
its contents.
|
||||||
|
|
||||||
|
If a `.out.bin` file exists, the object file will be linked, and the generated
|
||||||
|
ROM truncated to the length of the `.out.bin` file.
|
||||||
After that, the ROM must match the `.out.bin` file.
|
After that, the ROM must match the `.out.bin` file.
|
||||||
|
|
||||||
|
#### CLI tests
|
||||||
|
|
||||||
|
Each `.flags` file in `cli/` corresponds to one test.
|
||||||
|
RGBASM will be invoked, passing it the first line of the `.flags` file.
|
||||||
|
(There may be more lines, which will be ignored; they can serve as comments to
|
||||||
|
explain what the test is about.)
|
||||||
|
|
||||||
|
If a `.out` file exists, RGBASM's output (`print`, `println`, etc.) must match
|
||||||
|
its contents.
|
||||||
|
If a `.err` file exists, RGBASM's error output (`warn`, errors, etc.) must match
|
||||||
|
its contents.
|
||||||
|
|
||||||
### RGBLINK
|
### RGBLINK
|
||||||
|
|
||||||
Each `.asm` file corresponds to one test, or one *set* of tests.
|
Each `.asm` file corresponds to one test, or one *set* of tests.
|
||||||
|
|
||||||
All tests begin by assembling the `.asm` file into an object file, which will be linked in various ways depending on the test.
|
All tests begin by assembling the `.asm` file into an object file, which will be
|
||||||
|
linked in various ways depending on the test.
|
||||||
|
|
||||||
#### Simple tests
|
#### Simple tests
|
||||||
|
|
||||||
These simply check that RGBLINK's output matches some expected output.
|
These simply check that RGBLINK's output matches some expected output.
|
||||||
|
|
||||||
A `.out` file **must** exist, and RGBLINK's total output must match that file's contents.
|
A `.out` file **must** exist, and RGBLINK's total output must match that file's
|
||||||
|
contents.
|
||||||
|
|
||||||
Additionally, if a `.out.bin` file exists, the `.gb` file generated by RGBLINK must match it.
|
Additionally, if a `.out.bin` file exists, the `.gb` file generated by RGBLINK
|
||||||
|
must match it.
|
||||||
|
|
||||||
#### Linker script tests
|
#### Linker script tests
|
||||||
|
|
||||||
These allow applying various linker scripts to the same object file.
|
These allow applying various linker scripts to the same object file.
|
||||||
If one or more `.link` files exist, whose names start the same as the `.asm` file, then each of those files correspond to one test.
|
If one or more `.link` files exist, whose names start the same as the `.asm`
|
||||||
|
file, then each of those files correspond to one test.
|
||||||
|
|
||||||
Each `.link` linker script **must** be accompanied by a `.out` file, and RGBLINK's total output must match that file's contents when passed the corresponding linker script.
|
Each `.link` linker script **must** be accompanied by a `.out` file, and
|
||||||
|
RGBLINK's total output must match that file's contents when passed the
|
||||||
|
corresponding linker script.
|
||||||
|
|
||||||
#### Variant tests
|
#### Variant tests
|
||||||
|
|
||||||
These allow testing RGBLINK's `-d`, `-t`, and `-w` flags.
|
These allow testing RGBLINK's `-d`, `-t`, and `-w` flags.
|
||||||
If one or more <code>-<var><flag></var>.out</code> or <code>-no-<var><flag></var>.out</code> files exist, then each of them corresponds to one test.
|
If one or more
|
||||||
|
<code>-<var><flag></var>.out</code> or <code>-no-<var><flag></var>.out</code>
|
||||||
|
files exist, then each of them corresponds to one test.
|
||||||
|
|
||||||
The object file will be linked with and without said flag, respectively; and in each case, RGBLINK's total output must match the `.out` file's contents.
|
The object file will be linked with and without said flag, respectively; and in
|
||||||
|
each case, RGBLINK's total output must match the `.out` file's contents.
|
||||||
|
|
||||||
### RGBFIX
|
### RGBFIX
|
||||||
|
|
||||||
Each `.flags` file corresponds to one test.
|
Each `.flags` file corresponds to one test.
|
||||||
Each one is a text file whose first line contains flags to pass to RGBFIX.
|
Each one is a text file whose first line contains flags to pass to RGBFIX.
|
||||||
(There may be more lines, which will be ignored; they can serve as comments to explain what the test is about.)
|
(There may be more lines, which will be ignored; they can serve as comments to
|
||||||
|
explain what the test is about.)
|
||||||
|
|
||||||
RGBFIX will be invoked on the `.bin` file if it exists, or else on default-input.bin.
|
RGBFIX will be invoked on the `.bin` file if it exists, or else on
|
||||||
|
default-input.bin.
|
||||||
|
|
||||||
If no `.out` file exist, RGBFIX is not expected to output anything.
|
If no `.out` file exist, RGBFIX is not expected to output anything.
|
||||||
If one *does* exist, RGBFIX's output **must** match the `.out` file's contents.
|
If one *does* exist, RGBFIX's output **must** match the `.out` file's contents.
|
||||||
|
|
||||||
If no `.err` file exists, RGBFIX is simply expected to be able to process the file normally.
|
If no `.err` file exists, RGBFIX is simply expected to be able to process the
|
||||||
If one *does* exist, RGBFIX's return status is ignored, but its error output **must** match the `.err` file's contents.
|
file normally.
|
||||||
|
If one *does* exist, RGBFIX's return status is ignored, but its error output
|
||||||
|
**must** match the `.err` file's contents.
|
||||||
|
|
||||||
Additionally, if a `.gb` file exists, the output of RGBFIX must match the `.gb`.
|
Additionally, if a `.gb` file exists, the output of RGBFIX must match the `.gb`.
|
||||||
|
|
||||||
@@ -139,20 +185,27 @@ There are three kinds of test.
|
|||||||
|
|
||||||
Each `.png` file corresponds to one test.
|
Each `.png` file corresponds to one test.
|
||||||
RGBGFX will be invoked on the file.
|
RGBGFX will be invoked on the file.
|
||||||
If a `.flags` file exists, it will be used as part of the RGBGFX invocation (<code>@<var><file></var>.flags</code>).
|
If a `.flags` file exists, it will be used as part of the RGBGFX invocation
|
||||||
|
(<code>@<var><file></var>.flags</code>).
|
||||||
|
|
||||||
If `.out.1bpp`, `.out.2bpp`, `.out.pal`, `.out.tilemap`, `.out.attrmap`, or `.out.palmap` files exist, RGBGFX will create the corresponding kind of output, which must match the file's contents.
|
If `.out.1bpp`, `.out.2bpp`, `.out.pal`, `.out.tilemap`, `.out.attrmap`, or
|
||||||
|
`.out.palmap` files exist, RGBGFX will create the corresponding kind of output,
|
||||||
|
which must match the file's contents.
|
||||||
Multiple kinds of output may be tested for the same input.
|
Multiple kinds of output may be tested for the same input.
|
||||||
|
|
||||||
If no `.err` file exists, RGBGFX is simply expected to be able to process the file normally.
|
If no `.err` file exists, RGBGFX is simply expected to be able to process the
|
||||||
If one *does* exist, RGBGFX's return status is ignored, but its output **must** match the `.err` file's contents.
|
file normally.
|
||||||
|
If one *does* exist, RGBGFX's return status is ignored, but its output **must**
|
||||||
|
match the `.err` file's contents.
|
||||||
|
|
||||||
#### Reverse tests
|
#### Reverse tests
|
||||||
|
|
||||||
Each `.1bpp` or `.2bpp` file corresponds to one test.
|
Each `.1bpp` or `.2bpp` file corresponds to one test.
|
||||||
RGBGFX will be invoked on the file with `-r 1` for reverse mode, then invoked on the output without `-r 1`.
|
RGBGFX will be invoked on the file with `-r 1` for reverse mode, then invoked on
|
||||||
|
the output without `-r 1`.
|
||||||
The round-trip output must match the input file's contents.
|
The round-trip output must match the input file's contents.
|
||||||
If a `.flags` file exists, it will be used as part of the RGBGFX invocation (<code>@<var><file></var>.flags</code>).
|
If a `.flags` file exists, it will be used as part of the RGBGFX invocation
|
||||||
|
(<code>@<var><file></var>.flags</code>).
|
||||||
|
|
||||||
#### Random seed tests
|
#### Random seed tests
|
||||||
|
|
||||||
@@ -161,18 +214,24 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
|||||||
|
|
||||||
### Downstream projects
|
### Downstream projects
|
||||||
|
|
||||||
1. Make sure the downstream project supports <code>make <var><target></var> RGBDS=<var><path/to/RGBDS/></var></code>.
|
1. Make sure the downstream project supports
|
||||||
While the test suite supports any Make target name, only [Make](//gnu.org/software/make) is currently supported, and the Makefile must support a `RGBDS` variable to use a non-system RGBDS directory.
|
<code>make <var><target></var> RGBDS=<var><path/to/RGBDS/></var></code>.
|
||||||
|
While the test suite supports any Make target name, only
|
||||||
|
[Make](//gnu.org/software/make) is currently supported, and the Makefile must
|
||||||
|
support a `RGBDS` variable to use a non-system RGBDS directory.
|
||||||
|
|
||||||
Also, only projects hosted on GitHub are currently supported.
|
Also, only projects hosted on GitHub are currently supported.
|
||||||
2. Add the project to `test/fetch-test-deps.sh`: add a new `action` line at the bottom, following the existing pattern:
|
2. Add the project to `test/fetch-test-deps.sh`: add a new `action` line at the
|
||||||
|
bottom, following the existing pattern:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
action <owner> <repo> <date of last commit> <hash of last commit>
|
action <owner> <repo> <date of last commit> <hash of last commit>
|
||||||
```
|
```
|
||||||
|
|
||||||
(The date is used to avoid fetching too much history when cloning the repositories.)
|
(The date is used to avoid fetching too much history when cloning the
|
||||||
3. Add the project to `test/run-tests.sh`: add a new `test_downstream` line at the bottom, following the existing pattern:
|
repositories.)
|
||||||
|
3. Add the project to `test/run-tests.sh`: add a new `test_downstream` line at
|
||||||
|
the bottom, following the existing pattern:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
|
test_downstream <owner> <repo> <makefile target> <build file> <sha1 hash of build file>
|
||||||
@@ -180,11 +239,16 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
|||||||
|
|
||||||
## Container images
|
## Container images
|
||||||
|
|
||||||
The CI will [take care](https://github.com/gbdev/rgbds/blob/master/.github/workflows/build-container.yml) of updating the [rgbds container](https://github.com/gbdev/rgbds/pkgs/container/rgbds) image tagged `master`.
|
The CI will
|
||||||
|
[take care](https://github.com/gbdev/rgbds/blob/master/.github/workflows/build-container.yml)
|
||||||
|
of updating the
|
||||||
|
[rgbds container](https://github.com/gbdev/rgbds/pkgs/container/rgbds) image
|
||||||
|
tagged `master`.
|
||||||
|
|
||||||
When a git tag is pushed, the image is also tagged with that tag.
|
When a git tag is pushed, the image is also tagged with that tag.
|
||||||
|
|
||||||
The image can be built locally and pushed to the GitHub container registry by manually running:
|
The image can be built locally and pushed to the GitHub container registry by
|
||||||
|
manually running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# e.g. to build and tag as 'master'
|
# e.g. to build and tag as 'master'
|
||||||
|
|||||||
@@ -44,5 +44,5 @@ RGBGFX generates palettes using algorithms found in the paper
|
|||||||
([GitHub](https://github.com/pagination-problem/pagination), MIT license),
|
([GitHub](https://github.com/pagination-problem/pagination), MIT license),
|
||||||
by Aristide Grange, Imed Kacem, and Sébastien Martin.
|
by Aristide Grange, Imed Kacem, and Sébastien Martin.
|
||||||
|
|
||||||
RGBGFX's color palette was taken from [SameBoy](https://sameboy.github.io), with permission and help
|
RGBGFX's color palette was taken from [SameBoy](https://sameboy.github.io), with
|
||||||
by [LIJI](https://github.com/LIJI32).
|
permission and help by [LIJI](https://github.com/LIJI32).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
FROM debian:12-slim
|
FROM debian:12-slim
|
||||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||||
ARG version=1.0.0-rc1
|
ARG version=1.0.0-rc2
|
||||||
WORKDIR /rgbds
|
WORKDIR /rgbds
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -241,12 +241,12 @@ coverage:
|
|||||||
|
|
||||||
# Target used in development to format source code with clang-format.
|
# Target used in development to format source code with clang-format.
|
||||||
format:
|
format:
|
||||||
$Qclang-format -i $$(git ls-files 'include/**/*.hpp' 'src/**/*.cpp')
|
$Qclang-format -i $$(git ls-files '*.hpp' '*.cpp')
|
||||||
|
|
||||||
# Target used in development to check code with clang-tidy.
|
# Target used in development to check code with clang-tidy.
|
||||||
# Requires Bison-generated header files to exist.
|
# Requires Bison-generated header files to exist.
|
||||||
tidy: src/asm/parser.hpp src/link/script.hpp
|
tidy: src/asm/parser.hpp src/link/script.hpp
|
||||||
$Qclang-tidy -p . $$(git ls-files 'include/**/*.hpp' 'src/**/*.cpp')
|
$Qclang-tidy -p . $$(git ls-files '*.hpp' '*.cpp')
|
||||||
|
|
||||||
# Target used in development to remove unused `#include` headers.
|
# Target used in development to remove unused `#include` headers.
|
||||||
iwyu:
|
iwyu:
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
|
|
||||||
struct Expression;
|
struct Expression;
|
||||||
struct FileStackNode;
|
struct FileStackNode;
|
||||||
|
struct Symbol;
|
||||||
|
|
||||||
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
enum StateFeature { STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO, NB_STATE_FEATURES };
|
||||||
|
|
||||||
void out_RegisterNode(std::shared_ptr<FileStackNode> node);
|
void out_RegisterNode(std::shared_ptr<FileStackNode> node);
|
||||||
|
void out_RegisterSymbol(Symbol &sym);
|
||||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
|
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
|
||||||
void out_CreateAssert(
|
void out_CreateAssert(
|
||||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||||
|
|||||||
@@ -12,15 +12,25 @@
|
|||||||
|
|
||||||
struct Symbol;
|
struct Symbol;
|
||||||
|
|
||||||
|
struct RPNValue {
|
||||||
|
RPNCommand command; // The RPN_* command ID
|
||||||
|
std::variant<std::monostate, uint8_t, uint32_t, std::string> data; // Data after the ID, if any
|
||||||
|
|
||||||
|
RPNValue(RPNCommand cmd);
|
||||||
|
RPNValue(RPNCommand cmd, uint8_t val);
|
||||||
|
RPNValue(RPNCommand cmd, uint32_t val);
|
||||||
|
RPNValue(RPNCommand cmd, std::string const &name);
|
||||||
|
|
||||||
|
void appendEncoded(std::vector<uint8_t> &buffer) const;
|
||||||
|
};
|
||||||
|
|
||||||
struct Expression {
|
struct Expression {
|
||||||
std::variant<
|
std::variant<
|
||||||
int32_t, // If the expression's value is known, it's here
|
int32_t, // If the expression's value is known, it's here
|
||||||
std::string // Why the expression is not known, if it isn't
|
std::string // Why the expression is not known, if it isn't
|
||||||
>
|
>
|
||||||
data = 0;
|
data = 0;
|
||||||
bool isSymbol = false; // Whether the expression represents a symbol suitable for const diffing
|
std::vector<RPNValue> rpn{}; // Values to be serialized into the RPN expression
|
||||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
|
||||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
|
||||||
|
|
||||||
bool isKnown() const { return std::holds_alternative<int32_t>(data); }
|
bool isKnown() const { return std::holds_alternative<int32_t>(data); }
|
||||||
int32_t value() const { return std::get<int32_t>(data); }
|
int32_t value() const { return std::get<int32_t>(data); }
|
||||||
@@ -40,16 +50,13 @@ struct Expression {
|
|||||||
void makeUnaryOp(RPNCommand op, Expression &&src);
|
void makeUnaryOp(RPNCommand op, Expression &&src);
|
||||||
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
void makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||||
|
|
||||||
void makeCheckHRAM();
|
void addCheckHRAM();
|
||||||
void makeCheckRST();
|
void addCheckRST();
|
||||||
void makeCheckBitIndex(uint8_t mask);
|
void addCheckBitIndex(uint8_t mask);
|
||||||
|
|
||||||
void checkNBit(uint8_t n) const;
|
void checkNBit(uint8_t n) const;
|
||||||
|
|
||||||
private:
|
void encode(std::vector<uint8_t> &buffer) const;
|
||||||
void clear();
|
|
||||||
uint8_t *reserveSpace(uint32_t size);
|
|
||||||
uint8_t *reserveSpace(uint32_t size, uint32_t patchSize);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool checkNBit(int32_t v, uint8_t n, char const *name);
|
bool checkNBit(int32_t v, uint8_t n, char const *name);
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ struct Options {
|
|||||||
uint16_t height;
|
uint16_t height;
|
||||||
uint32_t right() const { return left + width * 8; }
|
uint32_t right() const { return left + width * 8; }
|
||||||
uint32_t bottom() const { return top + height * 8; }
|
uint32_t bottom() const { return top + height * 8; }
|
||||||
bool specified() const { return left || top || width || height; }
|
|
||||||
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
} inputSlice{0, 0, 0, 0}; // -L (margins in clockwise order, like CSS)
|
||||||
uint8_t basePalID = 0; // -l
|
uint8_t basePalID = 0; // -l
|
||||||
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
std::array<uint16_t, 2> maxNbTiles{UINT16_MAX, 0}; // -N
|
||||||
|
|||||||
@@ -18,10 +18,7 @@ struct Rgba {
|
|||||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||||
|
|
||||||
static constexpr Rgba fromCGBColor(uint16_t color) {
|
static constexpr Rgba fromCGBColor(uint16_t color) {
|
||||||
constexpr auto _5to8 = [](uint8_t channel) -> uint8_t {
|
constexpr auto _5to8 = [](uint8_t c) -> uint8_t { return ((c & 0b11111) * 255 + 15) / 31; };
|
||||||
channel &= 0b11111; // For caller's convenience
|
|
||||||
return channel << 3 | channel >> 2;
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
_5to8(color),
|
_5to8(color),
|
||||||
_5to8(color >> 5),
|
_5to8(color >> 5),
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ static inline int clz(unsigned int x) {
|
|||||||
#define EXPAND_AND_CAT(x, y) CAT(x, y)
|
#define EXPAND_AND_CAT(x, y) CAT(x, y)
|
||||||
|
|
||||||
// For lack of <ranges>, this adds some more brevity
|
// For lack of <ranges>, this adds some more brevity
|
||||||
#define RANGE(s) std::begin(s), std::end(s)
|
#define RANGE(s) std::begin(s), std::end(s)
|
||||||
|
#define RRANGE(s) std::rbegin(s), std::rend(s)
|
||||||
|
|
||||||
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
// MSVC does not inline `strlen()` or `.length()` of a constant string
|
||||||
template<int N>
|
template<int N>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ struct Section {
|
|||||||
// Extra info computed during linking
|
// Extra info computed during linking
|
||||||
std::vector<Symbol> *fileSymbols;
|
std::vector<Symbol> *fileSymbols;
|
||||||
std::vector<Symbol *> symbols;
|
std::vector<Symbol *> symbols;
|
||||||
std::unique_ptr<Section> nextu; // The next "component" of this unionized sect
|
std::unique_ptr<Section> nextPiece; // The next fragment or union "piece" of this section
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute a callback for each section currently registered.
|
// Execute a callback for each section currently registered.
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ struct Symbol {
|
|||||||
>
|
>
|
||||||
data;
|
data;
|
||||||
|
|
||||||
Label &label() { return std::get<Label>(data); }
|
void linkToSection(Section §ion);
|
||||||
Label const &label() const { return std::get<Label>(data); }
|
void fixSectionOffset();
|
||||||
};
|
};
|
||||||
|
|
||||||
void sym_ForEach(void (*callback)(Symbol &));
|
void sym_ForEach(void (*callback)(Symbol &));
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
#define setmode(fd, mode) (0)
|
#define setmode(fd, mode) (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// MingGW and Cygwin need POSIX functions which are not standard C explicitly enabled
|
// MingGW and Cygwin need POSIX functions which are not standard C explicitly enabled,
|
||||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -4,20 +4,33 @@
|
|||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctype.h>
|
#include <ctype.h> // toupper
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
enum NumberBase {
|
||||||
|
BASE_AUTO = 0,
|
||||||
|
BASE_2 = 2,
|
||||||
|
BASE_8 = 8,
|
||||||
|
BASE_10 = 10,
|
||||||
|
BASE_16 = 16,
|
||||||
|
};
|
||||||
|
|
||||||
bool isNewline(int c);
|
bool isNewline(int c);
|
||||||
bool isBlankSpace(int c);
|
bool isBlankSpace(int c);
|
||||||
bool isWhitespace(int c);
|
bool isWhitespace(int c);
|
||||||
bool isPrintable(int c);
|
bool isPrintable(int c);
|
||||||
|
bool isUpper(int c);
|
||||||
|
bool isLower(int c);
|
||||||
bool isLetter(int c);
|
bool isLetter(int c);
|
||||||
bool isDigit(int c);
|
bool isDigit(int c);
|
||||||
|
bool isBinDigit(int c);
|
||||||
bool isOctDigit(int c);
|
bool isOctDigit(int c);
|
||||||
bool isHexDigit(int c);
|
bool isHexDigit(int c);
|
||||||
bool isAlphanumeric(int c);
|
bool isAlphanumeric(int c);
|
||||||
@@ -25,6 +38,10 @@ bool isAlphanumeric(int c);
|
|||||||
bool startsIdentifier(int c);
|
bool startsIdentifier(int c);
|
||||||
bool continuesIdentifier(int c);
|
bool continuesIdentifier(int c);
|
||||||
|
|
||||||
|
uint8_t parseHexDigit(int c);
|
||||||
|
std::optional<uint64_t> parseNumber(char const *&str, NumberBase base = BASE_AUTO);
|
||||||
|
std::optional<uint64_t> parseWholeNumber(char const *str, NumberBase base = BASE_AUTO);
|
||||||
|
|
||||||
char const *printChar(int c);
|
char const *printChar(int c);
|
||||||
|
|
||||||
struct Uppercase {
|
struct Uppercase {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#ifndef RGBDS_VERBOSITY_HPP
|
#ifndef RGBDS_VERBOSITY_HPP
|
||||||
#define RGBDS_VERBOSITY_HPP
|
#define RGBDS_VERBOSITY_HPP
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
|
|
||||||
// This macro does not evaluate its arguments unless the condition is true.
|
// This macro does not evaluate its arguments unless the condition is true.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#define PACKAGE_VERSION_MAJOR 1
|
#define PACKAGE_VERSION_MAJOR 1
|
||||||
#define PACKAGE_VERSION_MINOR 0
|
#define PACKAGE_VERSION_MINOR 0
|
||||||
#define PACKAGE_VERSION_PATCH 0
|
#define PACKAGE_VERSION_PATCH 0
|
||||||
#define PACKAGE_VERSION_RC 1
|
#define PACKAGE_VERSION_RC 2
|
||||||
|
|
||||||
char const *get_package_version_string();
|
char const *get_package_version_string();
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt GBZ80 7
|
.Dt GBZ80 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
.Nm gbz80
|
.Nm gbz80
|
||||||
.Nd CPU opcode reference
|
.Nd Game Boy CPU instruction reference
|
||||||
.Sh DESCRIPTION
|
.Sh DESCRIPTION
|
||||||
This is the list of opcodes supported by
|
This is the list of instructions supported by
|
||||||
.Xr rgbasm 1 ,
|
.Xr rgbasm 1 ,
|
||||||
including a short description, the number of bytes needed to encode them and the number of CPU cycles at 1MHz (or 2MHz in GBC double speed mode) needed to complete them.
|
including a short description, the number of bytes needed to encode them and the number of CPU cycles at 1MHz (or 2MHz in GBC double speed mode) needed to complete them.
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBASM-OLD 5
|
.Dt RGBASM-OLD 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -371,7 +371,7 @@ Deprecated in 1.0.0.
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, use
|
Instead, use
|
||||||
.Dl -Wno-overwrite .
|
.Dl -Wno-overwrite .
|
||||||
.Ss rgbgfx -h
|
.Ss rgbgfx -h/--horizontal
|
||||||
Removed in 0.6.0.
|
Removed in 0.6.0.
|
||||||
.Pp
|
.Pp
|
||||||
Instead, use
|
Instead, use
|
||||||
@@ -461,6 +461,24 @@ Previously we had
|
|||||||
.Pp
|
.Pp
|
||||||
Instead, now we have
|
Instead, now we have
|
||||||
.Ql p ** q ** r == p ** (q ** r) .
|
.Ql p ** q ** r == p ** (q ** r) .
|
||||||
|
.Ss 8-bit and 5-bit color conversion
|
||||||
|
Changed in 1.0.0.
|
||||||
|
.Pp
|
||||||
|
RGBGFX takes 8-bit RGB colors as its PNG input, and outputs 5-bit GBC colors.
|
||||||
|
Its
|
||||||
|
.Ql -r/--reverse
|
||||||
|
mode does the opposite 5-bit to 8-bit conversion.
|
||||||
|
Instead of the previous inaccurate conversions, we now do accurate rounding to the nearest equivalent.
|
||||||
|
.Pp
|
||||||
|
Previously to convert an 8-bit color channel to 5-bit, we truncated it as
|
||||||
|
.Ql c >> 3 ;
|
||||||
|
and to reverse a 5-bit color channel to 8-bit, we extended it as
|
||||||
|
.Ql (c << 3) | (c >> 2) .
|
||||||
|
.Pp
|
||||||
|
Instead, now we round 8-bit to 5-bit as
|
||||||
|
.Ql (c * 31 + 127) / 255 ,
|
||||||
|
and round 5-bit to 8-bit as
|
||||||
|
.Ql (c * 255 + 15) / 31 .
|
||||||
.Sh BUGS
|
.Sh BUGS
|
||||||
These are misfeatures that may have been possible by mistake.
|
These are misfeatures that may have been possible by mistake.
|
||||||
They do not get deprecated, just fixed.
|
They do not get deprecated, just fixed.
|
||||||
@@ -491,6 +509,13 @@ Instead, use
|
|||||||
.Ql Label:
|
.Ql Label:
|
||||||
and
|
and
|
||||||
.Ql Label:: .
|
.Ql Label:: .
|
||||||
|
.Ss Extra underscores in integer constants
|
||||||
|
Fixed in 1.0.0.
|
||||||
|
.Pp
|
||||||
|
Underscores, the optional digit separators in integer constants, used to allow more than one in sequence, or trailing without digits on either side.
|
||||||
|
Now only one underscore is allowed between two digits, or between the base prefix and a digit, or between a digit and the
|
||||||
|
.Ql q
|
||||||
|
fixed-point precision suffix.
|
||||||
.Ss ADD r16 with implicit first HL operand
|
.Ss ADD r16 with implicit first HL operand
|
||||||
Fixed in 0.5.0.
|
Fixed in 0.5.0.
|
||||||
.Pp
|
.Pp
|
||||||
|
|||||||
11
man/rgbasm.1
11
man/rgbasm.1
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBASM 1
|
.Dt RGBASM 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -384,6 +384,15 @@ This warning is enabled by
|
|||||||
.Fl Wall .
|
.Fl Wall .
|
||||||
.It Fl Wdiv
|
.It Fl Wdiv
|
||||||
Warn when dividing the smallest negative integer (-2**31) by -1, which yields itself due to integer overflow.
|
Warn when dividing the smallest negative integer (-2**31) by -1, which yields itself due to integer overflow.
|
||||||
|
.It Fl Wempty-data-directive
|
||||||
|
Warn when
|
||||||
|
.Ic DB ,
|
||||||
|
.Ic DW ,
|
||||||
|
or
|
||||||
|
.Ic DL
|
||||||
|
is used without an argument in a ROM section.
|
||||||
|
This warning is enabled by
|
||||||
|
.Fl Wall .
|
||||||
.It Fl Wempty-macro-arg
|
.It Fl Wempty-macro-arg
|
||||||
Warn when a macro argument is empty.
|
Warn when a macro argument is empty.
|
||||||
This warning is enabled by
|
This warning is enabled by
|
||||||
|
|||||||
21
man/rgbasm.5
21
man/rgbasm.5
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBASM 5
|
.Dt RGBASM 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -586,6 +586,19 @@ is equivalent to
|
|||||||
or to
|
or to
|
||||||
.Ql STRCAT("str", \&"ing") .
|
.Ql STRCAT("str", \&"ing") .
|
||||||
.Pp
|
.Pp
|
||||||
|
You can use the
|
||||||
|
.Sq ===
|
||||||
|
and
|
||||||
|
.Sq !==
|
||||||
|
operators to compare two strings.
|
||||||
|
.Ql \&"str" === \&"ing"
|
||||||
|
is equivalent to
|
||||||
|
.Ql STRCMP("str", \&"ing") == 0 ,
|
||||||
|
and
|
||||||
|
.Ql \&"str" !== \&"ing"
|
||||||
|
is equivalent to
|
||||||
|
.Ql STRCMP("str", \&"ing") != 0 .
|
||||||
|
.Pp
|
||||||
The following functions operate on string expressions, and return strings themselves.
|
The following functions operate on string expressions, and return strings themselves.
|
||||||
.Bl -column "STRSLICE(str, start, stop)"
|
.Bl -column "STRSLICE(str, start, stop)"
|
||||||
.It Sy Name Ta Sy Operation
|
.It Sy Name Ta Sy Operation
|
||||||
@@ -638,12 +651,12 @@ and the values of a charmap entry are counted by
|
|||||||
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
When writing text strings that are meant to be displayed on the Game Boy, the character encoding in the ROM may need to be different than the source file encoding.
|
||||||
For example, the tiles used for uppercase letters may be placed starting at tile index 128, which differs from ASCII starting at 65.
|
For example, the tiles used for uppercase letters may be placed starting at tile index 128, which differs from ASCII starting at 65.
|
||||||
.Pp
|
.Pp
|
||||||
Character maps allow mapping strings to arbitrary sequences of numbers:
|
Character maps allow mapping strings or character literals to arbitrary sequences of numbers:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
CHARMAP "A", 42
|
CHARMAP "A", 42
|
||||||
CHARMAP ":)", 39
|
CHARMAP ':)', 39
|
||||||
CHARMAP "<br>", 13, 10
|
CHARMAP "<br>", 13, 10
|
||||||
CHARMAP "€", $20ac
|
CHARMAP '€', $20ac
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
.Pp
|
||||||
This would result in
|
This would result in
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBDS 5
|
.Dt RGBDS 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -405,9 +405,6 @@ Checks if the value is a valid bit index
|
|||||||
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
.Pq see e.g. Do BIT u3, r8 Dc in Xr gbz80 7 ,
|
||||||
that is, from 0 to 7.
|
that is, from 0 to 7.
|
||||||
The value is then ORed with the instruction's mask.
|
The value is then ORed with the instruction's mask.
|
||||||
.It Li $80 Ta Integer literal; followed by the
|
|
||||||
.Cm LONG
|
|
||||||
integer.
|
|
||||||
.It Li $70 Ta Cm HIGH
|
.It Li $70 Ta Cm HIGH
|
||||||
byte.
|
byte.
|
||||||
.It Li $71 Ta Cm LOW
|
.It Li $71 Ta Cm LOW
|
||||||
@@ -416,6 +413,9 @@ byte.
|
|||||||
value.
|
value.
|
||||||
.It Li $73 Ta Cm TZCOUNT
|
.It Li $73 Ta Cm TZCOUNT
|
||||||
value.
|
value.
|
||||||
|
.It Li $80 Ta Integer literal; followed by the
|
||||||
|
.Cm LONG
|
||||||
|
integer.
|
||||||
.It Li $81 Ta A symbol's value; followed by the symbol's
|
.It Li $81 Ta A symbol's value; followed by the symbol's
|
||||||
.Cm LONG
|
.Cm LONG
|
||||||
ID.
|
ID.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBDS 7
|
.Dt RGBDS 7
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBFIX 1
|
.Dt RGBFIX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
.\"
|
.\"
|
||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBGFX 1
|
.Dt RGBGFX 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
@@ -347,7 +347,7 @@ Output the image's palette set to this file.
|
|||||||
Same as
|
Same as
|
||||||
.Fl p Ar base_path Ns .pal
|
.Fl p Ar base_path Ns .pal
|
||||||
.Pq see Sx Automatic output paths .
|
.Pq see Sx Automatic output paths .
|
||||||
.It Fl q Ar pal_file , Fl \-palette-map Ar pal_file
|
.It Fl q Ar pal_map , Fl \-palette-map Ar pal_map
|
||||||
Output the image's palette map to this file.
|
Output the image's palette map to this file.
|
||||||
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
|
This is useful if the input image contains more than 8 palettes, as the attribute map only contains the lower 3 bits of the palette indices.
|
||||||
.It Fl Q , Fl \-auto-palette-map
|
.It Fl Q , Fl \-auto-palette-map
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBLINK 1
|
.Dt RGBLINK 1
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.\" SPDX-License-Identifier: MIT
|
.\" SPDX-License-Identifier: MIT
|
||||||
.\"
|
.\"
|
||||||
.Dd September 1, 2025
|
.Dd September 30, 2025
|
||||||
.Dt RGBLINK 5
|
.Dt RGBLINK 5
|
||||||
.Os
|
.Os
|
||||||
.Sh NAME
|
.Sh NAME
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ void act_Elif(int32_t condition) {
|
|||||||
if (lexer_ReachedELSEBlock()) {
|
if (lexer_ReachedELSEBlock()) {
|
||||||
fatal("Found `ELIF` after an `ELSE` block");
|
fatal("Found `ELIF` after an `ELSE` block");
|
||||||
}
|
}
|
||||||
lexer_SetMode(LEXER_SKIP_TO_ENDC);
|
// This should be redundant, as the lexer will have skipped to `ENDC` since
|
||||||
|
// an `ELIF` after a taken `IF` needs to not evaluate its condition.
|
||||||
|
lexer_SetMode(LEXER_SKIP_TO_ENDC); // LCOV_EXCL_LINE
|
||||||
} else if (condition) {
|
} else if (condition) {
|
||||||
lexer_RunIFBlock();
|
lexer_RunIFBlock();
|
||||||
} else {
|
} else {
|
||||||
@@ -61,7 +63,8 @@ void act_Else() {
|
|||||||
}
|
}
|
||||||
if (lexer_RanIFBlock()) {
|
if (lexer_RanIFBlock()) {
|
||||||
if (lexer_ReachedELSEBlock()) {
|
if (lexer_ReachedELSEBlock()) {
|
||||||
fatal("Found `ELSE` after an `ELSE` block");
|
// This should be redundant, as the lexer handles this error first.
|
||||||
|
fatal("Found `ELSE` after an `ELSE` block"); // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
lexer_SetMode(LEXER_SKIP_TO_ENDC);
|
lexer_SetMode(LEXER_SKIP_TO_ENDC);
|
||||||
} else {
|
} else {
|
||||||
@@ -186,7 +189,10 @@ uint32_t act_CharToNum(std::string const &str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t act_StringToNum(std::string const &str) {
|
uint32_t act_StringToNum(std::string const &str) {
|
||||||
warning(WARNING_OBSOLETE, "Treating strings as numbers is deprecated");
|
warning(
|
||||||
|
WARNING_OBSOLETE,
|
||||||
|
"Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead"
|
||||||
|
);
|
||||||
if (std::vector<int32_t> units = charmap_Convert(str); units.size() == 1) {
|
if (std::vector<int32_t> units = charmap_Convert(str); units.size() == 1) {
|
||||||
// The string is a single character with a single unit value,
|
// The string is a single character with a single unit value,
|
||||||
// which can be used directly as a number.
|
// which can be used directly as a number.
|
||||||
|
|||||||
@@ -11,68 +11,57 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "util.hpp" // isDigit
|
#include "util.hpp" // parseNumber
|
||||||
|
|
||||||
#include "asm/main.hpp" // options
|
#include "asm/main.hpp" // options
|
||||||
#include "asm/warning.hpp"
|
#include "asm/warning.hpp"
|
||||||
|
|
||||||
static size_t parseNumber(char const *spec, size_t &value) {
|
|
||||||
size_t i = 0;
|
|
||||||
|
|
||||||
value = 0;
|
|
||||||
for (; isDigit(spec[i]); ++i) {
|
|
||||||
value = value * 10 + (spec[i] - '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t FormatSpec::parseSpec(char const *spec) {
|
size_t FormatSpec::parseSpec(char const *spec) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
|
auto parseSpecNumber = [&spec, &i]() {
|
||||||
|
char const *end = &spec[i];
|
||||||
|
size_t number = parseNumber(end, BASE_10).value_or(0);
|
||||||
|
i += end - &spec[i];
|
||||||
|
return number;
|
||||||
|
};
|
||||||
|
|
||||||
// <sign>
|
// <sign>
|
||||||
if (char c = spec[i]; c == ' ' || c == '+') {
|
if (char c = spec[i]; c == ' ' || c == '+') {
|
||||||
++i;
|
++i;
|
||||||
sign = c;
|
sign = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <exact>
|
// <exact>
|
||||||
if (spec[i] == '#') {
|
if (spec[i] == '#') {
|
||||||
++i;
|
++i;
|
||||||
exact = true;
|
exact = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <align>
|
// <align>
|
||||||
if (spec[i] == '-') {
|
if (spec[i] == '-') {
|
||||||
++i;
|
++i;
|
||||||
alignLeft = true;
|
alignLeft = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <pad>
|
// <pad>
|
||||||
if (spec[i] == '0') {
|
if (spec[i] == '0') {
|
||||||
++i;
|
++i;
|
||||||
padZero = true;
|
padZero = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// <width>
|
// <width>
|
||||||
if (isDigit(spec[i])) {
|
if (isDigit(spec[i])) {
|
||||||
i += parseNumber(&spec[i], width);
|
width = parseSpecNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
// <frac>
|
// <frac>
|
||||||
if (spec[i] == '.') {
|
if (spec[i] == '.') {
|
||||||
++i;
|
++i;
|
||||||
hasFrac = true;
|
hasFrac = true;
|
||||||
i += parseNumber(&spec[i], fracWidth);
|
fracWidth = parseSpecNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
// <prec>
|
// <prec>
|
||||||
if (spec[i] == 'q') {
|
if (spec[i] == 'q') {
|
||||||
++i;
|
++i;
|
||||||
hasPrec = true;
|
hasPrec = true;
|
||||||
i += parseNumber(&spec[i], precision);
|
precision = parseSpecNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
// <type>
|
// <type>
|
||||||
switch (char c = spec[i]; c) {
|
switch (char c = spec[i]; c) {
|
||||||
case 'd':
|
case 'd':
|
||||||
@@ -87,7 +76,7 @@ size_t FormatSpec::parseSpec(char const *spec) {
|
|||||||
type = c;
|
type = c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Done parsing
|
||||||
parsed = true;
|
parsed = true;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@@ -188,36 +177,32 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
if (useType == 'd' || useType == 'f') {
|
if (useType == 'd' || useType == 'f') {
|
||||||
if (int32_t v = value; v < 0) {
|
if (int32_t v = value; v < 0) {
|
||||||
signChar = '-';
|
signChar = '-';
|
||||||
if (v != INT32_MIN) {
|
if (v != INT32_MIN) { // -INT32_MIN is UB
|
||||||
value = -v;
|
value = -v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char prefixChar = !useExact ? 0
|
// The longest possible formatted number is fixed-point with 10 digits, 255 fractional digits,
|
||||||
: useType == 'X' ? '$'
|
// and a precision suffix, for 270 total bytes (counting the NUL terminator).
|
||||||
: useType == 'x' ? '$'
|
// (Actually 269 since a 2-digit precision cannot reach 10 integer digits.)
|
||||||
: useType == 'b' ? '%'
|
// Make the buffer somewhat larger just in case.
|
||||||
: useType == 'o' ? '&'
|
char valueBuf[300];
|
||||||
: 0;
|
|
||||||
|
|
||||||
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
|
|
||||||
|
|
||||||
if (useType == 'b') {
|
if (useType == 'b') {
|
||||||
// Special case for binary
|
// Special case for binary (since `snprintf` doesn't support it)
|
||||||
char *ptr = valueBuf;
|
|
||||||
|
|
||||||
|
// Buffer the digits from least to greatest
|
||||||
|
char *ptr = valueBuf;
|
||||||
do {
|
do {
|
||||||
*ptr++ = (value & 1) + '0';
|
*ptr++ = (value & 1) + '0';
|
||||||
value >>= 1;
|
value >>= 1;
|
||||||
} while (value);
|
} while (value);
|
||||||
|
|
||||||
// Reverse the digits
|
// Reverse the digits and terminate the string
|
||||||
std::reverse(valueBuf, ptr);
|
std::reverse(valueBuf, ptr);
|
||||||
|
|
||||||
*ptr = '\0';
|
*ptr = '\0';
|
||||||
} else if (useType == 'f') {
|
} else if (useType == 'f') {
|
||||||
// Special case for fixed-point
|
// Special case for fixed-point (since it needs fractional part and precision)
|
||||||
|
|
||||||
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
// Default fractional width (C++'s is 6 for "%f"; here 5 is enough for Q16.16)
|
||||||
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
||||||
@@ -226,6 +211,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
useFracWidth = 255;
|
useFracWidth = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default precision taken from default `-Q` option
|
||||||
size_t defaultPrec = options.fixPrecision;
|
size_t defaultPrec = options.fixPrecision;
|
||||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||||
if (usePrec < 1 || usePrec > 31) {
|
if (usePrec < 1 || usePrec > 31) {
|
||||||
@@ -237,29 +223,30 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
|||||||
usePrec = defaultPrec;
|
usePrec = defaultPrec;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Floating-point formatting works for all fixed-point values
|
||||||
double fval = fabs(value / pow(2.0, usePrec));
|
double fval = fabs(value / pow(2.0, usePrec));
|
||||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||||
} else {
|
} else {
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
snprintf(valueBuf, sizeof(valueBuf), "%.*f", fracWidthArg, fval);
|
||||||
}
|
}
|
||||||
} else if (useType == 'd') {
|
|
||||||
// Decimal numbers may be formatted with a '-' sign by `snprintf`, so `abs` prevents that,
|
|
||||||
// with a special case for `INT32_MIN` since `labs(INT32_MIN)` is UB. The sign will be
|
|
||||||
// printed later from `signChar`.
|
|
||||||
uint32_t uval =
|
|
||||||
value != static_cast<uint32_t>(INT32_MIN) ? labs(static_cast<int32_t>(value)) : value;
|
|
||||||
snprintf(valueBuf, sizeof(valueBuf), "%" PRIu32, uval);
|
|
||||||
} else {
|
} else {
|
||||||
char const *spec = useType == 'u' ? "%" PRIu32
|
// `value` has already been made non-negative, so type 'd' is OK here even for `INT32_MIN`.
|
||||||
: useType == 'X' ? "%" PRIX32
|
// The sign will be printed later from `signChar`.
|
||||||
: useType == 'x' ? "%" PRIx32
|
char const *spec = useType == 'd' || useType == 'u' ? "%" PRIu32
|
||||||
: useType == 'o' ? "%" PRIo32
|
: useType == 'X' ? "%" PRIX32
|
||||||
: "%" PRIu32;
|
: useType == 'x' ? "%" PRIx32
|
||||||
|
: useType == 'o' ? "%" PRIo32
|
||||||
|
: "%" PRIu32;
|
||||||
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
snprintf(valueBuf, sizeof(valueBuf), spec, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char prefixChar = !useExact ? 0
|
||||||
|
: useType == 'X' || useType == 'x' ? '$'
|
||||||
|
: useType == 'b' ? '%'
|
||||||
|
: useType == 'o' ? '&'
|
||||||
|
: 0;
|
||||||
|
|
||||||
size_t valueLen = strlen(valueBuf);
|
size_t valueLen = strlen(valueBuf);
|
||||||
size_t numLen = (signChar != 0) + (prefixChar != 0) + valueLen;
|
size_t numLen = (signChar != 0) + (prefixChar != 0) + valueLen;
|
||||||
size_t totalLen = width > numLen ? width : numLen;
|
size_t totalLen = width > numLen ? width : numLen;
|
||||||
|
|||||||
@@ -382,60 +382,53 @@ bool fstk_RunInclude(std::string const &path, bool isQuiet) {
|
|||||||
return fstk_FileError(path, "INCLUDE");
|
return fstk_FileError(path, "INCLUDE");
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const *suggestDef(std::shared_ptr<MacroArgs> const macroArgs) {
|
|
||||||
std::shared_ptr<std::string> arg = macroArgs->getArg(1);
|
|
||||||
if (!arg) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
char const *str = arg->c_str();
|
|
||||||
static char const *types[] = {"EQUS", "EQU", "RB", "RW", "RL", "="};
|
|
||||||
for (size_t i = 0; i < std::size(types); ++i) {
|
|
||||||
if (char const *type = types[i]; strncasecmp(str, type, strlen(type)) == 0) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (strncasecmp(str, "SET", literal_strlen("SET")) == 0) {
|
|
||||||
return "=";
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fstk_RunMacro(
|
void fstk_RunMacro(
|
||||||
std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
|
std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
|
||||||
) {
|
) {
|
||||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
auto makeSuggestion = [¯oName, ¯oArgs]() -> std::optional<std::string> {
|
||||||
|
std::shared_ptr<std::string> arg = macroArgs->getArg(1);
|
||||||
|
if (!arg) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
if (!macro) {
|
char const *str = arg->c_str();
|
||||||
|
static char const *types[] = {"EQUS", "EQU", "RB", "RW", "RL", "="};
|
||||||
|
for (char const *type : types) {
|
||||||
|
if (strncasecmp(str, type, strlen(type)) == 0) {
|
||||||
|
return "\"DEF "s + macroName + " " + type + " ...\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (strncasecmp(str, "SET", literal_strlen("SET")) == 0) {
|
||||||
|
return "\"DEF "s + macroName + " = ...\"";
|
||||||
|
}
|
||||||
|
if (str[0] == ':') {
|
||||||
|
return "a label \""s + macroName + (str[1] == ':' ? "::" : ":") + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Symbol *macro = sym_FindExactSymbol(macroName); !macro) {
|
||||||
if (sym_IsPurgedExact(macroName)) {
|
if (sym_IsPurgedExact(macroName)) {
|
||||||
error("Undefined macro `%s`; it was purged", macroName.c_str());
|
error("Undefined macro `%s`; it was purged", macroName.c_str());
|
||||||
} else if (char const *defType = suggestDef(macroArgs); defType) {
|
} else if (std::optional<std::string> suggestion = makeSuggestion(); suggestion) {
|
||||||
error(
|
error(
|
||||||
"Undefined macro `%s` (did you mean \"DEF %s %s ...\"?)",
|
"Undefined macro `%s` (did you mean %s?)", macroName.c_str(), suggestion->c_str()
|
||||||
macroName.c_str(),
|
|
||||||
macroName.c_str(),
|
|
||||||
defType
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
error("Undefined macro `%s`", macroName.c_str());
|
error("Undefined macro `%s`", macroName.c_str());
|
||||||
}
|
}
|
||||||
return;
|
} else if (macro->type != SYM_MACRO) {
|
||||||
}
|
|
||||||
if (macro->type != SYM_MACRO) {
|
|
||||||
error("`%s` is not a macro", macroName.c_str());
|
error("`%s` is not a macro", macroName.c_str());
|
||||||
return;
|
} else {
|
||||||
|
newMacroContext(*macro, macroArgs, isQuiet || macro->isQuiet);
|
||||||
}
|
}
|
||||||
|
|
||||||
newMacroContext(*macro, macroArgs, isQuiet || macro->isQuiet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet) {
|
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet) {
|
||||||
if (count == 0) {
|
if (count) {
|
||||||
return;
|
newReptContext(reptLineNo, span, count, isQuiet);
|
||||||
}
|
}
|
||||||
|
|
||||||
newReptContext(reptLineNo, span, count, isQuiet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void fstk_RunFor(
|
void fstk_RunFor(
|
||||||
|
|||||||
@@ -879,7 +879,11 @@ static void discardBlockComment() {
|
|||||||
continue;
|
continue;
|
||||||
case '/':
|
case '/':
|
||||||
if (peek() == '*') {
|
if (peek() == '*') {
|
||||||
warning(WARNING_NESTED_COMMENT, "\"/*\" in block comment");
|
warning(
|
||||||
|
WARNING_NESTED_COMMENT,
|
||||||
|
"\"/" // Prevent simple syntax highlighters from seeing this as a comment
|
||||||
|
"*\" in block comment"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
case '*':
|
case '*':
|
||||||
@@ -1009,6 +1013,10 @@ static bool isValidDigit(char c) {
|
|||||||
return isAlphanumeric(c) || c == '.' || c == '#' || c == '@';
|
return isAlphanumeric(c) || c == '.' || c == '#' || c == '@';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool isCustomBinDigit(int c) {
|
||||||
|
return isBinDigit(c) || c == options.binDigits[0] || c == options.binDigits[1];
|
||||||
|
}
|
||||||
|
|
||||||
static bool checkDigitErrors(char const *digits, size_t n, char const *type) {
|
static bool checkDigitErrors(char const *digits, size_t n, char const *type) {
|
||||||
for (size_t i = 0; i < n; ++i) {
|
for (size_t i = 0; i < n; ++i) {
|
||||||
char c = digits[i];
|
char c = digits[i];
|
||||||
@@ -1074,10 +1082,7 @@ static uint32_t readBinaryNumber(char const *prefix) {
|
|||||||
if (value > (UINT32_MAX - bit) / 2) {
|
if (value > (UINT32_MAX - bit) / 2) {
|
||||||
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large");
|
warning(WARNING_LARGE_CONSTANT, "Integer constant is too large");
|
||||||
// Discard any additional digits
|
// Discard any additional digits
|
||||||
skipChars([](int d) {
|
skipChars([](int d) { return isCustomBinDigit(d) || d == '_'; });
|
||||||
return d == '0' || d == '1' || d == options.binDigits[0]
|
|
||||||
|| d == options.binDigits[1] || d == '_';
|
|
||||||
});
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
value = value * 2 + bit;
|
value = value * 2 + bit;
|
||||||
@@ -1107,11 +1112,10 @@ static uint32_t readOctalNumber(char const *prefix) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOctDigit(c)) {
|
if (!isOctDigit(c)) {
|
||||||
c = c - '0';
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
c = c - '0';
|
||||||
empty = false;
|
empty = false;
|
||||||
nonDigit = false;
|
nonDigit = false;
|
||||||
|
|
||||||
@@ -1148,11 +1152,10 @@ static uint32_t readDecimalNumber(int initial) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDigit(c)) {
|
if (!isDigit(c)) {
|
||||||
c = c - '0';
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
c = c - '0';
|
||||||
nonDigit = false;
|
nonDigit = false;
|
||||||
|
|
||||||
if (value > (UINT32_MAX - c) / 10) {
|
if (value > (UINT32_MAX - c) / 10) {
|
||||||
@@ -1185,15 +1188,10 @@ static uint32_t readHexNumber(char const *prefix) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (c >= 'a' && c <= 'f') {
|
if (!isHexDigit(c)) {
|
||||||
c = c - 'a' + 10;
|
|
||||||
} else if (c >= 'A' && c <= 'F') {
|
|
||||||
c = c - 'A' + 10;
|
|
||||||
} else if (isDigit(c)) {
|
|
||||||
c = c - '0';
|
|
||||||
} else {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
c = parseHexDigit(c);
|
||||||
empty = false;
|
empty = false;
|
||||||
nonDigit = false;
|
nonDigit = false;
|
||||||
|
|
||||||
@@ -1756,14 +1754,22 @@ static Token yylex_NORMAL() {
|
|||||||
case '^': // Either ^= or XOR
|
case '^': // Either ^= or XOR
|
||||||
return oneOrTwo('=', T_(POP_XOREQ), T_(OP_XOR));
|
return oneOrTwo('=', T_(POP_XOREQ), T_(OP_XOR));
|
||||||
|
|
||||||
case '=': // Either assignment or EQ
|
|
||||||
return oneOrTwo('=', T_(OP_LOGICEQU), T_(POP_EQUAL));
|
|
||||||
|
|
||||||
case '!': // Either a NEQ or negation
|
|
||||||
return oneOrTwo('=', T_(OP_LOGICNE), T_(OP_LOGICNOT));
|
|
||||||
|
|
||||||
// Handle ambiguous 1-, 2-, or 3-char tokens
|
// Handle ambiguous 1-, 2-, or 3-char tokens
|
||||||
|
|
||||||
|
case '=': // Either assignment, EQ or string EQ
|
||||||
|
if (peek() == '=') {
|
||||||
|
shiftChar();
|
||||||
|
return oneOrTwo('=', T_(OP_STREQU), T_(OP_LOGICEQU));
|
||||||
|
}
|
||||||
|
return Token(T_(POP_EQUAL));
|
||||||
|
|
||||||
|
case '!': // Either negation, NEQ, or string NEQ
|
||||||
|
if (peek() == '=') {
|
||||||
|
shiftChar();
|
||||||
|
return oneOrTwo('=', T_(OP_STRNE), T_(OP_LOGICNE));
|
||||||
|
}
|
||||||
|
return Token(T_(OP_LOGICNOT));
|
||||||
|
|
||||||
case '<': // Either <<=, LT, LTE, or left shift
|
case '<': // Either <<=, LT, LTE, or left shift
|
||||||
if (peek() == '<') {
|
if (peek() == '<') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
@@ -1834,8 +1840,7 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
case '%': // Either %=, MOD, or a binary constant
|
case '%': // Either %=, MOD, or a binary constant
|
||||||
c = peek();
|
c = peek();
|
||||||
if (c == '0' || c == '1' || c == options.binDigits[0] || c == options.binDigits[1]
|
if (isCustomBinDigit(c) || c == '_') {
|
||||||
|| c == '_') {
|
|
||||||
return Token(T_(NUMBER), readBinaryNumber("'%'"));
|
return Token(T_(NUMBER), readBinaryNumber("'%'"));
|
||||||
}
|
}
|
||||||
return oneOrTwo('=', T_(POP_MODEQ), T_(OP_MOD));
|
return oneOrTwo('=', T_(POP_MODEQ), T_(OP_MOD));
|
||||||
@@ -1954,28 +1959,9 @@ static Token yylex_NORMAL() {
|
|||||||
static Token yylex_RAW() {
|
static Token yylex_RAW() {
|
||||||
// This is essentially a highly modified `readString`
|
// This is essentially a highly modified `readString`
|
||||||
std::string str;
|
std::string str;
|
||||||
size_t parenDepth = 0;
|
|
||||||
int c;
|
int c;
|
||||||
|
|
||||||
// Trim left spaces (stops at a block comment)
|
for (size_t parenDepth = 0;;) {
|
||||||
for (;;) {
|
|
||||||
c = peek();
|
|
||||||
if (isBlankSpace(c)) {
|
|
||||||
shiftChar();
|
|
||||||
} else if (c == '\\') {
|
|
||||||
c = nextChar();
|
|
||||||
// If not a line continuation, handle as a normal char
|
|
||||||
if (!isWhitespace(c)) {
|
|
||||||
goto backslash;
|
|
||||||
}
|
|
||||||
// Line continuations count as "space"
|
|
||||||
discardLineContinuation();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
c = peek();
|
c = peek();
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@@ -2036,7 +2022,6 @@ static Token yylex_RAW() {
|
|||||||
case '\\': // Character escape
|
case '\\': // Character escape
|
||||||
c = nextChar();
|
c = nextChar();
|
||||||
|
|
||||||
backslash:
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case ',': // Escapes only valid inside a macro arg
|
case ',': // Escapes only valid inside a macro arg
|
||||||
case '(':
|
case '(':
|
||||||
@@ -2091,9 +2076,9 @@ append:
|
|||||||
}
|
}
|
||||||
|
|
||||||
finish: // Can't `break` out of a nested `for`-`switch`
|
finish: // Can't `break` out of a nested `for`-`switch`
|
||||||
// Trim right blank space
|
// Trim left and right blank space
|
||||||
auto rightPos = std::find_if_not(str.rbegin(), str.rend(), isBlankSpace);
|
str.erase(str.begin(), std::find_if_not(RANGE(str), isBlankSpace));
|
||||||
str.resize(rightPos.base() - str.begin());
|
str.erase(std::find_if_not(RRANGE(str), isBlankSpace).base(), str.end());
|
||||||
|
|
||||||
// Returning COMMAs to the parser would mean that two consecutive commas
|
// Returning COMMAs to the parser would mean that two consecutive commas
|
||||||
// (i.e. an empty argument) need to return two different tokens (STRING
|
// (i.e. an empty argument) need to return two different tokens (STRING
|
||||||
|
|||||||
105
src/asm/main.cpp
105
src/asm/main.cpp
@@ -6,6 +6,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -299,13 +300,15 @@ int main(int argc, char *argv[]) {
|
|||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
time_t now = time(nullptr);
|
time_t now = time(nullptr);
|
||||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
||||||
|
// Use `strtoul`, not `parseWholeNumber`, because SOURCE_DATE_EPOCH does
|
||||||
|
// not conventionally support our custom base prefixes
|
||||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
||||||
}
|
}
|
||||||
sym_Init(now);
|
sym_Init(now);
|
||||||
|
|
||||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
||||||
if (isatty(STDERR_FILENO)) {
|
if (isatty(STDERR_FILENO)) {
|
||||||
options.maxErrors = 100;
|
options.maxErrors = 100; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse CLI options
|
// Parse CLI options
|
||||||
@@ -378,51 +381,41 @@ int main(int argc, char *argv[]) {
|
|||||||
fstk_AddPreIncludeFile(musl_optarg);
|
fstk_AddPreIncludeFile(musl_optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p': {
|
case 'p':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> padByte = parseWholeNumber(musl_optarg); !padByte) {
|
||||||
unsigned long padByte = strtoul(musl_optarg, &endptr, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
|
||||||
fatal("Invalid argument for option '-p'");
|
fatal("Invalid argument for option '-p'");
|
||||||
}
|
} else if (*padByte > 0xFF) {
|
||||||
|
|
||||||
if (padByte > 0xFF) {
|
|
||||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
||||||
|
} else {
|
||||||
|
opt_P(*padByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
opt_P(padByte);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 'Q': {
|
case 'Q': {
|
||||||
char const *precisionArg = musl_optarg;
|
char const *precisionArg = musl_optarg;
|
||||||
if (precisionArg[0] == '.') {
|
if (precisionArg[0] == '.') {
|
||||||
++precisionArg;
|
++precisionArg;
|
||||||
}
|
}
|
||||||
char *endptr;
|
|
||||||
unsigned long precision = strtoul(precisionArg, &endptr, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
if (std::optional<uint64_t> precision = parseWholeNumber(precisionArg); !precision) {
|
||||||
fatal("Invalid argument for option '-Q'");
|
fatal("Invalid argument for option '-Q'");
|
||||||
}
|
} else if (*precision < 1 || *precision > 31) {
|
||||||
|
|
||||||
if (precision < 1 || precision > 31) {
|
|
||||||
fatal("Argument for option '-Q' must be between 1 and 31");
|
fatal("Argument for option '-Q' must be between 1 and 31");
|
||||||
|
} else {
|
||||||
|
opt_Q(*precision);
|
||||||
}
|
}
|
||||||
|
|
||||||
opt_Q(precision);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'r': {
|
case 'r':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> maxDepth = parseWholeNumber(musl_optarg); !maxDepth) {
|
||||||
options.maxRecursionDepth = strtoul(musl_optarg, &endptr, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
|
||||||
fatal("Invalid argument for option '-r'");
|
fatal("Invalid argument for option '-r'");
|
||||||
|
} else if (errno == ERANGE) {
|
||||||
|
fatal("Argument for option '-r' is out of range");
|
||||||
|
} else {
|
||||||
|
options.maxRecursionDepth = *maxDepth;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 's': {
|
case 's': {
|
||||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||||
@@ -459,21 +452,15 @@ int main(int argc, char *argv[]) {
|
|||||||
warnings.state.warningsEnabled = false;
|
warnings.state.warningsEnabled = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'X': {
|
case 'X':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> maxErrors = parseWholeNumber(musl_optarg); !maxErrors) {
|
||||||
uint64_t maxErrors = strtoul(musl_optarg, &endptr, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
|
||||||
fatal("Invalid argument for option '-X'");
|
fatal("Invalid argument for option '-X'");
|
||||||
}
|
} else if (*maxErrors > UINT64_MAX) {
|
||||||
|
|
||||||
if (maxErrors > UINT64_MAX) {
|
|
||||||
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
|
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
|
||||||
|
} else {
|
||||||
|
options.maxErrors = *maxErrors;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.maxErrors = maxErrors;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 0: // Long-only options
|
case 0: // Long-only options
|
||||||
switch (longOpt) {
|
switch (longOpt) {
|
||||||
@@ -510,9 +497,10 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Unrecognized options
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +521,11 @@ int main(int argc, char *argv[]) {
|
|||||||
verbosePrint(VERB_NOTICE, "Assembling \"%s\"\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
verbosePrint(VERB_NOTICE, "Assembling \"%s\"\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||||
|
|
||||||
if (dependFileName) {
|
if (dependFileName) {
|
||||||
|
if (options.targetFileName.empty()) {
|
||||||
|
fatal("Dependency files can only be created if a target file is specified with either "
|
||||||
|
"'-o', '-MQ' or '-MT'");
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp("-", dependFileName)) {
|
if (strcmp("-", dependFileName)) {
|
||||||
options.dependFile = fopen(dependFileName, "w");
|
options.dependFile = fopen(dependFileName, "w");
|
||||||
if (options.dependFile == nullptr) {
|
if (options.dependFile == nullptr) {
|
||||||
@@ -545,10 +538,6 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.dependFile && options.targetFileName.empty()) {
|
|
||||||
fatal("Dependency files can only be created if a target file is specified with either "
|
|
||||||
"'-o', '-MQ' or '-MT'");
|
|
||||||
}
|
|
||||||
options.printDep(mainFileName);
|
options.printDep(mainFileName);
|
||||||
|
|
||||||
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
||||||
@@ -558,28 +547,26 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
// Perform parse (`yy::parser` is auto-generated from `parser.y`)
|
||||||
if (yy::parser parser; parser.parse() != 0) {
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
if (warnings.nbErrors == 0) {
|
// Exited due to YYABORT or YYNOMEM
|
||||||
warnings.nbErrors = 1;
|
fatal("Unrecoverable error while parsing"); // LCOV_EXCL_LINE
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fstk_FailedOnMissingInclude()) {
|
// If parse aborted without errors due to a missing INCLUDE, and `-MG` was given, exit normally
|
||||||
sect_CheckUnionClosed();
|
|
||||||
sect_CheckLoadClosed();
|
|
||||||
sect_CheckSizes();
|
|
||||||
|
|
||||||
charmap_CheckStack();
|
|
||||||
opt_CheckStack();
|
|
||||||
sect_CheckStack();
|
|
||||||
}
|
|
||||||
|
|
||||||
requireZeroErrors();
|
|
||||||
|
|
||||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
|
||||||
if (fstk_FailedOnMissingInclude()) {
|
if (fstk_FailedOnMissingInclude()) {
|
||||||
|
requireZeroErrors();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sect_CheckUnionClosed();
|
||||||
|
sect_CheckLoadClosed();
|
||||||
|
sect_CheckSizes();
|
||||||
|
|
||||||
|
charmap_CheckStack();
|
||||||
|
opt_CheckStack();
|
||||||
|
sect_CheckStack();
|
||||||
|
|
||||||
|
requireZeroErrors();
|
||||||
|
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
|
|
||||||
for (auto const &[name, features] : stateFileSpecs) {
|
for (auto const &[name, features] : stateFileSpecs) {
|
||||||
|
|||||||
105
src/asm/opt.cpp
105
src/asm/opt.cpp
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <iterator> // std::size
|
#include <iterator> // std::size
|
||||||
|
#include <optional>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -9,8 +10,7 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "helpers.hpp" // assume
|
#include "util.hpp"
|
||||||
#include "util.hpp" // isBlankSpace
|
|
||||||
|
|
||||||
#include "asm/fstack.hpp"
|
#include "asm/fstack.hpp"
|
||||||
#include "asm/lexer.hpp"
|
#include "asm/lexer.hpp"
|
||||||
@@ -55,100 +55,75 @@ void opt_W(char const *flag) {
|
|||||||
|
|
||||||
void opt_Parse(char const *s) {
|
void opt_Parse(char const *s) {
|
||||||
if (s[0] == '-') {
|
if (s[0] == '-') {
|
||||||
++s;
|
++s; // Skip a leading '-'
|
||||||
}
|
}
|
||||||
switch (s[0]) {
|
|
||||||
|
char c = *s++;
|
||||||
|
|
||||||
|
while (isBlankSpace(*s)) {
|
||||||
|
++s; // Skip leading blank spaces
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(&s[1]) == 2) {
|
if (strlen(s) == 2) {
|
||||||
opt_B(&s[1]);
|
opt_B(s);
|
||||||
} else {
|
} else {
|
||||||
error("Must specify exactly 2 characters for option 'b'");
|
error("Must specify exactly 2 characters for option 'b'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(&s[1]) == 4) {
|
if (strlen(s) == 4) {
|
||||||
opt_G(&s[1]);
|
opt_G(s);
|
||||||
} else {
|
} else {
|
||||||
error("Must specify exactly 4 characters for option 'g'");
|
error("Must specify exactly 4 characters for option 'g'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p':
|
case 'p':
|
||||||
if (strlen(&s[1]) <= 2) {
|
if (std::optional<uint64_t> padByte = parseWholeNumber(s); !padByte) {
|
||||||
int result;
|
|
||||||
unsigned int padByte;
|
|
||||||
|
|
||||||
result = sscanf(&s[1], "%x", &padByte);
|
|
||||||
if (result != 1) {
|
|
||||||
error("Invalid argument for option 'p'");
|
|
||||||
} else {
|
|
||||||
// Two characters cannot be scanned as a hex number greater than 0xFF
|
|
||||||
assume(padByte <= 0xFF);
|
|
||||||
opt_P(padByte);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error("Invalid argument for option 'p'");
|
error("Invalid argument for option 'p'");
|
||||||
|
} else if (*padByte > 0xFF) {
|
||||||
|
error("Argument for option 'p' must be between 0 and 0xFF");
|
||||||
|
} else {
|
||||||
|
opt_P(*padByte);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q': {
|
case 'Q':
|
||||||
char const *precisionArg = &s[1];
|
if (s[0] == '.') {
|
||||||
if (precisionArg[0] == '.') {
|
++s; // Skip leading '.'
|
||||||
++precisionArg;
|
|
||||||
}
|
}
|
||||||
if (strlen(precisionArg) <= 2) {
|
if (std::optional<uint64_t> precision = parseWholeNumber(s); !precision) {
|
||||||
int result;
|
|
||||||
unsigned int fixPrecision;
|
|
||||||
|
|
||||||
result = sscanf(precisionArg, "%u", &fixPrecision);
|
|
||||||
if (result != 1) {
|
|
||||||
error("Invalid argument for option 'Q'");
|
|
||||||
} else if (fixPrecision < 1 || fixPrecision > 31) {
|
|
||||||
error("Argument for option 'Q' must be between 1 and 31");
|
|
||||||
} else {
|
|
||||||
opt_Q(fixPrecision);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error("Invalid argument for option 'Q'");
|
error("Invalid argument for option 'Q'");
|
||||||
}
|
} else if (*precision < 1 || *precision > 31) {
|
||||||
break;
|
error("Argument for option 'Q' must be between 1 and 31");
|
||||||
}
|
|
||||||
|
|
||||||
case 'r': {
|
|
||||||
++s; // Skip 'r'
|
|
||||||
while (isBlankSpace(*s)) {
|
|
||||||
++s; // Skip leading blank spaces
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s[0] == '\0') {
|
|
||||||
error("Missing argument for option 'r'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *endptr;
|
|
||||||
unsigned long maxRecursionDepth = strtoul(s, &endptr, 10);
|
|
||||||
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
error("Invalid argument for option 'r' (\"%s\")", s);
|
|
||||||
} else if (errno == ERANGE) {
|
|
||||||
error("Argument for option 'r' is out of range (\"%s\")", s);
|
|
||||||
} else {
|
} else {
|
||||||
opt_R(maxRecursionDepth);
|
opt_Q(*precision);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
if (std::optional<uint64_t> maxRecursionDepth = parseWholeNumber(s); !maxRecursionDepth) {
|
||||||
|
error("Invalid argument for option 'r'");
|
||||||
|
} else if (errno == ERANGE) {
|
||||||
|
error("Argument for option 'r' is out of range");
|
||||||
|
} else {
|
||||||
|
opt_R(*maxRecursionDepth);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
if (strlen(&s[1]) > 0) {
|
if (strlen(s) > 0) {
|
||||||
opt_W(&s[1]);
|
opt_W(s);
|
||||||
} else {
|
} else {
|
||||||
error("Must specify an argument for option 'W'");
|
error("Must specify an argument for option 'W'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error("Unknown option '%c'", s[0]);
|
error("Unknown option '%c'", c);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ static void writeSymbol(Symbol const &sym, FILE *file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerUnregisteredSymbol(Symbol &sym) {
|
void out_RegisterSymbol(Symbol &sym) {
|
||||||
// Check for `sym.src`, to skip any built-in symbol from rgbasm
|
// Check for `sym.src`, to skip any built-in symbol from rgbasm
|
||||||
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
|
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
|
||||||
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
||||||
@@ -132,90 +132,6 @@ static void registerUnregisteredSymbol(Symbol &sym) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeRpn(std::vector<uint8_t> &rpnexpr, std::vector<uint8_t> const &rpn) {
|
|
||||||
size_t rpnptr = 0;
|
|
||||||
|
|
||||||
for (size_t offset = 0; offset < rpn.size();) {
|
|
||||||
uint8_t rpndata = rpn[offset++];
|
|
||||||
|
|
||||||
auto getSymName = [&]() {
|
|
||||||
std::string symName;
|
|
||||||
for (uint8_t c; (c = rpn[offset++]) != 0;) {
|
|
||||||
symName += c;
|
|
||||||
}
|
|
||||||
return symName;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (rpndata) {
|
|
||||||
Symbol *sym;
|
|
||||||
uint32_t value;
|
|
||||||
uint8_t b;
|
|
||||||
|
|
||||||
case RPN_CONST:
|
|
||||||
rpnexpr[rpnptr++] = RPN_CONST;
|
|
||||||
rpnexpr[rpnptr++] = rpn[offset++];
|
|
||||||
rpnexpr[rpnptr++] = rpn[offset++];
|
|
||||||
rpnexpr[rpnptr++] = rpn[offset++];
|
|
||||||
rpnexpr[rpnptr++] = rpn[offset++];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RPN_SYM:
|
|
||||||
// The symbol name is always written expanded
|
|
||||||
sym = sym_FindExactSymbol(getSymName());
|
|
||||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
|
||||||
value = sym->ID;
|
|
||||||
|
|
||||||
rpnexpr[rpnptr++] = RPN_SYM;
|
|
||||||
rpnexpr[rpnptr++] = value & 0xFF;
|
|
||||||
rpnexpr[rpnptr++] = value >> 8;
|
|
||||||
rpnexpr[rpnptr++] = value >> 16;
|
|
||||||
rpnexpr[rpnptr++] = value >> 24;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RPN_BANK_SYM:
|
|
||||||
// The symbol name is always written expanded
|
|
||||||
sym = sym_FindExactSymbol(getSymName());
|
|
||||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
|
||||||
value = sym->ID;
|
|
||||||
|
|
||||||
rpnexpr[rpnptr++] = RPN_BANK_SYM;
|
|
||||||
rpnexpr[rpnptr++] = value & 0xFF;
|
|
||||||
rpnexpr[rpnptr++] = value >> 8;
|
|
||||||
rpnexpr[rpnptr++] = value >> 16;
|
|
||||||
rpnexpr[rpnptr++] = value >> 24;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RPN_BANK_SECT:
|
|
||||||
rpnexpr[rpnptr++] = RPN_BANK_SECT;
|
|
||||||
do {
|
|
||||||
b = rpn[offset++];
|
|
||||||
rpnexpr[rpnptr++] = b;
|
|
||||||
} while (b != 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RPN_SIZEOF_SECT:
|
|
||||||
rpnexpr[rpnptr++] = RPN_SIZEOF_SECT;
|
|
||||||
do {
|
|
||||||
b = rpn[offset++];
|
|
||||||
rpnexpr[rpnptr++] = b;
|
|
||||||
} while (b != 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RPN_STARTOF_SECT:
|
|
||||||
rpnexpr[rpnptr++] = RPN_STARTOF_SECT;
|
|
||||||
do {
|
|
||||||
b = rpn[offset++];
|
|
||||||
rpnexpr[rpnptr++] = b;
|
|
||||||
} while (b != 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
rpnexpr[rpnptr++] = rpndata;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void initPatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) {
|
static void initPatch(Patch &patch, uint32_t type, Expression const &expr, uint32_t ofs) {
|
||||||
patch.type = type;
|
patch.type = type;
|
||||||
patch.src = fstk_GetFileStack();
|
patch.src = fstk_GetFileStack();
|
||||||
@@ -225,20 +141,7 @@ static void initPatch(Patch &patch, uint32_t type, Expression const &expr, uint3
|
|||||||
patch.offset = ofs;
|
patch.offset = ofs;
|
||||||
patch.pcSection = sect_GetSymbolSection();
|
patch.pcSection = sect_GetSymbolSection();
|
||||||
patch.pcOffset = sect_GetSymbolOffset();
|
patch.pcOffset = sect_GetSymbolOffset();
|
||||||
|
expr.encode(patch.rpn);
|
||||||
if (expr.isKnown()) {
|
|
||||||
// If the RPN expr's value is known, output a constant directly
|
|
||||||
uint32_t val = expr.value();
|
|
||||||
patch.rpn.resize(5);
|
|
||||||
patch.rpn[0] = RPN_CONST;
|
|
||||||
patch.rpn[1] = val & 0xFF;
|
|
||||||
patch.rpn[2] = val >> 8;
|
|
||||||
patch.rpn[3] = val >> 16;
|
|
||||||
patch.rpn[4] = val >> 24;
|
|
||||||
} else {
|
|
||||||
patch.rpn.resize(expr.rpnPatchSize);
|
|
||||||
writeRpn(patch.rpn, expr.rpn);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
|
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
|
||||||
@@ -310,7 +213,7 @@ void out_WriteObject() {
|
|||||||
Defer closeFile{[&] { fclose(file); }};
|
Defer closeFile{[&] { fclose(file); }};
|
||||||
|
|
||||||
// Also write symbols that weren't written above
|
// Also write symbols that weren't written above
|
||||||
sym_ForEach(registerUnregisteredSymbol);
|
sym_ForEach(out_RegisterSymbol);
|
||||||
|
|
||||||
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||||
putLong(RGBDS_OBJECT_REV, file);
|
putLong(RGBDS_OBJECT_REV, file);
|
||||||
|
|||||||
@@ -107,6 +107,7 @@
|
|||||||
|
|
||||||
// String operators
|
// String operators
|
||||||
%token OP_CAT "++"
|
%token OP_CAT "++"
|
||||||
|
%token OP_STREQU "===" OP_STRNE "!=="
|
||||||
|
|
||||||
// Comparison operators
|
// Comparison operators
|
||||||
%token OP_LOGICEQU "==" OP_LOGICNE "!="
|
%token OP_LOGICEQU "==" OP_LOGICNE "!="
|
||||||
@@ -1049,6 +1050,9 @@ charmap:
|
|||||||
POP_CHARMAP string COMMA charmap_args trailing_comma {
|
POP_CHARMAP string COMMA charmap_args trailing_comma {
|
||||||
charmap_Add($2, std::move($4));
|
charmap_Add($2, std::move($4));
|
||||||
}
|
}
|
||||||
|
| POP_CHARMAP CHARACTER COMMA charmap_args trailing_comma {
|
||||||
|
charmap_Add($2, std::move($4));
|
||||||
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
charmap_args:
|
charmap_args:
|
||||||
@@ -1284,6 +1288,12 @@ relocexpr_no_str:
|
|||||||
| CHARACTER {
|
| CHARACTER {
|
||||||
$$.makeNumber(act_CharToNum($1));
|
$$.makeNumber(act_CharToNum($1));
|
||||||
}
|
}
|
||||||
|
| string OP_STREQU string {
|
||||||
|
$$.makeNumber($1.compare($3) == 0);
|
||||||
|
}
|
||||||
|
| string OP_STRNE string {
|
||||||
|
$$.makeNumber($1.compare($3) != 0);
|
||||||
|
}
|
||||||
| OP_LOGICNOT relocexpr %prec NEG {
|
| OP_LOGICNOT relocexpr %prec NEG {
|
||||||
$$.makeUnaryOp(RPN_LOGNOT, std::move($2));
|
$$.makeUnaryOp(RPN_LOGNOT, std::move($2));
|
||||||
}
|
}
|
||||||
@@ -1891,7 +1901,7 @@ sm83_and:
|
|||||||
sm83_bit:
|
sm83_bit:
|
||||||
SM83_BIT reloc_3bit COMMA reg_r {
|
SM83_BIT reloc_3bit COMMA reg_r {
|
||||||
uint8_t mask = static_cast<uint8_t>(0x40 | $4);
|
uint8_t mask = static_cast<uint8_t>(0x40 | $4);
|
||||||
$2.makeCheckBitIndex(mask);
|
$2.addCheckBitIndex(mask);
|
||||||
sect_ConstByte(0xCB);
|
sect_ConstByte(0xCB);
|
||||||
if (!$2.isKnown()) {
|
if (!$2.isKnown()) {
|
||||||
sect_RelByte($2, 0);
|
sect_RelByte($2, 0);
|
||||||
@@ -2024,7 +2034,7 @@ sm83_ldd:
|
|||||||
|
|
||||||
sm83_ldh:
|
sm83_ldh:
|
||||||
SM83_LDH MODE_A COMMA op_mem_ind {
|
SM83_LDH MODE_A COMMA op_mem_ind {
|
||||||
$4.makeCheckHRAM();
|
$4.addCheckHRAM();
|
||||||
sect_ConstByte(0xF0);
|
sect_ConstByte(0xF0);
|
||||||
if (!$4.isKnown()) {
|
if (!$4.isKnown()) {
|
||||||
sect_RelByte($4, 1);
|
sect_RelByte($4, 1);
|
||||||
@@ -2033,7 +2043,7 @@ sm83_ldh:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
| SM83_LDH op_mem_ind COMMA MODE_A {
|
| SM83_LDH op_mem_ind COMMA MODE_A {
|
||||||
$2.makeCheckHRAM();
|
$2.addCheckHRAM();
|
||||||
sect_ConstByte(0xE0);
|
sect_ConstByte(0xE0);
|
||||||
if (!$2.isKnown()) {
|
if (!$2.isKnown()) {
|
||||||
sect_RelByte($2, 1);
|
sect_RelByte($2, 1);
|
||||||
@@ -2217,7 +2227,7 @@ sm83_push:
|
|||||||
sm83_res:
|
sm83_res:
|
||||||
SM83_RES reloc_3bit COMMA reg_r {
|
SM83_RES reloc_3bit COMMA reg_r {
|
||||||
uint8_t mask = static_cast<uint8_t>(0x80 | $4);
|
uint8_t mask = static_cast<uint8_t>(0x80 | $4);
|
||||||
$2.makeCheckBitIndex(mask);
|
$2.addCheckBitIndex(mask);
|
||||||
sect_ConstByte(0xCB);
|
sect_ConstByte(0xCB);
|
||||||
if (!$2.isKnown()) {
|
if (!$2.isKnown()) {
|
||||||
sect_RelByte($2, 0);
|
sect_RelByte($2, 0);
|
||||||
@@ -2296,7 +2306,7 @@ sm83_rrca:
|
|||||||
|
|
||||||
sm83_rst:
|
sm83_rst:
|
||||||
SM83_RST reloc_8bit {
|
SM83_RST reloc_8bit {
|
||||||
$2.makeCheckRST();
|
$2.addCheckRST();
|
||||||
if (!$2.isKnown()) {
|
if (!$2.isKnown()) {
|
||||||
sect_RelByte($2, 0);
|
sect_RelByte($2, 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -2324,7 +2334,7 @@ sm83_scf:
|
|||||||
sm83_set:
|
sm83_set:
|
||||||
SM83_SET reloc_3bit COMMA reg_r {
|
SM83_SET reloc_3bit COMMA reg_r {
|
||||||
uint8_t mask = static_cast<uint8_t>(0xC0 | $4);
|
uint8_t mask = static_cast<uint8_t>(0xC0 | $4);
|
||||||
$2.makeCheckBitIndex(mask);
|
$2.addCheckBitIndex(mask);
|
||||||
sect_ConstByte(0xCB);
|
sect_ConstByte(0xCB);
|
||||||
if (!$2.isKnown()) {
|
if (!$2.isKnown()) {
|
||||||
sect_RelByte($2, 0);
|
sect_RelByte($2, 0);
|
||||||
|
|||||||
265
src/asm/rpn.cpp
265
src/asm/rpn.cpp
@@ -8,10 +8,11 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
@@ -24,24 +25,6 @@
|
|||||||
|
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
void Expression::clear() {
|
|
||||||
data = 0;
|
|
||||||
isSymbol = false;
|
|
||||||
rpn.clear();
|
|
||||||
rpnPatchSize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *Expression::reserveSpace(uint32_t size) {
|
|
||||||
return reserveSpace(size, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *Expression::reserveSpace(uint32_t size, uint32_t patchSize) {
|
|
||||||
rpnPatchSize += patchSize;
|
|
||||||
size_t curSize = rpn.size();
|
|
||||||
rpn.resize(curSize + size);
|
|
||||||
return &rpn[curSize];
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t Expression::getConstVal() const {
|
int32_t Expression::getConstVal() const {
|
||||||
if (!isKnown()) {
|
if (!isKnown()) {
|
||||||
error("Expected constant expression: %s", std::get<std::string>(data).c_str());
|
error("Expected constant expression: %s", std::get<std::string>(data).c_str());
|
||||||
@@ -51,10 +34,10 @@ int32_t Expression::getConstVal() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Symbol const *Expression::symbolOf() const {
|
Symbol const *Expression::symbolOf() const {
|
||||||
if (!isSymbol) {
|
if (rpn.size() != 1 || rpn[0].command != RPN_SYM) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return sym_FindScopedSymbol(reinterpret_cast<char const *>(&rpn[1]));
|
return sym_FindScopedSymbol(std::get<std::string>(rpn[0].data));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Expression::isDiffConstant(Symbol const *sym) const {
|
bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||||
@@ -71,12 +54,12 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeNumber(uint32_t value) {
|
void Expression::makeNumber(uint32_t value) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
data = static_cast<int32_t>(value);
|
data = static_cast<int32_t>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeSymbol(std::string const &symName) {
|
void Expression::makeSymbol(std::string const &symName) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||||
error("PC has no value outside of a section");
|
error("PC has no value outside of a section");
|
||||||
data = 0;
|
data = 0;
|
||||||
@@ -84,28 +67,20 @@ void Expression::makeSymbol(std::string const &symName) {
|
|||||||
error("`%s` is not a numeric symbol", symName.c_str());
|
error("`%s` is not a numeric symbol", symName.c_str());
|
||||||
data = 0;
|
data = 0;
|
||||||
} else if (!sym || !sym->isConstant()) {
|
} else if (!sym || !sym->isConstant()) {
|
||||||
isSymbol = true;
|
|
||||||
|
|
||||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||||
: (sym && sym->isDefined()
|
: (sym && sym->isDefined()
|
||||||
? "`"s + symName + "` is not constant at assembly time"
|
? "`"s + symName + "` is not constant at assembly time"
|
||||||
: "undefined symbol `"s + symName + "`")
|
: "undefined symbol `"s + symName + "`")
|
||||||
+ (sym_IsPurgedScoped(symName) ? "; it was purged" : "");
|
+ (sym_IsPurgedScoped(symName) ? "; it was purged" : "");
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
|
rpn.emplace_back(RPN_SYM, sym->name);
|
||||||
size_t nameLen = sym->name.length() + 1; // Don't forget NUL!
|
|
||||||
|
|
||||||
// 1-byte opcode + 4-byte symbol ID
|
|
||||||
uint8_t *ptr = reserveSpace(nameLen + 1, 5);
|
|
||||||
*ptr++ = RPN_SYM;
|
|
||||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
|
||||||
} else {
|
} else {
|
||||||
data = static_cast<int32_t>(sym->getConstantValue());
|
data = static_cast<int32_t>(sym->getConstantValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeBankSymbol(std::string const &symName) {
|
void Expression::makeBankSymbol(std::string const &symName) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
||||||
// The @ symbol is treated differently.
|
// The @ symbol is treated differently.
|
||||||
if (std::optional<uint32_t> outputBank = sect_GetOutputBank(); !outputBank) {
|
if (std::optional<uint32_t> outputBank = sect_GetOutputBank(); !outputBank) {
|
||||||
@@ -113,19 +88,16 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
|||||||
data = 1;
|
data = 1;
|
||||||
} else if (*outputBank == UINT32_MAX) {
|
} else if (*outputBank == UINT32_MAX) {
|
||||||
data = "Current section's bank is not known";
|
data = "Current section's bank is not known";
|
||||||
|
rpn.emplace_back(RPN_BANK_SELF);
|
||||||
*reserveSpace(1) = RPN_BANK_SELF;
|
|
||||||
} else {
|
} else {
|
||||||
data = static_cast<int32_t>(*outputBank);
|
data = static_cast<int32_t>(*outputBank);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
} else if (sym && !sym->isLabel()) {
|
} else if (sym && !sym->isLabel()) {
|
||||||
error("`BANK` argument must be a label");
|
error("`BANK` argument must be a label");
|
||||||
data = 1;
|
data = 1;
|
||||||
} else {
|
} else {
|
||||||
sym = sym_Ref(symName);
|
sym = sym_Ref(symName);
|
||||||
assume(sym); // If the symbol didn't exist, it should have been created
|
assume(sym); // If the symbol didn't exist, it should have been created
|
||||||
|
|
||||||
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
|
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
|
||||||
// Symbol's section is known and bank is fixed
|
// Symbol's section is known and bank is fixed
|
||||||
data = static_cast<int32_t>(sym->getSection()->bank);
|
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||||
@@ -133,78 +105,51 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
|||||||
data = sym_IsPurgedScoped(symName)
|
data = sym_IsPurgedScoped(symName)
|
||||||
? "`"s + symName + "`'s bank is not known; it was purged"
|
? "`"s + symName + "`'s bank is not known; it was purged"
|
||||||
: "`"s + symName + "`'s bank is not known";
|
: "`"s + symName + "`'s bank is not known";
|
||||||
|
rpn.emplace_back(RPN_BANK_SYM, sym->name);
|
||||||
size_t nameLen = sym->name.length() + 1; // Room for NUL!
|
|
||||||
|
|
||||||
// 1-byte opcode + 4-byte sect ID
|
|
||||||
uint8_t *ptr = reserveSpace(nameLen + 1, 5);
|
|
||||||
*ptr++ = RPN_BANK_SYM;
|
|
||||||
memcpy(ptr, sym->name.c_str(), nameLen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeBankSection(std::string const §Name) {
|
void Expression::makeBankSection(std::string const §Name) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
||||||
data = static_cast<int32_t>(sect->bank);
|
data = static_cast<int32_t>(sect->bank);
|
||||||
} else {
|
} else {
|
||||||
data = "Section \""s + sectName + "\"'s bank is not known";
|
data = "Section \""s + sectName + "\"'s bank is not known";
|
||||||
|
rpn.emplace_back(RPN_BANK_SECT, sectName);
|
||||||
size_t nameLen = sectName.length() + 1; // Room for NUL!
|
|
||||||
|
|
||||||
uint8_t *ptr = reserveSpace(nameLen + 1);
|
|
||||||
*ptr++ = RPN_BANK_SECT;
|
|
||||||
memcpy(ptr, sectName.data(), nameLen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeSizeOfSection(std::string const §Name) {
|
void Expression::makeSizeOfSection(std::string const §Name) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
||||||
data = static_cast<int32_t>(sect->size);
|
data = static_cast<int32_t>(sect->size);
|
||||||
} else {
|
} else {
|
||||||
data = "Section \""s + sectName + "\"'s size is not known";
|
data = "Section \""s + sectName + "\"'s size is not known";
|
||||||
|
rpn.emplace_back(RPN_SIZEOF_SECT, sectName);
|
||||||
size_t nameLen = sectName.length() + 1; // Room for NUL!
|
|
||||||
|
|
||||||
uint8_t *ptr = reserveSpace(nameLen + 1);
|
|
||||||
*ptr++ = RPN_SIZEOF_SECT;
|
|
||||||
memcpy(ptr, sectName.data(), nameLen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeStartOfSection(std::string const §Name) {
|
void Expression::makeStartOfSection(std::string const §Name) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
||||||
data = static_cast<int32_t>(sect->org);
|
data = static_cast<int32_t>(sect->org);
|
||||||
} else {
|
} else {
|
||||||
data = "Section \""s + sectName + "\"'s start is not known";
|
data = "Section \""s + sectName + "\"'s start is not known";
|
||||||
|
rpn.emplace_back(RPN_STARTOF_SECT, sectName);
|
||||||
size_t nameLen = sectName.length() + 1; // Room for NUL!
|
|
||||||
|
|
||||||
uint8_t *ptr = reserveSpace(nameLen + 1);
|
|
||||||
*ptr++ = RPN_STARTOF_SECT;
|
|
||||||
memcpy(ptr, sectName.data(), nameLen);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeSizeOfSectionType(SectionType type) {
|
void Expression::makeSizeOfSectionType(SectionType type) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
data = "Section type's size is not known";
|
data = "Section type's size is not known";
|
||||||
|
rpn.emplace_back(RPN_SIZEOF_SECTTYPE, static_cast<uint8_t>(type));
|
||||||
uint8_t *ptr = reserveSpace(2);
|
|
||||||
*ptr++ = RPN_SIZEOF_SECTTYPE;
|
|
||||||
*ptr = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeStartOfSectionType(SectionType type) {
|
void Expression::makeStartOfSectionType(SectionType type) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
data = "Section type's start is not known";
|
data = "Section type's start is not known";
|
||||||
|
rpn.emplace_back(RPN_STARTOF_SECTTYPE, static_cast<uint8_t>(type));
|
||||||
uint8_t *ptr = reserveSpace(2);
|
|
||||||
*ptr++ = RPN_STARTOF_SECTTYPE;
|
|
||||||
*ptr = type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool tryConstZero(Expression const &lhs, Expression const &rhs) {
|
static bool tryConstZero(Expression const &lhs, Expression const &rhs) {
|
||||||
@@ -302,7 +247,7 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
// First, check if the expression is known
|
// First, check if the expression is known
|
||||||
if (src.isKnown()) {
|
if (src.isKnown()) {
|
||||||
// If the expressions is known, just compute the value
|
// If the expressions is known, just compute the value
|
||||||
@@ -339,16 +284,15 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
|||||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||||
data = constVal;
|
data = constVal;
|
||||||
} else {
|
} else {
|
||||||
// If it's not known, just reuse its RPN buffer and append the operator
|
// If it's not known, just reuse its RPN vector and append the operator
|
||||||
rpnPatchSize = src.rpnPatchSize;
|
|
||||||
std::swap(rpn, src.rpn);
|
|
||||||
data = std::move(src.data);
|
data = std::move(src.data);
|
||||||
*reserveSpace(1) = op;
|
std::swap(rpn, src.rpn);
|
||||||
|
rpn.emplace_back(op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2) {
|
void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2) {
|
||||||
clear();
|
assume(rpn.empty());
|
||||||
// First, check if the expressions are known
|
// First, check if the expressions are known
|
||||||
if (src1.isKnown() && src2.isKnown()) {
|
if (src1.isKnown() && src2.isKnown()) {
|
||||||
// If both expressions are known, just compute the value
|
// If both expressions are known, just compute the value
|
||||||
@@ -480,57 +424,32 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
|||||||
// Convert the left-hand expression if it's constant
|
// Convert the left-hand expression if it's constant
|
||||||
if (src1.isKnown()) {
|
if (src1.isKnown()) {
|
||||||
uint32_t lval = src1.value();
|
uint32_t lval = src1.value();
|
||||||
uint8_t bytes[] = {
|
|
||||||
RPN_CONST,
|
|
||||||
static_cast<uint8_t>(lval),
|
|
||||||
static_cast<uint8_t>(lval >> 8),
|
|
||||||
static_cast<uint8_t>(lval >> 16),
|
|
||||||
static_cast<uint8_t>(lval >> 24),
|
|
||||||
};
|
|
||||||
rpn.clear();
|
|
||||||
rpnPatchSize = 0;
|
|
||||||
memcpy(reserveSpace(sizeof(bytes)), bytes, sizeof(bytes));
|
|
||||||
|
|
||||||
// Use the other expression's un-const reason
|
// Use the other expression's un-const reason
|
||||||
data = std::move(src2.data);
|
data = std::move(src2.data);
|
||||||
|
rpn.emplace_back(RPN_CONST, lval);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise just reuse its RPN buffer
|
// Otherwise just reuse its RPN vector
|
||||||
rpnPatchSize = src1.rpnPatchSize;
|
|
||||||
std::swap(rpn, src1.rpn);
|
|
||||||
data = std::move(src1.data);
|
data = std::move(src1.data);
|
||||||
|
std::swap(rpn, src1.rpn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, merge the right expression into the left one
|
// Now, merge the right expression into the left one
|
||||||
if (src2.isKnown()) {
|
if (src2.isKnown()) {
|
||||||
// If the right expression is constant, append a shim instead
|
// If the right expression is constant, append its value
|
||||||
uint32_t rval = src2.value();
|
uint32_t rval = src2.value();
|
||||||
uint8_t bytes[] = {
|
rpn.emplace_back(RPN_CONST, rval);
|
||||||
RPN_CONST,
|
|
||||||
static_cast<uint8_t>(rval),
|
|
||||||
static_cast<uint8_t>(rval >> 8),
|
|
||||||
static_cast<uint8_t>(rval >> 16),
|
|
||||||
static_cast<uint8_t>(rval >> 24),
|
|
||||||
};
|
|
||||||
uint8_t *ptr = reserveSpace(sizeof(bytes) + 1, sizeof(bytes) + 1);
|
|
||||||
memcpy(ptr, bytes, sizeof(bytes));
|
|
||||||
ptr[sizeof(bytes)] = op;
|
|
||||||
} else {
|
} else {
|
||||||
// Copy the right RPN and append the operator
|
// Otherwise just extend with its RPN vector
|
||||||
uint32_t rightRpnSize = src2.rpn.size();
|
rpn.insert(rpn.end(), RANGE(src2.rpn));
|
||||||
uint8_t *ptr = reserveSpace(rightRpnSize + 1, src2.rpnPatchSize + 1);
|
|
||||||
if (rightRpnSize > 0) {
|
|
||||||
// If `rightRpnSize == 0`, then `memcpy(ptr, nullptr, rightRpnSize)` would be UB
|
|
||||||
memcpy(ptr, src2.rpn.data(), rightRpnSize);
|
|
||||||
}
|
|
||||||
ptr[rightRpnSize] = op;
|
|
||||||
}
|
}
|
||||||
|
// Append the operator
|
||||||
|
rpn.emplace_back(op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeCheckHRAM() {
|
void Expression::addCheckHRAM() {
|
||||||
isSymbol = false;
|
|
||||||
if (!isKnown()) {
|
if (!isKnown()) {
|
||||||
*reserveSpace(1) = RPN_HRAM;
|
rpn.emplace_back(RPN_HRAM);
|
||||||
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
||||||
// That range is valid; only keep the lower byte
|
// That range is valid; only keep the lower byte
|
||||||
data = val & 0xFF;
|
data = val & 0xFF;
|
||||||
@@ -539,22 +458,19 @@ void Expression::makeCheckHRAM() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeCheckRST() {
|
void Expression::addCheckRST() {
|
||||||
if (!isKnown()) {
|
if (!isKnown()) {
|
||||||
*reserveSpace(1) = RPN_RST;
|
rpn.emplace_back(RPN_RST);
|
||||||
} else if (int32_t val = value(); val & ~0x38) {
|
} else if (int32_t val = value(); val & ~0x38) {
|
||||||
// A valid RST address must be masked with 0x38
|
// A valid RST address must be masked with 0x38
|
||||||
error("Invalid address $%" PRIx32 " for `RST`", val);
|
error("Invalid address $%" PRIx32 " for `RST`", val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expression::makeCheckBitIndex(uint8_t mask) {
|
void Expression::addCheckBitIndex(uint8_t mask) {
|
||||||
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
assume((mask & 0xC0) != 0x00); // The high two bits must correspond to BIT, RES, or SET
|
||||||
|
|
||||||
if (!isKnown()) {
|
if (!isKnown()) {
|
||||||
uint8_t *ptr = reserveSpace(2);
|
rpn.emplace_back(RPN_BIT_INDEX, mask);
|
||||||
*ptr++ = RPN_BIT_INDEX;
|
|
||||||
*ptr = mask;
|
|
||||||
} else if (int32_t val = value(); val & ~0x07) {
|
} else if (int32_t val = value(); val & ~0x07) {
|
||||||
// A valid bit index must be masked with 0x07
|
// A valid bit index must be masked with 0x07
|
||||||
static char const *instructions[4] = {"instruction", "`BIT`", "`RES`", "`SET`"};
|
static char const *instructions[4] = {"instruction", "`BIT`", "`RES`", "`SET`"};
|
||||||
@@ -596,3 +512,104 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Expression::encode(std::vector<uint8_t> &buffer) const {
|
||||||
|
assume(buffer.empty());
|
||||||
|
|
||||||
|
if (isKnown()) {
|
||||||
|
// If the RPN expression's value is known, output a constant directly
|
||||||
|
uint32_t val = value();
|
||||||
|
buffer.resize(5);
|
||||||
|
buffer[0] = RPN_CONST;
|
||||||
|
buffer[1] = val & 0xFF;
|
||||||
|
buffer[2] = val >> 8;
|
||||||
|
buffer[3] = val >> 16;
|
||||||
|
buffer[4] = val >> 24;
|
||||||
|
} else {
|
||||||
|
// If the RPN expression's value is not known, serialize its RPN values
|
||||||
|
buffer.reserve(rpn.size() * 2); // Rough estimate of the serialized size
|
||||||
|
for (RPNValue const &val : rpn) {
|
||||||
|
val.appendEncoded(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RPNValue::RPNValue(RPNCommand cmd) : command(cmd), data(std::monostate{}) {
|
||||||
|
assume(
|
||||||
|
cmd != RPN_SIZEOF_SECTTYPE && cmd != RPN_STARTOF_SECTTYPE && cmd != RPN_BIT_INDEX
|
||||||
|
&& cmd != RPN_CONST && cmd != RPN_SYM && cmd != RPN_BANK_SYM && cmd != RPN_BANK_SECT
|
||||||
|
&& cmd != RPN_SIZEOF_SECT && cmd != RPN_STARTOF_SECT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPNValue::RPNValue(RPNCommand cmd, uint8_t val) : command(cmd), data(val) {
|
||||||
|
assume(cmd == RPN_SIZEOF_SECTTYPE || cmd == RPN_STARTOF_SECTTYPE || cmd == RPN_BIT_INDEX);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPNValue::RPNValue(RPNCommand cmd, uint32_t val) : command(cmd), data(val) {
|
||||||
|
assume(cmd == RPN_CONST);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPNValue::RPNValue(RPNCommand cmd, std::string const &name) : command(cmd), data(name) {
|
||||||
|
assume(
|
||||||
|
cmd == RPN_SYM || cmd == RPN_BANK_SYM || cmd == RPN_BANK_SECT || cmd == RPN_SIZEOF_SECT
|
||||||
|
|| cmd == RPN_STARTOF_SECT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RPNValue::appendEncoded(std::vector<uint8_t> &buffer) const {
|
||||||
|
// Every command starts with its own ID
|
||||||
|
buffer.push_back(command);
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case RPN_CONST: {
|
||||||
|
// The command ID is followed by a four-byte integer
|
||||||
|
assume(std::holds_alternative<uint32_t>(data));
|
||||||
|
uint32_t val = std::get<uint32_t>(data);
|
||||||
|
buffer.push_back(val & 0xFF);
|
||||||
|
buffer.push_back(val >> 8);
|
||||||
|
buffer.push_back(val >> 16);
|
||||||
|
buffer.push_back(val >> 24);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RPN_SYM:
|
||||||
|
case RPN_BANK_SYM: {
|
||||||
|
// The command ID is followed by a four-byte symbol ID
|
||||||
|
assume(std::holds_alternative<std::string>(data));
|
||||||
|
// The symbol name is always written expanded
|
||||||
|
Symbol *sym = sym_FindExactSymbol(std::get<std::string>(data));
|
||||||
|
out_RegisterSymbol(*sym); // Ensure that `sym->ID` is set
|
||||||
|
buffer.push_back(sym->ID & 0xFF);
|
||||||
|
buffer.push_back(sym->ID >> 8);
|
||||||
|
buffer.push_back(sym->ID >> 16);
|
||||||
|
buffer.push_back(sym->ID >> 24);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RPN_BANK_SECT:
|
||||||
|
case RPN_SIZEOF_SECT:
|
||||||
|
case RPN_STARTOF_SECT: {
|
||||||
|
// The command ID is followed by a NUL-terminated section name string
|
||||||
|
assume(std::holds_alternative<std::string>(data));
|
||||||
|
std::string const &name = std::get<std::string>(data);
|
||||||
|
buffer.reserve(buffer.size() + name.length() + 1);
|
||||||
|
buffer.insert(buffer.end(), RANGE(name));
|
||||||
|
buffer.push_back('\0');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RPN_SIZEOF_SECTTYPE:
|
||||||
|
case RPN_STARTOF_SECTTYPE:
|
||||||
|
case RPN_BIT_INDEX:
|
||||||
|
// The command ID is followed by a byte value
|
||||||
|
assume(std::holds_alternative<uint8_t>(data));
|
||||||
|
buffer.push_back(std::get<uint8_t>(data));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Other command IDs are not followed by anything
|
||||||
|
assume(std::holds_alternative<std::monostate>(data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -218,14 +218,8 @@ static unsigned int
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (alignment != 0) {
|
} else if (alignment != 0) {
|
||||||
int32_t curOfs = (alignOffset - sect.size) % alignSize;
|
|
||||||
|
|
||||||
if (curOfs < 0) {
|
|
||||||
curOfs += alignSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure any fixed address given is compatible
|
// Make sure any fixed address given is compatible
|
||||||
if (sect.org != UINT32_MAX) {
|
if (uint32_t curOfs = (alignOffset - sect.size) & alignMask; sect.org != UINT32_MAX) {
|
||||||
if ((sect.org - curOfs) & alignMask) {
|
if ((sect.org - curOfs) & alignMask) {
|
||||||
sectError(
|
sectError(
|
||||||
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||||
@@ -406,13 +400,16 @@ static Section *getSection(
|
|||||||
bank = sectionTypeInfo[type].firstBank;
|
bank = sectionTypeInfo[type].firstBank;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This should be redundant, as the parser guarantees that `AlignmentSpec` will be valid.
|
||||||
if (alignOffset >= alignSize) {
|
if (alignOffset >= alignSize) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error(
|
error(
|
||||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%" PRIu32 ")",
|
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%" PRIu32 ")",
|
||||||
alignOffset,
|
alignOffset,
|
||||||
alignSize
|
alignSize
|
||||||
);
|
);
|
||||||
alignOffset = 0;
|
alignOffset = 0;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
if (org != UINT32_MAX) {
|
if (org != UINT32_MAX) {
|
||||||
@@ -621,10 +618,10 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need `(pcValue + curOffset + return value) % (1 << alignment) == offset`
|
// We need `(pcValue + curOffset + return value) & minAlignMask == offset`
|
||||||
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
uint16_t pcValue = isFixed ? sect->org : sect->alignOfs;
|
||||||
return static_cast<uint16_t>(offset - curOffset - pcValue)
|
uint32_t minAlignMask = (1u << std::min(alignment, curAlignment)) - 1;
|
||||||
% (1u << std::min(alignment, curAlignment));
|
return static_cast<uint16_t>(offset - curOffset - pcValue) & minAlignMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||||
@@ -634,13 +631,15 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
|||||||
|
|
||||||
assume(alignment <= 16); // Should be ensured by the caller
|
assume(alignment <= 16); // Should be ensured by the caller
|
||||||
uint32_t alignSize = 1u << alignment;
|
uint32_t alignSize = 1u << alignment;
|
||||||
|
uint32_t alignMask = alignSize - 1;
|
||||||
|
|
||||||
Section *sect = sect_GetSymbolSection();
|
Section *sect = sect_GetSymbolSection();
|
||||||
assume(sect->align <= 16); // Left-shifting by 32 or more would be UB
|
assume(sect->align <= 16); // Left-shifting by 32 or more would be UB
|
||||||
uint32_t sectAlignSize = 1u << sect->align;
|
uint32_t sectAlignSize = 1u << sect->align;
|
||||||
|
uint32_t sectAlignMask = sectAlignSize - 1;
|
||||||
|
|
||||||
if (sect->org != UINT32_MAX) {
|
if (sect->org != UINT32_MAX) {
|
||||||
if (uint32_t actualOffset = (sect->org + curOffset) % alignSize; actualOffset != offset) {
|
if (uint32_t actualOffset = (sect->org + curOffset) & alignMask; actualOffset != offset) {
|
||||||
error(
|
error(
|
||||||
"Section is misaligned (at PC = $%04" PRIx32 ", expected ALIGN[%" PRIu32
|
"Section is misaligned (at PC = $%04" PRIx32 ", expected ALIGN[%" PRIu32
|
||||||
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||||
@@ -651,8 +650,8 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
|||||||
actualOffset
|
actualOffset
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (uint32_t actualOffset = (sect->alignOfs + curOffset) % alignSize;
|
} else if (uint32_t actualOffset = (sect->alignOfs + curOffset) & alignMask;
|
||||||
sect->align != 0 && actualOffset % sectAlignSize != offset % sectAlignSize) {
|
sect->align != 0 && (actualOffset & sectAlignMask) != (offset & sectAlignMask)) {
|
||||||
error(
|
error(
|
||||||
"Section is misaligned ($%04" PRIx32 " bytes into the section, expected ALIGN[%" PRIu32
|
"Section is misaligned ($%04" PRIx32 " bytes into the section, expected ALIGN[%" PRIu32
|
||||||
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||||
@@ -669,8 +668,8 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
|||||||
sect->org = offset - curOffset;
|
sect->org = offset - curOffset;
|
||||||
} else if (alignment > sect->align) {
|
} else if (alignment > sect->align) {
|
||||||
sect->align = alignment;
|
sect->align = alignment;
|
||||||
// We need `(sect->alignOfs + curOffset) % alignSize == offset`
|
// We need `(sect->alignOfs + curOffset) & alignMask == offset`
|
||||||
sect->alignOfs = (offset - curOffset) % alignSize;
|
sect->alignOfs = (offset - curOffset) & alignMask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,12 +944,11 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
|
|||||||
// The file is seekable; skip to the specified start position
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (errno != ESPIPE) {
|
if (errno != ESPIPE) {
|
||||||
// LCOV_EXCL_START
|
|
||||||
error(
|
error(
|
||||||
"Error determining size of `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno)
|
"Error determining size of `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno)
|
||||||
);
|
);
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
}
|
||||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
@@ -961,6 +959,7 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||||
@@ -1010,12 +1009,11 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
|
|||||||
// The file is seekable; skip to the specified start position
|
// The file is seekable; skip to the specified start position
|
||||||
fseek(file, startPos, SEEK_SET);
|
fseek(file, startPos, SEEK_SET);
|
||||||
} else {
|
} else {
|
||||||
|
// LCOV_EXCL_START
|
||||||
if (errno != ESPIPE) {
|
if (errno != ESPIPE) {
|
||||||
// LCOV_EXCL_START
|
|
||||||
error(
|
error(
|
||||||
"Error determining size of `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno)
|
"Error determining size of `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno)
|
||||||
);
|
);
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
}
|
}
|
||||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||||
while (startPos--) {
|
while (startPos--) {
|
||||||
@@ -1026,21 +1024,22 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
|
|
||||||
while (length--) {
|
while (length--) {
|
||||||
if (int byte = fgetc(file); byte != EOF) {
|
if (int byte = fgetc(file); byte != EOF) {
|
||||||
writeByte(byte);
|
writeByte(byte);
|
||||||
} else if (ferror(file)) {
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
|
} else if (ferror(file)) {
|
||||||
error("Error reading `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno));
|
error("Error reading `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno));
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
} else {
|
} else {
|
||||||
error(
|
error(
|
||||||
"Premature end of `INCBIN` file \"%s\" (%" PRId32 " bytes left to read)",
|
"Premature end of `INCBIN` file \"%s\" (%" PRId32 " bytes left to read)",
|
||||||
name.c_str(),
|
name.c_str(),
|
||||||
length + 1
|
length + 1
|
||||||
);
|
);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
|
||||||
#include <stdlib.h> // strtoul
|
#include <optional>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
#include "platform.hpp" // strcasecmp
|
#include "platform.hpp" // strcasecmp
|
||||||
|
#include "util.hpp" // parseWholeNumber
|
||||||
|
|
||||||
Tracing tracing;
|
Tracing tracing;
|
||||||
|
|
||||||
@@ -22,8 +24,10 @@ bool trace_ParseTraceDepth(char const *arg) {
|
|||||||
tracing.loud = false;
|
tracing.loud = false;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
char *endptr;
|
std::optional<uint64_t> depth = parseWholeNumber(arg);
|
||||||
tracing.depth = strtoul(arg, &endptr, 0);
|
if (depth) {
|
||||||
return arg[0] != '\0' && *endptr == '\0';
|
tracing.depth = *depth;
|
||||||
|
}
|
||||||
|
return depth.has_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,25 +63,9 @@ std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::str
|
|||||||
return {state, std::nullopt};
|
return {state, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the rest of the string a decimal number?
|
// If the rest of the string is a decimal number, it's the parameter value
|
||||||
// We want to avoid `strtoul`'s whitespace and sign handling, so we parse manually
|
|
||||||
char const *ptr = flag.c_str() + equals + 1;
|
char const *ptr = flag.c_str() + equals + 1;
|
||||||
uint32_t param = 0;
|
uint64_t param = parseNumber(ptr, BASE_10).value_or(0);
|
||||||
bool overflowed = false;
|
|
||||||
|
|
||||||
for (; isDigit(*ptr); ++ptr) {
|
|
||||||
if (overflowed) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t c = *ptr - '0';
|
|
||||||
if (param > (UINT32_MAX - c) / 10) {
|
|
||||||
overflowed = true;
|
|
||||||
param = UINT32_MAX;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
param = param * 10 + c;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we reached the end of the string, truncate it at the '='
|
// If we reached the end of the string, truncate it at the '='
|
||||||
if (*ptr == '\0') {
|
if (*ptr == '\0') {
|
||||||
@@ -92,5 +76,5 @@ std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {state, param};
|
return {state, param > UINT32_MAX ? UINT32_MAX : param};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,16 +240,17 @@ static void
|
|||||||
std::vector<uint8_t> romx; // Buffer of ROMX bank data
|
std::vector<uint8_t> romx; // Buffer of ROMX bank data
|
||||||
uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0
|
uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0
|
||||||
size_t totalRomxLen = 0; // *Actual* size of ROMX data
|
size_t totalRomxLen = 0; // *Actual* size of ROMX data
|
||||||
uint8_t bank[BANK_SIZE]; // Temp buffer used to store a whole bank's worth of data
|
|
||||||
|
|
||||||
// Handle ROMX
|
// Handle ROMX
|
||||||
|
auto errorTooLarge = [&name]() {
|
||||||
|
error("\"%s\" has more than 65536 banks", name); // LCOV_EXCL_LINE
|
||||||
|
};
|
||||||
|
static constexpr off_t NB_BANKS_LIMIT = 0x10000;
|
||||||
|
static_assert(NB_BANKS_LIMIT * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
if (fileSize >= 0x10000 * BANK_SIZE) {
|
if (fileSize >= NB_BANKS_LIMIT * BANK_SIZE) {
|
||||||
error("\"%s\" has more than 65536 banks", name);
|
return errorTooLarge(); // LCOV_EXCL_LINE
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// This should be guaranteed from the size cap...
|
|
||||||
static_assert(0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS");
|
|
||||||
// Compute number of banks and ROMX len from file size
|
// Compute number of banks and ROMX len from file size
|
||||||
nbBanks = (fileSize + (BANK_SIZE - 1)) / BANK_SIZE; // ceil(fileSize / BANK_SIZE)
|
nbBanks = (fileSize + (BANK_SIZE - 1)) / BANK_SIZE; // ceil(fileSize / BANK_SIZE)
|
||||||
totalRomxLen = fileSize >= BANK_SIZE ? fileSize - BANK_SIZE : 0;
|
totalRomxLen = fileSize >= BANK_SIZE ? fileSize - BANK_SIZE : 0;
|
||||||
@@ -258,19 +259,13 @@ static void
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
romx.resize(nbBanks * BANK_SIZE);
|
romx.resize(nbBanks * BANK_SIZE);
|
||||||
ssize_t bankLen = readBytes(input, &romx[(nbBanks - 1) * BANK_SIZE], BANK_SIZE);
|
ssize_t bankLen = readBytes(input, &romx[(nbBanks - 1) * BANK_SIZE], BANK_SIZE);
|
||||||
|
|
||||||
// Update bank count, ONLY IF at least one byte was read
|
// Update bank count, ONLY IF at least one byte was read
|
||||||
if (bankLen) {
|
if (bankLen) {
|
||||||
// We're going to read another bank, check that it won't be too much
|
// We're going to read another bank, check that it won't be too much
|
||||||
static_assert(
|
if (nbBanks == NB_BANKS_LIMIT) {
|
||||||
0x10000 * BANK_SIZE <= SSIZE_MAX, "Max input file size too large for OS"
|
return errorTooLarge(); // LCOV_EXCL_LINE
|
||||||
);
|
|
||||||
if (nbBanks == 0x10000) {
|
|
||||||
error("\"%s\" has more than 65536 banks", name);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
++nbBanks;
|
++nbBanks;
|
||||||
|
|
||||||
// Update global checksum, too
|
// Update global checksum, too
|
||||||
for (uint16_t i = 0; i < bankLen; ++i) {
|
for (uint16_t i = 0; i < bankLen; ++i) {
|
||||||
globalSum += romx[totalRomxLen + i];
|
globalSum += romx[totalRomxLen + i];
|
||||||
@@ -341,6 +336,7 @@ static void
|
|||||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||||
if (input == output) {
|
if (input == output) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
uint8_t bank[BANK_SIZE];
|
||||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
||||||
|
|
||||||
for (uint16_t i = 0; i < bankLen; ++i) {
|
for (uint16_t i = 0; i < bankLen; ++i) {
|
||||||
@@ -432,8 +428,9 @@ static void
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
uint8_t bank[BANK_SIZE];
|
||||||
memset(bank, options.padValue, sizeof(bank));
|
memset(bank, options.padValue, sizeof(bank));
|
||||||
size_t len = (nbBanks - 1) * BANK_SIZE - totalRomxLen; // Don't count ROM0!
|
size_t len = (nbBanks - 1) * sizeof(bank) - totalRomxLen; // Don't count ROM0!
|
||||||
|
|
||||||
while (len) {
|
while (len) {
|
||||||
static_assert(sizeof(bank) <= SSIZE_MAX, "Bank too large for reading");
|
static_assert(sizeof(bank) <= SSIZE_MAX, "Bank too large for reading");
|
||||||
@@ -470,8 +467,10 @@ bool fix_ProcessFile(char const *name, char const *outputName) {
|
|||||||
} else {
|
} else {
|
||||||
output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600);
|
output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600);
|
||||||
if (output == -1) {
|
if (output == -1) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
error("Failed to open \"%s\" for writing: %s", outputName, strerror(errno));
|
error("Failed to open \"%s\" for writing: %s", outputName, strerror(errno));
|
||||||
return true;
|
return true;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
openedOutput = true;
|
openedOutput = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
#include "fix/main.hpp"
|
#include "fix/main.hpp"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <inttypes.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <optional>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
#include "usage.hpp"
|
#include "usage.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
|
||||||
#include "fix/fix.hpp"
|
#include "fix/fix.hpp"
|
||||||
@@ -89,25 +92,13 @@ static Usage usage = {
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
static void parseByte(uint16_t &output, char name) {
|
static void parseByte(uint16_t &output, char name) {
|
||||||
if (musl_optarg[0] == 0) {
|
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
|
||||||
fatal("Argument to option '-%c' may not be empty", name);
|
fatal("Invalid argument for option '-%c'", name);
|
||||||
}
|
} else if (*value > 0xFF) {
|
||||||
|
fatal("Argument for option '-%c' must be between 0 and 0xFF", name);
|
||||||
char *endptr;
|
|
||||||
unsigned long value;
|
|
||||||
if (musl_optarg[0] == '$') {
|
|
||||||
value = strtoul(&musl_optarg[1], &endptr, 16);
|
|
||||||
} else {
|
} else {
|
||||||
value = strtoul(musl_optarg, &endptr, 0);
|
output = *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*endptr) {
|
|
||||||
fatal("Expected number as argument to option '-%c', got \"%s\"", name, musl_optarg);
|
|
||||||
} else if (value > 0xFF) {
|
|
||||||
fatal("Argument to option '-%c' is larger than 255: %lu", name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
output = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t const nintendoLogo[] = {
|
static uint8_t const nintendoLogo[] = {
|
||||||
@@ -122,12 +113,16 @@ static void initLogo() {
|
|||||||
if (strcmp(options.logoFilename, "-")) {
|
if (strcmp(options.logoFilename, "-")) {
|
||||||
logoFile = fopen(options.logoFilename, "rb");
|
logoFile = fopen(options.logoFilename, "rb");
|
||||||
} else {
|
} else {
|
||||||
|
// LCOV_EXCL_START
|
||||||
options.logoFilename = "<stdin>";
|
options.logoFilename = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
logoFile = stdin;
|
logoFile = stdin;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
if (!logoFile) {
|
if (!logoFile) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to open \"%s\" for reading: %s", options.logoFilename, strerror(errno));
|
fatal("Failed to open \"%s\" for reading: %s", options.logoFilename, strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeLogo{[&] { fclose(logoFile); }};
|
Defer closeLogo{[&] { fclose(logoFile); }};
|
||||||
|
|
||||||
@@ -330,8 +325,10 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
199
src/fix/mbc.cpp
199
src/fix/mbc.cpp
@@ -2,15 +2,17 @@
|
|||||||
|
|
||||||
#include "fix/mbc.hpp"
|
#include "fix/mbc.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "helpers.hpp" // unreachable_
|
#include "helpers.hpp" // unreachable_
|
||||||
#include "platform.hpp" // strcasecmp
|
#include "platform.hpp" // strcasecmp
|
||||||
#include "util.hpp" // isBlankSpace, isDigit
|
#include "util.hpp" // isBlankSpace, isLower, isDigit
|
||||||
|
|
||||||
#include "fix/warning.hpp"
|
#include "fix/warning.hpp"
|
||||||
|
|
||||||
@@ -96,87 +98,59 @@ bool mbc_HasRAM(MbcType type) {
|
|||||||
return search != mbcData.end() && search->second.second;
|
return search != mbcData.end() && search->second.second;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void skipBlankSpace(char const *&ptr) {
|
|
||||||
while (isBlankSpace(*ptr)) {
|
|
||||||
++ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void skipMBCSpace(char const *&ptr) {
|
static void skipMBCSpace(char const *&ptr) {
|
||||||
while (isBlankSpace(*ptr) || *ptr == '_') {
|
ptr += strspn(ptr, " \t_");
|
||||||
++ptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char normalizeMBCChar(char c) {
|
static char normalizeMBCChar(char c) {
|
||||||
if (c >= 'a' && c <= 'z') { // Uppercase for comparison with `mbc_Name`s
|
if (isLower(c)) {
|
||||||
c = c - 'a' + 'A';
|
c = c - 'a' + 'A'; // Uppercase for comparison with `mbc_Name`s
|
||||||
} else if (c == '_') { // Treat underscores as spaces
|
} else if (c == '_') {
|
||||||
c = ' ';
|
c = ' '; // Treat underscores as spaces
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
[[noreturn]]
|
||||||
while (*expected) {
|
static void fatalUnknownMBC(char const *name) {
|
||||||
// If `name` is too short, the character will be '\0' and this will return `false`
|
fatal("Unknown MBC \"%s\"\n%s", name, acceptedMBCNames);
|
||||||
if (normalizeMBCChar(*name++) != *expected++) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void fatalUnknownMBC(char const *fullName) {
|
static void fatalWrongMBCFeatures(char const *name) {
|
||||||
fatal("Unknown MBC \"%s\"\n%s", fullName, acceptedMBCNames);
|
fatal("Features incompatible with MBC (\"%s\")\n%s", name, acceptedMBCNames);
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]]
|
|
||||||
static void fatalWrongMBCFeatures(char const *fullName) {
|
|
||||||
fatal("Features incompatible with MBC (\"%s\")\n%s", fullName, acceptedMBCNames);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor) {
|
MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor) {
|
||||||
char const *fullName = name;
|
char const *ptr = name + strspn(name, " \t"); // Skip leading blank space
|
||||||
|
|
||||||
if (!strcasecmp(name, "help") || !strcasecmp(name, "list")) {
|
if (!strcasecmp(ptr, "help") || !strcasecmp(ptr, "list")) {
|
||||||
puts(acceptedMBCNames); // Outputs to stdout and appends a newline
|
puts(acceptedMBCNames); // Outputs to stdout and appends a newline
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDigit(name[0]) || name[0] == '$') {
|
// Parse numeric MBC and return it as-is (unless it's too large)
|
||||||
int base = 0;
|
if (char c = *ptr; isDigit(c) || c == '$' || c == '&' || c == '%') {
|
||||||
|
if (std::optional<uint64_t> mbc = parseWholeNumber(ptr); !mbc) {
|
||||||
if (name[0] == '$') {
|
fatalUnknownMBC(name);
|
||||||
++name;
|
} else if (*mbc > 0xFF) {
|
||||||
base = 16;
|
fatal("Specified MBC ID out of range 0-255: \"%s\"", name);
|
||||||
|
} else {
|
||||||
|
return static_cast<MbcType>(*mbc);
|
||||||
}
|
}
|
||||||
// Parse number, and return it as-is (unless it's too large)
|
|
||||||
char *endptr;
|
|
||||||
unsigned long mbc = strtoul(name, &endptr, base);
|
|
||||||
|
|
||||||
if (*endptr) {
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
|
||||||
if (mbc > 0xFF) {
|
|
||||||
fatal("Specified MBC ID out of range 0-255: \"%s\"", fullName);
|
|
||||||
}
|
|
||||||
return static_cast<MbcType>(mbc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin by reading the MBC type:
|
// Begin by reading the MBC type:
|
||||||
uint16_t mbc;
|
uint16_t mbc = UINT16_MAX;
|
||||||
char const *ptr = name;
|
|
||||||
|
|
||||||
skipBlankSpace(ptr); // Trim off leading blank space
|
auto tryReadSlice = [&ptr, &name](char const *expected) {
|
||||||
|
while (*expected) {
|
||||||
#define tryReadSlice(expected) \
|
// If `name` is too short, the character will be '\0' and this will return `false`
|
||||||
do { \
|
if (normalizeMBCChar(*ptr++) != *expected++) {
|
||||||
if (!readMBCSlice(ptr, expected)) { \
|
fatalUnknownMBC(name);
|
||||||
fatalUnknownMBC(fullName); \
|
}
|
||||||
} \
|
}
|
||||||
} while (0)
|
};
|
||||||
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
case 'R': // ROM / ROM_ONLY
|
case 'R': // ROM / ROM_ONLY
|
||||||
@@ -196,13 +170,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
case 'B':
|
case 'B':
|
||||||
case 'b':
|
case 'b':
|
||||||
switch (*ptr++) {
|
tryReadSlice("C");
|
||||||
case 'C':
|
|
||||||
case 'c':
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
case '1':
|
case '1':
|
||||||
mbc = MBC1;
|
mbc = MBC1;
|
||||||
@@ -222,8 +190,6 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
case '7':
|
case '7':
|
||||||
mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY;
|
mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
@@ -231,8 +197,6 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
tryReadSlice("M01");
|
tryReadSlice("M01");
|
||||||
mbc = MMM01;
|
mbc = MMM01;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -255,39 +219,30 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
tryReadSlice("MA5");
|
tryReadSlice("MA5");
|
||||||
mbc = BANDAI_TAMA5;
|
mbc = BANDAI_TAMA5;
|
||||||
break;
|
break;
|
||||||
case 'P': {
|
case 'P':
|
||||||
tryReadSlice("P1");
|
tryReadSlice("P1");
|
||||||
// Parse version
|
// Parse version
|
||||||
skipMBCSpace(ptr);
|
skipMBCSpace(ptr);
|
||||||
// Major
|
// Major
|
||||||
char *endptr;
|
if (std::optional<uint64_t> major = parseNumber(ptr, BASE_10); !major) {
|
||||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
|
||||||
|
|
||||||
if (endptr == ptr) {
|
|
||||||
fatal("Failed to parse TPP1 major revision number");
|
fatal("Failed to parse TPP1 major revision number");
|
||||||
}
|
} else if (*major != 1) {
|
||||||
ptr = endptr;
|
|
||||||
if (val != 1) {
|
|
||||||
fatal("RGBFIX only supports TPP1 version 1.0");
|
fatal("RGBFIX only supports TPP1 version 1.0");
|
||||||
|
} else {
|
||||||
|
tpp1Major = *major;
|
||||||
}
|
}
|
||||||
tpp1Major = val;
|
|
||||||
tryReadSlice(".");
|
tryReadSlice(".");
|
||||||
// Minor
|
// Minor
|
||||||
val = strtoul(ptr, &endptr, 10);
|
if (std::optional<uint64_t> minor = parseNumber(ptr, BASE_10); !minor) {
|
||||||
if (endptr == ptr) {
|
|
||||||
fatal("Failed to parse TPP1 minor revision number");
|
fatal("Failed to parse TPP1 minor revision number");
|
||||||
}
|
} else if (*minor > 0xFF) {
|
||||||
ptr = endptr;
|
|
||||||
if (val > 0xFF) {
|
|
||||||
fatal("TPP1 minor revision number must be 8-bit");
|
fatal("TPP1 minor revision number must be 8-bit");
|
||||||
|
} else {
|
||||||
|
tpp1Minor = *minor;
|
||||||
}
|
}
|
||||||
tpp1Minor = val;
|
|
||||||
mbc = TPP1;
|
mbc = TPP1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'H': // HuC{1, 3}
|
case 'H': // HuC{1, 3}
|
||||||
@@ -300,13 +255,12 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
case '3':
|
case '3':
|
||||||
mbc = HUC3;
|
mbc = HUC3;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
if (mbc == UINT16_MAX) {
|
||||||
fatalUnknownMBC(fullName);
|
fatalUnknownMBC(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read "additional features"
|
// Read "additional features"
|
||||||
@@ -320,18 +274,10 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
for (;;) {
|
while (*ptr) {
|
||||||
skipBlankSpace(ptr); // Trim off trailing blank space
|
|
||||||
|
|
||||||
// If done, start processing "features"
|
|
||||||
if (!*ptr) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// We expect a '+' at this point
|
// We expect a '+' at this point
|
||||||
skipMBCSpace(ptr);
|
skipMBCSpace(ptr);
|
||||||
if (*ptr++ != '+') {
|
tryReadSlice("+");
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
|
||||||
skipMBCSpace(ptr);
|
skipMBCSpace(ptr);
|
||||||
|
|
||||||
switch (*ptr++) {
|
switch (*ptr++) {
|
||||||
@@ -360,8 +306,6 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
tryReadSlice("M");
|
tryReadSlice("M");
|
||||||
features |= RAM;
|
features |= RAM;
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -376,12 +320,8 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
tryReadSlice("IMER");
|
tryReadSlice("IMER");
|
||||||
features |= TIMER;
|
features |= TIMER;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#undef tryReadSlice
|
|
||||||
|
|
||||||
switch (mbc) {
|
switch (mbc) {
|
||||||
case ROM:
|
case ROM:
|
||||||
@@ -402,7 +342,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
} else if (features == (RAM | BATTERY)) {
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
} else if (features) {
|
} else if (features) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -410,7 +350,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
if (features == BATTERY) {
|
if (features == BATTERY) {
|
||||||
mbc = MBC2_BATTERY;
|
mbc = MBC2_BATTERY;
|
||||||
} else if (features) {
|
} else if (features) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -434,7 +374,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
} else if (features == (RAM | BATTERY)) {
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
} else if (features) {
|
} else if (features) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -452,7 +392,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
} else if (features == (RAM | BATTERY)) {
|
} else if (features == (RAM | BATTERY)) {
|
||||||
mbc += 2;
|
mbc += 2;
|
||||||
} else if (features) {
|
} else if (features) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -462,53 +402,50 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
|||||||
case HUC3:
|
case HUC3:
|
||||||
// No extra features accepted
|
// No extra features accepted
|
||||||
if (features) {
|
if (features) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HUC1_RAM_BATTERY:
|
case HUC1_RAM_BATTERY:
|
||||||
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TPP1:
|
case TPP1: {
|
||||||
|
// clang-format off: vertically align values
|
||||||
|
static constexpr uint8_t BATTERY_TPP1 = 1 << 3;
|
||||||
|
static constexpr uint8_t TIMER_TPP1 = 1 << 2;
|
||||||
|
static constexpr uint8_t MULTIRUMBLE_TPP1 = 1 << 1;
|
||||||
|
static constexpr uint8_t RUMBLE_TPP1 = 1 << 0;
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
if (features & RAM) {
|
if (features & RAM) {
|
||||||
warning(WARNING_MBC, "TPP1 requests RAM implicitly if given a non-zero RAM size");
|
warning(WARNING_MBC, "TPP1 requests RAM implicitly if given a non-zero RAM size");
|
||||||
}
|
}
|
||||||
if (features & BATTERY) {
|
if (features & BATTERY) {
|
||||||
mbc |= 0x08;
|
mbc |= BATTERY_TPP1;
|
||||||
}
|
}
|
||||||
if (features & TIMER) {
|
if (features & TIMER) {
|
||||||
mbc |= 0x04;
|
mbc |= TIMER_TPP1;
|
||||||
}
|
|
||||||
if (features & MULTIRUMBLE) {
|
|
||||||
mbc |= 0x03; // Also set the rumble flag
|
|
||||||
}
|
}
|
||||||
if (features & RUMBLE) {
|
if (features & RUMBLE) {
|
||||||
mbc |= 0x01;
|
mbc |= RUMBLE_TPP1;
|
||||||
}
|
}
|
||||||
if (features & SENSOR) {
|
if (features & SENSOR) {
|
||||||
fatalWrongMBCFeatures(fullName);
|
fatalWrongMBCFeatures(name);
|
||||||
}
|
}
|
||||||
// Multiple rumble speeds imply rumble
|
if (features & MULTIRUMBLE) {
|
||||||
if (mbc & 0x01) {
|
mbc |= MULTIRUMBLE_TPP1 | RUMBLE_TPP1; // Multiple rumble speeds imply rumble
|
||||||
assume(mbc & 0x02);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
skipBlankSpace(ptr); // Trim off trailing blank space
|
|
||||||
|
|
||||||
// If there is still something left, error out
|
|
||||||
if (*ptr) {
|
|
||||||
fatalUnknownMBC(fullName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<MbcType>(mbc);
|
return static_cast<MbcType>(mbc);
|
||||||
|
|||||||
132
src/gfx/main.cpp
132
src/gfx/main.cpp
@@ -3,10 +3,10 @@
|
|||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctype.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <ios>
|
#include <ios>
|
||||||
|
#include <optional>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -44,6 +44,8 @@ static struct LocalOptions {
|
|||||||
bool autoPalmap;
|
bool autoPalmap;
|
||||||
bool groupOutputs;
|
bool groupOutputs;
|
||||||
bool reverse;
|
bool reverse;
|
||||||
|
|
||||||
|
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
||||||
} localOptions;
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
@@ -120,76 +122,19 @@ static Usage usage = {
|
|||||||
|
|
||||||
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
// Parses a number at the beginning of a string, moving the pointer to skip the parsed characters.
|
||||||
// Returns the provided errVal on error.
|
// Returns the provided errVal on error.
|
||||||
static uint16_t parseNumber(char *&string, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
static uint16_t readNumber(char const *&str, char const *errPrefix, uint16_t errVal = UINT16_MAX) {
|
||||||
uint8_t base = 10;
|
if (std::optional<uint64_t> number = parseNumber(str); !number) {
|
||||||
if (*string == '\0') {
|
|
||||||
error("%s: expected number, but found nothing", errPrefix);
|
error("%s: expected number, but found nothing", errPrefix);
|
||||||
return errVal;
|
return errVal;
|
||||||
} else if (*string == '$') {
|
} else if (*number > UINT16_MAX) {
|
||||||
base = 16;
|
error("%s: the number is too large!", errPrefix);
|
||||||
++string;
|
|
||||||
} else if (*string == '%') {
|
|
||||||
base = 2;
|
|
||||||
++string;
|
|
||||||
} else if (*string == '0' && string[1] != '\0') {
|
|
||||||
// Check if we have a "0x" or "0b" here
|
|
||||||
if (string[1] == 'x' || string[1] == 'X') {
|
|
||||||
base = 16;
|
|
||||||
string += 2;
|
|
||||||
} else if (string[1] == 'b' || string[1] == 'B') {
|
|
||||||
base = 2;
|
|
||||||
string += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turns a digit into its numeric value in the current base, if it has one.
|
|
||||||
// Maximum is inclusive. The string_view is modified to "consume" all digits.
|
|
||||||
// Returns 255 on parse failure (including wrong char for base), in which case
|
|
||||||
// the string_view may be pointing on garbage.
|
|
||||||
auto charIndex = [&base](unsigned char c) -> uint8_t {
|
|
||||||
unsigned char index = c - '0'; // Use wrapping semantics
|
|
||||||
if (base == 2 && index >= 2) {
|
|
||||||
return 255;
|
|
||||||
} else if (index < 10) {
|
|
||||||
return index;
|
|
||||||
} else if (base != 16) {
|
|
||||||
return 255; // Letters are only valid in hex
|
|
||||||
}
|
|
||||||
index = tolower(c) - 'a'; // OK because we pass an `unsigned char`
|
|
||||||
if (index < 6) {
|
|
||||||
return index + 10;
|
|
||||||
}
|
|
||||||
return 255;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (charIndex(*string) == 255) {
|
|
||||||
error(
|
|
||||||
"%s: expected digit%s, but found nothing", errPrefix, base != 10 ? " after base" : ""
|
|
||||||
);
|
|
||||||
return errVal;
|
return errVal;
|
||||||
|
} else {
|
||||||
|
return *number;
|
||||||
}
|
}
|
||||||
uint16_t number = 0;
|
|
||||||
do {
|
|
||||||
// Read a character, and check if it's valid in the given base
|
|
||||||
uint8_t index = charIndex(*string);
|
|
||||||
if (index == 255) {
|
|
||||||
break; // Found an invalid character, end
|
|
||||||
}
|
|
||||||
++string;
|
|
||||||
|
|
||||||
number *= base;
|
|
||||||
number += index;
|
|
||||||
// The lax check covers the addition on top of the multiplication
|
|
||||||
if (number >= UINT16_MAX / base) {
|
|
||||||
error("%s: the number is too large!", errPrefix);
|
|
||||||
return errVal;
|
|
||||||
}
|
|
||||||
} while (*string != '\0'); // No more characters?
|
|
||||||
|
|
||||||
return number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void skipBlankSpace(char *&arg) {
|
static void skipBlankSpace(char const *&arg) {
|
||||||
arg += strspn(arg, " \t");
|
arg += strspn(arg, " \t");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +206,7 @@ static std::vector<size_t> readAtFile(std::string const &path, std::vector<char>
|
|||||||
// to an "at-file" path if one is encountered.
|
// to an "at-file" path if one is encountered.
|
||||||
static char *parseArgv(int argc, char *argv[]) {
|
static char *parseArgv(int argc, char *argv[]) {
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||||
char *arg = musl_optarg; // Make a copy for scanning
|
char const *arg = musl_optarg; // Make a copy for scanning
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -281,7 +226,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'b': {
|
case 'b': {
|
||||||
uint16_t number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
uint16_t number = readNumber(arg, "Bank 0 base tile ID", 0);
|
||||||
if (number >= 256) {
|
if (number >= 256) {
|
||||||
error("Bank 0 base tile ID must be below 256");
|
error("Bank 0 base tile ID must be below 256");
|
||||||
} else {
|
} else {
|
||||||
@@ -301,7 +246,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
++arg; // Skip comma
|
++arg; // Skip comma
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
number = parseNumber(arg, "Bank 1 base tile ID", 0);
|
number = readNumber(arg, "Bank 1 base tile ID", 0);
|
||||||
if (number >= 256) {
|
if (number >= 256) {
|
||||||
error("Bank 1 base tile ID must be below 256");
|
error("Bank 1 base tile ID must be below 256");
|
||||||
} else {
|
} else {
|
||||||
@@ -344,7 +289,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'd':
|
case 'd':
|
||||||
options.bitDepth = parseNumber(arg, "Bit depth", 2);
|
options.bitDepth = readNumber(arg, "Bit depth", 2);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", musl_optarg);
|
error("Bit depth ('-b') argument must be a valid number, not \"%s\"", musl_optarg);
|
||||||
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
||||||
@@ -366,7 +311,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
|
options.inputSlice.left = readNumber(arg, "Input slice left coordinate");
|
||||||
if (options.inputSlice.left > INT16_MAX) {
|
if (options.inputSlice.left > INT16_MAX) {
|
||||||
error("Input slice left coordinate is out of range!");
|
error("Input slice left coordinate is out of range!");
|
||||||
break;
|
break;
|
||||||
@@ -378,7 +323,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
++arg;
|
++arg;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate");
|
options.inputSlice.top = readNumber(arg, "Input slice upper coordinate");
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
if (*arg != ':') {
|
if (*arg != ':') {
|
||||||
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
|
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
|
||||||
@@ -386,7 +331,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
++arg;
|
++arg;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
options.inputSlice.width = parseNumber(arg, "Input slice width");
|
options.inputSlice.width = readNumber(arg, "Input slice width");
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
if (options.inputSlice.width == 0) {
|
if (options.inputSlice.width == 0) {
|
||||||
error("Input slice width may not be 0!");
|
error("Input slice width may not be 0!");
|
||||||
@@ -397,7 +342,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
++arg;
|
++arg;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
options.inputSlice.height = parseNumber(arg, "Input slice height");
|
options.inputSlice.height = readNumber(arg, "Input slice height");
|
||||||
if (options.inputSlice.height == 0) {
|
if (options.inputSlice.height == 0) {
|
||||||
error("Input slice height may not be 0!");
|
error("Input slice height may not be 0!");
|
||||||
}
|
}
|
||||||
@@ -407,7 +352,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l': {
|
case 'l': {
|
||||||
uint16_t number = parseNumber(arg, "Base palette ID", 0);
|
uint16_t number = readNumber(arg, "Base palette ID", 0);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error("Base palette ID must be a valid number, not \"%s\"", musl_optarg);
|
error("Base palette ID must be a valid number, not \"%s\"", musl_optarg);
|
||||||
} else if (number >= 256) {
|
} else if (number >= 256) {
|
||||||
@@ -428,7 +373,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'N':
|
case 'N':
|
||||||
options.maxNbTiles[0] = parseNumber(arg, "Number of tiles in bank 0", 256);
|
options.maxNbTiles[0] = readNumber(arg, "Number of tiles in bank 0", 256);
|
||||||
if (options.maxNbTiles[0] > 256) {
|
if (options.maxNbTiles[0] > 256) {
|
||||||
error("Bank 0 cannot contain more than 256 tiles");
|
error("Bank 0 cannot contain more than 256 tiles");
|
||||||
}
|
}
|
||||||
@@ -446,7 +391,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
++arg; // Skip comma
|
++arg; // Skip comma
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(arg);
|
||||||
options.maxNbTiles[1] = parseNumber(arg, "Number of tiles in bank 1", 256);
|
options.maxNbTiles[1] = readNumber(arg, "Number of tiles in bank 1", 256);
|
||||||
if (options.maxNbTiles[1] > 256) {
|
if (options.maxNbTiles[1] > 256) {
|
||||||
error("Bank 1 cannot contain more than 256 tiles");
|
error("Bank 1 cannot contain more than 256 tiles");
|
||||||
}
|
}
|
||||||
@@ -460,7 +405,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'n': {
|
case 'n': {
|
||||||
uint16_t number = parseNumber(arg, "Number of palettes", 256);
|
uint16_t number = readNumber(arg, "Number of palettes", 256);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error("Number of palettes ('-n') must be a valid number, not \"%s\"", musl_optarg);
|
error("Number of palettes ('-n') must be a valid number, not \"%s\"", musl_optarg);
|
||||||
}
|
}
|
||||||
@@ -511,7 +456,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
localOptions.reverse = true;
|
localOptions.reverse = true;
|
||||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
options.reversedWidth = readNumber(arg, "Reversed image stride");
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error(
|
error(
|
||||||
"Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg
|
"Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg
|
||||||
@@ -520,7 +465,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4);
|
options.nbColorsPerPal = readNumber(arg, "Number of colors per palette", 4);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error("Palette size ('-s') must be a valid number, not \"%s\"", musl_optarg);
|
error("Palette size ('-s') must be a valid number, not \"%s\"", musl_optarg);
|
||||||
}
|
}
|
||||||
@@ -562,7 +507,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'x':
|
case 'x':
|
||||||
options.trim = parseNumber(arg, "Number of tiles to trim", 0);
|
options.trim = readNumber(arg, "Number of tiles to trim", 0);
|
||||||
if (*arg != '\0') {
|
if (*arg != '\0') {
|
||||||
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", musl_optarg);
|
error("Tile trim ('-x') argument must be a valid number, not \"%s\"", musl_optarg);
|
||||||
}
|
}
|
||||||
@@ -597,8 +542,10 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,7 +632,8 @@ static void verboseOutputConfig() {
|
|||||||
fputs("\t]\n", stderr);
|
fputs("\t]\n", stderr);
|
||||||
}
|
}
|
||||||
// -L/--slice
|
// -L/--slice
|
||||||
if (options.inputSlice.specified()) {
|
if (options.inputSlice.width || options.inputSlice.height || options.inputSlice.left
|
||||||
|
|| options.inputSlice.top) {
|
||||||
fprintf(
|
fprintf(
|
||||||
stderr,
|
stderr,
|
||||||
"\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16
|
"\tInput image slice: %" PRIu16 "x%" PRIu16 " pixels starting at (%" PRIu16 ", %" PRIu16
|
||||||
@@ -846,6 +794,16 @@ int main(int argc, char *argv[]) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (localOptions.groupOutputs) {
|
||||||
|
if (!localOptions.autoAny()) {
|
||||||
|
warnx("Grouping outputs ('-O') is enabled, but without any automatic output paths "
|
||||||
|
"('-A', '-P', '-Q', or '-T')");
|
||||||
|
}
|
||||||
|
if (options.output.empty()) {
|
||||||
|
warnx("Grouping outputs ('-O') is enabled, but without an output tile data file ('-o')"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
|
auto autoOutPath = [](bool autoOptEnabled, std::string &path, char const *extension) {
|
||||||
if (!autoOptEnabled) {
|
if (!autoOptEnabled) {
|
||||||
return;
|
return;
|
||||||
@@ -918,21 +876,17 @@ auto Palette::begin() -> decltype(colors)::iterator {
|
|||||||
auto Palette::end() -> decltype(colors)::iterator {
|
auto Palette::end() -> decltype(colors)::iterator {
|
||||||
// Return an iterator pointing past the last non-empty element.
|
// Return an iterator pointing past the last non-empty element.
|
||||||
// Since the palette may contain gaps, we must scan from the end.
|
// Since the palette may contain gaps, we must scan from the end.
|
||||||
return std::find_if(
|
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
|
||||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
|
||||||
).base();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
||||||
// Skip the first slot if reserved for transparency
|
// Same as the non-const begin().
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
return colors.begin() + options.hasTransparentPixels;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Palette::end() const -> decltype(colors)::const_iterator {
|
auto Palette::end() const -> decltype(colors)::const_iterator {
|
||||||
// Same as the non-const end().
|
// Same as the non-const end().
|
||||||
return std::find_if(
|
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
|
||||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
|
||||||
).base();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t Palette::size() const {
|
uint8_t Palette::size() const {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
@@ -42,9 +43,11 @@ struct ColorSetAttrs {
|
|||||||
std::vector<bool> bannedPages;
|
std::vector<bool> bannedPages;
|
||||||
|
|
||||||
explicit ColorSetAttrs(size_t index) : colorSetIndex(index) {}
|
explicit ColorSetAttrs(size_t index) : colorSetIndex(index) {}
|
||||||
|
|
||||||
bool isBannedFrom(size_t index) const {
|
bool isBannedFrom(size_t index) const {
|
||||||
return index < bannedPages.size() && bannedPages[index];
|
return index < bannedPages.size() && bannedPages[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void banFrom(size_t index) {
|
void banFrom(size_t index) {
|
||||||
if (bannedPages.size() <= index) {
|
if (bannedPages.size() <= index) {
|
||||||
bannedPages.resize(index + 1);
|
bannedPages.resize(index + 1);
|
||||||
@@ -62,28 +65,31 @@ class AssignedSets {
|
|||||||
std::vector<ColorSet> const *_colorSets;
|
std::vector<ColorSet> const *_colorSets;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<typename... Ts>
|
AssignedSets(std::vector<ColorSet> const &colorSets, std::optional<ColorSetAttrs> &&attrs)
|
||||||
AssignedSets(std::vector<ColorSet> const &colorSets, Ts &&...elems)
|
: _assigned{attrs}, _colorSets{&colorSets} {}
|
||||||
: _assigned{std::forward<Ts>(elems)...}, _colorSets{&colorSets} {}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename Inner, template<typename> typename Constness>
|
// Template class for both const and non-const iterators over the non-empty `_assigned` slots
|
||||||
class Iter {
|
template<typename I, template<typename> typename Constness>
|
||||||
|
class AssignedSetsIter {
|
||||||
public:
|
public:
|
||||||
friend class AssignedSets;
|
friend class AssignedSets;
|
||||||
|
|
||||||
// For `iterator_traits`
|
// For `iterator_traits`
|
||||||
using difference_type = typename std::iterator_traits<Inner>::difference_type;
|
|
||||||
using value_type = ColorSetAttrs;
|
using value_type = ColorSetAttrs;
|
||||||
using pointer = Constness<value_type> *;
|
using difference_type = ptrdiff_t;
|
||||||
using reference = Constness<value_type> &;
|
using reference = Constness<value_type> &;
|
||||||
|
using pointer = Constness<value_type> *;
|
||||||
using iterator_category = std::forward_iterator_tag;
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Constness<decltype(_assigned)> *_array = nullptr;
|
Constness<decltype(_assigned)> *_array = nullptr;
|
||||||
Inner _iter{};
|
I _iter{};
|
||||||
|
|
||||||
Iter(decltype(_array) array, decltype(_iter) &&iter) : _array(array), _iter(iter) {}
|
AssignedSetsIter(decltype(_array) array, decltype(_iter) &&iter)
|
||||||
Iter &skipEmpty() {
|
: _array(array), _iter(iter) {}
|
||||||
|
|
||||||
|
AssignedSetsIter &skipEmpty() {
|
||||||
while (_iter != _array->end() && !_iter->has_value()) {
|
while (_iter != _array->end() && !_iter->has_value()) {
|
||||||
++_iter;
|
++_iter;
|
||||||
}
|
}
|
||||||
@@ -91,17 +97,17 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Iter() = default;
|
AssignedSetsIter() = default;
|
||||||
|
|
||||||
bool operator==(Iter const &rhs) const { return _iter == rhs._iter; }
|
bool operator==(AssignedSetsIter const &rhs) const { return _iter == rhs._iter; }
|
||||||
|
|
||||||
Iter &operator++() {
|
AssignedSetsIter &operator++() {
|
||||||
++_iter;
|
++_iter;
|
||||||
skipEmpty();
|
skipEmpty();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
Iter operator++(int) {
|
AssignedSetsIter operator++(int) {
|
||||||
Iter it = *this;
|
AssignedSetsIter it = *this;
|
||||||
++(*this);
|
++(*this);
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
@@ -114,56 +120,55 @@ private:
|
|||||||
return &(**this); // Invokes the operator above, not quite a no-op!
|
return &(**this); // Invokes the operator above, not quite a no-op!
|
||||||
}
|
}
|
||||||
|
|
||||||
friend void swap(Iter &lhs, Iter &rhs) {
|
friend void swap(AssignedSetsIter &lhs, AssignedSetsIter &rhs) {
|
||||||
std::swap(lhs._array, rhs._array);
|
std::swap(lhs._array, rhs._array);
|
||||||
std::swap(lhs._iter, rhs._iter);
|
std::swap(lhs._iter, rhs._iter);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using iterator = Iter<decltype(_assigned)::iterator, std::remove_const_t>;
|
using iterator = AssignedSetsIter<decltype(_assigned)::iterator, std::remove_const_t>;
|
||||||
iterator begin() { return iterator{&_assigned, _assigned.begin()}.skipEmpty(); }
|
iterator begin() { return iterator{&_assigned, _assigned.begin()}.skipEmpty(); }
|
||||||
iterator end() { return iterator{&_assigned, _assigned.end()}; }
|
iterator end() { return iterator{&_assigned, _assigned.end()}; }
|
||||||
using const_iterator = Iter<decltype(_assigned)::const_iterator, std::add_const_t>;
|
|
||||||
|
using const_iterator = AssignedSetsIter<decltype(_assigned)::const_iterator, std::add_const_t>;
|
||||||
const_iterator begin() const {
|
const_iterator begin() const {
|
||||||
return const_iterator{&_assigned, _assigned.begin()}.skipEmpty();
|
return const_iterator{&_assigned, _assigned.begin()}.skipEmpty();
|
||||||
}
|
}
|
||||||
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
const_iterator end() const { return const_iterator{&_assigned, _assigned.end()}; }
|
||||||
|
|
||||||
// Assigns a new ColorSetAttrs in a free slot, assuming there is one
|
void assign(ColorSetAttrs const &&attrs) {
|
||||||
// Args are passed to the `ColorSetAttrs`'s constructor
|
|
||||||
template<typename... Ts>
|
|
||||||
void assign(Ts &&...args) {
|
|
||||||
auto freeSlot =
|
auto freeSlot =
|
||||||
std::find_if_not(RANGE(_assigned), [](std::optional<ColorSetAttrs> const &slot) {
|
std::find_if_not(RANGE(_assigned), [](std::optional<ColorSetAttrs> const &slot) {
|
||||||
return slot.has_value();
|
return slot.has_value();
|
||||||
});
|
});
|
||||||
|
if (freeSlot == _assigned.end()) {
|
||||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
_assigned.emplace_back(attrs); // We are full, use a new slot
|
||||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
} else {
|
||||||
} else { // Reuse a free slot
|
freeSlot->emplace(attrs); // Reuse a free slot
|
||||||
freeSlot->emplace(std::forward<Ts>(args)...);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(iterator const &iter) {
|
void remove(iterator const &iter) {
|
||||||
iter._iter->reset(); // This time, we want to access the `optional` itself
|
iter._iter->reset(); // This time, we want to access the `optional` itself
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() { _assigned.clear(); }
|
void clear() { _assigned.clear(); }
|
||||||
|
|
||||||
bool empty() const {
|
bool empty() const {
|
||||||
return std::find_if(
|
return std::none_of(RANGE(_assigned), [](std::optional<ColorSetAttrs> const &slot) {
|
||||||
RANGE(_assigned),
|
return slot.has_value();
|
||||||
[](std::optional<ColorSetAttrs> const &slot) { return slot.has_value(); }
|
});
|
||||||
)
|
|
||||||
== _assigned.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t nbColorSets() const { return std::distance(RANGE(*this)); }
|
size_t nbColorSets() const { return std::distance(RANGE(*this)); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename Iter>
|
template<typename I>
|
||||||
static void addUniqueColors(
|
static void addUniqueColors(
|
||||||
std::unordered_set<uint16_t> &colors,
|
std::unordered_set<uint16_t> &colors,
|
||||||
Iter iter,
|
I iter,
|
||||||
Iter const &end,
|
I const &end,
|
||||||
std::vector<ColorSet> const &colorSets
|
std::vector<ColorSet> const &colorSets
|
||||||
) {
|
) {
|
||||||
for (; iter != end; ++iter) {
|
for (; iter != end; ++iter) {
|
||||||
@@ -171,6 +176,7 @@ private:
|
|||||||
colors.insert(RANGE(colorSet));
|
colors.insert(RANGE(colorSet));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function should stay private because it returns a reference to a unique object
|
// This function should stay private because it returns a reference to a unique object
|
||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
std::unordered_set<uint16_t> &uniqueColors() const {
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
||||||
@@ -181,9 +187,11 @@ private:
|
|||||||
addUniqueColors(colors, RANGE(*this), *_colorSets);
|
addUniqueColors(colors, RANGE(*this), *_colorSets);
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Returns the number of distinct colors
|
// Returns the number of distinct colors
|
||||||
size_t volume() const { return uniqueColors().size(); }
|
size_t volume() const { return uniqueColors().size(); }
|
||||||
|
|
||||||
bool canFit(ColorSet const &colorSet) const {
|
bool canFit(ColorSet const &colorSet) const {
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
std::unordered_set<uint16_t> &colors = uniqueColors();
|
||||||
colors.insert(RANGE(colorSet));
|
colors.insert(RANGE(colorSet));
|
||||||
@@ -232,18 +240,18 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Computes the "relative size" of a set of color sets on this palette
|
// Computes the "relative size" of a set of color sets on this palette
|
||||||
template<typename Iter>
|
template<typename I>
|
||||||
size_t combinedVolume(Iter &&begin, Iter const &end, std::vector<ColorSet> const &colorSets)
|
size_t combinedVolume(I &&begin, I const &end, std::vector<ColorSet> const &colorSets) const {
|
||||||
const {
|
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
std::unordered_set<uint16_t> &colors = uniqueColors();
|
||||||
addUniqueColors(colors, std::forward<Iter>(begin), end, colorSets);
|
addUniqueColors(colors, std::forward<I>(begin), end, colorSets);
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computes the "relative size" of a set of colors on this palette
|
// Computes the "relative size" of a set of colors on this palette
|
||||||
template<typename Iter>
|
template<typename I>
|
||||||
size_t combinedVolume(Iter &&begin, Iter &&end) const {
|
size_t combinedVolume(I &&begin, I &&end) const {
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
std::unordered_set<uint16_t> &colors = uniqueColors();
|
||||||
colors.insert(std::forward<Iter>(begin), std::forward<Iter>(end));
|
colors.insert(std::forward<I>(begin), std::forward<I>(end));
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -302,7 +310,7 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
|
|||||||
// If the entire palettes can be merged, move all of `from`'s color sets
|
// If the entire palettes can be merged, move all of `from`'s color sets
|
||||||
if (to.combinedVolume(RANGE(from), colorSets) <= options.maxOpaqueColors()) {
|
if (to.combinedVolume(RANGE(from), colorSets) <= options.maxOpaqueColors()) {
|
||||||
for (ColorSetAttrs &attrs : from) {
|
for (ColorSetAttrs &attrs : from) {
|
||||||
to.assign(attrs.colorSetIndex);
|
to.assign(std::move(attrs));
|
||||||
}
|
}
|
||||||
from.clear();
|
from.clear();
|
||||||
}
|
}
|
||||||
@@ -316,23 +324,23 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
|
|||||||
// We do this by adding the first available color set, and then looking for palettes with
|
// We do this by adding the first available color set, and then looking for palettes with
|
||||||
// common colors. (As an optimization, we know we can skip palettes already scanned.)
|
// common colors. (As an optimization, we know we can skip palettes already scanned.)
|
||||||
std::vector<bool> processed(from.nbColorSets(), false);
|
std::vector<bool> processed(from.nbColorSets(), false);
|
||||||
for (std::vector<bool>::iterator iter;
|
for (std::vector<bool>::iterator wasProcessed;
|
||||||
(iter = std::find(RANGE(processed), false)) != processed.end();) {
|
(wasProcessed = std::find(RANGE(processed), false)) != processed.end();) {
|
||||||
auto attrs = from.begin();
|
auto attrs = from.begin();
|
||||||
std::advance(attrs, iter - processed.begin());
|
std::advance(attrs, wasProcessed - processed.begin());
|
||||||
|
|
||||||
std::unordered_set<uint16_t> colors(RANGE(colorSets[attrs->colorSetIndex]));
|
std::unordered_set<uint16_t> colors(RANGE(colorSets[attrs->colorSetIndex]));
|
||||||
std::vector<size_t> members = {static_cast<size_t>(iter - processed.begin())};
|
std::vector<size_t> members = {static_cast<size_t>(wasProcessed - processed.begin())};
|
||||||
*iter = true; // Mark the first color set as processed
|
*wasProcessed = true; // Mark the first color set as processed
|
||||||
|
|
||||||
// Build up the "component"...
|
// Build up the "component"...
|
||||||
for (; ++iter != processed.end(); ++attrs) {
|
for (; ++wasProcessed != processed.end(); ++attrs) {
|
||||||
// If at least one color matches, add it
|
// If at least one color matches, add it
|
||||||
if (ColorSet const &colorSet = colorSets[attrs->colorSetIndex];
|
if (ColorSet const &colorSet = colorSets[attrs->colorSetIndex];
|
||||||
std::find_first_of(RANGE(colors), RANGE(colorSet)) != colors.end()) {
|
std::find_first_of(RANGE(colors), RANGE(colorSet)) != colors.end()) {
|
||||||
colors.insert(RANGE(colorSet));
|
colors.insert(RANGE(colorSet));
|
||||||
members.push_back(iter - processed.begin());
|
members.push_back(wasProcessed - processed.begin());
|
||||||
*iter = true; // Mark that color set as processed
|
*wasProcessed = true; // Mark that color set as processed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,10 +365,10 @@ static void decant(std::vector<AssignedSets> &assignments, std::vector<ColorSet>
|
|||||||
|
|
||||||
// Decant on individual color sets
|
// Decant on individual color sets
|
||||||
decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
|
decantOn([&colorSets](AssignedSets &to, AssignedSets &from) {
|
||||||
for (auto iter = from.begin(); iter != from.end(); ++iter) {
|
for (auto it = from.begin(); it != from.end(); ++it) {
|
||||||
if (to.canFit(colorSets[iter->colorSetIndex])) {
|
if (to.canFit(colorSets[it->colorSetIndex])) {
|
||||||
to.assign(std::move(*iter));
|
to.assign(std::move(*it));
|
||||||
from.remove(iter);
|
from.remove(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -525,10 +533,10 @@ std::pair<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> c
|
|||||||
// Place back any color sets now in the queue via first-fit
|
// Place back any color sets now in the queue via first-fit
|
||||||
for (ColorSetAttrs const &attrs : overloadQueue) {
|
for (ColorSetAttrs const &attrs : overloadQueue) {
|
||||||
ColorSet const &colorSet = colorSets[attrs.colorSetIndex];
|
ColorSet const &colorSet = colorSets[attrs.colorSetIndex];
|
||||||
auto iter = std::find_if(RANGE(assignments), [&colorSet](AssignedSets const &pal) {
|
auto palette = std::find_if(RANGE(assignments), [&colorSet](AssignedSets const &pal) {
|
||||||
return pal.canFit(colorSet);
|
return pal.canFit(colorSet);
|
||||||
});
|
});
|
||||||
if (iter == assignments.end()) { // No such page, create a new one
|
if (palette == assignments.end()) { // No such page, create a new one
|
||||||
verbosePrint(
|
verbosePrint(
|
||||||
VERB_DEBUG,
|
VERB_DEBUG,
|
||||||
"Adding new palette (%zu) for overflowing color set %zu\n",
|
"Adding new palette (%zu) for overflowing color set %zu\n",
|
||||||
@@ -541,9 +549,9 @@ std::pair<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> c
|
|||||||
VERB_DEBUG,
|
VERB_DEBUG,
|
||||||
"Assigning overflowing color set %zu to palette %zu\n",
|
"Assigning overflowing color set %zu to palette %zu\n",
|
||||||
attrs.colorSetIndex,
|
attrs.colorSetIndex,
|
||||||
iter - assignments.begin()
|
palette - assignments.begin()
|
||||||
);
|
);
|
||||||
iter->assign(std::move(attrs));
|
palette->assign(std::move(attrs));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,32 +32,22 @@ using namespace std::string_view_literals;
|
|||||||
|
|
||||||
static char const *hexDigits = "0123456789ABCDEFabcdef";
|
static char const *hexDigits = "0123456789ABCDEFabcdef";
|
||||||
|
|
||||||
template<typename Str> // Should be std::string or std::string_view
|
static void skipBlankSpace(std::string_view const &str, size_t &pos) {
|
||||||
static void skipBlankSpace(Str const &str, size_t &pos) {
|
|
||||||
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
|
pos = std::min(str.find_first_not_of(" \t"sv, pos), str.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr uint8_t nibble(char c) {
|
static uint8_t toHex(char c1, char c2) {
|
||||||
if (c >= 'a') {
|
return parseHexDigit(c1) * 16 + parseHexDigit(c2);
|
||||||
assume(c <= 'f');
|
|
||||||
return c - 'a' + 10;
|
|
||||||
} else if (c >= 'A') {
|
|
||||||
assume(c <= 'F');
|
|
||||||
return c - 'A' + 10;
|
|
||||||
} else {
|
|
||||||
assume(isDigit(c));
|
|
||||||
return c - '0';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr uint8_t toHex(char c1, char c2) {
|
static uint8_t singleToHex(char c) {
|
||||||
return nibble(c1) * 16 + nibble(c2);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr uint8_t singleToHex(char c) {
|
|
||||||
return toHex(c, c);
|
return toHex(c, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint16_t toWord(uint8_t low, uint8_t high) {
|
||||||
|
return high << 8 | low;
|
||||||
|
}
|
||||||
|
|
||||||
void parseInlinePalSpec(char const * const rawArg) {
|
void parseInlinePalSpec(char const * const rawArg) {
|
||||||
// List of #rrggbb/#rgb colors (or #none); comma-separated.
|
// List of #rrggbb/#rgb colors (or #none); comma-separated.
|
||||||
// Palettes are separated by colons.
|
// Palettes are separated by colons.
|
||||||
@@ -176,24 +166,6 @@ void parseInlinePalSpec(char const * const rawArg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
static T readBE(U const *bytes) {
|
|
||||||
T val = 0;
|
|
||||||
for (size_t i = 0; i < sizeof(val); ++i) {
|
|
||||||
val = val << 8 | static_cast<uint8_t>(bytes[i]);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T, typename U>
|
|
||||||
static T readLE(U const *bytes) {
|
|
||||||
T val = 0;
|
|
||||||
for (size_t i = 0; i < sizeof(val); ++i) {
|
|
||||||
val |= static_cast<uint8_t>(bytes[i]) << (i * 8);
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Appends the first line read from `file` to the end of the provided `buffer`.
|
// Appends the first line read from `file` to the end of the provided `buffer`.
|
||||||
// Returns true if a line was read.
|
// Returns true if a line was read.
|
||||||
[[gnu::warn_unused_result]]
|
[[gnu::warn_unused_result]]
|
||||||
@@ -436,7 +408,7 @@ static void parseACTFile(char const *filename, std::filebuf &file) {
|
|||||||
|
|
||||||
uint16_t nbColors = 256;
|
uint16_t nbColors = 256;
|
||||||
if (len == 772) {
|
if (len == 772) {
|
||||||
nbColors = readBE<uint16_t>(&buf[768]);
|
nbColors = toWord(buf[769], buf[768]);
|
||||||
if (nbColors > 256 || nbColors == 0) {
|
if (nbColors > 256 || nbColors == 0) {
|
||||||
error("Invalid number of colors in ACT file \"%s\" (%" PRIu16 ")", filename, nbColors);
|
error("Invalid number of colors in ACT file \"%s\" (%" PRIu16 ")", filename, nbColors);
|
||||||
return;
|
return;
|
||||||
@@ -487,7 +459,7 @@ static void parseACOFile(char const *filename, std::filebuf &file) {
|
|||||||
error("Failed to read ACO file version");
|
error("Failed to read ACO file version");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (readBE<uint16_t>(buf) != 1) {
|
if (toWord(buf[1], buf[0]) != 1) {
|
||||||
error("File \"%s\" is not a valid ACO v1 file", filename);
|
error("File \"%s\" is not a valid ACO v1 file", filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -496,7 +468,7 @@ static void parseACOFile(char const *filename, std::filebuf &file) {
|
|||||||
error("Failed to read number of colors in palette file");
|
error("Failed to read number of colors in palette file");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint16_t nbColors = readBE<uint16_t>(buf);
|
uint16_t nbColors = toWord(buf[1], buf[0]);
|
||||||
|
|
||||||
if (uint16_t maxNbColors = options.maxNbColors(); nbColors > maxNbColors) {
|
if (uint16_t maxNbColors = options.maxNbColors(); nbColors > maxNbColors) {
|
||||||
warnExtraColors("ACO", filename, nbColors, maxNbColors);
|
warnExtraColors("ACO", filename, nbColors, maxNbColors);
|
||||||
@@ -516,7 +488,7 @@ static void parseACOFile(char const *filename, std::filebuf &file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Rgba> &color = options.palSpec.back()[i % options.nbColorsPerPal];
|
std::optional<Rgba> &color = options.palSpec.back()[i % options.nbColorsPerPal];
|
||||||
uint16_t colorType = readBE<uint16_t>(buf);
|
uint16_t colorType = toWord(buf[1], buf[0]);
|
||||||
switch (colorType) {
|
switch (colorType) {
|
||||||
case 0: // RGB
|
case 0: // RGB
|
||||||
// Only keep the MSB of the (big-endian) 16-bit values.
|
// Only keep the MSB of the (big-endian) 16-bit values.
|
||||||
@@ -562,10 +534,10 @@ static void parseGBCFile(char const *filename, std::filebuf &file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options.palSpec.push_back({
|
options.palSpec.push_back({
|
||||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[0])),
|
Rgba::fromCGBColor(toWord(buf[0], buf[1])),
|
||||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[2])),
|
Rgba::fromCGBColor(toWord(buf[2], buf[3])),
|
||||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[4])),
|
Rgba::fromCGBColor(toWord(buf[4], buf[5])),
|
||||||
Rgba::fromCGBColor(readLE<uint16_t>(&buf[6])),
|
Rgba::fromCGBColor(toWord(buf[6], buf[7])),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ void reverse() {
|
|||||||
palette.begin() + options.nbColorsPerPal,
|
palette.begin() + options.nbColorsPerPal,
|
||||||
[&buf, i = 0]() mutable {
|
[&buf, i = 0]() mutable {
|
||||||
i += 2;
|
i += 2;
|
||||||
return Rgba::fromCGBColor(buf[i - 2] + (buf[i - 1] << 8));
|
return Rgba::fromCGBColor(buf[i - 2] | buf[i - 1] << 8); // little-endian
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ uint16_t Rgba::cgbColor() const {
|
|||||||
g = reverse_curve[g];
|
g = reverse_curve[g];
|
||||||
b = reverse_curve[b];
|
b = reverse_curve[b];
|
||||||
} else {
|
} else {
|
||||||
r >>= 3;
|
constexpr auto _8to5 = [](uint8_t c) -> uint8_t { return (c * 31 + 127) / 255; };
|
||||||
g >>= 3;
|
r = _8to5(r);
|
||||||
b >>= 3;
|
g = _8to5(g);
|
||||||
|
b = _8to5(b);
|
||||||
}
|
}
|
||||||
return r | g << 5 | b << 10;
|
return r | g << 5 | b << 10;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ void warning(WarningID id, char const *fmt, ...) {
|
|||||||
va_start(ap, fmt);
|
va_start(ap, fmt);
|
||||||
vfprintf(stderr, fmt, ap);
|
vfprintf(stderr, fmt, ap);
|
||||||
va_end(ap);
|
va_end(ap);
|
||||||
style_Set(stderr, STYLE_YELLOW, true);
|
style_Set(stderr, STYLE_RED, true);
|
||||||
fprintf(stderr, " [-Werror=%s]\n", flag);
|
fprintf(stderr, " [-Werror=%s]\n", flag);
|
||||||
style_Reset(stderr);
|
style_Reset(stderr);
|
||||||
|
|
||||||
|
|||||||
@@ -53,11 +53,10 @@ static void initFreeSpace() {
|
|||||||
static void assignSection(Section §ion, MemoryLocation const &location) {
|
static void assignSection(Section §ion, MemoryLocation const &location) {
|
||||||
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
// Propagate the assigned location to all UNIONs/FRAGMENTs
|
||||||
// so `jr` patches in them will have the correct offset
|
// so `jr` patches in them will have the correct offset
|
||||||
for (Section *next = §ion; next != nullptr; next = next->nextu.get()) {
|
for (Section *piece = §ion; piece != nullptr; piece = piece->nextPiece.get()) {
|
||||||
next->org = location.address;
|
piece->org = location.address;
|
||||||
next->bank = location.bank;
|
piece->bank = location.bank;
|
||||||
}
|
}
|
||||||
|
|
||||||
out_AddSection(section);
|
out_AddSection(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|||||||
layout_SetAddr(floatingAlignOffset);
|
layout_SetAddr(floatingAlignOffset);
|
||||||
} else {
|
} else {
|
||||||
uint32_t alignSize = 1u << alignment;
|
uint32_t alignSize = 1u << alignment;
|
||||||
|
uint32_t alignMask = alignSize - 1;
|
||||||
|
|
||||||
if (alignOfs >= alignSize) {
|
if (alignOfs >= alignSize) {
|
||||||
scriptError(
|
scriptError(
|
||||||
@@ -139,8 +140,8 @@ void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
floatingAlignMask = alignSize - 1;
|
floatingAlignMask = alignMask;
|
||||||
floatingAlignOffset = alignOfs % alignSize;
|
floatingAlignOffset = alignOfs & alignMask;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -158,6 +159,7 @@ void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|||||||
|
|
||||||
if (alignment < 16) {
|
if (alignment < 16) {
|
||||||
uint32_t alignSize = 1u << alignment;
|
uint32_t alignSize = 1u << alignment;
|
||||||
|
uint32_t alignMask = alignSize - 1;
|
||||||
|
|
||||||
if (alignOfs >= alignSize) {
|
if (alignOfs >= alignSize) {
|
||||||
scriptError(
|
scriptError(
|
||||||
@@ -170,7 +172,7 @@ void layout_AlignTo(uint32_t alignment, uint32_t alignOfs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assume(pc >= typeInfo.startAddr);
|
assume(pc >= typeInfo.startAddr);
|
||||||
length %= alignSize;
|
length &= alignMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) {
|
if (uint16_t offset = pc - typeInfo.startAddr; length > typeInfo.size - offset) {
|
||||||
@@ -248,10 +250,9 @@ void layout_PlaceSection(std::string const &name, bool isOptional) {
|
|||||||
typeInfo.name.c_str()
|
typeInfo.name.c_str()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// SDCC areas don't have a type assigned yet, so the linker script is used to give them
|
// SDCC areas don't have a type assigned yet, so the linker script gives them one.
|
||||||
// one.
|
for (Section *piece = section; piece != nullptr; piece = piece->nextPiece.get()) {
|
||||||
for (Section *fragment = section; fragment; fragment = fragment->nextu.get()) {
|
piece->type = activeType;
|
||||||
fragment->type = activeType;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (section->type != activeType) {
|
} else if (section->type != activeType) {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
#include "helpers.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
@@ -107,10 +106,6 @@ static yy::parser::symbol_type parseDecNumber(int c) {
|
|||||||
return yy::parser::make_number(number);
|
return yy::parser::make_number(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isBinDigit(int c) {
|
|
||||||
return c >= '0' && c <= '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
static yy::parser::symbol_type parseBinNumber(char const *prefix) {
|
static yy::parser::symbol_type parseBinNumber(char const *prefix) {
|
||||||
LexerStackEntry &context = lexerStack.back();
|
LexerStackEntry &context = lexerStack.back();
|
||||||
int c = context.file.sgetc();
|
int c = context.file.sgetc();
|
||||||
@@ -149,18 +144,6 @@ static yy::parser::symbol_type parseOctNumber(char const *prefix) {
|
|||||||
return yy::parser::make_number(number);
|
return yy::parser::make_number(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t parseHexDigit(int c) {
|
|
||||||
if (isDigit(c)) {
|
|
||||||
return c - '0';
|
|
||||||
} else if (c >= 'A' && c <= 'F') {
|
|
||||||
return c - 'A' + 10;
|
|
||||||
} else if (c >= 'a' && c <= 'f') {
|
|
||||||
return c - 'a' + 10;
|
|
||||||
} else {
|
|
||||||
unreachable_(); // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static yy::parser::symbol_type parseHexNumber(char const *prefix) {
|
static yy::parser::symbol_type parseHexNumber(char const *prefix) {
|
||||||
LexerStackEntry &context = lexerStack.back();
|
LexerStackEntry &context = lexerStack.back();
|
||||||
int c = context.file.sgetc();
|
int c = context.file.sgetc();
|
||||||
@@ -180,7 +163,7 @@ static yy::parser::symbol_type parseHexNumber(char const *prefix) {
|
|||||||
return yy::parser::make_number(number);
|
return yy::parser::make_number(number);
|
||||||
}
|
}
|
||||||
|
|
||||||
static yy::parser::symbol_type parseNumber(int c) {
|
static yy::parser::symbol_type parseAnyNumber(int c) {
|
||||||
LexerStackEntry &context = lexerStack.back();
|
LexerStackEntry &context = lexerStack.back();
|
||||||
if (c == '0') {
|
if (c == '0') {
|
||||||
switch (context.file.sgetc()) {
|
switch (context.file.sgetc()) {
|
||||||
@@ -278,7 +261,7 @@ yy::parser::symbol_type yylex() {
|
|||||||
} else if (c == '&') {
|
} else if (c == '&') {
|
||||||
return parseOctNumber("'&'");
|
return parseOctNumber("'&'");
|
||||||
} else if (isDigit(c)) {
|
} else if (isDigit(c)) {
|
||||||
return parseNumber(c);
|
return parseAnyNumber(c);
|
||||||
} else if (isLetter(c)) {
|
} else if (isLetter(c)) {
|
||||||
std::string keyword = readKeyword(c);
|
std::string keyword = readKeyword(c);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
#include <optional>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -265,21 +266,18 @@ static void parseScrambleSpec(char *spec) {
|
|||||||
|
|
||||||
uint16_t limit = search->second.second;
|
uint16_t limit = search->second.second;
|
||||||
if (regionSize) {
|
if (regionSize) {
|
||||||
char *endptr;
|
char const *ptr = regionSize + skipBlankSpace(regionSize);
|
||||||
unsigned long value = strtoul(regionSize, &endptr, 0);
|
if (std::optional<uint64_t> value = parseWholeNumber(ptr); !value) {
|
||||||
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
fatal("Invalid region size limit \"%s\" for option '-S'", regionSize);
|
fatal("Invalid region size limit \"%s\" for option '-S'", regionSize);
|
||||||
}
|
} else if (*value > limit) {
|
||||||
if (value > limit) {
|
|
||||||
fatal(
|
fatal(
|
||||||
"%s region size for option '-S' must be between 0 and %" PRIu16,
|
"%s region size for option '-S' must be between 0 and %" PRIu16,
|
||||||
search->first.c_str(),
|
search->first.c_str(),
|
||||||
limit
|
limit
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
limit = *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
limit = value;
|
|
||||||
} else if (search->second.first != &options.scrambleWRAMX) {
|
} else if (search->second.first != &options.scrambleWRAMX) {
|
||||||
// Only WRAMX limit can be implied, since ROMX and SRAM size may vary.
|
// Only WRAMX limit can be implied, since ROMX and SRAM size may vary.
|
||||||
fatal("Missing %s region size limit for option '-S'", search->first.c_str());
|
fatal("Missing %s region size limit for option '-S'", search->first.c_str());
|
||||||
@@ -353,21 +351,16 @@ int main(int argc, char *argv[]) {
|
|||||||
options.outputFileName = musl_optarg;
|
options.outputFileName = musl_optarg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p': {
|
case 'p':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> value = parseWholeNumber(musl_optarg); !value) {
|
||||||
unsigned long value = strtoul(musl_optarg, &endptr, 0);
|
|
||||||
|
|
||||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
|
||||||
fatal("Invalid argument for option '-p'");
|
fatal("Invalid argument for option '-p'");
|
||||||
}
|
} else if (*value > 0xFF) {
|
||||||
if (value > 0xFF) {
|
|
||||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
||||||
|
} else {
|
||||||
|
options.padValue = *value;
|
||||||
|
options.hasPadValue = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.padValue = value;
|
|
||||||
options.hasPadValue = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 'S':
|
case 'S':
|
||||||
parseScrambleSpec(musl_optarg);
|
parseScrambleSpec(musl_optarg);
|
||||||
@@ -407,8 +400,10 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,10 +437,10 @@ int main(int argc, char *argv[]) {
|
|||||||
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
||||||
|
|
||||||
if (lexer_Init(linkerScriptName)) {
|
if (lexer_Init(linkerScriptName)) {
|
||||||
yy::parser parser;
|
if (yy::parser parser; parser.parse() != 0) {
|
||||||
// We don't care about the return value, as any error increments the global error count,
|
// Exited due to YYABORT or YYNOMEM
|
||||||
// which is what `main` checks.
|
fatal("Unrecoverable error while reading linker script"); // LCOV_EXCL_LINE
|
||||||
(void)parser.parse();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the linker script produced any errors, some sections may be in an invalid state
|
// If the linker script produced any errors, some sections may be in an invalid state
|
||||||
|
|||||||
@@ -390,25 +390,6 @@ static void readSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Links a symbol to a section, keeping the section's symbol list sorted.
|
|
||||||
static void linkSymToSect(Symbol &symbol, Section §ion) {
|
|
||||||
uint32_t a = 0, b = section.symbols.size();
|
|
||||||
int32_t symbolOffset = symbol.label().offset;
|
|
||||||
|
|
||||||
while (a != b) {
|
|
||||||
uint32_t c = (a + b) / 2;
|
|
||||||
int32_t otherOffset = section.symbols[c]->label().offset;
|
|
||||||
|
|
||||||
if (otherOffset > symbolOffset) {
|
|
||||||
b = c;
|
|
||||||
} else {
|
|
||||||
a = c + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
section.symbols.insert(section.symbols.begin() + a, &symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reads an assertion from a file.
|
// Reads an assertion from a file.
|
||||||
static void readAssertion(
|
static void readAssertion(
|
||||||
FILE *file,
|
FILE *file,
|
||||||
@@ -512,15 +493,11 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
std::vector<uint32_t> nbSymPerSect(nbSections, 0);
|
std::vector<uint32_t> nbSymPerSect(nbSections, 0);
|
||||||
|
|
||||||
verbosePrint(VERB_INFO, "Reading %" PRIu32 " symbols...\n", nbSymbols);
|
verbosePrint(VERB_INFO, "Reading %" PRIu32 " symbols...\n", nbSymbols);
|
||||||
for (uint32_t i = 0; i < nbSymbols; ++i) {
|
for (Symbol &sym : fileSymbols) {
|
||||||
// Read symbol
|
readSymbol(file, sym, fileName, nodes[fileID]);
|
||||||
Symbol &symbol = fileSymbols[i];
|
sym_AddSymbol(sym);
|
||||||
|
if (std::holds_alternative<Label>(sym.data)) {
|
||||||
readSymbol(file, symbol, fileName, nodes[fileID]);
|
++nbSymPerSect[std::get<Label>(sym.data).sectionID];
|
||||||
|
|
||||||
sym_AddSymbol(symbol);
|
|
||||||
if (std::holds_alternative<Label>(symbol.data)) {
|
|
||||||
++nbSymPerSect[std::get<Label>(symbol.data).sectionID];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,9 +506,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
|
|
||||||
verbosePrint(VERB_INFO, "Reading %" PRIu32 " sections...\n", nbSections);
|
verbosePrint(VERB_INFO, "Reading %" PRIu32 " sections...\n", nbSections);
|
||||||
for (uint32_t i = 0; i < nbSections; ++i) {
|
for (uint32_t i = 0; i < nbSections; ++i) {
|
||||||
// Read section
|
|
||||||
fileSections[i] = std::make_unique<Section>();
|
fileSections[i] = std::make_unique<Section>();
|
||||||
fileSections[i]->nextu = nullptr;
|
fileSections[i]->nextPiece = nullptr;
|
||||||
readSection(file, *fileSections[i], fileName, nodes[fileID]);
|
readSection(file, *fileSections[i], fileName, nodes[fileID]);
|
||||||
fileSections[i]->fileSymbols = &fileSymbols;
|
fileSections[i]->fileSymbols = &fileSymbols;
|
||||||
fileSections[i]->symbols.reserve(nbSymPerSect[i]);
|
fileSections[i]->symbols.reserve(nbSymPerSect[i]);
|
||||||
@@ -549,21 +525,18 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Give patches' PC section pointers to their sections
|
// Give patches' PC section pointers to their sections
|
||||||
for (uint32_t i = 0; i < nbSections; ++i) {
|
for (std::unique_ptr<Section> const § : fileSections) {
|
||||||
if (sectTypeHasData(fileSections[i]->type)) {
|
if (sectTypeHasData(sect->type)) {
|
||||||
for (Patch &patch : fileSections[i]->patches) {
|
for (Patch &patch : sect->patches) {
|
||||||
linkPatchToPCSect(patch, fileSections);
|
linkPatchToPCSect(patch, fileSections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give symbols' section pointers to their sections
|
// Give symbols' section pointers to their sections
|
||||||
for (uint32_t i = 0; i < nbSymbols; ++i) {
|
for (Symbol &sym : fileSymbols) {
|
||||||
if (std::holds_alternative<Label>(fileSymbols[i].data)) {
|
if (std::holds_alternative<Label>(sym.data)) {
|
||||||
Label &label = std::get<Label>(fileSymbols[i].data);
|
sym.linkToSection(*fileSections[std::get<Label>(sym.data).sectionID]);
|
||||||
label.section = fileSections[label.sectionID].get();
|
|
||||||
// Give the section a pointer to the symbol as well
|
|
||||||
linkSymToSect(fileSymbols[i], *label.section);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,23 +545,11 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
sect_AddSection(std::move(fileSections[i]));
|
sect_AddSection(std::move(fileSections[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix symbols' section pointers to component sections
|
// Fix symbols' section pointers to section "pieces"
|
||||||
// This has to run **after** all the `sect_AddSection()` calls,
|
// This has to run **after** all the `sect_AddSection()` calls,
|
||||||
// so that `sect_GetSection()` will work
|
// so that `sect_GetSection()` will work
|
||||||
for (uint32_t i = 0; i < nbSymbols; ++i) {
|
for (Symbol &sym : fileSymbols) {
|
||||||
if (std::holds_alternative<Label>(fileSymbols[i].data)) {
|
sym.fixSectionOffset();
|
||||||
Label &label = std::get<Label>(fileSymbols[i].data);
|
|
||||||
Section *section = label.section;
|
|
||||||
if (section->modifier != SECTION_NORMAL) {
|
|
||||||
// Associate the symbol with the main section, not the "component" one
|
|
||||||
label.section = sect_GetSection(section->name);
|
|
||||||
}
|
|
||||||
if (section->modifier == SECTION_FRAGMENT) {
|
|
||||||
// Add the fragment's offset to the symbol's
|
|
||||||
// (`section->offset` is computed by `sect_AddSection`)
|
|
||||||
label.offset += section->offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -317,12 +317,12 @@ static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
|
|||||||
template<typename F>
|
template<typename F>
|
||||||
static void forEachSortedSection(SortedSections const &bankSections, F callback) {
|
static void forEachSortedSection(SortedSections const &bankSections, F callback) {
|
||||||
for (Section const *sect : bankSections.zeroLenSections) {
|
for (Section const *sect : bankSections.zeroLenSections) {
|
||||||
for (; sect; sect = sect->nextu.get()) {
|
for (; sect != nullptr; sect = sect->nextPiece.get()) {
|
||||||
callback(*sect);
|
callback(*sect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Section const *sect : bankSections.sections) {
|
for (Section const *sect : bankSections.sections) {
|
||||||
for (; sect; sect = sect->nextu.get()) {
|
for (; sect != nullptr; sect = sect->nextPiece.get()) {
|
||||||
callback(*sect);
|
callback(*sect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,13 +349,14 @@ static void writeSymBank(SortedSections const &bankSections, SectionType type, u
|
|||||||
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
|
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
uint16_t addr = static_cast<uint16_t>(sym->label().offset + sect.org);
|
assume(std::holds_alternative<Label>(sym->data));
|
||||||
|
uint16_t addr = static_cast<uint16_t>(std::get<Label>(sym->data).offset + sect.org);
|
||||||
uint16_t parentAddr = addr;
|
uint16_t parentAddr = addr;
|
||||||
if (auto pos = sym->name.find('.'); pos != std::string::npos) {
|
if (auto pos = sym->name.find('.'); pos != std::string::npos) {
|
||||||
std::string parentName = sym->name.substr(0, pos);
|
std::string parentName = sym->name.substr(0, pos);
|
||||||
if (Symbol const *parentSym = sym_GetSymbol(parentName);
|
if (Symbol const *parentSym = sym_GetSymbol(parentName);
|
||||||
parentSym && std::holds_alternative<Label>(parentSym->data)) {
|
parentSym && std::holds_alternative<Label>(parentSym->data)) {
|
||||||
Label const &parentLabel = parentSym->label();
|
Label const &parentLabel = std::get<Label>(parentSym->data);
|
||||||
Section const &parentSection = *parentLabel.section;
|
Section const &parentSection = *parentLabel.section;
|
||||||
parentAddr = static_cast<uint16_t>(parentLabel.offset + parentSection.org);
|
parentAddr = static_cast<uint16_t>(parentLabel.offset + parentSection.org);
|
||||||
}
|
}
|
||||||
@@ -433,25 +434,30 @@ uint16_t forEachSection(SortedSections const §List, F callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void writeMapSymbols(Section const *sect) {
|
static void writeMapSymbols(Section const *sect) {
|
||||||
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
|
uint16_t org = sect->org;
|
||||||
|
|
||||||
|
for (bool announced = true; sect != nullptr; sect = sect->nextPiece.get(), announced = false) {
|
||||||
for (Symbol *sym : sect->symbols) {
|
for (Symbol *sym : sect->symbols) {
|
||||||
// Don't output symbols that begin with an illegal character
|
// Don't output symbols that begin with an illegal character
|
||||||
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
|
if (sym->name.empty() || !startsIdentifier(sym->name[0])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Announce this "piece" before its contents
|
||||||
|
if (!announced) {
|
||||||
|
if (sect->modifier == SECTION_UNION) {
|
||||||
|
fputs("\t ; Next union\n", mapFile);
|
||||||
|
} else if (sect->modifier == SECTION_FRAGMENT) {
|
||||||
|
fputs("\t ; Next fragment\n", mapFile);
|
||||||
|
}
|
||||||
|
announced = true;
|
||||||
|
}
|
||||||
|
assume(std::holds_alternative<Label>(sym->data));
|
||||||
|
uint32_t address = std::get<Label>(sym->data).offset + org;
|
||||||
// Space matches "\tSECTION: $xxxx ..."
|
// Space matches "\tSECTION: $xxxx ..."
|
||||||
fprintf(mapFile, "\t $%04" PRIx32 " = ", sym->label().offset + org);
|
fprintf(mapFile, "\t $%04" PRIx32 " = ", address);
|
||||||
writeSymName(sym->name, mapFile);
|
writeSymName(sym->name, mapFile);
|
||||||
putc('\n', mapFile);
|
putc('\n', mapFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Announce the following "piece"
|
|
||||||
if (SectionModifier mod = sect->nextu ? sect->nextu->modifier : SECTION_NORMAL;
|
|
||||||
mod == SECTION_UNION) {
|
|
||||||
fputs("\t ; Next union\n", mapFile);
|
|
||||||
} else if (mod == SECTION_FRAGMENT) {
|
|
||||||
fputs("\t ; Next fragment\n", mapFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -505,8 +505,10 @@ void patch_CheckAssertions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void checkPatchSize(Patch const &patch, int32_t v, uint8_t n) {
|
static void checkPatchSize(Patch const &patch, int32_t v, uint8_t n) {
|
||||||
static constexpr unsigned m = CHAR_BIT * sizeof(int);
|
assume(n != 0); // That doesn't make sense
|
||||||
if (n < m && (v < -(1 << n) || v >= 1 << n)) {
|
assume(n < CHAR_BIT * sizeof(int)); // Otherwise `1 << n` is UB
|
||||||
|
|
||||||
|
if (v < -(1 << n) || v >= 1 << n) {
|
||||||
diagnosticAt(
|
diagnosticAt(
|
||||||
patch,
|
patch,
|
||||||
WARNING_TRUNCATION_1,
|
WARNING_TRUNCATION_1,
|
||||||
@@ -515,8 +517,7 @@ static void checkPatchSize(Patch const &patch, int32_t v, uint8_t n) {
|
|||||||
v < 0 ? " (may be negative?)" : "",
|
v < 0 ? " (may be negative?)" : "",
|
||||||
n
|
n
|
||||||
);
|
);
|
||||||
return;
|
} else if (v < -(1 << (n - 1))) {
|
||||||
} else if (n < m + 1 && v < -(1 << (n - 1))) {
|
|
||||||
diagnosticAt(
|
diagnosticAt(
|
||||||
patch,
|
patch,
|
||||||
WARNING_TRUNCATION_2,
|
WARNING_TRUNCATION_2,
|
||||||
@@ -568,7 +569,9 @@ static void applyFilePatches(Section §ion, Section &dataSection) {
|
|||||||
dataSection.data[offset] = jumpOffset & 0xFF;
|
dataSection.data[offset] = jumpOffset & 0xFF;
|
||||||
} else {
|
} else {
|
||||||
// Patch a certain number of bytes
|
// Patch a certain number of bytes
|
||||||
checkPatchSize(patch, value, typeSize * 8);
|
if (typeSize < sizeof(int)) {
|
||||||
|
checkPatchSize(patch, value, typeSize * 8);
|
||||||
|
}
|
||||||
for (uint8_t i = 0; i < typeSize; ++i) {
|
for (uint8_t i = 0; i < typeSize; ++i) {
|
||||||
dataSection.data[offset + i] = value & 0xFF;
|
dataSection.data[offset + i] = value & 0xFF;
|
||||||
value >>= 8;
|
value >>= 8;
|
||||||
@@ -577,14 +580,14 @@ static void applyFilePatches(Section §ion, Section &dataSection) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies all of a section's patches, iterating over "components" of unionized sections
|
// Applies all of a section's patches, iterating over "pieces" of unionized sections
|
||||||
static void applyPatches(Section §ion) {
|
static void applyPatches(Section §ion) {
|
||||||
if (!sectTypeHasData(section.type)) {
|
if (!sectTypeHasData(section.type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Section *component = §ion; component; component = component->nextu.get()) {
|
for (Section *piece = §ion; piece != nullptr; piece = piece->nextPiece.get()) {
|
||||||
applyFilePatches(*component, section);
|
applyFilePatches(*piece, section);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
#include "link/sdas_obj.hpp"
|
#include "link/sdas_obj.hpp"
|
||||||
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -15,18 +15,13 @@
|
|||||||
#include "helpers.hpp" // assume, literal_strlen
|
#include "helpers.hpp" // assume, literal_strlen
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
|
#include "util.hpp" // parseWholeNumber
|
||||||
|
|
||||||
#include "link/fstack.hpp"
|
#include "link/fstack.hpp"
|
||||||
#include "link/section.hpp"
|
#include "link/section.hpp"
|
||||||
#include "link/symbol.hpp"
|
#include "link/symbol.hpp"
|
||||||
#include "link/warning.hpp"
|
#include "link/warning.hpp"
|
||||||
|
|
||||||
enum NumberType {
|
|
||||||
HEX = 16, // X
|
|
||||||
DEC = 10, // D
|
|
||||||
OCT = 8, // Q
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Location {
|
struct Location {
|
||||||
FileStackNode const *src;
|
FileStackNode const *src;
|
||||||
uint32_t lineNo;
|
uint32_t lineNo;
|
||||||
@@ -84,36 +79,26 @@ static int nextLine(std::vector<char> &lineBuf, Location &where, FILE *file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t readNumber(char const *str, char const *&endptr, NumberType base) {
|
static uint64_t readNumber(Location const &where, char const *str, NumberBase base) {
|
||||||
for (uint32_t res = 0;;) {
|
std::optional<uint64_t> res = parseWholeNumber(str, base);
|
||||||
static char const *digits = "0123456789ABCDEF";
|
|
||||||
char const *ptr = strchr(digits, toupper(*str));
|
|
||||||
|
|
||||||
if (!ptr || ptr - digits >= base) {
|
if (!res) {
|
||||||
endptr = str;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
++str;
|
|
||||||
res = res * base + (ptr - digits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t parseNumber(Location const &where, char const *str, NumberType base) {
|
|
||||||
if (str[0] == '\0') {
|
|
||||||
fatalAt(where, "Expected number, got empty string");
|
|
||||||
}
|
|
||||||
|
|
||||||
char const *endptr;
|
|
||||||
uint32_t res = readNumber(str, endptr, base);
|
|
||||||
|
|
||||||
if (*endptr != '\0') {
|
|
||||||
fatalAt(where, "Expected number, got \"%s\"", str);
|
fatalAt(where, "Expected number, got \"%s\"", str);
|
||||||
}
|
}
|
||||||
return res;
|
return *res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t parseByte(Location const &where, char const *str, NumberType base) {
|
static uint32_t readInt(Location const &where, char const *str, NumberBase base) {
|
||||||
uint32_t num = parseNumber(where, str, base);
|
uint64_t num = readNumber(where, str, base);
|
||||||
|
|
||||||
|
if (num > UINT32_MAX) {
|
||||||
|
fatalAt(where, "\"%s\" is not an int", str);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t readByte(Location const &where, char const *str, NumberBase base) {
|
||||||
|
uint64_t num = readNumber(where, str, base);
|
||||||
|
|
||||||
if (num > UINT8_MAX) {
|
if (num > UINT8_MAX) {
|
||||||
fatalAt(where, "\"%s\" is not a byte", str);
|
fatalAt(where, "\"%s\" is not a byte", str);
|
||||||
@@ -184,18 +169,18 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
int lineType = nextLine(line, where, file);
|
int lineType = nextLine(line, where, file);
|
||||||
|
|
||||||
// The first letter (thus, the line type) identifies the integer type
|
// The first letter (thus, the line type) identifies the integer type
|
||||||
NumberType numberType;
|
NumberBase numberBase;
|
||||||
switch (lineType) {
|
switch (lineType) {
|
||||||
case EOF:
|
case EOF:
|
||||||
fatalAt(where, "SDCC object only contains comments and empty lines");
|
fatalAt(where, "SDCC object only contains comments and empty lines");
|
||||||
case 'X':
|
case 'X':
|
||||||
numberType = HEX;
|
numberBase = BASE_16;
|
||||||
break;
|
break;
|
||||||
case 'D':
|
case 'D':
|
||||||
numberType = DEC;
|
numberBase = BASE_10;
|
||||||
break;
|
break;
|
||||||
case 'Q':
|
case 'Q':
|
||||||
numberType = OCT;
|
numberBase = BASE_8;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fatalAt(
|
fatalAt(
|
||||||
@@ -239,12 +224,12 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
// Expected format: "A areas S global symbols"
|
// Expected format: "A areas S global symbols"
|
||||||
|
|
||||||
getToken(line.data(), "Empty 'H' line");
|
getToken(line.data(), "Empty 'H' line");
|
||||||
uint32_t expectedNbAreas = parseNumber(where, token, numberType);
|
uint32_t expectedNbAreas = readInt(where, token, numberBase);
|
||||||
|
|
||||||
expectToken("areas", 'H');
|
expectToken("areas", 'H');
|
||||||
|
|
||||||
getToken(nullptr, "'H' line is too short");
|
getToken(nullptr, "'H' line is too short");
|
||||||
uint32_t expectedNbSymbols = parseNumber(where, token, numberType);
|
uint32_t expectedNbSymbols = readInt(where, token, numberBase);
|
||||||
fileSymbols.reserve(expectedNbSymbols);
|
fileSymbols.reserve(expectedNbSymbols);
|
||||||
|
|
||||||
expectToken("global", 'H');
|
expectToken("global", 'H');
|
||||||
@@ -296,7 +281,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
getToken(nullptr, "'A' line is too short");
|
||||||
|
|
||||||
uint32_t tmp = parseNumber(where, token, numberType);
|
uint32_t tmp = readInt(where, token, numberBase);
|
||||||
|
|
||||||
if (tmp > UINT16_MAX) {
|
if (tmp > UINT16_MAX) {
|
||||||
fatalAt(
|
fatalAt(
|
||||||
@@ -310,7 +295,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
expectToken("flags", 'A');
|
expectToken("flags", 'A');
|
||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
getToken(nullptr, "'A' line is too short");
|
||||||
tmp = parseNumber(where, token, numberType);
|
tmp = readInt(where, token, numberBase);
|
||||||
if (tmp & (1 << AREA_PAGING)) {
|
if (tmp & (1 << AREA_PAGING)) {
|
||||||
fatalAt(where, "Paging is not supported");
|
fatalAt(where, "Paging is not supported");
|
||||||
}
|
}
|
||||||
@@ -329,7 +314,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
expectToken("addr", 'A');
|
expectToken("addr", 'A');
|
||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
getToken(nullptr, "'A' line is too short");
|
||||||
tmp = parseNumber(where, token, numberType);
|
tmp = readInt(where, token, numberBase);
|
||||||
curSection->org = tmp; // Truncation keeps the address portion only
|
curSection->org = tmp; // Truncation keeps the address portion only
|
||||||
curSection->bank = tmp >> 16;
|
curSection->bank = tmp >> 16;
|
||||||
|
|
||||||
@@ -364,7 +349,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
curSection->isAlignFixed = false; // No such concept!
|
curSection->isAlignFixed = false; // No such concept!
|
||||||
curSection->fileSymbols = &fileSymbols; // IDs are instead per-section
|
curSection->fileSymbols = &fileSymbols; // IDs are instead per-section
|
||||||
curSection->nextu = nullptr;
|
curSection->nextPiece = nullptr;
|
||||||
|
|
||||||
fileSections.push_back({.section = std::move(curSection), .writeIndex = 0});
|
fileSections.push_back({.section = std::move(curSection), .writeIndex = 0});
|
||||||
break;
|
break;
|
||||||
@@ -386,7 +371,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
getToken(nullptr, "'S' line is too short");
|
getToken(nullptr, "'S' line is too short");
|
||||||
|
|
||||||
if (int32_t value = parseNumber(where, &token[3], numberType); !fileSections.empty()) {
|
if (int32_t value = readInt(where, &token[3], numberBase); !fileSections.empty()) {
|
||||||
// Symbols in sections are labels; their value is an offset
|
// Symbols in sections are labels; their value is an offset
|
||||||
Section *section = fileSections.back().section.get();
|
Section *section = fileSections.back().section.get();
|
||||||
if (section->isAddressFixed) {
|
if (section->isAddressFixed) {
|
||||||
@@ -465,7 +450,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
data.clear();
|
data.clear();
|
||||||
for (token = strtok(line.data(), delim); token; token = strtok(nullptr, delim)) {
|
for (token = strtok(line.data(), delim); token; token = strtok(nullptr, delim)) {
|
||||||
data.push_back(parseByte(where, token, numberType));
|
data.push_back(readByte(where, token, numberBase));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.size() < addrSize) {
|
if (data.size() < addrSize) {
|
||||||
@@ -487,9 +472,9 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
uint16_t areaIdx;
|
uint16_t areaIdx;
|
||||||
|
|
||||||
getToken(nullptr, "'R' line is too short");
|
getToken(nullptr, "'R' line is too short");
|
||||||
areaIdx = parseByte(where, token, numberType);
|
areaIdx = readByte(where, token, numberBase);
|
||||||
getToken(nullptr, "'R' line is too short");
|
getToken(nullptr, "'R' line is too short");
|
||||||
areaIdx |= static_cast<uint16_t>(parseByte(where, token, numberType)) << 8;
|
areaIdx |= static_cast<uint16_t>(readByte(where, token, numberBase)) << 8;
|
||||||
if (areaIdx >= fileSections.size()) {
|
if (areaIdx >= fileSections.size()) {
|
||||||
fatalAt(
|
fatalAt(
|
||||||
where,
|
where,
|
||||||
@@ -549,16 +534,16 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
// appropriate RPN expression (depending on flags), plus an addition for the
|
// appropriate RPN expression (depending on flags), plus an addition for the
|
||||||
// bytes being patched over.
|
// bytes being patched over.
|
||||||
while ((token = strtok(nullptr, delim)) != nullptr) {
|
while ((token = strtok(nullptr, delim)) != nullptr) {
|
||||||
uint16_t flags = parseByte(where, token, numberType);
|
uint16_t flags = readByte(where, token, numberBase);
|
||||||
|
|
||||||
if ((flags & 0xF0) == 0xF0) {
|
if ((flags & 0xF0) == 0xF0) {
|
||||||
getToken(nullptr, "Incomplete relocation");
|
getToken(nullptr, "Incomplete relocation");
|
||||||
flags = (flags & 0x0F)
|
flags = (flags & 0x0F)
|
||||||
| static_cast<uint16_t>(parseByte(where, token, numberType)) << 4;
|
| static_cast<uint16_t>(readByte(where, token, numberBase)) << 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
getToken(nullptr, "Incomplete relocation");
|
||||||
uint8_t offset = parseByte(where, token, numberType);
|
uint8_t offset = readByte(where, token, numberBase);
|
||||||
|
|
||||||
if (offset < addrSize) {
|
if (offset < addrSize) {
|
||||||
fatalAt(
|
fatalAt(
|
||||||
@@ -578,10 +563,10 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
getToken(nullptr, "Incomplete relocation");
|
||||||
uint16_t idx = parseByte(where, token, numberType);
|
uint16_t idx = readByte(where, token, numberBase);
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
getToken(nullptr, "Incomplete relocation");
|
||||||
idx |= static_cast<uint16_t>(parseByte(where, token, numberType));
|
idx |= static_cast<uint16_t>(readByte(where, token, numberBase));
|
||||||
|
|
||||||
// Loudly fail on unknown flags
|
// Loudly fail on unknown flags
|
||||||
if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE)) {
|
if (flags & (1 << RELOC_ZPAGE | 1 << RELOC_NPAGE)) {
|
||||||
@@ -902,21 +887,10 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
sect_AddSection(std::move(section));
|
sect_AddSection(std::move(section));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fix symbols' section pointers to component sections
|
// Fix symbols' section pointers to section "pieces"
|
||||||
// This has to run **after** all the `sect_AddSection()` calls,
|
// This has to run **after** all the `sect_AddSection()` calls,
|
||||||
// so that `sect_GetSection()` will work
|
// so that `sect_GetSection()` will work
|
||||||
for (Symbol &sym : fileSymbols) {
|
for (Symbol &sym : fileSymbols) {
|
||||||
if (std::holds_alternative<Label>(sym.data)) {
|
sym.fixSectionOffset();
|
||||||
Label &label = std::get<Label>(sym.data);
|
|
||||||
if (Section *section = label.section; section->modifier != SECTION_NORMAL) {
|
|
||||||
if (section->modifier == SECTION_FRAGMENT) {
|
|
||||||
// Add the fragment's offset to the symbol's
|
|
||||||
// (`section->offset` is computed by `sect_AddSection`)
|
|
||||||
label.offset += section->offset;
|
|
||||||
}
|
|
||||||
// Associate the symbol with the main section, not the "component" one
|
|
||||||
label.section = sect_GetSection(section->name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ static void checkAgainstFixedAddress(Section const &target, Section const &other
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool checkAgainstFixedAlign(Section const &target, Section const &other, int32_t ofs) {
|
static bool checkAgainstFixedAlign(Section const &target, Section const &other, uint32_t ofs) {
|
||||||
if (target.isAddressFixed) {
|
if (target.isAddressFixed) {
|
||||||
if ((target.org - ofs) & other.alignMask) {
|
if ((target.org - ofs) & other.alignMask) {
|
||||||
fatalTwoAt(
|
fatalTwoAt(
|
||||||
@@ -109,10 +107,7 @@ static void checkFragmentCompat(Section &target, Section &other) {
|
|||||||
target.isAddressFixed = true;
|
target.isAddressFixed = true;
|
||||||
target.org = org;
|
target.org = org;
|
||||||
} else if (other.isAlignFixed) {
|
} else if (other.isAlignFixed) {
|
||||||
int32_t ofs = (other.alignOfs - target.size) % (other.alignMask + 1);
|
uint32_t ofs = (other.alignOfs - target.size) & other.alignMask;
|
||||||
if (ofs < 0) {
|
|
||||||
ofs += other.alignMask + 1;
|
|
||||||
}
|
|
||||||
if (checkAgainstFixedAlign(target, other, ofs)) {
|
if (checkAgainstFixedAlign(target, other, ofs)) {
|
||||||
target.isAlignFixed = true;
|
target.isAlignFixed = true;
|
||||||
target.alignMask = other.alignMask;
|
target.alignMask = other.alignMask;
|
||||||
@@ -191,10 +186,10 @@ static void mergeSections(Section &target, std::unique_ptr<Section> &&other) {
|
|||||||
}
|
}
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
// Note that the order in which fragments are stored in the `nextu` list does not
|
// Note that the order in which fragments are stored in the `nextPiece` list does not
|
||||||
// really matter, only that offsets were properly computed above
|
// really matter, only that offsets were properly computed above
|
||||||
other->nextu = std::move(target.nextu);
|
other->nextPiece = std::move(target.nextPiece);
|
||||||
target.nextu = std::move(other);
|
target.nextPiece = std::move(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sect_AddSection(std::unique_ptr<Section> &§ion) {
|
void sect_AddSection(std::unique_ptr<Section> &§ion) {
|
||||||
|
|||||||
@@ -90,3 +90,43 @@ void sym_TraceLocalAliasedSymbols(std::string const &name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Symbol::linkToSection(Section §ion) {
|
||||||
|
assume(std::holds_alternative<Label>(data));
|
||||||
|
Label &label = std::get<Label>(data);
|
||||||
|
// Link the symbol to the section
|
||||||
|
label.section = §ion;
|
||||||
|
// Link the section to the symbol, keeping the section's symbol list sorted
|
||||||
|
uint32_t a = 0, b = section.symbols.size();
|
||||||
|
while (a != b) {
|
||||||
|
uint32_t c = (a + b) / 2;
|
||||||
|
assume(std::holds_alternative<Label>(section.symbols[c]->data));
|
||||||
|
Label const &other = std::get<Label>(section.symbols[c]->data);
|
||||||
|
if (other.offset > label.offset) {
|
||||||
|
b = c;
|
||||||
|
} else {
|
||||||
|
a = c + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section.symbols.insert(section.symbols.begin() + a, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Symbol::fixSectionOffset() {
|
||||||
|
if (!std::holds_alternative<Label>(data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Label &label = std::get<Label>(data);
|
||||||
|
Section *section = label.section;
|
||||||
|
assume(section);
|
||||||
|
|
||||||
|
if (section->modifier != SECTION_NORMAL) {
|
||||||
|
// Associate the symbol with the main section, not the "piece"
|
||||||
|
label.section = sect_GetSection(section->name);
|
||||||
|
}
|
||||||
|
if (section->modifier == SECTION_FRAGMENT) {
|
||||||
|
// Add the fragment's offset to the symbol's
|
||||||
|
// (`section->offset` is computed by `sect_AddSection`)
|
||||||
|
label.offset += section->offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -186,11 +186,11 @@ void warning(FileStackNode const *src, uint32_t lineNo, WarningID id, char const
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case WarningBehavior::ENABLED:
|
case WarningBehavior::ENABLED:
|
||||||
printDiag(src, lineNo, fmt, args, "warning", STYLE_RED, "[-W%s]", flag);
|
printDiag(src, lineNo, fmt, args, "warning", STYLE_YELLOW, "[-W%s]", flag);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WarningBehavior::ERROR:
|
case WarningBehavior::ERROR:
|
||||||
printDiag(src, lineNo, fmt, args, "error", STYLE_YELLOW, "[-Werror=%s]", flag);
|
printDiag(src, lineNo, fmt, args, "error", STYLE_RED, "[-Werror=%s]", flag);
|
||||||
|
|
||||||
warnings.incrementErrors();
|
warnings.incrementErrors();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -7,14 +7,47 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "platform.hpp"
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
|
|
||||||
static constexpr size_t maxLineLen = 79;
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
#define WIN32_LEAN_AND_MEAN // Include less from `windows.h`
|
||||||
// LCOV_EXCL_START
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
void Usage::printAndExit(int code) const {
|
void Usage::printAndExit(int code) const {
|
||||||
FILE *file = code ? stderr : stdout;
|
FILE *file;
|
||||||
|
bool isTerminal;
|
||||||
|
if (code) {
|
||||||
|
file = stderr;
|
||||||
|
isTerminal = isatty(STDERR_FILENO);
|
||||||
|
} else {
|
||||||
|
file = stdout;
|
||||||
|
isTerminal = isatty(STDOUT_FILENO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the console window width minus 1 as the maximum line length for flags,
|
||||||
|
// or the historically common 80 minus 1 if the output is not to a console TTY
|
||||||
|
size_t maxLineLen = 79;
|
||||||
|
if (isTerminal) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||||
|
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||||
|
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
|
||||||
|
if (csbi.srWindow.Right > csbi.srWindow.Left) {
|
||||||
|
maxLineLen = static_cast<size_t>(csbi.srWindow.Right - csbi.srWindow.Left);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
struct winsize winSize;
|
||||||
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &winSize);
|
||||||
|
if (winSize.ws_col > 1) {
|
||||||
|
maxLineLen = static_cast<size_t>(winSize.ws_col - 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
}
|
||||||
|
|
||||||
// Print "Usage: <program name>"
|
// Print "Usage: <program name>"
|
||||||
style_Set(file, STYLE_GREEN, true);
|
style_Set(file, STYLE_GREEN, true);
|
||||||
@@ -34,6 +67,7 @@ void Usage::printAndExit(int code) const {
|
|||||||
fprintf(file, " %s", flag.c_str());
|
fprintf(file, " %s", flag.c_str());
|
||||||
flagsWidth += 1 + flag.length();
|
flagsWidth += 1 + flag.length();
|
||||||
}
|
}
|
||||||
|
style_Reset(file);
|
||||||
fputs("\n\n", file);
|
fputs("\n\n", file);
|
||||||
|
|
||||||
// Measure the options' flags
|
// Measure the options' flags
|
||||||
@@ -50,12 +84,13 @@ void Usage::printAndExit(int code) const {
|
|||||||
padOpts = pad;
|
padOpts = pad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int optIndent = static_cast<int>(literal_strlen(" ") + padOpts);
|
size_t optOffset = literal_strlen(" ") + padOpts;
|
||||||
|
|
||||||
// Print the options
|
// Print the options
|
||||||
if (!options.empty()) {
|
if (!options.empty()) {
|
||||||
style_Set(file, STYLE_GREEN, true);
|
style_Set(file, STYLE_GREEN, true);
|
||||||
fputs("Useful options:\n", file);
|
fputs("Useful options:\n", file);
|
||||||
|
style_Reset(file);
|
||||||
}
|
}
|
||||||
for (auto const &[opts, description] : options) {
|
for (auto const &[opts, description] : options) {
|
||||||
fputs(" ", file);
|
fputs(" ", file);
|
||||||
@@ -64,23 +99,36 @@ void Usage::printAndExit(int code) const {
|
|||||||
size_t optWidth = 0;
|
size_t optWidth = 0;
|
||||||
for (size_t i = 0; i < opts.size(); ++i) {
|
for (size_t i = 0; i < opts.size(); ++i) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
style_Reset(file);
|
|
||||||
fputs(", ", file);
|
fputs(", ", file);
|
||||||
optWidth += literal_strlen(", ");
|
optWidth += literal_strlen(", ");
|
||||||
}
|
}
|
||||||
style_Set(file, STYLE_CYAN, false);
|
style_Set(file, STYLE_CYAN, false);
|
||||||
fputs(opts[i].c_str(), file);
|
fputs(opts[i].c_str(), file);
|
||||||
|
style_Reset(file);
|
||||||
optWidth += opts[i].length();
|
optWidth += opts[i].length();
|
||||||
}
|
}
|
||||||
if (optWidth < padOpts) {
|
|
||||||
|
// Measure the description lines
|
||||||
|
size_t descLen = 0;
|
||||||
|
for (std::string const &descLine : description) {
|
||||||
|
if (descLine.length() > descLen) {
|
||||||
|
descLen = descLine.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the description lines would wrap around the console, put them on their own lines
|
||||||
|
size_t optIndent = optOffset;
|
||||||
|
if (optIndent + literal_strlen(" ") + descLen > maxLineLen) {
|
||||||
|
optIndent = 6;
|
||||||
|
fprintf(file, "\n%*c", static_cast<int>(optIndent), ' ');
|
||||||
|
} else if (optWidth < padOpts) {
|
||||||
fprintf(file, "%*c", static_cast<int>(padOpts - optWidth), ' ');
|
fprintf(file, "%*c", static_cast<int>(padOpts - optWidth), ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print the description lines, indented to the same level
|
// Print the description lines, indented to the same level
|
||||||
for (size_t i = 0; i < description.size(); ++i) {
|
for (size_t i = 0; i < description.size(); ++i) {
|
||||||
style_Reset(file);
|
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
fprintf(file, "\n%*c", optIndent, ' ');
|
fprintf(file, "\n%*c", static_cast<int>(optIndent), ' ');
|
||||||
}
|
}
|
||||||
fprintf(file, " %s", description[i].c_str());
|
fprintf(file, " %s", description[i].c_str());
|
||||||
}
|
}
|
||||||
@@ -88,7 +136,6 @@ void Usage::printAndExit(int code) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Print the link for further help information
|
// Print the link for further help information
|
||||||
style_Reset(file);
|
|
||||||
fputs("\nFor more help, use \"", file);
|
fputs("\nFor more help, use \"", file);
|
||||||
style_Set(file, STYLE_CYAN, true);
|
style_Set(file, STYLE_CYAN, true);
|
||||||
fprintf(file, "man %s", name.c_str());
|
fprintf(file, "man %s", name.c_str());
|
||||||
@@ -114,5 +161,3 @@ void Usage::printAndExit(char const *fmt, ...) const {
|
|||||||
|
|
||||||
printAndExit(1);
|
printAndExit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|||||||
116
src/util.cpp
116
src/util.cpp
@@ -2,8 +2,13 @@
|
|||||||
|
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <string.h> // strspn
|
||||||
|
|
||||||
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
bool isNewline(int c) {
|
bool isNewline(int c) {
|
||||||
return c == '\r' || c == '\n';
|
return c == '\r' || c == '\n';
|
||||||
@@ -21,14 +26,26 @@ bool isPrintable(int c) {
|
|||||||
return c >= ' ' && c <= '~';
|
return c >= ' ' && c <= '~';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isUpper(int c) {
|
||||||
|
return c >= 'A' && c <= 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLower(int c) {
|
||||||
|
return c >= 'a' && c <= 'z';
|
||||||
|
}
|
||||||
|
|
||||||
bool isLetter(int c) {
|
bool isLetter(int c) {
|
||||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
return isUpper(c) || isLower(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isDigit(int c) {
|
bool isDigit(int c) {
|
||||||
return c >= '0' && c <= '9';
|
return c >= '0' && c <= '9';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isBinDigit(int c) {
|
||||||
|
return c == '0' || c == '1';
|
||||||
|
}
|
||||||
|
|
||||||
bool isOctDigit(int c) {
|
bool isOctDigit(int c) {
|
||||||
return c >= '0' && c <= '7';
|
return c >= '0' && c <= '7';
|
||||||
}
|
}
|
||||||
@@ -51,6 +68,103 @@ bool continuesIdentifier(int c) {
|
|||||||
return startsIdentifier(c) || isDigit(c) || c == '#' || c == '$' || c == '@';
|
return startsIdentifier(c) || isDigit(c) || c == '#' || c == '$' || c == '@';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t parseHexDigit(int c) {
|
||||||
|
if (c >= 'A' && c <= 'F') {
|
||||||
|
return c - 'A' + 10;
|
||||||
|
} else if (c >= 'a' && c <= 'f') {
|
||||||
|
return c - 'a' + 10;
|
||||||
|
} else {
|
||||||
|
assume(isDigit(c));
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a number from a string, moving the pointer to skip the parsed characters.
|
||||||
|
std::optional<uint64_t> parseNumber(char const *&str, NumberBase base) {
|
||||||
|
// Identify the base if not specified
|
||||||
|
// Does *not* support '+' or '-' sign prefix (unlike `strtoul` and `std::from_chars`)
|
||||||
|
if (base == BASE_AUTO) {
|
||||||
|
base = BASE_10;
|
||||||
|
|
||||||
|
// Skips leading blank space (like `strtoul`)
|
||||||
|
str += strspn(str, " \t");
|
||||||
|
|
||||||
|
// Supports traditional ("0b", "0o", "0x") and RGBASM ('%', '&', '$') base prefixes
|
||||||
|
switch (str[0]) {
|
||||||
|
case '%':
|
||||||
|
base = BASE_2;
|
||||||
|
++str;
|
||||||
|
break;
|
||||||
|
case '&':
|
||||||
|
base = BASE_8;
|
||||||
|
++str;
|
||||||
|
break;
|
||||||
|
case '$':
|
||||||
|
base = BASE_16;
|
||||||
|
++str;
|
||||||
|
break;
|
||||||
|
case '0':
|
||||||
|
switch (str[1]) {
|
||||||
|
case 'B':
|
||||||
|
case 'b':
|
||||||
|
base = BASE_2;
|
||||||
|
str += 2;
|
||||||
|
break;
|
||||||
|
case 'O':
|
||||||
|
case 'o':
|
||||||
|
base = BASE_8;
|
||||||
|
str += 2;
|
||||||
|
break;
|
||||||
|
case 'X':
|
||||||
|
case 'x':
|
||||||
|
base = BASE_16;
|
||||||
|
str += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the digit-condition function corresponding to the base
|
||||||
|
bool (*canParseDigit)(int c) = base == BASE_2 ? isBinDigit
|
||||||
|
: base == BASE_8 ? isOctDigit
|
||||||
|
: base == BASE_10 ? isDigit
|
||||||
|
: base == BASE_16 ? isHexDigit
|
||||||
|
: nullptr; // LCOV_EXCL_LINE
|
||||||
|
assume(canParseDigit != nullptr);
|
||||||
|
|
||||||
|
char const * const startDigits = str;
|
||||||
|
|
||||||
|
// Parse the number one digit at a time
|
||||||
|
// Does *not* support '_' digit separators
|
||||||
|
uint64_t result = 0;
|
||||||
|
for (; canParseDigit(str[0]); ++str) {
|
||||||
|
uint8_t digit = parseHexDigit(str[0]);
|
||||||
|
if (result > (UINT64_MAX - digit) / base) {
|
||||||
|
// Skip remaining digits and set errno = ERANGE on overflow
|
||||||
|
while (canParseDigit(str[0])) {
|
||||||
|
++str;
|
||||||
|
}
|
||||||
|
result = UINT64_MAX;
|
||||||
|
errno = ERANGE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result = result * base + digit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the parsed number if there were any digit characters
|
||||||
|
if (str - startDigits == 0) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a number from an entire string, returning nothing if there are more unparsed characters.
|
||||||
|
std::optional<uint64_t> parseWholeNumber(char const *str, NumberBase base) {
|
||||||
|
std::optional<uint64_t> result = parseNumber(str, base);
|
||||||
|
return str[0] == '\0' ? result : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
char const *printChar(int c) {
|
char const *printChar(int c) {
|
||||||
// "'A'" + '\0': 4 bytes
|
// "'A'" + '\0': 4 bytes
|
||||||
// "'\\n'" + '\0': 5 bytes
|
// "'\\n'" + '\0': 5 bytes
|
||||||
|
|||||||
Binary file not shown.
15
test/asm/backtrace-collapsed.asm
Normal file
15
test/asm/backtrace-collapsed.asm
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
macro careful
|
||||||
|
if _NARG == 20
|
||||||
|
warn "You're in too deep!"
|
||||||
|
else
|
||||||
|
careful \#, deeper
|
||||||
|
endc
|
||||||
|
endm
|
||||||
|
careful surface
|
||||||
|
|
||||||
|
macro recurse
|
||||||
|
recurse
|
||||||
|
endm
|
||||||
|
rept 3
|
||||||
|
recurse
|
||||||
|
endr
|
||||||
4
test/asm/backtrace-collapsed.err
Normal file
4
test/asm/backtrace-collapsed.err
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
warning: You're in too deep! [-Wuser]
|
||||||
|
at backtrace-collapsed.asm::careful(3) <- backtrace-collapsed.asm::careful(5) <- backtrace-collapsed.asm::careful(5) <- ...16 more... <- backtrace-collapsed.asm::careful(5) <- backtrace-collapsed.asm(8)
|
||||||
|
FATAL: Recursion limit (64) exceeded
|
||||||
|
at backtrace-collapsed.asm::recurse(11) <- backtrace-collapsed.asm::recurse(11) <- backtrace-collapsed.asm::recurse(11) <- ...60 more... <- backtrace-collapsed.asm::REPT~1(14) <- backtrace-collapsed.asm(13)
|
||||||
1
test/asm/backtrace-collapsed.flags
Normal file
1
test/asm/backtrace-collapsed.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-B 5 -B collapse
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
def s equs "d"
|
def s equs "d"
|
||||||
|
|
||||||
charmap "A", 1
|
charmap 'A', 1
|
||||||
charmap "B", 2
|
charmap 'B', 2
|
||||||
charmap "c{s}e", 3
|
charmap 'c{s}e', 3
|
||||||
charmap "F", 4, 5, 6
|
charmap 'F', 4, 5, 6
|
||||||
charmap "'", 42
|
charmap "'", 42
|
||||||
charmap "\"", 1234
|
charmap '"', 1234
|
||||||
charmap "\n\r\t\0", 1337
|
charmap '\n\r\t\0', 1337
|
||||||
charmap "',\",\\", 99
|
charmap '\',\",\\', 99
|
||||||
|
|
||||||
MACRO char
|
MACRO char
|
||||||
assert (\1) == (\2)
|
assert (\1) == (\2)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
opt Wno-unmapped-char, Wno-obsolete
|
opt Wno-unmapped-char
|
||||||
charmap "<NULL>", $00
|
charmap "<NULL>", $00
|
||||||
charmap "A", $10
|
charmap "A", $10
|
||||||
charmap "B", $20
|
charmap "B", $20
|
||||||
|
|||||||
24
test/asm/charlen-strchar.err
Normal file
24
test/asm/charlen-strchar.err
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(16)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(16)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(17)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(17)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(17)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(18)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(26)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(26)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(27)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(27)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(27)
|
||||||
|
warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete]
|
||||||
|
at charlen-strchar.asm(28)
|
||||||
1
test/asm/cli/bad-dep-file.err
Normal file
1
test/asm/cli/bad-dep-file.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Dependency files can only be created if a target file is specified with either '-o', '-MQ' or '-MT'
|
||||||
1
test/asm/cli/bad-dep-file.flags
Normal file
1
test/asm/cli/bad-dep-file.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-M depfile inputfile
|
||||||
1
test/asm/cli/empty-s-feature.err
Normal file
1
test/asm/cli/empty-s-feature.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Empty feature for option '-s'
|
||||||
1
test/asm/cli/empty-s-feature.flags
Normal file
1
test/asm/cli/empty-s-feature.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-s :-
|
||||||
1
test/asm/cli/invalid-B.err
Normal file
1
test/asm/cli/invalid-B.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '-B'
|
||||||
1
test/asm/cli/invalid-B.flags
Normal file
1
test/asm/cli/invalid-B.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-B nan
|
||||||
1
test/asm/cli/invalid-Q.err
Normal file
1
test/asm/cli/invalid-Q.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '-Q'
|
||||||
1
test/asm/cli/invalid-Q.flags
Normal file
1
test/asm/cli/invalid-Q.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-Q invalid
|
||||||
1
test/asm/cli/invalid-X.err
Normal file
1
test/asm/cli/invalid-X.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '-X'
|
||||||
1
test/asm/cli/invalid-X.flags
Normal file
1
test/asm/cli/invalid-X.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-X 0c777
|
||||||
1
test/asm/cli/invalid-b-digits.err
Normal file
1
test/asm/cli/invalid-b-digits.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Must specify exactly 2 characters for option '-b'
|
||||||
1
test/asm/cli/invalid-b-digits.flags
Normal file
1
test/asm/cli/invalid-b-digits.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-b 01X
|
||||||
1
test/asm/cli/invalid-color.err
Normal file
1
test/asm/cli/invalid-color.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '--color'
|
||||||
1
test/asm/cli/invalid-color.flags
Normal file
1
test/asm/cli/invalid-color.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
--color=always --color=never --color=auto --color=yes
|
||||||
1
test/asm/cli/invalid-g-digits.err
Normal file
1
test/asm/cli/invalid-g-digits.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Must specify exactly 4 characters for option '-g'
|
||||||
1
test/asm/cli/invalid-g-digits.flags
Normal file
1
test/asm/cli/invalid-g-digits.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-g 0123X
|
||||||
1
test/asm/cli/invalid-p.err
Normal file
1
test/asm/cli/invalid-p.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '-p'
|
||||||
1
test/asm/cli/invalid-p.flags
Normal file
1
test/asm/cli/invalid-p.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-p 123abc
|
||||||
1
test/asm/cli/invalid-r.err
Normal file
1
test/asm/cli/invalid-r.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '-r'
|
||||||
1
test/asm/cli/invalid-r.flags
Normal file
1
test/asm/cli/invalid-r.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-r nan
|
||||||
1
test/asm/cli/invalid-s-feature.err
Normal file
1
test/asm/cli/invalid-s-feature.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid feature for option '-s': "invalid"
|
||||||
1
test/asm/cli/invalid-s-feature.flags
Normal file
1
test/asm/cli/invalid-s-feature.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-s invalid:-
|
||||||
1
test/asm/cli/invalid-s.err
Normal file
1
test/asm/cli/invalid-s.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Invalid argument for option '-s'
|
||||||
1
test/asm/cli/invalid-s.flags
Normal file
1
test/asm/cli/invalid-s.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-s invalid
|
||||||
17
test/asm/cli/multiple-inputs.err
Normal file
17
test/asm/cli/multiple-inputs.err
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FATAL: More than one input file specified
|
||||||
|
Usage: rgbasm [-EhVvw] [-B depth] [-b chars] [-D name[=value]] [-g chars]
|
||||||
|
[-I path] [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file]
|
||||||
|
[-MQ target_file] [-o out_file] [-P include_file] [-p pad_value]
|
||||||
|
[-Q precision] [-r depth] [-s features:state_file] [-W warning]
|
||||||
|
[-X max_errors] <file>
|
||||||
|
|
||||||
|
Useful options:
|
||||||
|
-E, --export-all export all labels
|
||||||
|
-M, --dependfile <path> set the output dependency file
|
||||||
|
-o, --output <path> set the output object file
|
||||||
|
-p, --pad-value <value> set the value to use for `DS`
|
||||||
|
-s, --state <features>:<path> set an output state file
|
||||||
|
-V, --version print RGBASM version and exit
|
||||||
|
-W, --warning <warning> enable or disable warnings
|
||||||
|
|
||||||
|
For more help, use "man rgbasm" or go to https://rgbds.gbdev.io/docs/
|
||||||
1
test/asm/cli/multiple-inputs.flags
Normal file
1
test/asm/cli/multiple-inputs.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
one two
|
||||||
1
test/asm/cli/out-of-range-Q.err
Normal file
1
test/asm/cli/out-of-range-Q.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Argument for option '-Q' must be between 1 and 31
|
||||||
1
test/asm/cli/out-of-range-Q.flags
Normal file
1
test/asm/cli/out-of-range-Q.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-Q .999
|
||||||
1
test/asm/cli/out-of-range-p.err
Normal file
1
test/asm/cli/out-of-range-p.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Argument for option '-p' must be between 0 and 0xFF
|
||||||
1
test/asm/cli/out-of-range-p.flags
Normal file
1
test/asm/cli/out-of-range-p.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-p 999
|
||||||
1
test/asm/cli/out-of-range-r.err
Normal file
1
test/asm/cli/out-of-range-r.err
Normal file
@@ -0,0 +1 @@
|
|||||||
|
FATAL: Argument for option '-r' is out of range
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user