mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Compare commits
74 Commits
v1.0.0-rc1
...
f065243cd2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f065243cd2 | ||
|
|
a0bb830679 | ||
|
|
7654c6e27a | ||
|
|
400375b2e5 | ||
|
|
7462bccb72 | ||
|
|
2873e0b8c8 | ||
|
|
1badba03d8 | ||
|
|
64bcef99bd | ||
|
|
aa672bbec9 | ||
|
|
651877e094 | ||
|
|
26c48cc409 | ||
|
|
23b9039716 | ||
|
|
711fba5e35 | ||
|
|
089fc11e31 | ||
|
|
837f552987 | ||
|
|
cb8c973453 | ||
|
|
cca3794dd0 | ||
|
|
02c2408f58 | ||
|
|
fba0562650 | ||
|
|
0c9920d4a6 | ||
|
|
7733ccdeb6 | ||
|
|
13e85b5151 | ||
|
|
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 |
@@ -71,6 +71,7 @@ Language: Cpp
|
|||||||
MaxEmptyLinesToKeep: 1
|
MaxEmptyLinesToKeep: 1
|
||||||
NamespaceIndentation: None
|
NamespaceIndentation: None
|
||||||
PPIndentWidth: -1
|
PPIndentWidth: -1
|
||||||
|
PenaltyBreakScopeResolution: 1000
|
||||||
PointerAlignment: Right
|
PointerAlignment: Right
|
||||||
QualifierAlignment: Right
|
QualifierAlignment: Right
|
||||||
ReflowComments: true
|
ReflowComments: true
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
266
ARCHITECTURE.md
266
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,231 @@ 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.
|
||||||
|
- **`cli.cpp`:**
|
||||||
|
A function for parsing command-line options, including RGBDS-specific "at-files" (a filename containing more options, prepended with an "`@`").
|
||||||
|
This is the only file to use the extern/getopt.cpp variables and functions.
|
||||||
|
- **`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`).
|
||||||
|
- **`palette.cpp`:**
|
||||||
|
`Palette` methods for working with up to four GBC-native (RGB555) 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 . .
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -51,6 +51,7 @@ all: rgbasm rgblink rgbfix rgbgfx
|
|||||||
|
|
||||||
common_obj := \
|
common_obj := \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
|
src/cli.o \
|
||||||
src/diagnostics.o \
|
src/diagnostics.o \
|
||||||
src/style.o \
|
src/style.o \
|
||||||
src/usage.o \
|
src/usage.o \
|
||||||
@@ -118,6 +119,7 @@ rgbgfx_obj := \
|
|||||||
src/gfx/pal_packing.o \
|
src/gfx/pal_packing.o \
|
||||||
src/gfx/pal_sorting.o \
|
src/gfx/pal_sorting.o \
|
||||||
src/gfx/pal_spec.o \
|
src/gfx/pal_spec.o \
|
||||||
|
src/gfx/palette.o \
|
||||||
src/gfx/png.o \
|
src/gfx/png.o \
|
||||||
src/gfx/process.o \
|
src/gfx/process.o \
|
||||||
src/gfx/reverse.o \
|
src/gfx/reverse.o \
|
||||||
@@ -241,12 +243,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:
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#ifndef RGBDS_ASM_MAIN_HPP
|
#ifndef RGBDS_ASM_MAIN_HPP
|
||||||
#define RGBDS_ASM_MAIN_HPP
|
#define RGBDS_ASM_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -20,10 +21,10 @@ struct Options {
|
|||||||
char binDigits[2] = {'0', '1'}; // -b
|
char binDigits[2] = {'0', '1'}; // -b
|
||||||
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
||||||
FILE *dependFile = nullptr; // -M
|
FILE *dependFile = nullptr; // -M
|
||||||
std::string targetFileName; // -MQ, -MT
|
std::optional<std::string> targetFileName{}; // -MQ, -MT
|
||||||
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
||||||
bool generatePhonyDeps = false; // -MP
|
bool generatePhonyDeps = false; // -MP
|
||||||
std::string objectFileName; // -o
|
std::optional<std::string> objectFileName{}; // -o
|
||||||
uint8_t padByte = 0; // -p
|
uint8_t padByte = 0; // -p
|
||||||
uint64_t maxErrors = 0; // -X
|
uint64_t maxErrors = 0; // -X
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ struct Options {
|
|||||||
|
|
||||||
void printDep(std::string const &depName) {
|
void printDep(std::string const &depName) {
|
||||||
if (dependFile) {
|
if (dependFile) {
|
||||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), depName.c_str());
|
fprintf(dependFile, "%s: %s\n", targetFileName->c_str(), depName.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
|
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
|
|
||||||
|
#define TRACE_SEPARATOR "<-"
|
||||||
|
#define NODE_SEPARATOR "::"
|
||||||
|
#define REPT_NODE_PREFIX "REPT~"
|
||||||
|
|
||||||
struct Tracing {
|
struct Tracing {
|
||||||
uint64_t depth = 0;
|
uint64_t depth = 0;
|
||||||
bool collapse = false;
|
bool collapse = false;
|
||||||
@@ -21,20 +25,20 @@ extern Tracing tracing;
|
|||||||
|
|
||||||
bool trace_ParseTraceDepth(char const *arg);
|
bool trace_ParseTraceDepth(char const *arg);
|
||||||
|
|
||||||
template<typename T, typename M, typename N>
|
template<typename NodeT, typename NameFnT, typename LineNoFnT>
|
||||||
void trace_PrintBacktrace(std::vector<T> const &stack, M getName, N getLineNo) {
|
void trace_PrintBacktrace(std::vector<NodeT> const &stack, NameFnT getName, LineNoFnT getLineNo) {
|
||||||
size_t n = stack.size();
|
size_t n = stack.size();
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
return; // LCOV_EXCL_LINE
|
return; // LCOV_EXCL_LINE
|
||||||
}
|
}
|
||||||
|
|
||||||
auto printLocation = [&](size_t i) {
|
auto printLocation = [&](size_t i) {
|
||||||
T const &item = stack[n - i - 1];
|
NodeT const &item = stack[n - i - 1];
|
||||||
style_Reset(stderr);
|
style_Reset(stderr);
|
||||||
if (!tracing.collapse) {
|
if (!tracing.collapse) {
|
||||||
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
||||||
}
|
}
|
||||||
fprintf(stderr, " %s ", i == 0 ? "at" : "<-");
|
fprintf(stderr, " %s ", i == 0 ? "at" : TRACE_SEPARATOR);
|
||||||
style_Set(stderr, STYLE_CYAN, true);
|
style_Set(stderr, STYLE_CYAN, true);
|
||||||
fputs(getName(item), stderr);
|
fputs(getName(item), stderr);
|
||||||
style_Set(stderr, STYLE_CYAN, false);
|
style_Set(stderr, STYLE_CYAN, false);
|
||||||
@@ -62,7 +66,7 @@ void trace_PrintBacktrace(std::vector<T> const &stack, M getName, N getLineNo) {
|
|||||||
style_Reset(stderr);
|
style_Reset(stderr);
|
||||||
|
|
||||||
if (tracing.collapse) {
|
if (tracing.collapse) {
|
||||||
fputs(" <-", stderr);
|
fputs(" " TRACE_SEPARATOR, stderr);
|
||||||
} else {
|
} else {
|
||||||
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
||||||
}
|
}
|
||||||
|
|||||||
20
include/cli.hpp
Normal file
20
include/cli.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef RGBDS_CLI_HPP
|
||||||
|
#define RGBDS_CLI_HPP
|
||||||
|
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "extern/getopt.hpp" // option
|
||||||
|
|
||||||
|
void cli_ParseArgs(
|
||||||
|
int argc,
|
||||||
|
char *argv[],
|
||||||
|
char const *shortOpts,
|
||||||
|
option const *longOpts,
|
||||||
|
void (*parseArg)(int, char *),
|
||||||
|
void (*fatal)(char const *, ...)
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif // RGBDS_CLI_HPP
|
||||||
@@ -30,35 +30,35 @@ struct WarningState {
|
|||||||
|
|
||||||
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
|
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
|
||||||
|
|
||||||
template<typename L>
|
template<typename LevelEnumT>
|
||||||
struct WarningFlag {
|
struct WarningFlag {
|
||||||
char const *name;
|
char const *name;
|
||||||
L level;
|
LevelEnumT level;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||||
|
|
||||||
template<typename W>
|
template<typename WarningEnumT>
|
||||||
struct ParamWarning {
|
struct ParamWarning {
|
||||||
W firstID;
|
WarningEnumT firstID;
|
||||||
W lastID;
|
WarningEnumT lastID;
|
||||||
uint8_t defaultLevel;
|
uint8_t defaultLevel;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename W>
|
template<typename WarningEnumT>
|
||||||
struct DiagnosticsState {
|
struct DiagnosticsState {
|
||||||
WarningState flagStates[W::NB_WARNINGS];
|
WarningState flagStates[WarningEnumT::NB_WARNINGS];
|
||||||
WarningState metaStates[W::NB_WARNINGS];
|
WarningState metaStates[WarningEnumT::NB_WARNINGS];
|
||||||
bool warningsEnabled = true;
|
bool warningsEnabled = true;
|
||||||
bool warningsAreErrors = false;
|
bool warningsAreErrors = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename L, typename W>
|
template<typename LevelEnumT, typename WarningEnumT>
|
||||||
struct Diagnostics {
|
struct Diagnostics {
|
||||||
std::vector<WarningFlag<L>> metaWarnings;
|
std::vector<WarningFlag<LevelEnumT>> metaWarnings;
|
||||||
std::vector<WarningFlag<L>> warningFlags;
|
std::vector<WarningFlag<LevelEnumT>> warningFlags;
|
||||||
std::vector<ParamWarning<W>> paramWarnings;
|
std::vector<ParamWarning<WarningEnumT>> paramWarnings;
|
||||||
DiagnosticsState<W> state;
|
DiagnosticsState<WarningEnumT> state;
|
||||||
uint64_t nbErrors;
|
uint64_t nbErrors;
|
||||||
|
|
||||||
void incrementErrors() {
|
void incrementErrors() {
|
||||||
@@ -67,12 +67,12 @@ struct Diagnostics {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WarningBehavior getWarningBehavior(W id) const;
|
WarningBehavior getWarningBehavior(WarningEnumT id) const;
|
||||||
void processWarningFlag(char const *flag);
|
void processWarningFlag(char const *flag);
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename L, typename W>
|
template<typename LevelEnumT, typename WarningEnumT>
|
||||||
WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
WarningBehavior Diagnostics<LevelEnumT, WarningEnumT>::getWarningBehavior(WarningEnumT id) const {
|
||||||
// Check if warnings are globally disabled
|
// Check if warnings are globally disabled
|
||||||
if (!state.warningsEnabled) {
|
if (!state.warningsEnabled) {
|
||||||
return WarningBehavior::DISABLED;
|
return WarningBehavior::DISABLED;
|
||||||
@@ -112,7 +112,7 @@ WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no meta flag is specified, check the default state of this warning flag
|
// If no meta flag is specified, check the default state of this warning flag
|
||||||
if (warningFlags[id].level == L::LEVEL_DEFAULT) { // enabled by default
|
if (warningFlags[id].level == LevelEnumT::LEVEL_DEFAULT) { // enabled by default
|
||||||
return enabledBehavior;
|
return enabledBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +120,8 @@ WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
|||||||
return WarningBehavior::DISABLED;
|
return WarningBehavior::DISABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename L, typename W>
|
template<typename LevelEnumT, typename WarningEnumT>
|
||||||
void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
void Diagnostics<LevelEnumT, WarningEnumT>::processWarningFlag(char const *flag) {
|
||||||
std::string rootFlag = flag;
|
std::string rootFlag = flag;
|
||||||
|
|
||||||
// Check for `-Werror` or `-Wno-error` to return early
|
// Check for `-Werror` or `-Wno-error` to return early
|
||||||
@@ -140,8 +140,8 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
|||||||
// Try to match the flag against a parametric warning
|
// Try to match the flag against a parametric warning
|
||||||
// If there was an equals sign, it will have set `param`; if not, `param` will be 0,
|
// If there was an equals sign, it will have set `param`; if not, `param` will be 0,
|
||||||
// which applies to all levels
|
// which applies to all levels
|
||||||
for (ParamWarning<W> const ¶mWarning : paramWarnings) {
|
for (ParamWarning<WarningEnumT> const ¶mWarning : paramWarnings) {
|
||||||
W baseID = paramWarning.firstID;
|
WarningEnumT baseID = paramWarning.firstID;
|
||||||
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
||||||
assume(paramWarning.defaultLevel <= maxParam);
|
assume(paramWarning.defaultLevel <= maxParam);
|
||||||
|
|
||||||
@@ -183,13 +183,13 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to match against a "meta" warning
|
// Try to match against a "meta" warning
|
||||||
for (WarningFlag<L> const &metaWarning : metaWarnings) {
|
for (WarningFlag<LevelEnumT> const &metaWarning : metaWarnings) {
|
||||||
if (rootFlag != metaWarning.name) {
|
if (rootFlag != metaWarning.name) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set each of the warning flags that meets this level
|
// Set each of the warning flags that meets this level
|
||||||
for (W id : EnumSeq(W::NB_WARNINGS)) {
|
for (WarningEnumT id : EnumSeq(WarningEnumT::NB_WARNINGS)) {
|
||||||
if (metaWarning.level >= warningFlags[id].level) {
|
if (metaWarning.level >= warningFlags[id].level) {
|
||||||
state.metaStates[id].update(flagState);
|
state.metaStates[id].update(flagState);
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ void Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to match against a "normal" flag
|
// Try to match against a "normal" flag
|
||||||
for (W id : EnumSeq(W::NB_PLAIN_WARNINGS)) {
|
for (WarningEnumT id : EnumSeq(WarningEnumT::NB_PLAIN_WARNINGS)) {
|
||||||
if (rootFlag == warningFlags[id].name) {
|
if (rootFlag == warningFlags[id].name) {
|
||||||
state.flagStates[id].update(flagState);
|
state.flagStates[id].update(flagState);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
#ifndef RGBDS_FIX_MAIN_HPP
|
#ifndef RGBDS_FIX_MAIN_HPP
|
||||||
#define RGBDS_FIX_MAIN_HPP
|
#define RGBDS_FIX_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
||||||
|
|
||||||
@@ -28,19 +30,19 @@ struct Options {
|
|||||||
uint16_t ramSize = UNSPECIFIED; // -r
|
uint16_t ramSize = UNSPECIFIED; // -r
|
||||||
bool sgb = false; // -s
|
bool sgb = false; // -s
|
||||||
|
|
||||||
char const *gameID = nullptr; // -i
|
std::optional<std::string> gameID; // -i
|
||||||
uint8_t gameIDLen;
|
uint8_t gameIDLen;
|
||||||
|
|
||||||
char const *newLicensee = nullptr; // -k
|
std::optional<std::string> newLicensee; // -k
|
||||||
uint8_t newLicenseeLen;
|
uint8_t newLicenseeLen;
|
||||||
|
|
||||||
char const *logoFilename = nullptr; // -L
|
std::optional<std::string> logoFilename; // -L
|
||||||
uint8_t logo[48] = {};
|
uint8_t logo[48] = {};
|
||||||
|
|
||||||
MbcType cartridgeType = MBC_NONE; // -m
|
MbcType cartridgeType = MBC_NONE; // -m
|
||||||
uint8_t tpp1Rev[2];
|
uint8_t tpp1Rev[2];
|
||||||
|
|
||||||
char const *title = nullptr; // -t
|
std::optional<std::string> title; // -t
|
||||||
uint8_t titleLen;
|
uint8_t titleLen;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
23
include/gfx/flip.hpp
Normal file
23
include/gfx/flip.hpp
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_FLIP_HPP
|
||||||
|
#define RGBDS_GFX_FLIP_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
||||||
|
static std::array<uint16_t, 256> flipTable = ([]() constexpr {
|
||||||
|
std::array<uint16_t, 256> table{};
|
||||||
|
for (uint16_t i = 0; i < table.size(); ++i) {
|
||||||
|
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
||||||
|
uint16_t byte = i;
|
||||||
|
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
||||||
|
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
||||||
|
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
||||||
|
table[i] = byte;
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
})();
|
||||||
|
|
||||||
|
#endif // RGBDS_GFX_FLIP_HPP
|
||||||
@@ -5,12 +5,11 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp" // assume
|
||||||
|
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
@@ -41,7 +40,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
|
||||||
@@ -70,35 +68,4 @@ struct Options {
|
|||||||
|
|
||||||
extern Options options;
|
extern Options options;
|
||||||
|
|
||||||
struct Palette {
|
|
||||||
// An array of 4 GBC-native (RGB555) colors
|
|
||||||
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
|
||||||
|
|
||||||
void addColor(uint16_t color);
|
|
||||||
uint8_t indexOf(uint16_t color) const;
|
|
||||||
uint16_t &operator[](size_t index) { return colors[index]; }
|
|
||||||
uint16_t const &operator[](size_t index) const { return colors[index]; }
|
|
||||||
|
|
||||||
decltype(colors)::iterator begin();
|
|
||||||
decltype(colors)::iterator end();
|
|
||||||
decltype(colors)::const_iterator begin() const;
|
|
||||||
decltype(colors)::const_iterator end() const;
|
|
||||||
|
|
||||||
uint8_t size() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Flipping tends to happen fairly often, so take a bite out of dcache to speed it up
|
|
||||||
static std::array<uint16_t, 256> flipTable = ([]() constexpr {
|
|
||||||
std::array<uint16_t, 256> table{};
|
|
||||||
for (uint16_t i = 0; i < table.size(); ++i) {
|
|
||||||
// To flip all the bits, we'll flip both nibbles, then each nibble half, etc.
|
|
||||||
uint16_t byte = i;
|
|
||||||
byte = (byte & 0b0000'1111) << 4 | (byte & 0b1111'0000) >> 4;
|
|
||||||
byte = (byte & 0b0011'0011) << 2 | (byte & 0b1100'1100) >> 2;
|
|
||||||
byte = (byte & 0b0101'0101) << 1 | (byte & 0b1010'1010) >> 1;
|
|
||||||
table[i] = byte;
|
|
||||||
}
|
|
||||||
return table;
|
|
||||||
})();
|
|
||||||
|
|
||||||
#endif // RGBDS_GFX_MAIN_HPP
|
#endif // RGBDS_GFX_MAIN_HPP
|
||||||
|
|||||||
27
include/gfx/palette.hpp
Normal file
27
include/gfx/palette.hpp
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef RGBDS_GFX_PALETTE_HPP
|
||||||
|
#define RGBDS_GFX_PALETTE_HPP
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct Palette {
|
||||||
|
// An array of 4 GBC-native (RGB555) colors
|
||||||
|
std::array<uint16_t, 4> colors{UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||||
|
|
||||||
|
void addColor(uint16_t color);
|
||||||
|
uint8_t indexOf(uint16_t color) const;
|
||||||
|
uint16_t &operator[](size_t index) { return colors[index]; }
|
||||||
|
uint16_t const &operator[](size_t index) const { return colors[index]; }
|
||||||
|
|
||||||
|
decltype(colors)::iterator begin();
|
||||||
|
decltype(colors)::iterator end();
|
||||||
|
decltype(colors)::const_iterator begin() const;
|
||||||
|
decltype(colors)::const_iterator end() const;
|
||||||
|
|
||||||
|
uint8_t size() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RGBDS_GFX_PALETTE_HPP
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -97,18 +97,19 @@ static inline int clz(unsigned int x) {
|
|||||||
|
|
||||||
// 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 SizeOfString>
|
||||||
static constexpr int literal_strlen(char const (&)[N]) {
|
static constexpr int literal_strlen(char const (&)[SizeOfString]) {
|
||||||
return N - 1;
|
return SizeOfString - 1; // Don't count the ending '\0'
|
||||||
}
|
}
|
||||||
|
|
||||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||||
template<typename T>
|
template<typename DeferredFnT>
|
||||||
struct Defer {
|
struct Defer {
|
||||||
T deferred;
|
DeferredFnT deferred;
|
||||||
Defer(T func) : deferred(func) {}
|
Defer(DeferredFnT func) : deferred(func) {}
|
||||||
~Defer() { deferred(); }
|
~Defer() { deferred(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,35 @@
|
|||||||
#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>
|
||||||
|
|
||||||
template<typename T>
|
// A wrapper around iterables to reverse their iteration order; used in `for`-each loops.
|
||||||
|
template<typename IterableT>
|
||||||
|
struct ReversedIterable {
|
||||||
|
IterableT &_iterable;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename IterableT>
|
||||||
|
auto begin(ReversedIterable<IterableT> r) {
|
||||||
|
return std::rbegin(r._iterable);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename IterableT>
|
||||||
|
auto end(ReversedIterable<IterableT> r) {
|
||||||
|
return std::rend(r._iterable);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename IterableT>
|
||||||
|
ReversedIterable<IterableT> reversed(IterableT &&_iterable) {
|
||||||
|
return {_iterable};
|
||||||
|
}
|
||||||
|
|
||||||
|
// A map from `std::string` keys to `ItemT` items, iterable in the order the items were inserted.
|
||||||
|
template<typename ItemT>
|
||||||
class InsertionOrderedMap {
|
class InsertionOrderedMap {
|
||||||
std::deque<T> list;
|
std::deque<ItemT> list;
|
||||||
std::unordered_map<std::string, size_t> map; // Indexes into `list`
|
std::unordered_map<std::string, size_t> map; // Indexes into `list`
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -23,25 +46,25 @@ public:
|
|||||||
|
|
||||||
bool contains(std::string const &name) const { return map.find(name) != map.end(); }
|
bool contains(std::string const &name) const { return map.find(name) != map.end(); }
|
||||||
|
|
||||||
T &operator[](size_t i) { return list[i]; }
|
ItemT &operator[](size_t i) { return list[i]; }
|
||||||
|
|
||||||
typename decltype(list)::iterator begin() { return list.begin(); }
|
typename decltype(list)::iterator begin() { return list.begin(); }
|
||||||
typename decltype(list)::iterator end() { return list.end(); }
|
typename decltype(list)::iterator end() { return list.end(); }
|
||||||
typename decltype(list)::const_iterator begin() const { return list.begin(); }
|
typename decltype(list)::const_iterator begin() const { return list.begin(); }
|
||||||
typename decltype(list)::const_iterator end() const { return list.end(); }
|
typename decltype(list)::const_iterator end() const { return list.end(); }
|
||||||
|
|
||||||
T &add(std::string const &name) {
|
ItemT &add(std::string const &name) {
|
||||||
map[name] = list.size();
|
map[name] = list.size();
|
||||||
return list.emplace_back();
|
return list.emplace_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
T &add(std::string const &name, T &&value) {
|
ItemT &add(std::string const &name, ItemT &&value) {
|
||||||
map[name] = list.size();
|
map[name] = list.size();
|
||||||
list.emplace_back(std::move(value));
|
list.emplace_back(std::move(value));
|
||||||
return list.back();
|
return list.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
T &addAnonymous() {
|
ItemT &addAnonymous() {
|
||||||
// Add the new item to the list, but do not update the map
|
// Add the new item to the list, but do not update the map
|
||||||
return list.emplace_back();
|
return list.emplace_back();
|
||||||
}
|
}
|
||||||
@@ -54,43 +77,45 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
// An iterable of `enum` values in the half-open range [start, stop).
|
||||||
|
template<typename EnumT>
|
||||||
class EnumSeq {
|
class EnumSeq {
|
||||||
T _start;
|
EnumT _start;
|
||||||
T _stop;
|
EnumT _stop;
|
||||||
|
|
||||||
class Iterator {
|
class Iterator {
|
||||||
T _value;
|
EnumT _value;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Iterator(T value) : _value(value) {}
|
explicit Iterator(EnumT value) : _value(value) {}
|
||||||
|
|
||||||
Iterator &operator++() {
|
Iterator &operator++() {
|
||||||
_value = static_cast<T>(_value + 1);
|
_value = static_cast<EnumT>(_value + 1);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
T operator*() const { return _value; }
|
EnumT operator*() const { return _value; }
|
||||||
|
|
||||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
|
explicit EnumSeq(EnumT stop) : _start(static_cast<EnumT>(0)), _stop(stop) {}
|
||||||
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
explicit EnumSeq(EnumT start, EnumT stop) : _start(start), _stop(stop) {}
|
||||||
|
|
||||||
Iterator begin() { return Iterator(_start); }
|
Iterator begin() { return Iterator(_start); }
|
||||||
Iterator end() { return Iterator(_stop); }
|
Iterator end() { return Iterator(_stop); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only needed inside `ZipContainer` below.
|
||||||
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
// This is not a fully generic implementation; its current use cases only require for-loop behavior.
|
||||||
// We also assume that all iterators have the same length.
|
// We also assume that all iterators have the same length.
|
||||||
template<typename... Ts>
|
template<typename... IteratorTs>
|
||||||
class ZipIterator {
|
class ZipIterator {
|
||||||
std::tuple<Ts...> _iters;
|
std::tuple<IteratorTs...> _iters;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
|
explicit ZipIterator(std::tuple<IteratorTs...> &&iters) : _iters(iters) {}
|
||||||
|
|
||||||
ZipIterator &operator++() {
|
ZipIterator &operator++() {
|
||||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
||||||
@@ -108,12 +133,14 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Ts>
|
// Only needed inside `zip` below.
|
||||||
|
template<typename... IterableTs>
|
||||||
class ZipContainer {
|
class ZipContainer {
|
||||||
std::tuple<Ts...> _containers;
|
std::tuple<IterableTs...> _containers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
|
explicit ZipContainer(IterableTs &&...containers)
|
||||||
|
: _containers(std::forward<IterableTs>(containers)...) {}
|
||||||
|
|
||||||
auto begin() {
|
auto begin() {
|
||||||
return ZipIterator(std::apply(
|
return ZipIterator(std::apply(
|
||||||
@@ -136,15 +163,19 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only needed inside `zip` below.
|
||||||
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
||||||
template<typename T>
|
template<typename IterableT>
|
||||||
using Holder = std::
|
using ZipHolder = std::conditional_t<
|
||||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
|
std::is_lvalue_reference_v<IterableT>,
|
||||||
|
IterableT,
|
||||||
|
std::remove_cv_t<std::remove_reference_t<IterableT>>>;
|
||||||
|
|
||||||
|
// Iterates over N containers at once, yielding tuples of N items at a time.
|
||||||
// Does the same number of iterations as the first container's iterator!
|
// Does the same number of iterations as the first container's iterator!
|
||||||
template<typename... Ts>
|
template<typename... IterableTs>
|
||||||
static constexpr auto zip(Ts &&...cs) {
|
static constexpr auto zip(IterableTs &&...containers) {
|
||||||
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
|
return ZipContainer<ZipHolder<IterableTs>...>(std::forward<IterableTs>(containers)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // RGBDS_ITERTOOLS_HPP
|
#endif // RGBDS_ITERTOOLS_HPP
|
||||||
|
|||||||
@@ -10,6 +10,6 @@ void lexer_TraceCurrent();
|
|||||||
void lexer_IncludeFile(std::string &&path);
|
void lexer_IncludeFile(std::string &&path);
|
||||||
void lexer_IncLineNo();
|
void lexer_IncLineNo();
|
||||||
|
|
||||||
bool lexer_Init(char const *linkerScriptName);
|
bool lexer_Init(std::string const &linkerScriptName);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_LEXER_HPP
|
#endif // RGBDS_LINK_LEXER_HPP
|
||||||
|
|||||||
@@ -3,15 +3,17 @@
|
|||||||
#ifndef RGBDS_LINK_MAIN_HPP
|
#ifndef RGBDS_LINK_MAIN_HPP
|
||||||
#define RGBDS_LINK_MAIN_HPP
|
#define RGBDS_LINK_MAIN_HPP
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
bool isDmgMode; // -d
|
bool isDmgMode; // -d
|
||||||
char const *mapFileName; // -m
|
std::optional<std::string> mapFileName; // -m
|
||||||
bool noSymInMap; // -M
|
bool noSymInMap; // -M
|
||||||
char const *symFileName; // -n
|
std::optional<std::string> symFileName; // -n
|
||||||
char const *overlayFileName; // -O
|
std::optional<std::string> overlayFileName; // -O
|
||||||
char const *outputFileName; // -o
|
std::optional<std::string> outputFileName; // -o
|
||||||
uint8_t padValue; // -p
|
uint8_t padValue; // -p
|
||||||
bool hasPadValue = false;
|
bool hasPadValue = false;
|
||||||
// Setting these three to 0 disables the functionality
|
// Setting these three to 0 disables the functionality
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
#ifndef RGBDS_LINK_OBJECT_HPP
|
#ifndef RGBDS_LINK_OBJECT_HPP
|
||||||
#define RGBDS_LINK_OBJECT_HPP
|
#define RGBDS_LINK_OBJECT_HPP
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
// Read an object (.o) file, and add its info to the data structures.
|
// Read an object (.o) file, and add its info to the data structures.
|
||||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
void obj_ReadFile(std::string const &filePath, size_t fileID);
|
||||||
|
|
||||||
// Sets up object file reading
|
// Sets up object file reading
|
||||||
void obj_Setup(unsigned int nbFiles);
|
void obj_Setup(size_t nbFiles);
|
||||||
|
|
||||||
#endif // RGBDS_LINK_OBJECT_HPP
|
#endif // RGBDS_LINK_OBJECT_HPP
|
||||||
|
|||||||
@@ -48,7 +48,40 @@ 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
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Template class for both const and non-const iterators over the "pieces" of this section
|
||||||
|
template<typename SectionT>
|
||||||
|
class PiecesIterable {
|
||||||
|
SectionT *_firstPiece;
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
SectionT *_piece;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Iterator(SectionT *piece) : _piece(piece) {}
|
||||||
|
|
||||||
|
Iterator &operator++() {
|
||||||
|
_piece = _piece->nextPiece.get();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SectionT &operator*() const { return *_piece; }
|
||||||
|
|
||||||
|
bool operator==(Iterator const &rhs) const { return _piece == rhs._piece; }
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PiecesIterable(SectionT *firstPiece) : _firstPiece(firstPiece) {}
|
||||||
|
|
||||||
|
Iterator begin() { return Iterator(_firstPiece); }
|
||||||
|
Iterator end() { return Iterator(nullptr); }
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
PiecesIterable<Section> pieces() { return PiecesIterable(this); }
|
||||||
|
PiecesIterable<Section const> pieces() const { return PiecesIterable(this); }
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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,9 +55,18 @@
|
|||||||
#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
|
||||||
|
|
||||||
|
// gcc and clang have their own `musttail` attributes for tail recursion
|
||||||
|
#if defined(__clang__) && __has_cpp_attribute(clang::musttail)
|
||||||
|
#define MUSTTAIL [[clang::musttail]]
|
||||||
|
#elif defined(__GNUC__) && __has_cpp_attribute(gnu::musttail)
|
||||||
|
#define MUSTTAIL [[gnu::musttail]]
|
||||||
|
#else
|
||||||
|
#define MUSTTAIL
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // RGBDS_PLATFORM_HPP
|
#endif // RGBDS_PLATFORM_HPP
|
||||||
|
|||||||
@@ -4,46 +4,68 @@
|
|||||||
#define RGBDS_UTIL_HPP
|
#define RGBDS_UTIL_HPP
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctype.h>
|
|
||||||
#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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Locale-independent character class functions
|
||||||
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);
|
||||||
|
|
||||||
|
// Locale-independent character transform functions
|
||||||
|
char toLower(char c);
|
||||||
|
char toUpper(char 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 {
|
||||||
// FNV-1a hash of an uppercased string
|
// FNV-1a hash of an uppercased string
|
||||||
constexpr size_t operator()(std::string const &str) const {
|
constexpr size_t operator()(std::string const &str) const {
|
||||||
return std::accumulate(RANGE(str), 0x811C9DC5, [](size_t hash, char c) {
|
return std::accumulate(RANGE(str), 0x811C9DC5, [](size_t hash, char c) {
|
||||||
return (hash ^ toupper(c)) * 16777619;
|
return (hash ^ toUpper(c)) * 16777619;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare two strings without case-sensitivity (by converting to uppercase)
|
// Compare two strings without case-sensitivity (by converting to uppercase)
|
||||||
constexpr bool operator()(std::string const &str1, std::string const &str2) const {
|
constexpr bool operator()(std::string const &str1, std::string const &str2) const {
|
||||||
return std::equal(RANGE(str1), RANGE(str2), [](char c1, char c2) {
|
return std::equal(RANGE(str1), RANGE(str2), [](char c1, char c2) {
|
||||||
return toupper(c1) == toupper(c2);
|
return toUpper(c1) == toUpper(c2);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
// An unordered map from case-insensitive `std::string` keys to `ItemT` items
|
||||||
using UpperMap = std::unordered_map<std::string, T, Uppercase, Uppercase>;
|
template<typename ItemT>
|
||||||
|
using UpperMap = std::unordered_map<std::string, ItemT, Uppercase, Uppercase>;
|
||||||
|
|
||||||
#endif // RGBDS_UTIL_HPP
|
#endif // RGBDS_UTIL_HPP
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
103
man/rgbasm.1
103
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
|
||||||
@@ -36,14 +36,14 @@ The
|
|||||||
program creates an RGB object file from an assembly source file.
|
program creates an RGB object file from an assembly source file.
|
||||||
The object file format is documented in
|
The object file format is documented in
|
||||||
.Xr rgbds 5 .
|
.Xr rgbds 5 .
|
||||||
.Pp
|
.Sh ARGUMENTS
|
||||||
The input
|
.Nm
|
||||||
.Ar asmfile
|
accepts the usual short and long options, such as
|
||||||
can be a path to a file, or
|
.Fl V
|
||||||
.Cm \-
|
and
|
||||||
to read from standard input.
|
.Fl -version .
|
||||||
.Pp
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
.Fl \-verb
|
||||||
is
|
is
|
||||||
.Fl \-verbose ,
|
.Fl \-verbose ,
|
||||||
@@ -51,7 +51,52 @@ but
|
|||||||
.Fl \-ver
|
.Fl \-ver
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.Fl \-version .
|
||||||
The arguments are as follows:
|
.Pp
|
||||||
|
Unless otherwise noted, passing
|
||||||
|
.Ql -
|
||||||
|
(a single dash) as a file name makes
|
||||||
|
.Nm
|
||||||
|
use standard input (for input files) or standard output (for output files).
|
||||||
|
To suppress this behavior, and open a file in the current directory actually called
|
||||||
|
.Ql - ,
|
||||||
|
pass
|
||||||
|
.Ql ./-
|
||||||
|
instead.
|
||||||
|
Using standard input or output for more than one file in a single command may produce unexpected results.
|
||||||
|
.Pp
|
||||||
|
.Nm
|
||||||
|
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
|
||||||
|
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
|
||||||
|
.Ql $
|
||||||
|
or
|
||||||
|
.Ql 0x ;
|
||||||
|
octal numbers must be prefixed with either
|
||||||
|
.Ql &
|
||||||
|
or
|
||||||
|
.Ql 0o ;
|
||||||
|
and binary numbers must be prefixed with either
|
||||||
|
.Ql %
|
||||||
|
or
|
||||||
|
.Ql 0b .
|
||||||
|
(The prefixes
|
||||||
|
.Ql $
|
||||||
|
and
|
||||||
|
.Ql &
|
||||||
|
will likely need escaping or quoting to avoid being interpreted by the shell.)
|
||||||
|
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
||||||
|
For example, all of these are equivalent:
|
||||||
|
.Ql 42 ,
|
||||||
|
.Ql 042 ,
|
||||||
|
.Ql 0x2A ,
|
||||||
|
.Ql 0X2A ,
|
||||||
|
.Ql 0x2a ,
|
||||||
|
.Ql &52 ,
|
||||||
|
.Ql 0o52 ,
|
||||||
|
.Ql 0O052 ,
|
||||||
|
.Ql 0b00101010 ,
|
||||||
|
.Ql 0B101010 .
|
||||||
|
.Pp
|
||||||
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl B Ar param , Fl \-backtrace Ar param
|
.It Fl B Ar param , Fl \-backtrace Ar param
|
||||||
Configures how location backtraces are printed if warnings or errors occur.
|
Configures how location backtraces are printed if warnings or errors occur.
|
||||||
@@ -306,6 +351,23 @@ disables this behavior.
|
|||||||
The default is 100 if
|
The default is 100 if
|
||||||
.Nm
|
.Nm
|
||||||
is printing errors to a terminal, and 0 otherwise.
|
is printing errors to a terminal, and 0 otherwise.
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
.El
|
.El
|
||||||
.Sh DIAGNOSTICS
|
.Sh DIAGNOSTICS
|
||||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
|
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
|
||||||
@@ -344,9 +406,9 @@ Enables literally every warning.
|
|||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||||
Note that each of these flags also has a negation (for example,
|
Note that each of these flags also has a negation (for example,
|
||||||
.Fl Wcharmap-redef
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-charmap-redef
|
.Fl Wno-obsolete
|
||||||
disables; and
|
disables; and
|
||||||
.Fl Wall
|
.Fl Wall
|
||||||
enables every warning that
|
enables every warning that
|
||||||
@@ -384,6 +446,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
|
||||||
@@ -432,10 +503,10 @@ or
|
|||||||
.Fl Wno-purge
|
.Fl Wno-purge
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wpurge=1
|
.Fl Wpurge=1
|
||||||
or just
|
|
||||||
.Fl Wpurge
|
|
||||||
warns when purging any exported symbol (regardless of type).
|
warns when purging any exported symbol (regardless of type).
|
||||||
.Fl Wpurge=2
|
.Fl Wpurge=2
|
||||||
|
or just
|
||||||
|
.Fl Wpurge
|
||||||
also warns when purging any label (even if not exported).
|
also warns when purging any label (even if not exported).
|
||||||
.It Fl Wshift
|
.It Fl Wshift
|
||||||
Warn when shifting right a negative value.
|
Warn when shifting right a negative value.
|
||||||
@@ -451,10 +522,10 @@ or
|
|||||||
.Fl Wno-truncation
|
.Fl Wno-truncation
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wtruncation=1
|
.Fl Wtruncation=1
|
||||||
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
|
||||||
.Fl Wtruncation=2
|
|
||||||
or just
|
or just
|
||||||
.Fl Wtruncation
|
.Fl Wtruncation
|
||||||
|
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
||||||
|
.Fl Wtruncation=2
|
||||||
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
||||||
.It Fl Wunmapped-char=
|
.It Fl Wunmapped-char=
|
||||||
Warn when a character goes through charmap conversion but has no defined mapping.
|
Warn when a character goes through charmap conversion but has no defined mapping.
|
||||||
|
|||||||
42
man/rgbasm.5
42
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
|
||||||
@@ -1698,15 +1711,21 @@ Note also that only exported symbols will appear in symbol and map files produce
|
|||||||
.Ss Purging symbols
|
.Ss Purging symbols
|
||||||
.Ic PURGE
|
.Ic PURGE
|
||||||
allows you to completely remove a symbol from the symbol table, as if it had never been defined.
|
allows you to completely remove a symbol from the symbol table, as if it had never been defined.
|
||||||
|
.Bd -literal -offset indent
|
||||||
|
DEF value EQU 42
|
||||||
|
PURGE value
|
||||||
|
DEF value EQUS "I'm a string now"
|
||||||
|
ASSERT DEF(value)
|
||||||
|
PURGE value
|
||||||
|
ASSERT !DEF(value)
|
||||||
|
.Ed
|
||||||
|
.Pp
|
||||||
Be
|
Be
|
||||||
.Em very
|
.Em very
|
||||||
careful when purging symbols, especially labels, because it could result in unpredictable errors if something depends on the missing symbol (for example, expressions the linker needs to calculate).
|
careful when purging symbols that have been referenced in section data, or that have been exported, because it could result in unpredictable errors if something depends on the missing symbol (for example, expressions the linker needs to calculate).
|
||||||
.Bd -literal -offset indent
|
Purging labels at all is
|
||||||
DEF Kamikaze EQUS "I don't want to live anymore"
|
.Em not
|
||||||
AOLer: DB "Me too lol"
|
recommended.
|
||||||
PURGE Kamikaze, AOLer
|
|
||||||
ASSERT !DEF(Kamikaze) && !DEF(AOLer)
|
|
||||||
.Ed
|
|
||||||
.Pp
|
.Pp
|
||||||
String constants are not expanded within the symbol names.
|
String constants are not expanded within the symbol names.
|
||||||
.Ss Predeclared symbols
|
.Ss Predeclared symbols
|
||||||
@@ -1716,6 +1735,7 @@ The following symbols are defined by the assembler:
|
|||||||
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
.It Dv @ Ta Ic EQU Ta PC value (essentially, the current memory address)
|
||||||
.It Dv . Ta Ic EQUS Ta The current global label scope
|
.It Dv . Ta Ic EQUS Ta The current global label scope
|
||||||
.It Dv .. Ta Ic EQUS Ta The current local label scope
|
.It Dv .. Ta Ic EQUS Ta The current local label scope
|
||||||
|
.It Dv __SCOPE__ Ta Ic EQUS Ta The innermost current label scope level (empty, ".", or "..")
|
||||||
.It Dv _RS Ta Ic = Ta _RS Counter
|
.It Dv _RS Ta Ic = Ta _RS Counter
|
||||||
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
|
.It Dv _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
|
||||||
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
101
man/rgbfix.1
101
man/rgbfix.1
@@ -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
|
||||||
@@ -39,23 +39,67 @@ Developers are advised to fill those fields with 0x00 bytes in their source code
|
|||||||
.Nm ,
|
.Nm ,
|
||||||
and to have already populated whichever fields they don't specify using
|
and to have already populated whichever fields they don't specify using
|
||||||
.Nm .
|
.Nm .
|
||||||
.Pp
|
.Sh ARGUMENTS
|
||||||
The input
|
.Nm
|
||||||
.Ar file
|
accepts the usual short and long options, such as
|
||||||
can be a path to a file, or
|
.Fl V
|
||||||
.Cm \-
|
and
|
||||||
to read from standard input.
|
.Fl -version .
|
||||||
.Pp
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
|
||||||
is
|
|
||||||
.Fl \-verbose ,
|
|
||||||
but
|
|
||||||
.Fl \-ver
|
.Fl \-ver
|
||||||
|
is
|
||||||
|
.Fl \-version ,
|
||||||
|
but
|
||||||
|
.Fl \-v
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.Fl \-validate .
|
||||||
Options later in the command line override those set earlier.
|
.Pp
|
||||||
Accepted options are as follows:
|
Unless otherwise noted, passing
|
||||||
|
.Ql -
|
||||||
|
(a single dash) as a file name makes
|
||||||
|
.Nm
|
||||||
|
use standard input (for input files) or standard output (for output files).
|
||||||
|
To suppress this behavior, and open a file in the current directory actually called
|
||||||
|
.Ql - ,
|
||||||
|
pass
|
||||||
|
.Ql ./-
|
||||||
|
instead.
|
||||||
|
Using standard input or output for more than one file in a single command may produce unexpected results.
|
||||||
|
.Pp
|
||||||
|
.Nm
|
||||||
|
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
|
||||||
|
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
|
||||||
|
.Ql $
|
||||||
|
or
|
||||||
|
.Ql 0x ;
|
||||||
|
octal numbers must be prefixed with either
|
||||||
|
.Ql &
|
||||||
|
or
|
||||||
|
.Ql 0o ;
|
||||||
|
and binary numbers must be prefixed with either
|
||||||
|
.Ql %
|
||||||
|
or
|
||||||
|
.Ql 0b .
|
||||||
|
(The prefixes
|
||||||
|
.Ql $
|
||||||
|
and
|
||||||
|
.Ql &
|
||||||
|
will likely need escaping or quoting to avoid being interpreted by the shell.)
|
||||||
|
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
||||||
|
For example, all of these are equivalent:
|
||||||
|
.Ql 42 ,
|
||||||
|
.Ql 042 ,
|
||||||
|
.Ql 0x2A ,
|
||||||
|
.Ql 0X2A ,
|
||||||
|
.Ql 0x2a ,
|
||||||
|
.Ql &52 ,
|
||||||
|
.Ql 0o52 ,
|
||||||
|
.Ql 0O052 ,
|
||||||
|
.Ql 0b00101010 ,
|
||||||
|
.Ql 0B101010 .
|
||||||
|
.Pp
|
||||||
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl C , Fl \-color-only
|
.It Fl C , Fl \-color-only
|
||||||
Set the Game Boy Color\(enonly flag
|
Set the Game Boy Color\(enonly flag
|
||||||
@@ -199,6 +243,23 @@ See the
|
|||||||
section for a list of warnings.
|
section for a list of warnings.
|
||||||
.It Fl w
|
.It Fl w
|
||||||
Disable all warning output, even when turned into errors.
|
Disable all warning output, even when turned into errors.
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
.El
|
.El
|
||||||
.Sh DIAGNOSTICS
|
.Sh DIAGNOSTICS
|
||||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
||||||
@@ -212,7 +273,7 @@ to prevent turning all warnings into errors.
|
|||||||
.It Fl Werror=
|
.It Fl Werror=
|
||||||
Make the specified warning or meta warning into an error.
|
Make the specified warning or meta warning into an error.
|
||||||
A warning's name is appended
|
A warning's name is appended
|
||||||
.Pq example: Fl Werror=overwrite ,
|
.Pq example: Fl Werror=obsolete ,
|
||||||
and this warning is implicitly enabled and turned into an error.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This can be negated as
|
This can be negated as
|
||||||
.Fl Wno-error=
|
.Fl Wno-error=
|
||||||
@@ -234,10 +295,10 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||||
Note that each of these flag also has a negation (for example,
|
Note that each of these flags also has a negation (for example,
|
||||||
.Fl Wtruncation
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-truncation
|
.Fl Wno-obsolete
|
||||||
disables; and
|
disables; and
|
||||||
.Fl Wall
|
.Fl Wall
|
||||||
enables every warning that
|
enables every warning that
|
||||||
|
|||||||
144
man/rgbgfx.1
144
man/rgbgfx.1
@@ -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
|
||||||
@@ -43,7 +43,13 @@ is to divide the input PNG into 8\[tmu]8 pixel
|
|||||||
convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file.
|
convert each of those squares into 1bpp or 2bpp tile data, and save all of the tile data in a file.
|
||||||
It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below.
|
It also has options to generate a tile map, attribute map, and/or palette set as well; more on that and how the conversion process can be tweaked below.
|
||||||
.Sh ARGUMENTS
|
.Sh ARGUMENTS
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
.Nm
|
||||||
|
accepts the usual short and long options, such as
|
||||||
|
.Fl V
|
||||||
|
and
|
||||||
|
.Fl -version .
|
||||||
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
.Fl \-verb
|
||||||
is
|
is
|
||||||
.Fl \-verbose ,
|
.Fl \-verbose ,
|
||||||
@@ -52,26 +58,6 @@ but
|
|||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.Fl \-version .
|
||||||
.Pp
|
.Pp
|
||||||
.Nm
|
|
||||||
accepts decimal, binary, and hexadecimal numbers in option arguments.
|
|
||||||
Decimal numbers are written as usual; binary numbers must be prefixed with either
|
|
||||||
.Ql %
|
|
||||||
or
|
|
||||||
.Ql 0b ,
|
|
||||||
and hexadecimal numbers must be prefixed with either
|
|
||||||
.Ql $
|
|
||||||
(which will likely need escaping or quoting to avoid being interpreted by the shell), or
|
|
||||||
.Ql 0x .
|
|
||||||
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
|
||||||
All of these are equivalent:
|
|
||||||
.Ql 42 ,
|
|
||||||
.Ql 042 ,
|
|
||||||
.Ql 0b00101010 ,
|
|
||||||
.Ql 0B101010 ,
|
|
||||||
.Ql 0x2A ,
|
|
||||||
.Ql 0X2A ,
|
|
||||||
.Ql 0x2a .
|
|
||||||
.Pp
|
|
||||||
Unless otherwise noted, passing
|
Unless otherwise noted, passing
|
||||||
.Ql -
|
.Ql -
|
||||||
(a single dash) as a file name makes
|
(a single dash) as a file name makes
|
||||||
@@ -82,7 +68,39 @@ To suppress this behavior, and open a file in the current directory actually cal
|
|||||||
pass
|
pass
|
||||||
.Ql ./-
|
.Ql ./-
|
||||||
instead.
|
instead.
|
||||||
Using standard input or output more than once in a single command will likely produce unexpected results.
|
Using standard input or output for more than one file in a single command may produce unexpected results.
|
||||||
|
.Pp
|
||||||
|
.Nm
|
||||||
|
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
|
||||||
|
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
|
||||||
|
.Ql $
|
||||||
|
or
|
||||||
|
.Ql 0x ;
|
||||||
|
octal numbers must be prefixed with either
|
||||||
|
.Ql &
|
||||||
|
or
|
||||||
|
.Ql 0o ;
|
||||||
|
and binary numbers must be prefixed with either
|
||||||
|
.Ql %
|
||||||
|
or
|
||||||
|
.Ql 0b .
|
||||||
|
(The prefixes
|
||||||
|
.Ql $
|
||||||
|
and
|
||||||
|
.Ql &
|
||||||
|
will likely need escaping or quoting to avoid being interpreted by the shell.)
|
||||||
|
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
||||||
|
For example, all of these are equivalent:
|
||||||
|
.Ql 42 ,
|
||||||
|
.Ql 042 ,
|
||||||
|
.Ql 0x2A ,
|
||||||
|
.Ql 0X2A ,
|
||||||
|
.Ql 0x2a ,
|
||||||
|
.Ql &52 ,
|
||||||
|
.Ql 0o52 ,
|
||||||
|
.Ql 0O052 ,
|
||||||
|
.Ql 0b00101010 ,
|
||||||
|
.Ql 0B101010 .
|
||||||
.Pp
|
.Pp
|
||||||
The following options are accepted:
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
@@ -347,7 +365,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
|
||||||
@@ -469,69 +487,53 @@ Implies
|
|||||||
.It Fl Z , Fl \-columns
|
.It Fl Z , Fl \-columns
|
||||||
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
Read squares from the PNG in column-major order (column by column), instead of the default row-major order (line by line).
|
||||||
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
This primarily affects tile map and attribute map output, although it may also change generated tile data and palettes.
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
|
.Pp
|
||||||
|
See
|
||||||
|
.Sx At-files
|
||||||
|
below for an explanation of how this can be useful.
|
||||||
.El
|
.El
|
||||||
.Ss At-files
|
.Ss At-files
|
||||||
In a given project, many images are to be converted with different flags.
|
In a given project, many images are to be converted with different flags.
|
||||||
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile / build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
|
The traditional way of solving this problem has been to specify the different flags for each image in the Makefile or build script; this can be inconvenient, as it centralizes all those flags away from the images they concern.
|
||||||
.Pp
|
.Pp
|
||||||
To avoid these drawbacks,
|
To avoid these drawbacks, you can use
|
||||||
.Nm
|
|
||||||
supports
|
|
||||||
.Dq at-files :
|
.Dq at-files :
|
||||||
any command-line argument that begins with an at sign
|
any command-line argument that begins with an at sign
|
||||||
.Pq Ql @
|
.Pq Ql @
|
||||||
is interpreted as one.
|
is interpreted as one, as documented above.
|
||||||
The rest of the argument (without the @, that is) is interpreted as the path to a file, whose contents are interpreted as if given on the command line.
|
|
||||||
At-files can be stored right next to the corresponding image, for example:
|
At-files can be stored right next to the corresponding image, for example:
|
||||||
.Pp
|
.Pp
|
||||||
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
||||||
.Pp
|
.Pp
|
||||||
This will read additional flags from file
|
This will read additional flags from the file
|
||||||
.Ql image.flags ,
|
.Ql image.flags ,
|
||||||
which could contains for example
|
which could contain, for example,
|
||||||
.Ql -b 128
|
.Ql -b 128
|
||||||
to specify a base offset for the image's tiles.
|
to specify a base offset for the image's tiles.
|
||||||
The above command could be generated from the following
|
The above command could be generated from the following
|
||||||
.Xr make 1
|
.Xr make 1
|
||||||
rule, for example:
|
rule:
|
||||||
.Bd -literal -offset indent
|
.Bd -literal -offset indent
|
||||||
%.2bpp %.tilemap: %.flags %.png
|
%.2bpp %.tilemap: %.flags %.png
|
||||||
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
||||||
.Ed
|
.Ed
|
||||||
.Pp
|
|
||||||
Since the contents of at-files are interpreted by
|
|
||||||
.Nm ,
|
|
||||||
.Sy no shell processing is performed ;
|
|
||||||
for example, shell variables are not expanded
|
|
||||||
.Ql ( $PWD ,
|
|
||||||
.Ql %WINDIR% ,
|
|
||||||
etc.).
|
|
||||||
In at-files, lines that are empty or contain only whitespace are ignored; lines that begin with a hash sign
|
|
||||||
.Pq Ql # ,
|
|
||||||
optionally preceded by whitespace, are considered comments and also ignored.
|
|
||||||
Each line can contain any number of arguments, which are separated by whitespace.
|
|
||||||
.Pq \&No quoting feature to prevent this is provided.
|
|
||||||
.Pp
|
|
||||||
Note that a leading
|
|
||||||
.Ql @
|
|
||||||
has no special meaning on option arguments, and that the standard
|
|
||||||
.Ql --
|
|
||||||
to stop option processing also disables at-file processing.
|
|
||||||
For example, the following command line reads command-line options from
|
|
||||||
.Ql tilesets/town.flags
|
|
||||||
then
|
|
||||||
.Ql tilesets.flags ,
|
|
||||||
but processes
|
|
||||||
.Ql @tilesets/town.png
|
|
||||||
as the input image and outputs tile data to
|
|
||||||
.Ql @tilesets/town.2bpp :
|
|
||||||
.Pp
|
|
||||||
.Dl $ rgbgfx -o @tilesets/town.2bpp @tilesets/town.flags @tilesets.flags -- @tilesets/town.png
|
|
||||||
.Pp
|
|
||||||
At-files can also specify the input image directly, and call for more at-files, both using the regular syntax.
|
|
||||||
Note that while
|
|
||||||
.Ql --
|
|
||||||
can be used in an at-file (with identical semantics), it is only effective inside of it\(emnormal option processing continues in the parent scope.
|
|
||||||
.Sh PALETTE SPECIFICATION FORMATS
|
.Sh PALETTE SPECIFICATION FORMATS
|
||||||
The following formats are supported:
|
The following formats are supported:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
@@ -769,7 +771,7 @@ to prevent turning all warnings into errors.
|
|||||||
.It Fl Werror=
|
.It Fl Werror=
|
||||||
Make the specified warning or meta warning into an error.
|
Make the specified warning or meta warning into an error.
|
||||||
A warning's name is appended
|
A warning's name is appended
|
||||||
.Pq example: Fl Werror=embedded ,
|
.Pq example: Fl Werror=obsolete ,
|
||||||
and this warning is implicitly enabled and turned into an error.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This can be negated as
|
This can be negated as
|
||||||
.Fl Wno-error=
|
.Fl Wno-error=
|
||||||
@@ -791,10 +793,10 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||||
Note that each of these flag also has a negation (for example,
|
Note that each of these flags also has a negation (for example,
|
||||||
.Fl Wtrim-nonempty
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-trim-nonempty
|
.Fl Wno-obsolete
|
||||||
disables; and
|
disables; and
|
||||||
.Fl Wall
|
.Fl Wall
|
||||||
enables every warning that
|
enables every warning that
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -48,14 +48,14 @@ Also, if your ROM is designed for a monochrome Game Boy, you can make sure that
|
|||||||
option, which implies
|
option, which implies
|
||||||
.Fl w
|
.Fl w
|
||||||
but also prohibits the use of banked VRAM.
|
but also prohibits the use of banked VRAM.
|
||||||
.Pp
|
.Sh ARGUMENTS
|
||||||
The input
|
.Nm
|
||||||
.Ar file
|
accepts the usual short and long options, such as
|
||||||
can be a path to a file, or
|
.Fl V
|
||||||
.Cm \-
|
and
|
||||||
to read from standard input.
|
.Fl -version .
|
||||||
.Pp
|
Options later in the command line override those set earlier, except for when duplicate options are considered an error.
|
||||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
Options can be abbreviated as long as the abbreviation is unambiguous:
|
||||||
.Fl \-verb
|
.Fl \-verb
|
||||||
is
|
is
|
||||||
.Fl \-verbose ,
|
.Fl \-verbose ,
|
||||||
@@ -63,7 +63,52 @@ but
|
|||||||
.Fl \-ver
|
.Fl \-ver
|
||||||
is invalid because it could also be
|
is invalid because it could also be
|
||||||
.Fl \-version .
|
.Fl \-version .
|
||||||
The arguments are as follows:
|
.Pp
|
||||||
|
Unless otherwise noted, passing
|
||||||
|
.Ql -
|
||||||
|
(a single dash) as a file name makes
|
||||||
|
.Nm
|
||||||
|
use standard input (for input files) or standard output (for output files).
|
||||||
|
To suppress this behavior, and open a file in the current directory actually called
|
||||||
|
.Ql - ,
|
||||||
|
pass
|
||||||
|
.Ql ./-
|
||||||
|
instead.
|
||||||
|
Using standard input or output for more than one file in a single command may produce unexpected results.
|
||||||
|
.Pp
|
||||||
|
.Nm
|
||||||
|
accepts decimal, hexadecimal, octal, and binary for numeric option arguments.
|
||||||
|
Decimal numbers are written as usual; hexadecimal numbers must be prefixed with either
|
||||||
|
.Ql $
|
||||||
|
or
|
||||||
|
.Ql 0x ;
|
||||||
|
octal numbers must be prefixed with either
|
||||||
|
.Ql &
|
||||||
|
or
|
||||||
|
.Ql 0o ;
|
||||||
|
and binary numbers must be prefixed with either
|
||||||
|
.Ql %
|
||||||
|
or
|
||||||
|
.Ql 0b .
|
||||||
|
(The prefixes
|
||||||
|
.Ql $
|
||||||
|
and
|
||||||
|
.Ql &
|
||||||
|
will likely need escaping or quoting to avoid being interpreted by the shell.)
|
||||||
|
Leading zeros (after the base prefix, if any) are accepted, and letters are not case-sensitive.
|
||||||
|
For example, all of these are equivalent:
|
||||||
|
.Ql 42 ,
|
||||||
|
.Ql 042 ,
|
||||||
|
.Ql 0x2A ,
|
||||||
|
.Ql 0X2A ,
|
||||||
|
.Ql 0x2a ,
|
||||||
|
.Ql &52 ,
|
||||||
|
.Ql 0o52 ,
|
||||||
|
.Ql 0O052 ,
|
||||||
|
.Ql 0b00101010 ,
|
||||||
|
.Ql 0B101010 .
|
||||||
|
.Pp
|
||||||
|
The following options are accepted:
|
||||||
.Bl -tag -width Ds
|
.Bl -tag -width Ds
|
||||||
.It Fl B Ar param , Fl \-backtrace Ar param
|
.It Fl B Ar param , Fl \-backtrace Ar param
|
||||||
Configures how location backtraces are printed if warnings or errors occur.
|
Configures how location backtraces are printed if warnings or errors occur.
|
||||||
@@ -190,6 +235,23 @@ You can use this to make binary files that are not a ROM.
|
|||||||
When making a ROM, note that not using this is not a replacement for
|
When making a ROM, note that not using this is not a replacement for
|
||||||
.Xr rgbfix 1 Ap s Fl p
|
.Xr rgbfix 1 Ap s Fl p
|
||||||
option!
|
option!
|
||||||
|
.It @ Ns Ar at_file
|
||||||
|
Read more options and arguments from a file, as if its contents were given on the command line.
|
||||||
|
Arguments are separated by whitespace or newlines.
|
||||||
|
Lines starting with a hash sign
|
||||||
|
.Pq Ql #
|
||||||
|
are considered comments and ignored.
|
||||||
|
.Pp
|
||||||
|
No shell processing is performed, such as wildcard or variable expansion.
|
||||||
|
There is no support for escaping or quoting whitespace to be included in arguments.
|
||||||
|
The standard
|
||||||
|
.Ql --
|
||||||
|
to stop option processing also disables at-file processing.
|
||||||
|
Note that while
|
||||||
|
.Ql --
|
||||||
|
can be used
|
||||||
|
.Em inside
|
||||||
|
an at-file, it only disables option processing within that at-file, and processing continues in the parent scope.
|
||||||
.El
|
.El
|
||||||
.Ss Scrambling algorithm
|
.Ss Scrambling algorithm
|
||||||
The default section placement algorithm tries to place sections into as few banks as possible.
|
The default section placement algorithm tries to place sections into as few banks as possible.
|
||||||
@@ -258,7 +320,7 @@ to prevent turning all warnings into errors.
|
|||||||
.It Fl Werror=
|
.It Fl Werror=
|
||||||
Make the specified warning or meta warning into an error.
|
Make the specified warning or meta warning into an error.
|
||||||
A warning's name is appended
|
A warning's name is appended
|
||||||
.Pq example: Fl Werror=assert ,
|
.Pq example: Fl Werror=obsolete ,
|
||||||
and this warning is implicitly enabled and turned into an error.
|
and this warning is implicitly enabled and turned into an error.
|
||||||
This can be negated as
|
This can be negated as
|
||||||
.Fl Wno-error=
|
.Fl Wno-error=
|
||||||
@@ -280,7 +342,7 @@ Enables literally every warning.
|
|||||||
.El
|
.El
|
||||||
.Pp
|
.Pp
|
||||||
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
The following warnings are actual warning flags; with each description, the corresponding warning flag is included.
|
||||||
Note that each of these flag also has a negation (for example,
|
Note that each of these flags also has a negation (for example,
|
||||||
.Fl Wobsolete
|
.Fl Wobsolete
|
||||||
enables the warning that
|
enables the warning that
|
||||||
.Fl Wno-obsolete
|
.Fl Wno-obsolete
|
||||||
@@ -327,10 +389,10 @@ or
|
|||||||
.Fl Wno-truncation
|
.Fl Wno-truncation
|
||||||
disables this warning.
|
disables this warning.
|
||||||
.Fl Wtruncation=1
|
.Fl Wtruncation=1
|
||||||
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
|
||||||
.Fl Wtruncation=2
|
|
||||||
or just
|
or just
|
||||||
.Fl Wtruncation
|
.Fl Wtruncation
|
||||||
|
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
||||||
|
.Fl Wtruncation=2
|
||||||
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
also warns when an N-bit value is less than -2**(N-1), which will not fit in two's complement encoding.
|
||||||
.El
|
.El
|
||||||
.Sh EXAMPLES
|
.Sh EXAMPLES
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
|||||||
|
|
||||||
set(common_src
|
set(common_src
|
||||||
"extern/getopt.cpp"
|
"extern/getopt.cpp"
|
||||||
|
"cli.cpp"
|
||||||
"diagnostics.cpp"
|
"diagnostics.cpp"
|
||||||
"style.cpp"
|
"style.cpp"
|
||||||
"usage.cpp"
|
"usage.cpp"
|
||||||
@@ -92,6 +93,7 @@ set(rgbgfx_src
|
|||||||
"gfx/pal_packing.cpp"
|
"gfx/pal_packing.cpp"
|
||||||
"gfx/pal_sorting.cpp"
|
"gfx/pal_sorting.cpp"
|
||||||
"gfx/pal_spec.cpp"
|
"gfx/pal_spec.cpp"
|
||||||
|
"gfx/palette.cpp"
|
||||||
"gfx/png.cpp"
|
"gfx/png.cpp"
|
||||||
"gfx/process.cpp"
|
"gfx/process.cpp"
|
||||||
"gfx/reverse.cpp"
|
"gfx/reverse.cpp"
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ struct Charmap {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
bool forEachChar(Charmap const &charmap, F callback) {
|
bool forEachChar(Charmap const &charmap, CallbackFnT callback) {
|
||||||
// clang-format off: nested initializers
|
// clang-format off: nested initializers
|
||||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|||||||
@@ -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`.
|
||||||
|
// The sign will be printed later from `signChar`.
|
||||||
|
char const *spec = useType == 'd' || useType == 'u' ? "%" PRIu32
|
||||||
: useType == 'X' ? "%" PRIX32
|
: useType == 'X' ? "%" PRIX32
|
||||||
: useType == 'x' ? "%" PRIx32
|
: useType == 'x' ? "%" PRIx32
|
||||||
: useType == 'o' ? "%" PRIo32
|
: useType == 'o' ? "%" PRIo32
|
||||||
: "%" PRIu32;
|
: "%" 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;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "itertools.hpp" // reversed
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "platform.hpp" // strncasecmp
|
#include "platform.hpp" // strncasecmp
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
@@ -56,16 +57,6 @@ static std::vector<std::string> includePaths = {""}; // -I
|
|||||||
static std::deque<std::string> preIncludeNames; // -P
|
static std::deque<std::string> preIncludeNames; // -P
|
||||||
static bool failedOnMissingInclude = false;
|
static bool failedOnMissingInclude = false;
|
||||||
|
|
||||||
static std::string reptChain(FileStackNode const &node) {
|
|
||||||
std::string chain;
|
|
||||||
std::vector<uint32_t> const &nodeIters = node.iters();
|
|
||||||
for (uint32_t i = nodeIters.size(); i--;) {
|
|
||||||
chain.append("::REPT~");
|
|
||||||
chain.append(std::to_string(nodeIters[i]));
|
|
||||||
}
|
|
||||||
return chain;
|
|
||||||
}
|
|
||||||
|
|
||||||
using TraceNode = std::pair<std::string, uint32_t>;
|
using TraceNode = std::pair<std::string, uint32_t>;
|
||||||
|
|
||||||
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) {
|
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) {
|
||||||
@@ -89,7 +80,12 @@ static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curL
|
|||||||
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
||||||
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
||||||
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
||||||
traceNodes.emplace_back(traceNodes.back().first + reptChain(node), curLineNo);
|
std::string reptName = traceNodes.back().first;
|
||||||
|
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) {
|
||||||
|
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
|
||||||
|
reptName.append(std::to_string(nodeIters.front()));
|
||||||
|
}
|
||||||
|
traceNodes.emplace_back(reptName, curLineNo);
|
||||||
} else {
|
} else {
|
||||||
traceNodes.emplace_back(node.name(), curLineNo);
|
traceNodes.emplace_back(node.name(), curLineNo);
|
||||||
}
|
}
|
||||||
@@ -299,9 +295,13 @@ static void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (macro.src->type == NODE_REPT) {
|
if (macro.src->type == NODE_REPT) {
|
||||||
fileInfoName.append(reptChain(*macro.src));
|
std::vector<uint32_t> const &srcIters = macro.src->iters();
|
||||||
|
for (uint32_t iter : reversed(srcIters)) {
|
||||||
|
fileInfoName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
|
||||||
|
fileInfoName.append(std::to_string(iter));
|
||||||
}
|
}
|
||||||
fileInfoName.append("::");
|
}
|
||||||
|
fileInfoName.append(NODE_SEPARATOR);
|
||||||
fileInfoName.append(macro.name);
|
fileInfoName.append(macro.name);
|
||||||
|
|
||||||
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);
|
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);
|
||||||
@@ -382,61 +382,54 @@ 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) {
|
void fstk_RunMacro(
|
||||||
|
std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
|
||||||
|
) {
|
||||||
|
auto makeSuggestion = [¯oName, ¯oArgs]() -> std::optional<std::string> {
|
||||||
std::shared_ptr<std::string> arg = macroArgs->getArg(1);
|
std::shared_ptr<std::string> arg = macroArgs->getArg(1);
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
return nullptr;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
char const *str = arg->c_str();
|
char const *str = arg->c_str();
|
||||||
static char const *types[] = {"EQUS", "EQU", "RB", "RW", "RL", "="};
|
static char const *types[] = {"EQUS", "EQU", "RB", "RW", "RL", "="};
|
||||||
for (size_t i = 0; i < std::size(types); ++i) {
|
for (char const *type : types) {
|
||||||
if (char const *type = types[i]; strncasecmp(str, type, strlen(type)) == 0) {
|
if (strncasecmp(str, type, strlen(type)) == 0) {
|
||||||
return type;
|
return "\"DEF "s + macroName + " " + type + " ...\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (strncasecmp(str, "SET", literal_strlen("SET")) == 0) {
|
if (strncasecmp(str, "SET", literal_strlen("SET")) == 0) {
|
||||||
return "=";
|
return "\"DEF "s + macroName + " = ...\"";
|
||||||
|
}
|
||||||
|
if (str[0] == ':') {
|
||||||
|
return "a label \""s + macroName + (str[1] == ':' ? "::" : ":") + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return std::nullopt;
|
||||||
}
|
};
|
||||||
|
|
||||||
void fstk_RunMacro(
|
if (Symbol *macro = sym_FindExactSymbol(macroName); !macro) {
|
||||||
std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
|
|
||||||
) {
|
|
||||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
|
||||||
|
|
||||||
if (!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(
|
||||||
std::string const &symName,
|
std::string const &symName,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
@@ -56,7 +57,7 @@ struct Token {
|
|||||||
|
|
||||||
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
|
// This map lists all RGBASM keywords which `yylex_NORMAL` lexes as identifiers.
|
||||||
// All non-identifier tokens are lexed separately.
|
// All non-identifier tokens are lexed separately.
|
||||||
static UpperMap<int> const keywordDict{
|
static UpperMap<int> const keywords{
|
||||||
{"ADC", T_(SM83_ADC) },
|
{"ADC", T_(SM83_ADC) },
|
||||||
{"ADD", T_(SM83_ADD) },
|
{"ADD", T_(SM83_ADD) },
|
||||||
{"AND", T_(SM83_AND) },
|
{"AND", T_(SM83_AND) },
|
||||||
@@ -743,7 +744,7 @@ static int peek() {
|
|||||||
lexerState->expansionScanDistance += str->length();
|
lexerState->expansionScanDistance += str->length();
|
||||||
}
|
}
|
||||||
|
|
||||||
return peek(); // Tail recursion
|
MUSTTAIL return peek();
|
||||||
} else if (c == '{') {
|
} else if (c == '{') {
|
||||||
// If character is an open brace, do symbol interpolation
|
// If character is an open brace, do symbol interpolation
|
||||||
shiftChar();
|
shiftChar();
|
||||||
@@ -751,7 +752,7 @@ static int peek() {
|
|||||||
beginExpansion(interp.second, interp.first->name);
|
beginExpansion(interp.second, interp.first->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return peek(); // Tail recursion
|
MUSTTAIL return peek();
|
||||||
} else {
|
} else {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -815,8 +816,8 @@ static int nextChar() {
|
|||||||
return peek();
|
return peek();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename P>
|
template<typename PredicateFnT>
|
||||||
static int skipChars(P predicate) {
|
static int skipChars(PredicateFnT predicate) {
|
||||||
int c = peek();
|
int c = peek();
|
||||||
while (predicate(c)) {
|
while (predicate(c)) {
|
||||||
c = nextChar();
|
c = nextChar();
|
||||||
@@ -879,7 +880,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 +1014,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 +1083,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 +1113,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 +1153,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 +1189,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;
|
||||||
|
|
||||||
@@ -1271,21 +1270,27 @@ static uint32_t readGfxConstant() {
|
|||||||
|
|
||||||
static Token readIdentifier(char firstChar, bool raw) {
|
static Token readIdentifier(char firstChar, bool raw) {
|
||||||
std::string identifier(1, firstChar);
|
std::string identifier(1, firstChar);
|
||||||
|
bool keywordBeforeLocal = false;
|
||||||
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
|
int tokenType = firstChar == '.' ? T_(LOCAL) : T_(SYMBOL);
|
||||||
|
|
||||||
// Continue reading while the char is in the identifier charset
|
// Continue reading while the char is in the identifier charset
|
||||||
for (int c = peek(); continuesIdentifier(c); c = nextChar()) {
|
for (int c = peek(); continuesIdentifier(c); c = nextChar()) {
|
||||||
identifier += c;
|
|
||||||
|
|
||||||
// If the char was a dot, the identifier is a local label
|
// If the char was a dot, the identifier is a local label
|
||||||
if (c == '.') {
|
if (c == '.') {
|
||||||
tokenType = T_(LOCAL);
|
// Check for a keyword before a non-raw local label
|
||||||
}
|
if (!raw && tokenType != T_(LOCAL) && keywords.find(identifier) != keywords.end()) {
|
||||||
|
keywordBeforeLocal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to check for a keyword if the identifier is not raw or a local label
|
tokenType = T_(LOCAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
identifier += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a keyword if the identifier is not raw and not a local label
|
||||||
if (!raw && tokenType != T_(LOCAL)) {
|
if (!raw && tokenType != T_(LOCAL)) {
|
||||||
if (auto search = keywordDict.find(identifier); search != keywordDict.end()) {
|
if (auto search = keywords.find(identifier); search != keywords.end()) {
|
||||||
return Token(search->second);
|
return Token(search->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1295,6 +1300,14 @@ static Token readIdentifier(char firstChar, bool raw) {
|
|||||||
tokenType = T_(SYMBOL);
|
tokenType = T_(SYMBOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A keyword before a non-raw local label is an error
|
||||||
|
if (keywordBeforeLocal) {
|
||||||
|
error(
|
||||||
|
"Identifier \"%s\" begins with a keyword; did you mean to put a space between them?",
|
||||||
|
identifier.c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Token(tokenType, identifier);
|
return Token(tokenType, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1338,7 +1351,7 @@ static std::pair<Symbol const *, std::shared_ptr<std::string>> readInterpolation
|
|||||||
if (identifier.starts_with('#')) {
|
if (identifier.starts_with('#')) {
|
||||||
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
|
// Skip a '#' raw symbol prefix, but after expanding any nested interpolations.
|
||||||
identifier.erase(0, 1);
|
identifier.erase(0, 1);
|
||||||
} else if (keywordDict.find(identifier) != keywordDict.end()) {
|
} else if (keywords.find(identifier) != keywords.end()) {
|
||||||
// Don't allow symbols that alias keywords without a '#' prefix.
|
// Don't allow symbols that alias keywords without a '#' prefix.
|
||||||
error(
|
error(
|
||||||
"Interpolated symbol `%s` is a reserved keyword; add a '#' prefix to use it as a raw "
|
"Interpolated symbol `%s` is a reserved keyword; add a '#' prefix to use it as a raw "
|
||||||
@@ -1682,7 +1695,7 @@ static Token yylex_NORMAL() {
|
|||||||
return Token(nextToken);
|
return Token(nextToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;; lexerState->atLineStart = false) {
|
for (;;) {
|
||||||
int c = bumpChar();
|
int c = bumpChar();
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@@ -1694,7 +1707,7 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
case ' ':
|
case ' ':
|
||||||
case '\t':
|
case '\t':
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
// Handle unambiguous single-char tokens
|
// Handle unambiguous single-char tokens
|
||||||
|
|
||||||
@@ -1746,7 +1759,7 @@ static Token yylex_NORMAL() {
|
|||||||
if (peek() == '*') {
|
if (peek() == '*') {
|
||||||
shiftChar();
|
shiftChar();
|
||||||
discardBlockComment();
|
discardBlockComment();
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
return oneOrTwo('=', T_(POP_DIVEQ), T_(OP_DIV));
|
return oneOrTwo('=', T_(POP_DIVEQ), T_(OP_DIV));
|
||||||
|
|
||||||
@@ -1756,14 +1769,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 +1855,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));
|
||||||
@@ -1877,7 +1897,7 @@ static Token yylex_NORMAL() {
|
|||||||
// Macro args were handled by `peek`, and character escapes do not exist
|
// Macro args were handled by `peek`, and character escapes do not exist
|
||||||
// outside of string literals, so this must be a line continuation.
|
// outside of string literals, so this must be a line continuation.
|
||||||
discardLineContinuation();
|
discardLineContinuation();
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
// Handle raw strings... or fall through if '#' is not followed by '"'
|
// Handle raw strings... or fall through if '#' is not followed by '"'
|
||||||
|
|
||||||
@@ -1898,7 +1918,7 @@ static Token yylex_NORMAL() {
|
|||||||
c = bumpChar();
|
c = bumpChar();
|
||||||
} else if (!startsIdentifier(c)) {
|
} else if (!startsIdentifier(c)) {
|
||||||
reportGarbageCharacters(c);
|
reportGarbageCharacters(c);
|
||||||
continue;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token token = readIdentifier(c, raw);
|
Token token = readIdentifier(c, raw);
|
||||||
@@ -1923,7 +1943,9 @@ static Token yylex_NORMAL() {
|
|||||||
if (Symbol const *sym = sym_FindExactSymbol(std::get<std::string>(token.value));
|
if (Symbol const *sym = sym_FindExactSymbol(std::get<std::string>(token.value));
|
||||||
sym && sym->type == SYM_EQUS) {
|
sym && sym->type == SYM_EQUS) {
|
||||||
beginExpansion(sym->getEqus(), sym->name);
|
beginExpansion(sym->getEqus(), sym->name);
|
||||||
return yylex_NORMAL(); // Tail recursion
|
// We cannot do `MUSTTAIL return yylex_NORMAL();` because tail call optimization
|
||||||
|
// requires the return value to be "trivially destructible", and `Token` is not.
|
||||||
|
continue; // Restart, reading from the new buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1948,34 +1970,19 @@ static Token yylex_NORMAL() {
|
|||||||
|
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we exited the switch, i.e. read some characters without yet returning a token,
|
||||||
|
// we can't be at the start of the line
|
||||||
|
lexerState->atLineStart = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +2043,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 +2097,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
|
||||||
@@ -2281,8 +2287,8 @@ yy::parser::symbol_type yylex() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
static Capture makeCapture(char const *name, F callback) {
|
static Capture makeCapture(char const *name, CallbackFnT callback) {
|
||||||
// Due to parser internals, it reads the EOL after the expression before calling this.
|
// Due to parser internals, it reads the EOL after the expression before calling this.
|
||||||
// Thus, we don't need to keep one in the buffer afterwards.
|
// Thus, we don't need to keep one in the buffer afterwards.
|
||||||
// The following assumption checks that.
|
// The following assumption checks that.
|
||||||
|
|||||||
498
src/asm/main.cpp
498
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>
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "parser.hpp" // Generated from parser.y
|
#include "parser.hpp" // Generated from parser.y
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
@@ -39,13 +40,17 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
static char const *dependFileName = nullptr; // -M
|
// Flags which must be processed after the option parsing finishes
|
||||||
static std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
static struct LocalOptions {
|
||||||
|
std::optional<std::string> dependFileName; // -M
|
||||||
|
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
||||||
|
std::optional<std::string> inputFileName; // <file>
|
||||||
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color` and variants of `-M`
|
static int longOpt; // `--color` and variants of `-M`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -104,134 +109,6 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
static void verboseOutputConfig(int argc, char *argv[]) {
|
|
||||||
if (!checkVerbosity(VERB_CONFIG)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
|
||||||
|
|
||||||
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
|
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
|
||||||
// -E/--export-all
|
|
||||||
if (options.exportAll) {
|
|
||||||
fputs("\tExport all labels by default\n", stderr);
|
|
||||||
}
|
|
||||||
// -b/--binary-digits
|
|
||||||
if (options.binDigits[0] != '0' || options.binDigits[1] != '1') {
|
|
||||||
fprintf(
|
|
||||||
stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// -g/--gfx-chars
|
|
||||||
if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2'
|
|
||||||
|| options.gfxDigits[3] != '3') {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"\tGraphics characters: '%c', '%c', '%c', '%c'\n",
|
|
||||||
options.gfxDigits[0],
|
|
||||||
options.gfxDigits[1],
|
|
||||||
options.gfxDigits[2],
|
|
||||||
options.gfxDigits[3]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// -Q/--q-precision
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"\tFixed-point precision: Q%d.%" PRIu8 "\n",
|
|
||||||
32 - options.fixPrecision,
|
|
||||||
options.fixPrecision
|
|
||||||
);
|
|
||||||
// -p/--pad-value
|
|
||||||
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte);
|
|
||||||
// -r/--recursion-depth
|
|
||||||
fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth);
|
|
||||||
// -X/--max-errors
|
|
||||||
if (options.maxErrors) {
|
|
||||||
fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors);
|
|
||||||
}
|
|
||||||
// -D/--define
|
|
||||||
static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it
|
|
||||||
sym_ForEach([](Symbol &sym) {
|
|
||||||
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
|
||||||
if (!hasDefines) {
|
|
||||||
fputs("\tDefinitions:\n", stderr);
|
|
||||||
hasDefines = true;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// -s/--state
|
|
||||||
if (!stateFileSpecs.empty()) {
|
|
||||||
fputs("\tOutput state files:\n", stderr);
|
|
||||||
static char const *featureNames[NB_STATE_FEATURES] = {
|
|
||||||
"equ",
|
|
||||||
"var",
|
|
||||||
"equs",
|
|
||||||
"char",
|
|
||||||
"macro",
|
|
||||||
};
|
|
||||||
for (auto const &[name, features] : stateFileSpecs) {
|
|
||||||
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
|
|
||||||
for (size_t i = 0; i < features.size(); ++i) {
|
|
||||||
if (i > 0) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
fputs(featureNames[features[i]], stderr);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// asmfile
|
|
||||||
if (musl_optind < argc) {
|
|
||||||
fprintf(stderr, "\tInput asm file: %s", argv[musl_optind]);
|
|
||||||
if (musl_optind + 1 < argc) {
|
|
||||||
fprintf(stderr, " (and %d more)", argc - musl_optind - 1);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
// -o/--output
|
|
||||||
if (!options.objectFileName.empty()) {
|
|
||||||
fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName.c_str());
|
|
||||||
}
|
|
||||||
fstk_VerboseOutputConfig();
|
|
||||||
if (dependFileName) {
|
|
||||||
fprintf(
|
|
||||||
stderr,
|
|
||||||
"\tOutput dependency file: %s\n",
|
|
||||||
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
|
|
||||||
);
|
|
||||||
// -MT or -MQ
|
|
||||||
if (!options.targetFileName.empty()) {
|
|
||||||
fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName.c_str());
|
|
||||||
}
|
|
||||||
// -MG or -MC
|
|
||||||
switch (options.missingIncludeState) {
|
|
||||||
case INC_ERROR:
|
|
||||||
fputs("\tExit with an error on a missing dependency\n", stderr);
|
|
||||||
break;
|
|
||||||
case GEN_EXIT:
|
|
||||||
fputs("\tExit normally on a missing dependency\n", stderr);
|
|
||||||
break;
|
|
||||||
case GEN_CONTINUE:
|
|
||||||
fputs("\tContinue processing after a missing dependency\n", stderr);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// -MP
|
|
||||||
if (options.generatePhonyDeps) {
|
|
||||||
fputs("\tGenerate phony dependencies\n", stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fputs("Ready.\n", stderr);
|
|
||||||
|
|
||||||
style_Reset(stderr);
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
static std::string escapeMakeChars(std::string &str) {
|
static std::string escapeMakeChars(std::string &str) {
|
||||||
std::string escaped;
|
std::string escaped;
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
@@ -294,44 +171,29 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
|||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
static void parseArg(int ch, char *arg) {
|
||||||
// Support SOURCE_DATE_EPOCH for reproducible builds
|
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
|
||||||
time_t now = time(nullptr);
|
|
||||||
if (char const *sourceDateEpoch = getenv("SOURCE_DATE_EPOCH"); sourceDateEpoch) {
|
|
||||||
now = static_cast<time_t>(strtoul(sourceDateEpoch, nullptr, 0));
|
|
||||||
}
|
|
||||||
sym_Init(now);
|
|
||||||
|
|
||||||
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
|
||||||
if (isatty(STDERR_FILENO)) {
|
|
||||||
options.maxErrors = 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse CLI options
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'B':
|
case 'B':
|
||||||
if (!trace_ParseTraceDepth(musl_optarg)) {
|
if (!trace_ParseTraceDepth(arg)) {
|
||||||
fatal("Invalid argument for option '-B'");
|
fatal("Invalid argument for option '-B'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'b':
|
case 'b':
|
||||||
if (strlen(musl_optarg) == 2) {
|
if (strlen(arg) == 2) {
|
||||||
opt_B(musl_optarg);
|
opt_B(arg);
|
||||||
} else {
|
} else {
|
||||||
fatal("Must specify exactly 2 characters for option '-b'");
|
fatal("Must specify exactly 2 characters for option '-b'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'D': {
|
case 'D': {
|
||||||
char *equals = strchr(musl_optarg, '=');
|
char *equals = strchr(arg, '=');
|
||||||
if (equals) {
|
if (equals) {
|
||||||
*equals = '\0';
|
*equals = '\0';
|
||||||
sym_AddString(musl_optarg, std::make_shared<std::string>(equals + 1));
|
sym_AddString(arg, std::make_shared<std::string>(equals + 1));
|
||||||
} else {
|
} else {
|
||||||
sym_AddString(musl_optarg, std::make_shared<std::string>("1"));
|
sym_AddString(arg, std::make_shared<std::string>("1"));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -341,8 +203,8 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g':
|
case 'g':
|
||||||
if (strlen(musl_optarg) == 4) {
|
if (strlen(arg) == 4) {
|
||||||
opt_G(musl_optarg);
|
opt_G(arg);
|
||||||
} else {
|
} else {
|
||||||
fatal("Must specify exactly 4 characters for option '-g'");
|
fatal("Must specify exactly 4 characters for option '-g'");
|
||||||
}
|
}
|
||||||
@@ -354,90 +216,81 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'I':
|
case 'I':
|
||||||
fstk_AddIncludePath(musl_optarg);
|
fstk_AddIncludePath(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
if (dependFileName) {
|
if (localOptions.dependFileName) {
|
||||||
warnx(
|
warnx(
|
||||||
"Overriding dependency file \"%s\"",
|
"Overriding dependency file \"%s\"",
|
||||||
strcmp(dependFileName, "-") ? dependFileName : "<stdout>"
|
*localOptions.dependFileName == "-" ? "<stdout>"
|
||||||
|
: localOptions.dependFileName->c_str()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
dependFileName = musl_optarg;
|
localOptions.dependFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
if (!options.objectFileName.empty()) {
|
if (options.objectFileName) {
|
||||||
warnx("Overriding output file \"%s\"", options.objectFileName.c_str());
|
warnx("Overriding output file \"%s\"", options.objectFileName->c_str());
|
||||||
}
|
}
|
||||||
options.objectFileName = musl_optarg;
|
options.objectFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
fstk_AddPreIncludeFile(musl_optarg);
|
fstk_AddPreIncludeFile(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p': {
|
case 'p':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> padByte = parseWholeNumber(arg); !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 = arg;
|
||||||
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(arg); !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 `arg` is "<features>" and `name` is "<name>"
|
||||||
char *name = strchr(musl_optarg, ':');
|
char *name = strchr(arg, ':');
|
||||||
if (!name) {
|
if (!name) {
|
||||||
fatal("Invalid argument for option '-s'");
|
fatal("Invalid argument for option '-s'");
|
||||||
}
|
}
|
||||||
*name++ = '\0';
|
*name++ = '\0';
|
||||||
|
|
||||||
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
|
std::vector<StateFeature> features = parseStateFeatures(arg);
|
||||||
|
|
||||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
if (localOptions.stateFileSpecs.find(name) != localOptions.stateFileSpecs.end()) {
|
||||||
warnx("Overriding state file \"%s\"", name);
|
warnx("Overriding state file \"%s\"", name);
|
||||||
}
|
}
|
||||||
stateFileSpecs.emplace(name, std::move(features));
|
localOptions.stateFileSpecs.emplace(name, std::move(features));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,33 +305,27 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
opt_W(musl_optarg);
|
opt_W(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
warnings.state.warningsEnabled = false;
|
warnings.state.warningsEnabled = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'X': {
|
case 'X':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> maxErrors = parseWholeNumber(arg); !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) {
|
||||||
case 'c':
|
case 'c':
|
||||||
if (!style_Parse(musl_optarg)) {
|
if (!style_Parse(arg)) {
|
||||||
fatal("Invalid argument for option '--color'");
|
fatal("Invalid argument for option '--color'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -497,73 +344,236 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
case 'T': {
|
case 'T': {
|
||||||
std::string newTarget = musl_optarg;
|
std::string newTarget = arg;
|
||||||
if (longOpt == 'Q') {
|
if (longOpt == 'Q') {
|
||||||
newTarget = escapeMakeChars(newTarget);
|
newTarget = escapeMakeChars(newTarget);
|
||||||
}
|
}
|
||||||
if (!options.targetFileName.empty()) {
|
if (options.targetFileName) {
|
||||||
options.targetFileName += ' ';
|
*options.targetFileName += ' ';
|
||||||
|
*options.targetFileName += newTarget;
|
||||||
|
} else {
|
||||||
|
options.targetFileName = newTarget;
|
||||||
}
|
}
|
||||||
options.targetFileName += newTarget;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Unrecognized options
|
case 1: // Positional argument
|
||||||
|
if (localOptions.inputFileName) {
|
||||||
|
usage.printAndExit("More than one input file specified");
|
||||||
|
}
|
||||||
|
localOptions.inputFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.targetFileName.empty() && !options.objectFileName.empty()) {
|
// LCOV_EXCL_START
|
||||||
|
static void verboseOutputConfig() {
|
||||||
|
if (!checkVerbosity(VERB_CONFIG)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
|
fprintf(stderr, "rgbasm %s\n", get_package_version_string());
|
||||||
|
|
||||||
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
|
fputs("Options:\n", stderr);
|
||||||
|
// -E/--export-all
|
||||||
|
if (options.exportAll) {
|
||||||
|
fputs("\tExport all labels by default\n", stderr);
|
||||||
|
}
|
||||||
|
// -b/--binary-digits
|
||||||
|
if (options.binDigits[0] != '0' || options.binDigits[1] != '1') {
|
||||||
|
fprintf(
|
||||||
|
stderr, "\tBinary digits: '%c', '%c'\n", options.binDigits[0], options.binDigits[1]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -g/--gfx-chars
|
||||||
|
if (options.gfxDigits[0] != '0' || options.gfxDigits[1] != '1' || options.gfxDigits[2] != '2'
|
||||||
|
|| options.gfxDigits[3] != '3') {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"\tGraphics characters: '%c', '%c', '%c', '%c'\n",
|
||||||
|
options.gfxDigits[0],
|
||||||
|
options.gfxDigits[1],
|
||||||
|
options.gfxDigits[2],
|
||||||
|
options.gfxDigits[3]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -Q/--q-precision
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"\tFixed-point precision: Q%d.%" PRIu8 "\n",
|
||||||
|
32 - options.fixPrecision,
|
||||||
|
options.fixPrecision
|
||||||
|
);
|
||||||
|
// -p/--pad-value
|
||||||
|
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padByte);
|
||||||
|
// -r/--recursion-depth
|
||||||
|
fprintf(stderr, "\tMaximum recursion depth %zu\n", options.maxRecursionDepth);
|
||||||
|
// -X/--max-errors
|
||||||
|
if (options.maxErrors) {
|
||||||
|
fprintf(stderr, "\tMaximum %" PRIu64 " errors\n", options.maxErrors);
|
||||||
|
}
|
||||||
|
// -D/--define
|
||||||
|
static bool hasDefines = false; // `static` so `sym_ForEach` callback can see it
|
||||||
|
sym_ForEach([](Symbol &sym) {
|
||||||
|
if (!sym.isBuiltin && sym.type == SYM_EQUS) {
|
||||||
|
if (!hasDefines) {
|
||||||
|
fputs("\tDefinitions:\n", stderr);
|
||||||
|
hasDefines = true;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "\t - def %s equs \"%s\"\n", sym.name.c_str(), sym.getEqus()->c_str());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// -s/--state
|
||||||
|
if (!localOptions.stateFileSpecs.empty()) {
|
||||||
|
fputs("\tOutput state files:\n", stderr);
|
||||||
|
static char const *featureNames[NB_STATE_FEATURES] = {
|
||||||
|
"equ",
|
||||||
|
"var",
|
||||||
|
"equs",
|
||||||
|
"char",
|
||||||
|
"macro",
|
||||||
|
};
|
||||||
|
for (auto const &[name, features] : localOptions.stateFileSpecs) {
|
||||||
|
fprintf(stderr, "\t - %s: ", name == "-" ? "<stdout>" : name.c_str());
|
||||||
|
for (size_t i = 0; i < features.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
fputs(featureNames[features[i]], stderr);
|
||||||
|
}
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// asmfile
|
||||||
|
if (localOptions.inputFileName) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"\tInput asm file: %s\n",
|
||||||
|
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// -o/--output
|
||||||
|
if (options.objectFileName) {
|
||||||
|
fprintf(stderr, "\tOutput object file: %s\n", options.objectFileName->c_str());
|
||||||
|
}
|
||||||
|
fstk_VerboseOutputConfig();
|
||||||
|
if (localOptions.dependFileName) {
|
||||||
|
fprintf(stderr, "\tOutput dependency file: %s\n", localOptions.dependFileName->c_str());
|
||||||
|
// -MT or -MQ
|
||||||
|
if (options.targetFileName) {
|
||||||
|
fprintf(stderr, "\tTarget file(s): %s\n", options.targetFileName->c_str());
|
||||||
|
}
|
||||||
|
// -MG or -MC
|
||||||
|
switch (options.missingIncludeState) {
|
||||||
|
case INC_ERROR:
|
||||||
|
fputs("\tExit with an error on a missing dependency\n", stderr);
|
||||||
|
break;
|
||||||
|
case GEN_EXIT:
|
||||||
|
fputs("\tExit normally on a missing dependency\n", stderr);
|
||||||
|
break;
|
||||||
|
case GEN_CONTINUE:
|
||||||
|
fputs("\tContinue processing after a missing dependency\n", stderr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// -MP
|
||||||
|
if (options.generatePhonyDeps) {
|
||||||
|
fputs("\tGenerate phony dependencies\n", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fputs("Ready.\n", stderr);
|
||||||
|
|
||||||
|
style_Reset(stderr);
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
// Support SOURCE_DATE_EPOCH for reproducible builds
|
||||||
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
time_t now = time(nullptr);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
sym_Init(now);
|
||||||
|
|
||||||
|
// Maximum of 100 errors only applies if rgbasm is printing errors to a terminal
|
||||||
|
if (isatty(STDERR_FILENO)) {
|
||||||
|
options.maxErrors = 100; // LCOV_EXCL_LINE
|
||||||
|
}
|
||||||
|
|
||||||
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
|
|
||||||
|
if (!options.targetFileName && options.objectFileName) {
|
||||||
options.targetFileName = options.objectFileName;
|
options.targetFileName = options.objectFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(argc, argv);
|
verboseOutputConfig();
|
||||||
|
|
||||||
if (argc == musl_optind) {
|
if (!localOptions.inputFileName) {
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
} else if (argc != musl_optind + 1) {
|
|
||||||
usage.printAndExit("More than one input file specified");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string mainFileName = argv[musl_optind];
|
|
||||||
|
|
||||||
verbosePrint(VERB_NOTICE, "Assembling \"%s\"\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
|
||||||
|
|
||||||
if (dependFileName) {
|
|
||||||
if (strcmp("-", dependFileName)) {
|
|
||||||
options.dependFile = fopen(dependFileName, "w");
|
|
||||||
if (options.dependFile == nullptr) {
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
fatal("Failed to open dependency file \"%s\": %s", dependFileName, strerror(errno));
|
verbosePrint(
|
||||||
|
VERB_NOTICE,
|
||||||
|
"Assembling \"%s\"\n",
|
||||||
|
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
|
||||||
|
);
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
|
||||||
} else {
|
|
||||||
options.dependFile = stdout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.dependFile && options.targetFileName.empty()) {
|
if (localOptions.dependFileName) {
|
||||||
|
if (!options.targetFileName) {
|
||||||
fatal("Dependency files can only be created if a target file is specified with either "
|
fatal("Dependency files can only be created if a target file is specified with either "
|
||||||
"'-o', '-MQ' or '-MT'");
|
"'-o', '-MQ' or '-MT'");
|
||||||
}
|
}
|
||||||
options.printDep(mainFileName);
|
|
||||||
|
if (*localOptions.dependFileName == "-") {
|
||||||
|
options.dependFile = stdout;
|
||||||
|
} else {
|
||||||
|
options.dependFile = fopen(localOptions.dependFileName->c_str(), "w");
|
||||||
|
if (options.dependFile == nullptr) {
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
fatal(
|
||||||
|
"Failed to open dependency file \"%s\": %s",
|
||||||
|
localOptions.dependFileName->c_str(),
|
||||||
|
strerror(errno)
|
||||||
|
);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options.printDep(*localOptions.inputFileName);
|
||||||
|
|
||||||
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
charmap_New(DEFAULT_CHARMAP_NAME, nullptr);
|
||||||
|
|
||||||
// Init lexer and file stack, providing file info
|
// Init lexer and file stack, providing file info
|
||||||
fstk_Init(mainFileName);
|
fstk_Init(*localOptions.inputFileName);
|
||||||
|
|
||||||
// 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 parse aborted without errors due to a missing INCLUDE, and `-MG` was given, exit normally
|
||||||
|
if (fstk_FailedOnMissingInclude()) {
|
||||||
|
requireZeroErrors();
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fstk_FailedOnMissingInclude()) {
|
|
||||||
sect_CheckUnionClosed();
|
sect_CheckUnionClosed();
|
||||||
sect_CheckLoadClosed();
|
sect_CheckLoadClosed();
|
||||||
sect_CheckSizes();
|
sect_CheckSizes();
|
||||||
@@ -571,18 +581,12 @@ int main(int argc, char *argv[]) {
|
|||||||
charmap_CheckStack();
|
charmap_CheckStack();
|
||||||
opt_CheckStack();
|
opt_CheckStack();
|
||||||
sect_CheckStack();
|
sect_CheckStack();
|
||||||
}
|
|
||||||
|
|
||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
|
|
||||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
|
||||||
if (fstk_FailedOnMissingInclude()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_WriteObject();
|
out_WriteObject();
|
||||||
|
|
||||||
for (auto const &[name, features] : stateFileSpecs) {
|
for (auto const &[name, features] : localOptions.stateFileSpecs) {
|
||||||
out_WriteState(name, features);
|
out_WriteState(name, features);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'");
|
error("Invalid argument for option 'p'");
|
||||||
|
} else if (*padByte > 0xFF) {
|
||||||
|
error("Argument for option 'p' must be between 0 and 0xFF");
|
||||||
} else {
|
} else {
|
||||||
// Two characters cannot be scanned as a hex number greater than 0xFF
|
opt_P(*padByte);
|
||||||
assume(padByte <= 0xFF);
|
|
||||||
opt_P(padByte);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error("Invalid argument for option 'p'");
|
|
||||||
}
|
}
|
||||||
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'");
|
error("Invalid argument for option 'Q'");
|
||||||
} else if (fixPrecision < 1 || fixPrecision > 31) {
|
} else if (*precision < 1 || *precision > 31) {
|
||||||
error("Argument for option 'Q' must be between 1 and 31");
|
error("Argument for option 'Q' must be between 1 and 31");
|
||||||
} else {
|
} else {
|
||||||
opt_Q(fixPrecision);
|
opt_Q(*precision);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error("Invalid argument for option 'Q'");
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case 'r': {
|
case 'r':
|
||||||
++s; // Skip 'r'
|
if (std::optional<uint64_t> maxRecursionDepth = parseWholeNumber(s); !maxRecursionDepth) {
|
||||||
while (isBlankSpace(*s)) {
|
error("Invalid argument for option 'r'");
|
||||||
++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) {
|
} else if (errno == ERANGE) {
|
||||||
error("Argument for option 'r' is out of range (\"%s\")", s);
|
error("Argument for option 'r' is out of range");
|
||||||
} else {
|
} else {
|
||||||
opt_R(maxRecursionDepth);
|
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) {
|
||||||
@@ -281,36 +184,35 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
|||||||
|
|
||||||
putLong(nodeIters.size(), file);
|
putLong(nodeIters.size(), file);
|
||||||
// Iters are stored by decreasing depth, so reverse the order for output
|
// Iters are stored by decreasing depth, so reverse the order for output
|
||||||
for (uint32_t i = nodeIters.size(); i--;) {
|
for (uint32_t iter : reversed(nodeIters)) {
|
||||||
putLong(nodeIters[i], file);
|
putLong(iter, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void out_WriteObject() {
|
void out_WriteObject() {
|
||||||
if (options.objectFileName.empty()) {
|
if (!options.objectFileName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
||||||
if (options.objectFileName != "-") {
|
char const *objectFileName = options.objectFileName->c_str();
|
||||||
file = fopen(options.objectFileName.c_str(), "wb");
|
if (*options.objectFileName != "-") {
|
||||||
|
file = fopen(objectFileName, "wb");
|
||||||
} else {
|
} else {
|
||||||
options.objectFileName = "<stdout>";
|
objectFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
file = stdout;
|
file = stdout;
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
fatal(
|
fatal("Failed to open object file \"%s\": %s", objectFileName, strerror(errno));
|
||||||
"Failed to open object file \"%s\": %s", options.objectFileName.c_str(), strerror(errno)
|
|
||||||
);
|
|
||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
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);
|
||||||
@@ -320,12 +222,10 @@ void out_WriteObject() {
|
|||||||
|
|
||||||
putLong(fileStackNodes.size(), file);
|
putLong(fileStackNodes.size(), file);
|
||||||
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
|
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
|
||||||
FileStackNode const &node = **it;
|
writeFileStackNode(**it, file);
|
||||||
|
|
||||||
writeFileStackNode(node, file);
|
|
||||||
|
|
||||||
// The list is supposed to have decrementing IDs
|
// The list is supposed to have decrementing IDs
|
||||||
assume(it + 1 == fileStackNodes.end() || it[1]->ID == node.ID - 1);
|
assume(it + 1 == fileStackNodes.end() || it[1]->ID == it[0]->ID - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Symbol const *sym : objectSymbols) {
|
for (Symbol const *sym : objectSymbols) {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@
|
|||||||
|
|
||||||
%code {
|
%code {
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctype.h>
|
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -42,6 +41,7 @@
|
|||||||
|
|
||||||
#include "extern/utf8decoder.hpp"
|
#include "extern/utf8decoder.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
|
#include "util.hpp" // toLower, toUpper
|
||||||
|
|
||||||
#include "asm/charmap.hpp"
|
#include "asm/charmap.hpp"
|
||||||
#include "asm/fixpoint.hpp"
|
#include "asm/fixpoint.hpp"
|
||||||
@@ -57,8 +57,10 @@
|
|||||||
|
|
||||||
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
|
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
|
||||||
|
|
||||||
template <typename N, typename S>
|
template<typename NumCallbackFnT, typename StrCallbackFnT>
|
||||||
static auto handleSymbolByType(std::string const &symName, N numCallback, S strCallback) {
|
static auto handleSymbolByType(
|
||||||
|
std::string const &symName, NumCallbackFnT numCallback, StrCallbackFnT strCallback
|
||||||
|
) {
|
||||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
|
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
|
||||||
return strCallback(*sym->getEqus());
|
return strCallback(*sym->getEqus());
|
||||||
} else {
|
} else {
|
||||||
@@ -107,6 +109,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 +1052,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 +1290,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));
|
||||||
}
|
}
|
||||||
@@ -1589,11 +1601,11 @@ string_literal:
|
|||||||
}
|
}
|
||||||
| OP_STRUPR LPAREN string RPAREN {
|
| OP_STRUPR LPAREN string RPAREN {
|
||||||
$$ = std::move($3);
|
$$ = std::move($3);
|
||||||
std::transform(RANGE($$), $$.begin(), [](char c) { return toupper(c); });
|
std::transform(RANGE($$), $$.begin(), toUpper);
|
||||||
}
|
}
|
||||||
| OP_STRLWR LPAREN string RPAREN {
|
| OP_STRLWR LPAREN string RPAREN {
|
||||||
$$ = std::move($3);
|
$$ = std::move($3);
|
||||||
std::transform(RANGE($$), $$.begin(), [](char c) { return tolower(c); });
|
std::transform(RANGE($$), $$.begin(), toLower);
|
||||||
}
|
}
|
||||||
| OP_STRRPL LPAREN string COMMA string COMMA string RPAREN {
|
| OP_STRRPL LPAREN string COMMA string COMMA string RPAREN {
|
||||||
$$ = act_StringReplace($3, $5, $7);
|
$$ = act_StringReplace($3, $5, $7);
|
||||||
@@ -1891,7 +1903,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 +2036,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 +2045,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 +2229,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 +2308,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 +2336,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 {
|
||||||
if (errno != ESPIPE) {
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
|
if (errno != ESPIPE) {
|
||||||
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 {
|
||||||
if (errno != ESPIPE) {
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
|
if (errno != ESPIPE) {
|
||||||
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;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ static Symbol const *localScope = nullptr; // Current section's local label sco
|
|||||||
|
|
||||||
static Symbol *PCSymbol;
|
static Symbol *PCSymbol;
|
||||||
static Symbol *NARGSymbol;
|
static Symbol *NARGSymbol;
|
||||||
|
static Symbol *SCOPESymbol;
|
||||||
static Symbol *globalScopeSymbol;
|
static Symbol *globalScopeSymbol;
|
||||||
static Symbol *localScopeSymbol;
|
static Symbol *localScopeSymbol;
|
||||||
static Symbol *RSSymbol;
|
static Symbol *RSSymbol;
|
||||||
@@ -67,6 +68,19 @@ static int32_t NARGCallback() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::shared_ptr<std::string> SCOPECallback() {
|
||||||
|
if (localScope) {
|
||||||
|
return std::make_shared<std::string>("..");
|
||||||
|
} else if (globalScope) {
|
||||||
|
return std::make_shared<std::string>(".");
|
||||||
|
} else {
|
||||||
|
if (!sect_GetSymbolSection()) {
|
||||||
|
error("`__SCOPE__` has no value outside of a section");
|
||||||
|
}
|
||||||
|
return std::make_shared<std::string>("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static std::shared_ptr<std::string> globalScopeCallback() {
|
static std::shared_ptr<std::string> globalScopeCallback() {
|
||||||
if (!globalScope) {
|
if (!globalScope) {
|
||||||
error("`.` has no value outside of a label scope");
|
error("`.` has no value outside of a label scope");
|
||||||
@@ -296,6 +310,10 @@ Symbol *sym_FindScopedValidSymbol(std::string const &symName) {
|
|||||||
if (sym == localScopeSymbol && !localScope) {
|
if (sym == localScopeSymbol && !localScope) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
// `__SCOPE__` has no value outside of a section
|
||||||
|
if (sym == SCOPESymbol && !sect_GetSymbolSection()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return sym;
|
return sym;
|
||||||
}
|
}
|
||||||
@@ -683,6 +701,11 @@ void sym_Init(time_t now) {
|
|||||||
localScopeSymbol->data = localScopeCallback;
|
localScopeSymbol->data = localScopeCallback;
|
||||||
localScopeSymbol->isBuiltin = true;
|
localScopeSymbol->isBuiltin = true;
|
||||||
|
|
||||||
|
SCOPESymbol = &createSymbol("__SCOPE__"s);
|
||||||
|
SCOPESymbol->type = SYM_EQUS;
|
||||||
|
SCOPESymbol->data = SCOPECallback;
|
||||||
|
SCOPESymbol->isBuiltin = true;
|
||||||
|
|
||||||
RSSymbol = sym_AddVar("_RS"s, 0);
|
RSSymbol = sym_AddVar("_RS"s, 0);
|
||||||
RSSymbol->isBuiltin = true;
|
RSSymbol->isBuiltin = true;
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
|||||||
},
|
},
|
||||||
.paramWarnings = {
|
.paramWarnings = {
|
||||||
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||||
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
{WARNING_PURGE_1, WARNING_PURGE_2, 2},
|
||||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
|
||||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||||
},
|
},
|
||||||
.state = DiagnosticsState<WarningID>(),
|
.state = DiagnosticsState<WarningID>(),
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
153
src/cli.cpp
Normal file
153
src/cli.cpp
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
#include "cli.hpp"
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "extern/getopt.hpp"
|
||||||
|
#include "util.hpp" // isBlankSpace
|
||||||
|
|
||||||
|
using namespace std::literals;
|
||||||
|
|
||||||
|
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
||||||
|
static std::vector<size_t> readAtFile(
|
||||||
|
std::string const &path, std::vector<char> &argPool, void (*fatal)(char const *, ...)
|
||||||
|
) {
|
||||||
|
std::vector<size_t> argvOfs;
|
||||||
|
|
||||||
|
std::filebuf file;
|
||||||
|
if (!file.open(path, std::ios_base::in)) {
|
||||||
|
std::string msg = "Error reading at-file \""s + path + "\": " + strerror(errno);
|
||||||
|
fatal(msg.c_str());
|
||||||
|
return argvOfs; // Since we can't mark the `fatal` function pointer as [[noreturn]]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int c = file.sbumpc();
|
||||||
|
|
||||||
|
// First, discard any leading blank space
|
||||||
|
while (isBlankSpace(c)) {
|
||||||
|
c = file.sbumpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a comment, discard everything until EOL
|
||||||
|
if (c == '#') {
|
||||||
|
c = file.sbumpc();
|
||||||
|
while (c != EOF && !isNewline(c)) {
|
||||||
|
c = file.sbumpc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == EOF) {
|
||||||
|
return argvOfs;
|
||||||
|
} else if (isNewline(c)) {
|
||||||
|
continue; // Start processing the next line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alright, now we can parse the line
|
||||||
|
do {
|
||||||
|
argvOfs.push_back(argPool.size());
|
||||||
|
|
||||||
|
// Read one argument (until the next whitespace char).
|
||||||
|
// We know there is one because we already have its first character in `c`.
|
||||||
|
for (; c != EOF && !isWhitespace(c); c = file.sbumpc()) {
|
||||||
|
argPool.push_back(c);
|
||||||
|
}
|
||||||
|
argPool.push_back('\0');
|
||||||
|
|
||||||
|
// Discard blank space until the next argument (candidate)
|
||||||
|
while (isBlankSpace(c)) {
|
||||||
|
c = file.sbumpc();
|
||||||
|
}
|
||||||
|
} while (c != EOF && !isNewline(c)); // End if we reached EOL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cli_ParseArgs(
|
||||||
|
int argc,
|
||||||
|
char *argv[],
|
||||||
|
char const *shortOpts,
|
||||||
|
option const *longOpts,
|
||||||
|
void (*parseArg)(int, char *),
|
||||||
|
void (*fatal)(char const *, ...)
|
||||||
|
) {
|
||||||
|
struct AtFileStackEntry {
|
||||||
|
int parentInd; // Saved offset into parent argv
|
||||||
|
std::vector<char *> argv; // This context's arg pointer vec
|
||||||
|
|
||||||
|
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
||||||
|
: parentInd(parentInd_), argv(argv_) {}
|
||||||
|
};
|
||||||
|
std::vector<AtFileStackEntry> atFileStack;
|
||||||
|
|
||||||
|
int curArgc = argc;
|
||||||
|
char **curArgv = argv;
|
||||||
|
std::string optString = "-"s + shortOpts; // Request position arguments with a leading '-'
|
||||||
|
std::vector<std::vector<char>> argPools;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
char *atFileName = nullptr;
|
||||||
|
for (int ch;
|
||||||
|
(ch = musl_getopt_long_only(curArgc, curArgv, optString.c_str(), longOpts, nullptr))
|
||||||
|
!= -1;) {
|
||||||
|
if (ch == 1 && musl_optarg[0] == '@') {
|
||||||
|
atFileName = &musl_optarg[1];
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
parseArg(ch, musl_optarg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atFileName) {
|
||||||
|
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
|
||||||
|
// previous at-files may have generated to their own arg pools.
|
||||||
|
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
|
||||||
|
std::vector<char> &argPool = argPools.emplace_back();
|
||||||
|
|
||||||
|
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
||||||
|
AtFileStackEntry &stackEntry =
|
||||||
|
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
||||||
|
|
||||||
|
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
||||||
|
// that; so we must compute the offsets after the pool is fixed
|
||||||
|
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool, fatal);
|
||||||
|
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
||||||
|
for (size_t ofs : offsets) {
|
||||||
|
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
||||||
|
}
|
||||||
|
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
||||||
|
|
||||||
|
curArgc = stackEntry.argv.size() - 1;
|
||||||
|
curArgv = stackEntry.argv.data();
|
||||||
|
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
||||||
|
} else {
|
||||||
|
if (musl_optind != curArgc) {
|
||||||
|
// This happens if `--` is passed, process the remaining arg(s) as positional
|
||||||
|
assume(musl_optind < curArgc);
|
||||||
|
for (int i = musl_optind; i < curArgc; ++i) {
|
||||||
|
parseArg(1, argv[i]); // Positional argument
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop off the top stack entry, or end parsing if none
|
||||||
|
if (atFileStack.empty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// OK to restore `optind` directly, because `optpos` must be 0 right now.
|
||||||
|
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
|
||||||
|
musl_optind = atFileStack.back().parentInd;
|
||||||
|
atFileStack.pop_back();
|
||||||
|
if (atFileStack.empty()) {
|
||||||
|
curArgc = argc;
|
||||||
|
curArgv = argv;
|
||||||
|
} else {
|
||||||
|
std::vector<char *> &vec = atFileStack.back().argv;
|
||||||
|
curArgc = vec.size();
|
||||||
|
curArgv = vec.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,11 @@ static void
|
|||||||
|
|
||||||
if (options.title) {
|
if (options.title) {
|
||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0, 0x134, reinterpret_cast<uint8_t const *>(options.title), options.titleLen, "title"
|
rom0,
|
||||||
|
0x134,
|
||||||
|
reinterpret_cast<uint8_t const *>(options.title->c_str()),
|
||||||
|
options.titleLen,
|
||||||
|
"title"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +153,7 @@ static void
|
|||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x13F,
|
0x13F,
|
||||||
reinterpret_cast<uint8_t const *>(options.gameID),
|
reinterpret_cast<uint8_t const *>(options.gameID->c_str()),
|
||||||
options.gameIDLen,
|
options.gameIDLen,
|
||||||
"manufacturer code"
|
"manufacturer code"
|
||||||
);
|
);
|
||||||
@@ -163,7 +167,7 @@ static void
|
|||||||
overwriteBytes(
|
overwriteBytes(
|
||||||
rom0,
|
rom0,
|
||||||
0x144,
|
0x144,
|
||||||
reinterpret_cast<uint8_t const *>(options.newLicensee),
|
reinterpret_cast<uint8_t const *>(options.newLicensee->c_str()),
|
||||||
options.newLicenseeLen,
|
options.newLicenseeLen,
|
||||||
"new licensee code"
|
"new licensee code"
|
||||||
);
|
);
|
||||||
@@ -240,16 +244,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 +263,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 +340,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 +432,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 +471,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;
|
||||||
}
|
}
|
||||||
|
|||||||
406
src/fix/main.cpp
406
src/fix/main.cpp
@@ -3,19 +3,22 @@
|
|||||||
#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>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#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"
|
||||||
@@ -24,10 +27,16 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
|
// Flags which must be processed after the option parsing finishes
|
||||||
|
static struct LocalOptions {
|
||||||
|
std::optional<std::string> outputFileName; // -o
|
||||||
|
std::vector<std::string> inputFileNames; // <file>...
|
||||||
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
static char const *optstring = "Ccf:hi:jk:L:l:m:n:Oo:p:r:st:VvW:w";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -88,26 +97,192 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
static void parseByte(uint16_t &output, char name) {
|
static uint16_t parseByte(char const *input, char name) {
|
||||||
if (musl_optarg[0] == 0) {
|
if (std::optional<uint64_t> value = parseWholeNumber(input); !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);
|
return *value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (*endptr) {
|
static void parseArg(int ch, char *arg) {
|
||||||
fatal("Expected number as argument to option '-%c', got \"%s\"", name, musl_optarg);
|
switch (ch) {
|
||||||
} else if (value > 0xFF) {
|
case 'C':
|
||||||
fatal("Argument to option '-%c' is larger than 255: %lu", name, value);
|
case 'c':
|
||||||
|
options.model = ch == 'c' ? BOTH : CGB;
|
||||||
|
if (options.titleLen > 15) {
|
||||||
|
options.titleLen = 15;
|
||||||
|
assume(options.title.has_value());
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION, "Truncating title \"%s\" to 15 chars", options.title->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
options.fixSpec = 0;
|
||||||
|
while (*arg) {
|
||||||
|
switch (*arg) {
|
||||||
|
#define overrideSpec(cur, bad, curFlag, badFlag) \
|
||||||
|
case cur: \
|
||||||
|
if (options.fixSpec & badFlag) { \
|
||||||
|
warnx("'%c' overriding '%c' in fix spec", cur, bad); \
|
||||||
|
} \
|
||||||
|
options.fixSpec = (options.fixSpec & ~badFlag) | curFlag; \
|
||||||
|
break
|
||||||
|
#define overrideSpecPair(fix, fixFlag, trash, trashFlag) \
|
||||||
|
overrideSpec(fix, trash, fixFlag, trashFlag); \
|
||||||
|
overrideSpec(trash, fix, trashFlag, fixFlag)
|
||||||
|
overrideSpecPair('l', FIX_LOGO, 'L', TRASH_LOGO);
|
||||||
|
overrideSpecPair('h', FIX_HEADER_SUM, 'H', TRASH_HEADER_SUM);
|
||||||
|
overrideSpecPair('g', FIX_GLOBAL_SUM, 'G', TRASH_GLOBAL_SUM);
|
||||||
|
#undef overrideSpec
|
||||||
|
#undef overrideSpecPair
|
||||||
|
|
||||||
|
default:
|
||||||
|
fatal("Invalid character '%c' in fix spec", *arg);
|
||||||
|
}
|
||||||
|
++arg;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'h':
|
||||||
|
usage.printAndExit(0);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'i': {
|
||||||
|
options.gameID = arg;
|
||||||
|
size_t len = options.gameID->length();
|
||||||
|
if (len > 4) {
|
||||||
|
len = 4;
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION, "Truncating game ID \"%s\" to 4 chars", options.gameID->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
options.gameIDLen = len;
|
||||||
|
if (options.titleLen > 11) {
|
||||||
|
options.titleLen = 11;
|
||||||
|
assume(options.title.has_value());
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION, "Truncating title \"%s\" to 11 chars", options.title->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
output = value;
|
case 'j':
|
||||||
|
options.japanese = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'k': {
|
||||||
|
options.newLicensee = arg;
|
||||||
|
size_t len = options.newLicensee->length();
|
||||||
|
if (len > 2) {
|
||||||
|
len = 2;
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION,
|
||||||
|
"Truncating new licensee \"%s\" to 2 chars",
|
||||||
|
options.newLicensee->c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
options.newLicenseeLen = len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'L':
|
||||||
|
options.logoFilename = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'l':
|
||||||
|
options.oldLicensee = parseByte(arg, 'l');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'm':
|
||||||
|
options.cartridgeType = mbc_ParseName(arg, options.tpp1Rev[0], options.tpp1Rev[1]);
|
||||||
|
if (options.cartridgeType == ROM_RAM || options.cartridgeType == ROM_RAM_BATTERY) {
|
||||||
|
warning(WARNING_MBC, "MBC \"%s\" is under-specified and poorly supported", arg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
options.romVersion = parseByte(arg, 'n');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'O':
|
||||||
|
warning(WARNING_OBSOLETE, "'-O' is deprecated; use '-Wno-overwrite' instead");
|
||||||
|
warnings.processWarningFlag("no-overwrite");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'o':
|
||||||
|
localOptions.outputFileName = arg;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
options.padValue = parseByte(arg, 'p');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
options.ramSize = parseByte(arg, 'r');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
options.sgb = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 't': {
|
||||||
|
options.title = arg;
|
||||||
|
size_t len = options.title->length();
|
||||||
|
uint8_t maxLen = options.gameID ? 11 : options.model != DMG ? 15 : 16;
|
||||||
|
|
||||||
|
if (len > maxLen) {
|
||||||
|
len = maxLen;
|
||||||
|
warning(
|
||||||
|
WARNING_TRUNCATION,
|
||||||
|
"Truncating title \"%s\" to %u chars",
|
||||||
|
options.title->c_str(),
|
||||||
|
maxLen
|
||||||
|
);
|
||||||
|
}
|
||||||
|
options.titleLen = len;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
case 'V':
|
||||||
|
printf("rgbfix %s\n", get_package_version_string());
|
||||||
|
exit(0);
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
options.fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
||||||
|
break;
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
case 'W':
|
||||||
|
warnings.processWarningFlag(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'w':
|
||||||
|
warnings.state.warningsEnabled = false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0: // Long-only options
|
||||||
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
|
fatal("Invalid argument for option '--color'");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1: // Positional arguments
|
||||||
|
localOptions.inputFileNames.push_back(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
|
default:
|
||||||
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t const nintendoLogo[] = {
|
static uint8_t const nintendoLogo[] = {
|
||||||
@@ -119,22 +294,27 @@ static uint8_t const nintendoLogo[] = {
|
|||||||
static void initLogo() {
|
static void initLogo() {
|
||||||
if (options.logoFilename) {
|
if (options.logoFilename) {
|
||||||
FILE *logoFile;
|
FILE *logoFile;
|
||||||
if (strcmp(options.logoFilename, "-")) {
|
char const *logoFilename = options.logoFilename->c_str();
|
||||||
logoFile = fopen(options.logoFilename, "rb");
|
if (*options.logoFilename != "-") {
|
||||||
|
logoFile = fopen(logoFilename, "rb");
|
||||||
} else {
|
} else {
|
||||||
options.logoFilename = "<stdin>";
|
// LCOV_EXCL_START
|
||||||
|
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) {
|
||||||
fatal("Failed to open \"%s\" for reading: %s", options.logoFilename, strerror(errno));
|
// LCOV_EXCL_START
|
||||||
|
fatal("Failed to open \"%s\" for reading: %s", logoFilename, strerror(errno));
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
Defer closeLogo{[&] { fclose(logoFile); }};
|
Defer closeLogo{[&] { fclose(logoFile); }};
|
||||||
|
|
||||||
uint8_t logoBpp[sizeof(options.logo)];
|
uint8_t logoBpp[sizeof(options.logo)];
|
||||||
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
|
if (size_t nbRead = fread(logoBpp, 1, sizeof(logoBpp), logoFile);
|
||||||
nbRead != sizeof(options.logo) || fgetc(logoFile) != EOF || ferror(logoFile)) {
|
nbRead != sizeof(options.logo) || fgetc(logoFile) != EOF || ferror(logoFile)) {
|
||||||
fatal("\"%s\" is not %zu bytes", options.logoFilename, sizeof(options.logo));
|
fatal("\"%s\" is not %zu bytes", logoFilename, sizeof(options.logo));
|
||||||
}
|
}
|
||||||
auto highs = [&logoBpp](size_t i) {
|
auto highs = [&logoBpp](size_t i) {
|
||||||
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
|
return (logoBpp[i * 2] & 0xF0) | ((logoBpp[i * 2 + 1] & 0xF0) >> 4);
|
||||||
@@ -166,174 +346,7 @@ static void initLogo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
char const *outputFilename = nullptr;
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
|
|
||||||
// Parse CLI options
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
|
||||||
case 'C':
|
|
||||||
case 'c':
|
|
||||||
options.model = ch == 'c' ? BOTH : CGB;
|
|
||||||
if (options.titleLen > 15) {
|
|
||||||
options.titleLen = 15;
|
|
||||||
assume(options.title != nullptr);
|
|
||||||
warning(WARNING_TRUNCATION, "Truncating title \"%s\" to 15 chars", options.title);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f':
|
|
||||||
options.fixSpec = 0;
|
|
||||||
while (*musl_optarg) {
|
|
||||||
switch (*musl_optarg) {
|
|
||||||
#define OVERRIDE_SPEC(cur, bad, curFlag, badFlag) \
|
|
||||||
case STR(cur)[0]: \
|
|
||||||
if (options.fixSpec & badFlag) { \
|
|
||||||
warnx("'" STR(cur) "' overriding '" STR(bad) "' in fix spec"); \
|
|
||||||
} \
|
|
||||||
options.fixSpec = (options.fixSpec & ~badFlag) | curFlag; \
|
|
||||||
break
|
|
||||||
#define overrideSpecs(fix, fixFlag, trash, trashFlag) \
|
|
||||||
OVERRIDE_SPEC(fix, trash, fixFlag, trashFlag); \
|
|
||||||
OVERRIDE_SPEC(trash, fix, trashFlag, fixFlag)
|
|
||||||
overrideSpecs(l, FIX_LOGO, L, TRASH_LOGO);
|
|
||||||
overrideSpecs(h, FIX_HEADER_SUM, H, TRASH_HEADER_SUM);
|
|
||||||
overrideSpecs(g, FIX_GLOBAL_SUM, G, TRASH_GLOBAL_SUM);
|
|
||||||
#undef OVERRIDE_SPEC
|
|
||||||
#undef overrideSpecs
|
|
||||||
|
|
||||||
default:
|
|
||||||
fatal("Invalid character '%c' in fix spec", *musl_optarg);
|
|
||||||
}
|
|
||||||
++musl_optarg;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'h':
|
|
||||||
usage.printAndExit(0);
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'i': {
|
|
||||||
options.gameID = musl_optarg;
|
|
||||||
size_t len = strlen(options.gameID);
|
|
||||||
if (len > 4) {
|
|
||||||
len = 4;
|
|
||||||
warning(WARNING_TRUNCATION, "Truncating game ID \"%s\" to 4 chars", options.gameID);
|
|
||||||
}
|
|
||||||
options.gameIDLen = len;
|
|
||||||
if (options.titleLen > 11) {
|
|
||||||
options.titleLen = 11;
|
|
||||||
assume(options.title != nullptr);
|
|
||||||
warning(WARNING_TRUNCATION, "Truncating title \"%s\" to 11 chars", options.title);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'j':
|
|
||||||
options.japanese = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'k': {
|
|
||||||
options.newLicensee = musl_optarg;
|
|
||||||
size_t len = strlen(options.newLicensee);
|
|
||||||
if (len > 2) {
|
|
||||||
len = 2;
|
|
||||||
warning(
|
|
||||||
WARNING_TRUNCATION,
|
|
||||||
"Truncating new licensee \"%s\" to 2 chars",
|
|
||||||
options.newLicensee
|
|
||||||
);
|
|
||||||
}
|
|
||||||
options.newLicenseeLen = len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'L':
|
|
||||||
options.logoFilename = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'l':
|
|
||||||
parseByte(options.oldLicensee, 'l');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'm':
|
|
||||||
options.cartridgeType =
|
|
||||||
mbc_ParseName(musl_optarg, options.tpp1Rev[0], options.tpp1Rev[1]);
|
|
||||||
if (options.cartridgeType == ROM_RAM || options.cartridgeType == ROM_RAM_BATTERY) {
|
|
||||||
warning(
|
|
||||||
WARNING_MBC, "MBC \"%s\" is under-specified and poorly supported", musl_optarg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'n':
|
|
||||||
parseByte(options.romVersion, 'n');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'O':
|
|
||||||
warning(WARNING_OBSOLETE, "'-O' is deprecated; use '-Wno-overwrite' instead");
|
|
||||||
warnings.processWarningFlag("no-overwrite");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'o':
|
|
||||||
outputFilename = musl_optarg;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
parseByte(options.padValue, 'p');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'r':
|
|
||||||
parseByte(options.ramSize, 'r');
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's':
|
|
||||||
options.sgb = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 't': {
|
|
||||||
options.title = musl_optarg;
|
|
||||||
size_t len = strlen(options.title);
|
|
||||||
uint8_t maxLen = options.gameID ? 11 : options.model != DMG ? 15 : 16;
|
|
||||||
|
|
||||||
if (len > maxLen) {
|
|
||||||
len = maxLen;
|
|
||||||
warning(
|
|
||||||
WARNING_TRUNCATION, "Truncating title \"%s\" to %u chars", options.title, maxLen
|
|
||||||
);
|
|
||||||
}
|
|
||||||
options.titleLen = len;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
case 'V':
|
|
||||||
printf("rgbfix %s\n", get_package_version_string());
|
|
||||||
exit(0);
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
options.fixSpec = FIX_LOGO | FIX_HEADER_SUM | FIX_GLOBAL_SUM;
|
|
||||||
break;
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
case 'W':
|
|
||||||
warnings.processWarningFlag(musl_optarg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'w':
|
|
||||||
warnings.state.warningsEnabled = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0: // Long-only options
|
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
|
||||||
fatal("Invalid argument for option '--color'");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
if ((options.cartridgeType & 0xFF00) == TPP1 && !options.japanese) {
|
||||||
warning(
|
warning(
|
||||||
@@ -385,19 +398,20 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
initLogo();
|
initLogo();
|
||||||
|
|
||||||
argv += musl_optind;
|
if (localOptions.inputFileNames.empty()) {
|
||||||
if (!*argv) {
|
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputFilename && argc != musl_optind + 1) {
|
if (localOptions.outputFileName && localOptions.inputFileNames.size() != 1) {
|
||||||
usage.printAndExit("If '-o' is set then only a single input file may be specified");
|
usage.printAndExit("If '-o' is set then only a single input file may be specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char const *outputFileName =
|
||||||
|
localOptions.outputFileName ? localOptions.outputFileName->c_str() : nullptr;
|
||||||
bool failed = warnings.nbErrors > 0;
|
bool failed = warnings.nbErrors > 0;
|
||||||
do {
|
for (std::string const &inputFileName : localOptions.inputFileNames) {
|
||||||
failed |= fix_ProcessFile(*argv, outputFilename);
|
failed |= fix_ProcessFile(inputFileName.c_str(), outputFileName);
|
||||||
} while (*++argv);
|
}
|
||||||
|
|
||||||
return failed;
|
return failed;
|
||||||
}
|
}
|
||||||
|
|||||||
197
src/fix/mbc.cpp
197
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"
|
||||||
|
|
||||||
#include "fix/warning.hpp"
|
#include "fix/warning.hpp"
|
||||||
|
|
||||||
@@ -96,87 +98,57 @@ 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 (c == '_') {
|
||||||
c = c - 'a' + 'A';
|
return ' '; // Treat underscores as spaces
|
||||||
} else if (c == '_') { // Treat underscores as spaces
|
|
||||||
c = ' ';
|
|
||||||
}
|
}
|
||||||
return c;
|
return toUpper(c); // Uppercase for comparison with `mbc_Name`s
|
||||||
}
|
|
||||||
|
|
||||||
static bool readMBCSlice(char const *&name, char const *expected) {
|
|
||||||
while (*expected) {
|
|
||||||
// If `name` is too short, the character will be '\0' and this will return `false`
|
|
||||||
if (normalizeMBCChar(*name++) != *expected++) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void fatalUnknownMBC(char const *fullName) {
|
static void fatalUnknownMBC(char const *name) {
|
||||||
fatal("Unknown MBC \"%s\"\n%s", fullName, acceptedMBCNames);
|
fatal("Unknown MBC \"%s\"\n%s", name, acceptedMBCNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void fatalWrongMBCFeatures(char const *fullName) {
|
static void fatalWrongMBCFeatures(char const *name) {
|
||||||
fatal("Features incompatible with MBC (\"%s\")\n%s", fullName, acceptedMBCNames);
|
fatal("Features incompatible with MBC (\"%s\")\n%s", name, 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 +168,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 +188,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 +195,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 +217,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 +253,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 +272,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 +304,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 +318,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 +340,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 +348,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 +372,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 +390,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 +400,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);
|
||||||
|
|||||||
486
src/gfx/main.cpp
486
src/gfx/main.cpp
@@ -2,21 +2,20 @@
|
|||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <ctype.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>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "platform.hpp"
|
#include "platform.hpp"
|
||||||
@@ -36,20 +35,23 @@ using namespace std::literals::string_view_literals;
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
|
// Flags which must be processed after the option parsing finishes
|
||||||
static struct LocalOptions {
|
static struct LocalOptions {
|
||||||
char const *externalPalSpec;
|
std::optional<std::string> externalPalSpec; // -c
|
||||||
bool autoAttrmap;
|
bool autoAttrmap; // -A
|
||||||
bool autoTilemap;
|
bool autoTilemap; // -T
|
||||||
bool autoPalettes;
|
bool autoPalettes; // -P
|
||||||
bool autoPalmap;
|
bool autoPalmap; // -Q
|
||||||
bool groupOutputs;
|
bool groupOutputs; // -O
|
||||||
bool reverse;
|
bool reverse; // -r
|
||||||
|
|
||||||
|
bool autoAny() const { return autoAttrmap || autoTilemap || autoPalettes || autoPalmap; }
|
||||||
} localOptions;
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "-Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
static char const *optstring = "Aa:B:b:Cc:d:hi:L:l:mN:n:Oo:Pp:Qq:r:s:Tt:U:uVvW:wXx:YZ";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -120,148 +122,24 @@ 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;
|
|
||||||
++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;
|
|
||||||
}
|
|
||||||
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);
|
error("%s: the number is too large!", errPrefix);
|
||||||
return errVal;
|
return errVal;
|
||||||
|
} else {
|
||||||
|
return *number;
|
||||||
}
|
}
|
||||||
} 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");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registerInput(char const *arg) {
|
static void parseArg(int ch, char *arg) {
|
||||||
if (!options.input.empty()) {
|
char const *argPtr = arg; // Make a copy for scanning
|
||||||
usage.printAndExit(
|
|
||||||
"Input image specified more than once! (first \"%s\", then \"%s\")",
|
|
||||||
options.input.c_str(),
|
|
||||||
arg
|
|
||||||
);
|
|
||||||
} else if (arg[0] == '\0') { // Empty input path
|
|
||||||
usage.printAndExit("Input image path cannot be empty");
|
|
||||||
} else {
|
|
||||||
options.input = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Turn an at-file's contents into an argv that `getopt` can handle, appending them to `argPool`.
|
|
||||||
static std::vector<size_t> readAtFile(std::string const &path, std::vector<char> &argPool) {
|
|
||||||
File file;
|
|
||||||
if (!file.open(path, std::ios_base::in)) {
|
|
||||||
fatal("Error reading at-file \"%s\": %s", file.c_str(path), strerror(errno));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::vector<size_t> argvOfs;;) {
|
|
||||||
int c = file->sbumpc();
|
|
||||||
|
|
||||||
// First, discard any leading blank space
|
|
||||||
while (isBlankSpace(c)) {
|
|
||||||
c = file->sbumpc();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a comment, discard everything until EOL
|
|
||||||
if (c == '#') {
|
|
||||||
c = file->sbumpc();
|
|
||||||
while (c != EOF && !isNewline(c)) {
|
|
||||||
c = file->sbumpc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == EOF) {
|
|
||||||
return argvOfs;
|
|
||||||
} else if (isNewline(c)) {
|
|
||||||
continue; // Start processing the next line
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alright, now we can parse the line
|
|
||||||
do {
|
|
||||||
argvOfs.push_back(argPool.size());
|
|
||||||
|
|
||||||
// Read one argument (until the next whitespace char).
|
|
||||||
// We know there is one because we already have its first character in `c`.
|
|
||||||
for (; c != EOF && !isWhitespace(c); c = file->sbumpc()) {
|
|
||||||
argPool.push_back(c);
|
|
||||||
}
|
|
||||||
argPool.push_back('\0');
|
|
||||||
|
|
||||||
// Discard blank space until the next argument (candidate)
|
|
||||||
while (isBlankSpace(c)) {
|
|
||||||
c = file->sbumpc();
|
|
||||||
}
|
|
||||||
} while (c != EOF && !isNewline(c)); // End if we reached EOL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses an arg vector, modifying `options` and `localOptions` as options are read.
|
|
||||||
// The `localOptions` struct is for flags which must be processed after the option parsing finishes.
|
|
||||||
// Returns `nullptr` if the vector was fully parsed, or a pointer (which is part of the arg vector)
|
|
||||||
// to an "at-file" path if one is encountered.
|
|
||||||
static char *parseArgv(int argc, char *argv[]) {
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
char *arg = musl_optarg; // Make a copy for scanning
|
|
||||||
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -273,45 +151,39 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.attrmap.empty()) {
|
if (!options.attrmap.empty()) {
|
||||||
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
|
warnx("Overriding attrmap file \"%s\"", options.attrmap.c_str());
|
||||||
}
|
}
|
||||||
options.attrmap = musl_optarg;
|
options.attrmap = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'B':
|
case 'B':
|
||||||
parseBackgroundPalSpec(musl_optarg);
|
parseBackgroundPalSpec(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'b': {
|
case 'b': {
|
||||||
uint16_t number = parseNumber(arg, "Bank 0 base tile ID", 0);
|
uint16_t number = readNumber(argPtr, "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 {
|
||||||
options.baseTileIDs[0] = number;
|
options.baseTileIDs[0] = number;
|
||||||
}
|
}
|
||||||
if (*arg == '\0') {
|
if (*argPtr == '\0') {
|
||||||
options.baseTileIDs[1] = 0;
|
options.baseTileIDs[1] = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error(
|
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg; // Skip comma
|
++argPtr; // Skip comma
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
number = parseNumber(arg, "Bank 1 base tile ID", 0);
|
number = readNumber(argPtr, "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 {
|
||||||
options.baseTileIDs[1] = number;
|
options.baseTileIDs[1] = number;
|
||||||
}
|
}
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error(
|
error("Base tile IDs must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Base tile IDs must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -322,31 +194,31 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'c':
|
case 'c':
|
||||||
localOptions.externalPalSpec = nullptr; // Allow overriding a previous pal spec
|
localOptions.externalPalSpec = std::nullopt; // Allow overriding a previous pal spec
|
||||||
if (musl_optarg[0] == '#') {
|
if (arg[0] == '#') {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
parseInlinePalSpec(musl_optarg);
|
parseInlinePalSpec(arg);
|
||||||
} else if (strcasecmp(musl_optarg, "embedded") == 0) {
|
} else if (strcasecmp(arg, "embedded") == 0) {
|
||||||
// Use PLTE, error out if missing
|
// Use PLTE, error out if missing
|
||||||
options.palSpecType = Options::EMBEDDED;
|
options.palSpecType = Options::EMBEDDED;
|
||||||
} else if (strcasecmp(musl_optarg, "auto") == 0) {
|
} else if (strcasecmp(arg, "auto") == 0) {
|
||||||
options.palSpecType = Options::NO_SPEC;
|
options.palSpecType = Options::NO_SPEC;
|
||||||
} else if (strcasecmp(musl_optarg, "dmg") == 0) {
|
} else if (strcasecmp(arg, "dmg") == 0) {
|
||||||
options.palSpecType = Options::DMG;
|
options.palSpecType = Options::DMG;
|
||||||
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
parseDmgPalSpec(0xE4); // Same darkest-first order as `sortGrayscale`
|
||||||
} else if (strncasecmp(musl_optarg, "dmg=", literal_strlen("dmg=")) == 0) {
|
} else if (strncasecmp(arg, "dmg=", literal_strlen("dmg=")) == 0) {
|
||||||
options.palSpecType = Options::DMG;
|
options.palSpecType = Options::DMG;
|
||||||
parseDmgPalSpec(&musl_optarg[literal_strlen("dmg=")]);
|
parseDmgPalSpec(&arg[literal_strlen("dmg=")]);
|
||||||
} else {
|
} else {
|
||||||
options.palSpecType = Options::EXPLICIT;
|
options.palSpecType = Options::EXPLICIT;
|
||||||
localOptions.externalPalSpec = musl_optarg;
|
localOptions.externalPalSpec = arg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'd':
|
case 'd':
|
||||||
options.bitDepth = parseNumber(arg, "Bit depth", 2);
|
options.bitDepth = readNumber(argPtr, "Bit depth", 2);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\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\"", arg);
|
||||||
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
} else if (options.bitDepth != 1 && options.bitDepth != 2) {
|
||||||
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
|
error("Bit depth must be 1 or 2, not %" PRIu8, options.bitDepth);
|
||||||
options.bitDepth = 2;
|
options.bitDepth = 2;
|
||||||
@@ -362,54 +234,54 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.inputTileset.empty()) {
|
if (!options.inputTileset.empty()) {
|
||||||
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
|
warnx("Overriding input tileset file \"%s\"", options.inputTileset.c_str());
|
||||||
}
|
}
|
||||||
options.inputTileset = musl_optarg;
|
options.inputTileset = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'L':
|
case 'L':
|
||||||
options.inputSlice.left = parseNumber(arg, "Input slice left coordinate");
|
options.inputSlice.left = readNumber(argPtr, "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;
|
||||||
}
|
}
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error("Missing comma after left coordinate in \"%s\"", musl_optarg);
|
error("Missing comma after left coordinate in \"%s\"", arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg;
|
++argPtr;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.inputSlice.top = parseNumber(arg, "Input slice upper coordinate");
|
options.inputSlice.top = readNumber(argPtr, "Input slice upper coordinate");
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ':') {
|
if (*argPtr != ':') {
|
||||||
error("Missing colon after upper coordinate in \"%s\"", musl_optarg);
|
error("Missing colon after upper coordinate in \"%s\"", arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg;
|
++argPtr;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.inputSlice.width = parseNumber(arg, "Input slice width");
|
options.inputSlice.width = readNumber(argPtr, "Input slice width");
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
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!");
|
||||||
}
|
}
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error("Missing comma after width in \"%s\"", musl_optarg);
|
error("Missing comma after width in \"%s\"", arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg;
|
++argPtr;
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.inputSlice.height = parseNumber(arg, "Input slice height");
|
options.inputSlice.height = readNumber(argPtr, "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!");
|
||||||
}
|
}
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error("Unexpected extra characters after slice spec in \"%s\"", musl_optarg);
|
error("Unexpected extra characters after slice spec in \"%s\"", arg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'l': {
|
case 'l': {
|
||||||
uint16_t number = parseNumber(arg, "Base palette ID", 0);
|
uint16_t number = readNumber(argPtr, "Base palette ID", 0);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\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\"", arg);
|
||||||
} else if (number >= 256) {
|
} else if (number >= 256) {
|
||||||
error("Base palette ID must be below 256");
|
error("Base palette ID must be below 256");
|
||||||
} else {
|
} else {
|
||||||
@@ -428,41 +300,35 @@ 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(argPtr, "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");
|
||||||
}
|
}
|
||||||
if (*arg == '\0') {
|
if (*argPtr == '\0') {
|
||||||
options.maxNbTiles[1] = 0;
|
options.maxNbTiles[1] = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
if (*arg != ',') {
|
if (*argPtr != ',') {
|
||||||
error(
|
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
++arg; // Skip comma
|
++argPtr; // Skip comma
|
||||||
skipBlankSpace(arg);
|
skipBlankSpace(argPtr);
|
||||||
options.maxNbTiles[1] = parseNumber(arg, "Number of tiles in bank 1", 256);
|
options.maxNbTiles[1] = readNumber(argPtr, "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");
|
||||||
}
|
}
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error(
|
error("Bank capacity must be one or two comma-separated numbers, not \"%s\"", arg);
|
||||||
"Bank capacity must be one or two comma-separated numbers, not \"%s\"",
|
|
||||||
musl_optarg
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'n': {
|
case 'n': {
|
||||||
uint16_t number = parseNumber(arg, "Number of palettes", 256);
|
uint16_t number = readNumber(argPtr, "Number of palettes", 256);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\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\"", arg);
|
||||||
}
|
}
|
||||||
if (number > 256) {
|
if (number > 256) {
|
||||||
error("Number of palettes ('-n') must not exceed 256!");
|
error("Number of palettes ('-n') must not exceed 256!");
|
||||||
@@ -482,7 +348,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.output.empty()) {
|
if (!options.output.empty()) {
|
||||||
warnx("Overriding tile data file %s", options.output.c_str());
|
warnx("Overriding tile data file %s", options.output.c_str());
|
||||||
}
|
}
|
||||||
options.output = musl_optarg;
|
options.output = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'P':
|
case 'P':
|
||||||
@@ -494,7 +360,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.palettes.empty()) {
|
if (!options.palettes.empty()) {
|
||||||
warnx("Overriding palettes file %s", options.palettes.c_str());
|
warnx("Overriding palettes file %s", options.palettes.c_str());
|
||||||
}
|
}
|
||||||
options.palettes = musl_optarg;
|
options.palettes = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'Q':
|
case 'Q':
|
||||||
@@ -506,23 +372,21 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.palmap.empty()) {
|
if (!options.palmap.empty()) {
|
||||||
warnx("Overriding palette map file %s", options.palmap.c_str());
|
warnx("Overriding palette map file %s", options.palmap.c_str());
|
||||||
}
|
}
|
||||||
options.palmap = musl_optarg;
|
options.palmap = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'r':
|
case 'r':
|
||||||
localOptions.reverse = true;
|
localOptions.reverse = true;
|
||||||
options.reversedWidth = parseNumber(arg, "Reversed image stride");
|
options.reversedWidth = readNumber(argPtr, "Reversed image stride");
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\0') {
|
||||||
error(
|
error("Reversed image stride ('-r') must be a valid number, not \"%s\"", arg);
|
||||||
"Reversed image stride ('-r') must be a valid number, not \"%s\"", musl_optarg
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
options.nbColorsPerPal = parseNumber(arg, "Number of colors per palette", 4);
|
options.nbColorsPerPal = readNumber(argPtr, "Number of colors per palette", 4);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\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\"", arg);
|
||||||
}
|
}
|
||||||
if (options.nbColorsPerPal > 4) {
|
if (options.nbColorsPerPal > 4) {
|
||||||
error("Palette size ('-s') must not exceed 4!");
|
error("Palette size ('-s') must not exceed 4!");
|
||||||
@@ -540,7 +404,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
if (!options.tilemap.empty()) {
|
if (!options.tilemap.empty()) {
|
||||||
warnx("Overriding tilemap file %s", options.tilemap.c_str());
|
warnx("Overriding tilemap file %s", options.tilemap.c_str());
|
||||||
}
|
}
|
||||||
options.tilemap = musl_optarg;
|
options.tilemap = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
@@ -554,7 +418,7 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
warnings.processWarningFlag(musl_optarg);
|
warnings.processWarningFlag(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -562,9 +426,9 @@ 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(argPtr, "Number of tiles to trim", 0);
|
||||||
if (*arg != '\0') {
|
if (*argPtr != '\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\"", arg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -583,28 +447,32 @@ static char *parseArgv(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0: // Long-only options
|
case 0: // Long-only options
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
fatal("Invalid argument for option '--color'");
|
fatal("Invalid argument for option '--color'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1: // Positional argument, requested by leading `-` in opt string
|
case 1: // Positional argument
|
||||||
if (musl_optarg[0] == '@') {
|
if (!options.input.empty()) {
|
||||||
// Instruct the caller to process that at-file
|
usage.printAndExit(
|
||||||
return &musl_optarg[1];
|
"Input image specified more than once! (first \"%s\", then \"%s\")",
|
||||||
|
options.input.c_str(),
|
||||||
|
arg
|
||||||
|
);
|
||||||
|
} else if (arg[0] == '\0') { // Empty input path
|
||||||
|
usage.printAndExit("Input image path cannot be empty");
|
||||||
} else {
|
} else {
|
||||||
registerInput(musl_optarg);
|
options.input = arg;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr; // Done processing this argv
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
// LCOV_EXCL_START
|
||||||
static void verboseOutputConfig() {
|
static void verboseOutputConfig() {
|
||||||
if (!checkVerbosity(VERB_CONFIG)) {
|
if (!checkVerbosity(VERB_CONFIG)) {
|
||||||
@@ -685,7 +553,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
|
||||||
@@ -770,70 +639,7 @@ static void replaceExtension(std::string &path, char const *extension) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
struct AtFileStackEntry {
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
int parentInd; // Saved offset into parent argv
|
|
||||||
std::vector<char *> argv; // This context's arg pointer vec
|
|
||||||
|
|
||||||
AtFileStackEntry(int parentInd_, std::vector<char *> argv_)
|
|
||||||
: parentInd(parentInd_), argv(argv_) {}
|
|
||||||
};
|
|
||||||
std::vector<AtFileStackEntry> atFileStack;
|
|
||||||
|
|
||||||
// Parse CLI options
|
|
||||||
int curArgc = argc;
|
|
||||||
char **curArgv = argv;
|
|
||||||
std::vector<std::vector<char>> argPools;
|
|
||||||
for (;;) {
|
|
||||||
char *atFileName = parseArgv(curArgc, curArgv);
|
|
||||||
if (atFileName) {
|
|
||||||
// We need to allocate a new arg pool for each at-file, so as not to invalidate pointers
|
|
||||||
// previous at-files may have generated to their own arg pools.
|
|
||||||
// But for the same reason, the arg pool must also outlive the at-file's stack entry!
|
|
||||||
std::vector<char> &argPool = argPools.emplace_back();
|
|
||||||
|
|
||||||
// Copy `argv[0]` for error reporting, and because option parsing skips it
|
|
||||||
AtFileStackEntry &stackEntry =
|
|
||||||
atFileStack.emplace_back(musl_optind, std::vector{atFileName});
|
|
||||||
// It would be nice to compute the char pointers on the fly, but reallocs don't allow
|
|
||||||
// that; so we must compute the offsets after the pool is fixed
|
|
||||||
std::vector<size_t> offsets = readAtFile(&musl_optarg[1], argPool);
|
|
||||||
stackEntry.argv.reserve(offsets.size() + 2); // Avoid a bunch of reallocs
|
|
||||||
for (size_t ofs : offsets) {
|
|
||||||
stackEntry.argv.push_back(&argPool.data()[ofs]);
|
|
||||||
}
|
|
||||||
stackEntry.argv.push_back(nullptr); // Don't forget the arg vector terminator!
|
|
||||||
|
|
||||||
curArgc = stackEntry.argv.size() - 1;
|
|
||||||
curArgv = stackEntry.argv.data();
|
|
||||||
musl_optind = 1; // Don't use 0 because we're not scanning a different argv per se
|
|
||||||
continue; // Begin scanning that arg vector
|
|
||||||
}
|
|
||||||
|
|
||||||
if (musl_optind != curArgc) {
|
|
||||||
// This happens if `--` is passed, process the remaining arg(s) as positional
|
|
||||||
assume(musl_optind < curArgc);
|
|
||||||
for (int i = musl_optind; i < curArgc; ++i) {
|
|
||||||
registerInput(argv[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop off the top stack entry, or end parsing if none
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// OK to restore `optind` directly, because `optpos` must be 0 right now.
|
|
||||||
// (Providing 0 would be a "proper" reset, but we want to resume parsing)
|
|
||||||
musl_optind = atFileStack.back().parentInd;
|
|
||||||
atFileStack.pop_back();
|
|
||||||
if (atFileStack.empty()) {
|
|
||||||
curArgc = argc;
|
|
||||||
curArgv = argv;
|
|
||||||
} else {
|
|
||||||
std::vector<char *> &vec = atFileStack.back().argv;
|
|
||||||
curArgc = vec.size();
|
|
||||||
curArgv = vec.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.nbColorsPerPal == 0) {
|
if (options.nbColorsPerPal == 0) {
|
||||||
options.nbColorsPerPal = 1u << options.bitDepth;
|
options.nbColorsPerPal = 1u << options.bitDepth;
|
||||||
@@ -846,6 +652,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;
|
||||||
@@ -866,7 +682,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
// Execute deferred external pal spec parsing, now that all other params are known
|
// Execute deferred external pal spec parsing, now that all other params are known
|
||||||
if (localOptions.externalPalSpec) {
|
if (localOptions.externalPalSpec) {
|
||||||
parseExternalPalSpec(localOptions.externalPalSpec);
|
parseExternalPalSpec(localOptions.externalPalSpec->c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(); // LCOV_EXCL_LINE
|
verboseOutputConfig(); // LCOV_EXCL_LINE
|
||||||
@@ -890,51 +706,3 @@ int main(int argc, char *argv[]) {
|
|||||||
requireZeroErrors();
|
requireZeroErrors();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Palette::addColor(uint16_t color) {
|
|
||||||
for (size_t i = 0; true; ++i) {
|
|
||||||
assume(i < colors.size()); // The packing should guarantee this
|
|
||||||
if (colors[i] == color) { // The color is already present
|
|
||||||
break;
|
|
||||||
} else if (colors[i] == UINT16_MAX) { // Empty slot
|
|
||||||
colors[i] = color;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns the ID of the color in the palette, or `size()` if the color is not in
|
|
||||||
uint8_t Palette::indexOf(uint16_t color) const {
|
|
||||||
return color == Rgba::transparent
|
|
||||||
? 0
|
|
||||||
: std::find(begin(), colors.end(), color) - begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::begin() -> decltype(colors)::iterator {
|
|
||||||
// Skip the first slot if reserved for transparency
|
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::end() -> decltype(colors)::iterator {
|
|
||||||
// Return an iterator pointing past the last non-empty element.
|
|
||||||
// Since the palette may contain gaps, we must scan from the end.
|
|
||||||
return std::find_if(
|
|
||||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
|
||||||
).base();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
|
||||||
// Skip the first slot if reserved for transparency
|
|
||||||
return colors.begin() + options.hasTransparentPixels;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Palette::end() const -> decltype(colors)::const_iterator {
|
|
||||||
// Same as the non-const end().
|
|
||||||
return std::find_if(
|
|
||||||
colors.rbegin(), colors.rend(), [](uint16_t c) { return c != UINT16_MAX; }
|
|
||||||
).base();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t Palette::size() const {
|
|
||||||
return end() - colors.begin();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 IteratorT, 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{};
|
IteratorT _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()) {
|
||||||
|
_assigned.emplace_back(attrs); // We are full, use a new slot
|
||||||
|
} else {
|
||||||
|
freeSlot->emplace(attrs); // Reuse a free slot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (freeSlot == _assigned.end()) { // We are full, use a new slot
|
|
||||||
_assigned.emplace_back(std::forward<Ts>(args)...);
|
|
||||||
} else { // 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 IteratorT>
|
||||||
static void addUniqueColors(
|
static void addUniqueColors(
|
||||||
std::unordered_set<uint16_t> &colors,
|
std::unordered_set<uint16_t> &colors,
|
||||||
Iter iter,
|
IteratorT iter,
|
||||||
Iter const &end,
|
IteratorT const &end,
|
||||||
std::vector<ColorSet> const &colorSets
|
std::vector<ColorSet> const &colorSets
|
||||||
) {
|
) {
|
||||||
for (; iter != end; ++iter) {
|
for (; iter != end; ++iter) {
|
||||||
@@ -171,25 +176,32 @@ private:
|
|||||||
colors.insert(RANGE(colorSet));
|
colors.insert(RANGE(colorSet));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This function should stay private because it returns a reference to a unique object
|
|
||||||
std::unordered_set<uint16_t> &uniqueColors() const {
|
|
||||||
// We check for *distinct* colors by stuffing them into a `set`; this should be
|
|
||||||
// faster than "back-checking" on every element (O(n^2))
|
|
||||||
static std::unordered_set<uint16_t> colors;
|
|
||||||
|
|
||||||
colors.clear();
|
public:
|
||||||
|
// Returns the set of distinct colors
|
||||||
|
std::unordered_set<uint16_t> uniqueColors() const {
|
||||||
|
std::unordered_set<uint16_t> colors;
|
||||||
addUniqueColors(colors, RANGE(*this), *_colorSets);
|
addUniqueColors(colors, RANGE(*this), *_colorSets);
|
||||||
return colors;
|
return colors;
|
||||||
}
|
}
|
||||||
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));
|
||||||
return colors.size() <= options.maxOpaqueColors();
|
return colors.size() <= options.maxOpaqueColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Counts how many of our color sets this color also belongs to
|
||||||
|
uint32_t multiplicity(uint16_t color) const {
|
||||||
|
return std::count_if(RANGE(*this), [this, &color](ColorSetAttrs const &attrs) {
|
||||||
|
ColorSet const &pal = (*_colorSets)[attrs.colorSetIndex];
|
||||||
|
return std::find(RANGE(pal), color) != pal.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// The `relSizeOf` method below should compute the sum, for each color in `colorSet`, of
|
// The `relSizeOf` method below should compute the sum, for each color in `colorSet`, of
|
||||||
// the reciprocal of the "multiplicity" of the color across "our" color sets.
|
// the reciprocal of the "multiplicity" of the color across "our" color sets.
|
||||||
// However, literally computing the reciprocals would involve floating-point division, which
|
// However, literally computing the reciprocals would involve floating-point division, which
|
||||||
@@ -209,41 +221,36 @@ public:
|
|||||||
// Computes the "relative size" of a color set on this palette;
|
// Computes the "relative size" of a color set on this palette;
|
||||||
// it's a measure of how much this color set would "cost" to introduce.
|
// it's a measure of how much this color set would "cost" to introduce.
|
||||||
uint32_t relSizeOf(ColorSet const &colorSet) const {
|
uint32_t relSizeOf(ColorSet const &colorSet) const {
|
||||||
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
|
|
||||||
|
|
||||||
uint32_t relSize = 0;
|
uint32_t relSize = 0;
|
||||||
for (uint16_t color : colorSet) {
|
for (uint16_t color : colorSet) {
|
||||||
// How many of our color sets does this color also belong to?
|
uint32_t n = multiplicity(color);
|
||||||
uint32_t multiplicity =
|
|
||||||
std::count_if(RANGE(*this), [this, &color](ColorSetAttrs const &attrs) {
|
|
||||||
ColorSet const &pal = (*_colorSets)[attrs.colorSetIndex];
|
|
||||||
return std::find(RANGE(pal), color) != pal.end();
|
|
||||||
});
|
|
||||||
// We increase the denominator by 1 here; the reference code does this,
|
// We increase the denominator by 1 here; the reference code does this,
|
||||||
// but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0
|
// but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0
|
||||||
// (that is, if the color is not found in any color set), and adding 1 still seems
|
// (that is, if the color is not found in any color set), and adding 1 still seems
|
||||||
// to preserve the paper's reasoning.
|
// to preserve the paper's reasoning.
|
||||||
//
|
//
|
||||||
// The scale factor should ensure integer divisions only.
|
// The scale factor should ensure integer divisions only.
|
||||||
assume(scaleFactor % (multiplicity + 1) == 0);
|
assume(scaleFactor % (n + 1) == 0);
|
||||||
relSize += scaleFactor / (multiplicity + 1);
|
relSize += scaleFactor / (n + 1);
|
||||||
}
|
}
|
||||||
return relSize;
|
return relSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 IteratorT>
|
||||||
size_t combinedVolume(Iter &&begin, Iter const &end, std::vector<ColorSet> const &colorSets)
|
size_t combinedVolume(
|
||||||
const {
|
IteratorT &&begin, IteratorT const &end, std::vector<ColorSet> const &colorSets
|
||||||
std::unordered_set<uint16_t> &colors = uniqueColors();
|
) const {
|
||||||
addUniqueColors(colors, std::forward<Iter>(begin), end, colorSets);
|
std::unordered_set<uint16_t> colors = uniqueColors();
|
||||||
|
addUniqueColors(colors, std::forward<IteratorT>(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 IteratorT>
|
||||||
size_t combinedVolume(Iter &&begin, Iter &&end) const {
|
size_t combinedVolume(IteratorT &&begin, IteratorT &&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<IteratorT>(begin), std::forward<IteratorT>(end));
|
||||||
return colors.size();
|
return colors.size();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -302,7 +309,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 +323,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 +364,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 +532,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 +548,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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/palette.hpp"
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal) {
|
void sortIndexed(std::vector<Palette> &palettes, std::vector<Rgba> const &embPal) {
|
||||||
|
|||||||
@@ -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]]
|
||||||
@@ -216,14 +188,6 @@ static bool readLine(std::filebuf &file, std::string &buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#define requireLine(kind, filename, file, buffer) \
|
|
||||||
do { \
|
|
||||||
if (!readLine(file, buffer)) { \
|
|
||||||
error(kind " palette file \"%s\" is shorter than expected", filename); \
|
|
||||||
return; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
static void warnExtraColors(
|
static void warnExtraColors(
|
||||||
char const *kind, char const *filename, uint16_t nbColors, uint16_t maxNbColors
|
char const *kind, char const *filename, uint16_t nbColors, uint16_t maxNbColors
|
||||||
) {
|
) {
|
||||||
@@ -238,15 +202,15 @@ static void warnExtraColors(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parses the initial part of a string_view, advancing the "read index" as it does
|
// Parses the initial part of a string_view, advancing the "read index" as it does
|
||||||
template<typename U> // Should be uint*_t
|
template<typename UintT> // Should be uint*_t
|
||||||
static std::optional<U> parseDec(std::string const &str, size_t &n) {
|
static std::optional<UintT> parseDec(std::string const &str, size_t &n) {
|
||||||
uintmax_t value = 0;
|
uintmax_t value = 0;
|
||||||
auto result = std::from_chars(str.data() + n, str.data() + str.length(), value);
|
auto result = std::from_chars(str.data() + n, str.data() + str.length(), value);
|
||||||
if (static_cast<bool>(result.ec)) {
|
if (static_cast<bool>(result.ec)) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
n = result.ptr - str.data();
|
n = result.ptr - str.data();
|
||||||
return std::optional<U>{value};
|
return std::optional<UintT>{value};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_t i) {
|
static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_t i) {
|
||||||
@@ -282,21 +246,28 @@ static std::optional<Rgba> parseColor(std::string const &str, size_t &n, uint16_
|
|||||||
static void parsePSPFile(char const *filename, std::filebuf &file) {
|
static void parsePSPFile(char const *filename, std::filebuf &file) {
|
||||||
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
// https://www.selapa.net/swatches/colors/fileformats.php#psp_pal
|
||||||
|
|
||||||
|
#define requireLine() \
|
||||||
|
do { \
|
||||||
|
line.clear(); \
|
||||||
|
if (!readLine(file, line)) { \
|
||||||
|
error("PSP palette file \"%s\" is shorter than expected", filename); \
|
||||||
|
return; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
if (!readLine(file, line) || line != "JASC-PAL") {
|
if (!readLine(file, line) || line != "JASC-PAL") {
|
||||||
error("File \"%s\" is not a valid PSP palette file", filename);
|
error("File \"%s\" is not a valid PSP palette file", filename);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
line.clear();
|
requireLine();
|
||||||
requireLine("PSP", filename, file, line);
|
|
||||||
if (line != "0100") {
|
if (line != "0100") {
|
||||||
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
error("Unsupported PSP palette file version \"%s\"", line.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
line.clear();
|
requireLine();
|
||||||
requireLine("PSP", filename, file, line);
|
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
std::optional<uint16_t> nbColors = parseDec<uint16_t>(line, n);
|
||||||
if (!nbColors || n != line.length()) {
|
if (!nbColors || n != line.length()) {
|
||||||
@@ -312,8 +283,7 @@ static void parsePSPFile(char const *filename, std::filebuf &file) {
|
|||||||
options.palSpec.clear();
|
options.palSpec.clear();
|
||||||
|
|
||||||
for (uint16_t i = 0; i < *nbColors; ++i) {
|
for (uint16_t i = 0; i < *nbColors; ++i) {
|
||||||
line.clear();
|
requireLine();
|
||||||
requireLine("PSP", filename, file, line);
|
|
||||||
|
|
||||||
n = 0;
|
n = 0;
|
||||||
std::optional<Rgba> color = parseColor(line, n, i + 1);
|
std::optional<Rgba> color = parseColor(line, n, i + 1);
|
||||||
@@ -334,6 +304,8 @@ static void parsePSPFile(char const *filename, std::filebuf &file) {
|
|||||||
}
|
}
|
||||||
options.palSpec.back()[i % options.nbColorsPerPal] = *color;
|
options.palSpec.back()[i % options.nbColorsPerPal] = *color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef requireLine
|
||||||
}
|
}
|
||||||
|
|
||||||
static void parseGPLFile(char const *filename, std::filebuf &file) {
|
static void parseGPLFile(char const *filename, std::filebuf &file) {
|
||||||
@@ -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])),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
src/gfx/palette.cpp
Normal file
55
src/gfx/palette.cpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include "gfx/palette.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "helpers.hpp"
|
||||||
|
|
||||||
|
#include "gfx/main.hpp"
|
||||||
|
#include "gfx/rgba.hpp"
|
||||||
|
|
||||||
|
void Palette::addColor(uint16_t color) {
|
||||||
|
for (size_t i = 0; true; ++i) {
|
||||||
|
assume(i < colors.size()); // The packing should guarantee this
|
||||||
|
if (colors[i] == color) { // The color is already present
|
||||||
|
break;
|
||||||
|
} else if (colors[i] == UINT16_MAX) { // Empty slot
|
||||||
|
colors[i] = color;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the ID of the color in the palette, or `size()` if the color is not in
|
||||||
|
uint8_t Palette::indexOf(uint16_t color) const {
|
||||||
|
return color == Rgba::transparent
|
||||||
|
? 0
|
||||||
|
: std::find(begin(), colors.end(), color) - begin() + options.hasTransparentPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Palette::begin() -> decltype(colors)::iterator {
|
||||||
|
// Skip the first slot if reserved for transparency
|
||||||
|
return colors.begin() + options.hasTransparentPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Palette::end() -> decltype(colors)::iterator {
|
||||||
|
// Return an iterator pointing past the last non-empty element.
|
||||||
|
// Since the palette may contain gaps, we must scan from the end.
|
||||||
|
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Palette::begin() const -> decltype(colors)::const_iterator {
|
||||||
|
// Same as the non-const begin().
|
||||||
|
return colors.begin() + options.hasTransparentPixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Palette::end() const -> decltype(colors)::const_iterator {
|
||||||
|
// Same as the non-const end().
|
||||||
|
return std::find_if(RRANGE(colors), [](uint16_t c) { return c != UINT16_MAX; }).base();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t Palette::size() const {
|
||||||
|
return end() - colors.begin();
|
||||||
|
}
|
||||||
@@ -31,15 +31,19 @@ struct Input {
|
|||||||
|
|
||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void handleError(png_structp png, char const *msg) {
|
static void handleError(png_structp png, char const *msg) {
|
||||||
Input const &input = *reinterpret_cast<Input *>(png_get_error_ptr(png));
|
fatal(
|
||||||
|
"libpng error while reading PNG image (\"%s\"): %s",
|
||||||
fatal("Error reading PNG image (\"%s\"): %s", input.filename, msg);
|
reinterpret_cast<Input *>(png_get_error_ptr(png))->filename,
|
||||||
|
msg
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleWarning(png_structp png, char const *msg) {
|
static void handleWarning(png_structp png, char const *msg) {
|
||||||
Input const &input = *reinterpret_cast<Input *>(png_get_error_ptr(png));
|
warnx(
|
||||||
|
"libpng found while reading PNG image (\"%s\"): %s",
|
||||||
warnx("In PNG image (\"%s\"): %s", input.filename, msg);
|
reinterpret_cast<Input *>(png_get_error_ptr(png))->filename,
|
||||||
|
msg
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void readData(png_structp png, png_bytep data, size_t length) {
|
static void readData(png_structp png, png_bytep data, size_t length) {
|
||||||
|
|||||||
@@ -26,9 +26,11 @@
|
|||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "gfx/color_set.hpp"
|
#include "gfx/color_set.hpp"
|
||||||
|
#include "gfx/flip.hpp"
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
#include "gfx/pal_packing.hpp"
|
#include "gfx/pal_packing.hpp"
|
||||||
#include "gfx/pal_sorting.hpp"
|
#include "gfx/pal_sorting.hpp"
|
||||||
|
#include "gfx/palette.hpp"
|
||||||
#include "gfx/png.hpp"
|
#include "gfx/png.hpp"
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
#include "gfx/warning.hpp"
|
#include "gfx/warning.hpp"
|
||||||
@@ -695,12 +697,6 @@ static void outputUnoptimizedMaps(
|
|||||||
uint8_t tileID = 0;
|
uint8_t tileID = 0;
|
||||||
uint8_t bank = 0;
|
uint8_t bank = 0;
|
||||||
for (AttrmapEntry const &attr : attrmap) {
|
for (AttrmapEntry const &attr : attrmap) {
|
||||||
if (tileID == options.maxNbTiles[bank]) {
|
|
||||||
assume(bank == 0);
|
|
||||||
bank = 1;
|
|
||||||
tileID = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tilemapOutput.has_value()) {
|
if (tilemapOutput.has_value()) {
|
||||||
(*tilemapOutput)
|
(*tilemapOutput)
|
||||||
->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]);
|
->sputc((attr.isBackgroundTile() ? 0 : tileID) + options.baseTileIDs[bank]);
|
||||||
@@ -714,8 +710,17 @@ static void outputUnoptimizedMaps(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Background tiles are skipped in the tile data, so they should be skipped in the maps too.
|
// Background tiles are skipped in the tile data, so they should be skipped in the maps too.
|
||||||
if (!attr.isBackgroundTile()) {
|
if (attr.isBackgroundTile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare with `maxNbTiles` *before* incrementing, due to unsigned overflow!
|
||||||
|
if (tileID + 1 < options.maxNbTiles[bank]) {
|
||||||
++tileID;
|
++tileID;
|
||||||
|
} else {
|
||||||
|
assume(bank == 0);
|
||||||
|
bank = 1;
|
||||||
|
tileID = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "helpers.hpp" // assume
|
#include "helpers.hpp" // assume
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
|
#include "gfx/flip.hpp"
|
||||||
#include "gfx/main.hpp"
|
#include "gfx/main.hpp"
|
||||||
#include "gfx/rgba.hpp"
|
#include "gfx/rgba.hpp"
|
||||||
#include "gfx/warning.hpp"
|
#include "gfx/warning.hpp"
|
||||||
@@ -61,16 +62,16 @@ static std::vector<uint8_t> readInto(std::string const &path) {
|
|||||||
[[noreturn]]
|
[[noreturn]]
|
||||||
static void pngError(png_structp png, char const *msg) {
|
static void pngError(png_structp png, char const *msg) {
|
||||||
fatal(
|
fatal(
|
||||||
"Error writing reversed image (\"%s\"): %s",
|
"libpng error while writing reversed image (\"%s\"): %s",
|
||||||
static_cast<char const *>(png_get_error_ptr(png)),
|
reinterpret_cast<char const *>(png_get_error_ptr(png)),
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pngWarning(png_structp png, char const *msg) {
|
static void pngWarning(png_structp png, char const *msg) {
|
||||||
warnx(
|
warnx(
|
||||||
"While writing reversed image (\"%s\"): %s",
|
"libpng found while writing reversed image (\"%s\"): %s",
|
||||||
static_cast<char const *>(png_get_error_ptr(png)),
|
reinterpret_cast<char const *>(png_get_error_ptr(png)),
|
||||||
msg
|
msg
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -235,7 +236,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);
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include "helpers.hpp"
|
#include "helpers.hpp"
|
||||||
#include "itertools.hpp"
|
#include "itertools.hpp"
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
|
#include "platform.hpp" // MUSTTAIL
|
||||||
#include "verbosity.hpp"
|
#include "verbosity.hpp"
|
||||||
|
|
||||||
#include "link/main.hpp"
|
#include "link/main.hpp"
|
||||||
@@ -53,11 +54,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 : section.pieces()) {
|
||||||
next->org = location.address;
|
piece.org = location.address;
|
||||||
next->bank = location.bank;
|
piece.bank = location.bank;
|
||||||
}
|
}
|
||||||
|
|
||||||
out_AddSection(section);
|
out_AddSection(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ static std::optional<size_t> getPlacement(Section const §ion, MemoryLocation
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getPlacement(section, location); // Tail recursion
|
MUSTTAIL return getPlacement(section, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string getSectionDescription(Section const §ion) {
|
static std::string getSectionDescription(Section const §ion) {
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curL
|
|||||||
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
||||||
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
||||||
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
||||||
std::string reptChain = traceNodes.back().first;
|
std::string reptName = traceNodes.back().first;
|
||||||
for (uint32_t iter : node.iters()) {
|
if (std::vector<uint32_t> const &nodeIters = node.iters(); !nodeIters.empty()) {
|
||||||
reptChain.append("::REPT~");
|
reptName.append(NODE_SEPARATOR REPT_NODE_PREFIX);
|
||||||
reptChain.append(std::to_string(iter));
|
reptName.append(std::to_string(nodeIters.back()));
|
||||||
}
|
}
|
||||||
traceNodes.emplace_back(reptChain, curLineNo);
|
traceNodes.emplace_back(reptName, curLineNo);
|
||||||
} else {
|
} else {
|
||||||
traceNodes.emplace_back(node.name(), curLineNo);
|
traceNodes.emplace_back(node.name(), curLineNo);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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->pieces()) {
|
||||||
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);
|
||||||
|
|
||||||
@@ -324,10 +307,10 @@ yy::parser::symbol_type yylex() {
|
|||||||
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
// Not marking as unreachable; this will generate a warning if any codepath forgets to return.
|
||||||
}
|
}
|
||||||
|
|
||||||
bool lexer_Init(char const *linkerScriptName) {
|
bool lexer_Init(std::string const &linkerScriptName) {
|
||||||
if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName));
|
if (LexerStackEntry &newContext = lexerStack.emplace_back(std::string(linkerScriptName));
|
||||||
!newContext.file.open(newContext.path, std::ios_base::in)) {
|
!newContext.file.open(newContext.path, std::ios_base::in)) {
|
||||||
error("Failed to open linker script \"%s\"", linkerScriptName);
|
error("Failed to open linker script \"%s\"", linkerScriptName.c_str());
|
||||||
lexerStack.clear();
|
lexerStack.clear();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -12,8 +13,8 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "backtrace.hpp"
|
#include "backtrace.hpp"
|
||||||
|
#include "cli.hpp"
|
||||||
#include "diagnostics.hpp"
|
#include "diagnostics.hpp"
|
||||||
#include "extern/getopt.hpp"
|
|
||||||
#include "linkdefs.hpp"
|
#include "linkdefs.hpp"
|
||||||
#include "script.hpp" // Generated from script.y
|
#include "script.hpp" // Generated from script.y
|
||||||
#include "style.hpp"
|
#include "style.hpp"
|
||||||
@@ -32,12 +33,16 @@
|
|||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
|
|
||||||
static char const *linkerScriptName = nullptr; // -l
|
// Flags which must be processed after the option parsing finishes
|
||||||
|
static struct LocalOptions {
|
||||||
|
std::optional<std::string> linkerScriptName; // -l
|
||||||
|
std::vector<std::string> inputFileNames; // <file>...
|
||||||
|
} localOptions;
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
|
static char const *optstring = "B:dhl:m:Mn:O:o:p:S:tVvW:wx";
|
||||||
|
|
||||||
// Variables for the long-only options
|
// Long-only option variable
|
||||||
static int longOpt; // `--color`
|
static int longOpt; // `--color`
|
||||||
|
|
||||||
// Equivalent long options
|
// Equivalent long options
|
||||||
@@ -89,97 +94,6 @@ static Usage usage = {
|
|||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// LCOV_EXCL_START
|
|
||||||
static void verboseOutputConfig(int argc, char *argv[]) {
|
|
||||||
if (!checkVerbosity(VERB_CONFIG)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
style_Set(stderr, STYLE_MAGENTA, false);
|
|
||||||
|
|
||||||
fprintf(stderr, "rgblink %s\n", get_package_version_string());
|
|
||||||
|
|
||||||
printVVVVVVerbosity();
|
|
||||||
|
|
||||||
fputs("Options:\n", stderr);
|
|
||||||
// -d/--dmg
|
|
||||||
if (options.isDmgMode) {
|
|
||||||
fputs("\tDMG mode prohibits non-DMG section types\n", stderr);
|
|
||||||
}
|
|
||||||
// -t/--tiny
|
|
||||||
if (options.is32kMode) {
|
|
||||||
fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr);
|
|
||||||
}
|
|
||||||
// -w/--wramx
|
|
||||||
if (options.isWRAM0Mode) {
|
|
||||||
fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr);
|
|
||||||
}
|
|
||||||
// -x/--nopad
|
|
||||||
if (options.disablePadding) {
|
|
||||||
fputs("\tNo padding at the end of the ROM file\n", stderr);
|
|
||||||
}
|
|
||||||
// -p/--pad
|
|
||||||
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue);
|
|
||||||
// -S/--scramble
|
|
||||||
if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) {
|
|
||||||
fputs("\tScramble: ", stderr);
|
|
||||||
if (options.scrambleROMX) {
|
|
||||||
fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX);
|
|
||||||
if (options.scrambleWRAMX || options.scrambleSRAM) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.scrambleWRAMX) {
|
|
||||||
fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX);
|
|
||||||
if (options.scrambleSRAM) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.scrambleSRAM) {
|
|
||||||
fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
// file ...
|
|
||||||
if (musl_optind < argc) {
|
|
||||||
fprintf(stderr, "\tInput object files: ");
|
|
||||||
for (int i = musl_optind; i < argc; ++i) {
|
|
||||||
if (i > musl_optind) {
|
|
||||||
fputs(", ", stderr);
|
|
||||||
}
|
|
||||||
if (i - musl_optind == 10) {
|
|
||||||
fprintf(stderr, "and %d more", argc - i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
fputs(argv[i], stderr);
|
|
||||||
}
|
|
||||||
putc('\n', stderr);
|
|
||||||
}
|
|
||||||
auto printPath = [](char const *name, char const *path) {
|
|
||||||
if (path) {
|
|
||||||
fprintf(stderr, "\t%s: %s\n", name, path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// -O/--overlay
|
|
||||||
printPath("Overlay file", options.overlayFileName);
|
|
||||||
// -l/--linkerscript
|
|
||||||
printPath("Linker script", linkerScriptName);
|
|
||||||
// -o/--output
|
|
||||||
printPath("Output ROM file", options.outputFileName);
|
|
||||||
// -m/--map
|
|
||||||
printPath("Output map file", options.mapFileName);
|
|
||||||
// -M/--no-sym-in-map
|
|
||||||
if (options.mapFileName && options.noSymInMap) {
|
|
||||||
fputs("\tNo symbols in map file\n", stderr);
|
|
||||||
}
|
|
||||||
// -n/--sym
|
|
||||||
printPath("Output sym file", options.symFileName);
|
|
||||||
fputs("Ready.\n", stderr);
|
|
||||||
|
|
||||||
style_Reset(stderr);
|
|
||||||
}
|
|
||||||
// LCOV_EXCL_STOP
|
|
||||||
|
|
||||||
static size_t skipBlankSpace(char const *str) {
|
static size_t skipBlankSpace(char const *str) {
|
||||||
return strspn(str, " \t");
|
return strspn(str, " \t");
|
||||||
}
|
}
|
||||||
@@ -265,21 +179,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());
|
||||||
@@ -294,12 +205,10 @@ static void parseScrambleSpec(char *spec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
static void parseArg(int ch, char *arg) {
|
||||||
// Parse CLI options
|
|
||||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
case 'B':
|
case 'B':
|
||||||
if (!trace_ParseTraceDepth(musl_optarg)) {
|
if (!trace_ParseTraceDepth(arg)) {
|
||||||
fatal("Invalid argument for option '-B'");
|
fatal("Invalid argument for option '-B'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -315,10 +224,10 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'l':
|
case 'l':
|
||||||
if (linkerScriptName) {
|
if (localOptions.linkerScriptName) {
|
||||||
warnx("Overriding linker script file \"%s\"", linkerScriptName);
|
warnx("Overriding linker script file \"%s\"", localOptions.linkerScriptName->c_str());
|
||||||
}
|
}
|
||||||
linkerScriptName = musl_optarg;
|
localOptions.linkerScriptName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
@@ -327,50 +236,45 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
case 'm':
|
case 'm':
|
||||||
if (options.mapFileName) {
|
if (options.mapFileName) {
|
||||||
warnx("Overriding map file \"%s\"", options.mapFileName);
|
warnx("Overriding map file \"%s\"", options.mapFileName->c_str());
|
||||||
}
|
}
|
||||||
options.mapFileName = musl_optarg;
|
options.mapFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'n':
|
case 'n':
|
||||||
if (options.symFileName) {
|
if (options.symFileName) {
|
||||||
warnx("Overriding sym file \"%s\"", options.symFileName);
|
warnx("Overriding sym file \"%s\"", options.symFileName->c_str());
|
||||||
}
|
}
|
||||||
options.symFileName = musl_optarg;
|
options.symFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'O':
|
case 'O':
|
||||||
if (options.overlayFileName) {
|
if (options.overlayFileName) {
|
||||||
warnx("Overriding overlay file \"%s\"", options.overlayFileName);
|
warnx("Overriding overlay file \"%s\"", options.overlayFileName->c_str());
|
||||||
}
|
}
|
||||||
options.overlayFileName = musl_optarg;
|
options.overlayFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'o':
|
case 'o':
|
||||||
if (options.outputFileName) {
|
if (options.outputFileName) {
|
||||||
warnx("Overriding output file \"%s\"", options.outputFileName);
|
warnx("Overriding output file \"%s\"", options.outputFileName->c_str());
|
||||||
}
|
}
|
||||||
options.outputFileName = musl_optarg;
|
options.outputFileName = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'p': {
|
case 'p':
|
||||||
char *endptr;
|
if (std::optional<uint64_t> value = parseWholeNumber(arg); !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.padValue = value;
|
|
||||||
options.hasPadValue = true;
|
options.hasPadValue = true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'S':
|
case 'S':
|
||||||
parseScrambleSpec(musl_optarg);
|
parseScrambleSpec(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't':
|
case 't':
|
||||||
@@ -388,7 +292,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// LCOV_EXCL_STOP
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
case 'W':
|
case 'W':
|
||||||
warnings.processWarningFlag(musl_optarg);
|
warnings.processWarningFlag(arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
@@ -402,19 +306,120 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0: // Long-only options
|
case 0: // Long-only options
|
||||||
if (longOpt == 'c' && !style_Parse(musl_optarg)) {
|
if (longOpt == 'c' && !style_Parse(arg)) {
|
||||||
fatal("Invalid argument for option '--color'");
|
fatal("Invalid argument for option '--color'");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 1: // Positional argument
|
||||||
|
localOptions.inputFileNames.push_back(arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// LCOV_EXCL_START
|
||||||
default:
|
default:
|
||||||
usage.printAndExit(1); // LCOV_EXCL_LINE
|
usage.printAndExit(1);
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseOutputConfig(argc, argv);
|
// LCOV_EXCL_START
|
||||||
|
static void verboseOutputConfig() {
|
||||||
|
if (!checkVerbosity(VERB_CONFIG)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (musl_optind == argc) {
|
style_Set(stderr, STYLE_MAGENTA, false);
|
||||||
|
|
||||||
|
fprintf(stderr, "rgblink %s\n", get_package_version_string());
|
||||||
|
|
||||||
|
printVVVVVVerbosity();
|
||||||
|
|
||||||
|
fputs("Options:\n", stderr);
|
||||||
|
// -d/--dmg
|
||||||
|
if (options.isDmgMode) {
|
||||||
|
fputs("\tDMG mode prohibits non-DMG section types\n", stderr);
|
||||||
|
}
|
||||||
|
// -t/--tiny
|
||||||
|
if (options.is32kMode) {
|
||||||
|
fputs("\tROM0 covers the full 32 KiB of ROM\n", stderr);
|
||||||
|
}
|
||||||
|
// -w/--wramx
|
||||||
|
if (options.isWRAM0Mode) {
|
||||||
|
fputs("\tWRAM0 covers the full 8 KiB of WRAM\n", stderr);
|
||||||
|
}
|
||||||
|
// -x/--nopad
|
||||||
|
if (options.disablePadding) {
|
||||||
|
fputs("\tNo padding at the end of the ROM file\n", stderr);
|
||||||
|
}
|
||||||
|
// -p/--pad
|
||||||
|
fprintf(stderr, "\tPad value: 0x%02" PRIx8 "\n", options.padValue);
|
||||||
|
// -S/--scramble
|
||||||
|
if (options.scrambleROMX || options.scrambleWRAMX || options.scrambleSRAM) {
|
||||||
|
fputs("\tScramble: ", stderr);
|
||||||
|
if (options.scrambleROMX) {
|
||||||
|
fprintf(stderr, "ROMX = %" PRIu16, options.scrambleROMX);
|
||||||
|
if (options.scrambleWRAMX || options.scrambleSRAM) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.scrambleWRAMX) {
|
||||||
|
fprintf(stderr, "WRAMX = %" PRIu16, options.scrambleWRAMX);
|
||||||
|
if (options.scrambleSRAM) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (options.scrambleSRAM) {
|
||||||
|
fprintf(stderr, "SRAM = %" PRIu16, options.scrambleSRAM);
|
||||||
|
}
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
// file ...
|
||||||
|
if (!localOptions.inputFileNames.empty()) {
|
||||||
|
fprintf(stderr, "\tInput object files: ");
|
||||||
|
size_t nbFiles = localOptions.inputFileNames.size();
|
||||||
|
for (size_t i = 0; i < nbFiles; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
fputs(", ", stderr);
|
||||||
|
}
|
||||||
|
if (i == 10) {
|
||||||
|
fprintf(stderr, "and %zu more", nbFiles - i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fputs(localOptions.inputFileNames[i].c_str(), stderr);
|
||||||
|
}
|
||||||
|
putc('\n', stderr);
|
||||||
|
}
|
||||||
|
auto printPath = [](char const *name, std::optional<std::string> const &path) {
|
||||||
|
if (path) {
|
||||||
|
fprintf(stderr, "\t%s: %s\n", name, path->c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// -O/--overlay
|
||||||
|
printPath("Overlay file", options.overlayFileName);
|
||||||
|
// -l/--linkerscript
|
||||||
|
printPath("Linker script", localOptions.linkerScriptName);
|
||||||
|
// -o/--output
|
||||||
|
printPath("Output ROM file", options.outputFileName);
|
||||||
|
// -m/--map
|
||||||
|
printPath("Output map file", options.mapFileName);
|
||||||
|
// -M/--no-sym-in-map
|
||||||
|
if (options.mapFileName && options.noSymInMap) {
|
||||||
|
fputs("\tNo symbols in map file\n", stderr);
|
||||||
|
}
|
||||||
|
// -n/--sym
|
||||||
|
printPath("Output sym file", options.symFileName);
|
||||||
|
fputs("Ready.\n", stderr);
|
||||||
|
|
||||||
|
style_Reset(stderr);
|
||||||
|
}
|
||||||
|
// LCOV_EXCL_STOP
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
cli_ParseArgs(argc, argv, optstring, longopts, parseArg, fatal);
|
||||||
|
|
||||||
|
verboseOutputConfig();
|
||||||
|
|
||||||
|
if (localOptions.inputFileNames.empty()) {
|
||||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,20 +437,21 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read all object files first,
|
// Read all object files first,
|
||||||
obj_Setup(argc - musl_optind);
|
size_t nbFiles = localOptions.inputFileNames.size();
|
||||||
for (int i = musl_optind; i < argc; ++i) {
|
obj_Setup(nbFiles);
|
||||||
obj_ReadFile(argv[i], argc - i - 1);
|
for (size_t i = 0; i < nbFiles; ++i) {
|
||||||
|
obj_ReadFile(localOptions.inputFileNames[i], nbFiles - i - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the linker script's modifications,
|
// apply the linker script's modifications,
|
||||||
if (linkerScriptName) {
|
if (localOptions.linkerScriptName) {
|
||||||
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
verbosePrint(VERB_NOTICE, "Reading linker script...\n");
|
||||||
|
|
||||||
if (lexer_Init(linkerScriptName)) {
|
if (lexer_Init(*localOptions.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,
|
||||||
@@ -424,9 +405,10 @@ static void readAssertion(
|
|||||||
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
tryReadString(assert.message, file, "%s: Cannot read assertion's message: %s", fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
void obj_ReadFile(std::string const &filePath, size_t fileID) {
|
||||||
FILE *file;
|
FILE *file;
|
||||||
if (strcmp(fileName, "-")) {
|
char const *fileName = filePath.c_str();
|
||||||
|
if (filePath != "-") {
|
||||||
file = fopen(fileName, "rb");
|
file = fopen(fileName, "rb");
|
||||||
} else {
|
} else {
|
||||||
fileName = "<stdin>";
|
fileName = "<stdin>";
|
||||||
@@ -503,8 +485,8 @@ void obj_ReadFile(char const *fileName, unsigned int fileID) {
|
|||||||
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
tryReadLong(nbNodes, file, "%s: Cannot read number of nodes: %s", fileName);
|
||||||
nodes[fileID].resize(nbNodes);
|
nodes[fileID].resize(nbNodes);
|
||||||
verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes);
|
verbosePrint(VERB_INFO, "Reading %u nodes...\n", nbNodes);
|
||||||
for (uint32_t i = nbNodes; i--;) {
|
for (uint32_t nodeID = nbNodes; nodeID--;) {
|
||||||
readFileStackNode(file, nodes[fileID], i, fileName);
|
readFileStackNode(file, nodes[fileID], nodeID, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This file's symbols, kept to link sections to them
|
// This file's symbols, kept to link sections to them
|
||||||
@@ -512,15 +494,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 +507,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 +526,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,26 +546,14 @@ 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void obj_Setup(unsigned int nbFiles) {
|
void obj_Setup(size_t nbFiles) {
|
||||||
nodes.resize(nbFiles);
|
nodes.resize(nbFiles);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,15 +208,16 @@ static void
|
|||||||
|
|
||||||
static void writeROM() {
|
static void writeROM() {
|
||||||
if (options.outputFileName) {
|
if (options.outputFileName) {
|
||||||
if (strcmp(options.outputFileName, "-")) {
|
char const *outputFileName = options.outputFileName->c_str();
|
||||||
outputFile = fopen(options.outputFileName, "wb");
|
if (*options.outputFileName != "-") {
|
||||||
|
outputFile = fopen(outputFileName, "wb");
|
||||||
} else {
|
} else {
|
||||||
options.outputFileName = "<stdout>";
|
outputFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||||
outputFile = stdout;
|
outputFile = stdout;
|
||||||
}
|
}
|
||||||
if (!outputFile) {
|
if (!outputFile) {
|
||||||
fatal("Failed to open output file \"%s\": %s", options.outputFileName, strerror(errno));
|
fatal("Failed to open output file \"%s\": %s", outputFileName, strerror(errno));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Defer closeOutputFile{[&] {
|
Defer closeOutputFile{[&] {
|
||||||
@@ -226,17 +227,16 @@ static void writeROM() {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
if (options.overlayFileName) {
|
if (options.overlayFileName) {
|
||||||
if (strcmp(options.overlayFileName, "-")) {
|
char const *overlayFileName = options.overlayFileName->c_str();
|
||||||
overlayFile = fopen(options.overlayFileName, "rb");
|
if (*options.overlayFileName != "-") {
|
||||||
|
overlayFile = fopen(overlayFileName, "rb");
|
||||||
} else {
|
} else {
|
||||||
options.overlayFileName = "<stdin>";
|
overlayFileName = "<stdin>";
|
||||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||||
overlayFile = stdin;
|
overlayFile = stdin;
|
||||||
}
|
}
|
||||||
if (!overlayFile) {
|
if (!overlayFile) {
|
||||||
fatal(
|
fatal("Failed to open overlay file \"%s\": %s", overlayFileName, strerror(errno));
|
||||||
"Failed to open overlay file \"%s\": %s", options.overlayFileName, strerror(errno)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Defer closeOverlayFile{[&] {
|
Defer closeOverlayFile{[&] {
|
||||||
@@ -314,16 +314,16 @@ static bool compareSymbols(SortedSymbol const &sym1, SortedSymbol const &sym2) {
|
|||||||
< std::tie(sym2.addr, sym2_local, sym2.parentAddr, sym2_name);
|
< std::tie(sym2.addr, sym2_local, sym2.parentAddr, sym2_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
static void forEachSortedSection(SortedSections const &bankSections, F callback) {
|
static void forEachSortedSection(SortedSections const &bankSections, CallbackFnT callback) {
|
||||||
for (Section const *sect : bankSections.zeroLenSections) {
|
for (Section const *sect : bankSections.zeroLenSections) {
|
||||||
for (; sect; sect = sect->nextu.get()) {
|
for (Section const &piece : sect->pieces()) {
|
||||||
callback(*sect);
|
callback(piece);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Section const *sect : bankSections.sections) {
|
for (Section const *sect : bankSections.sections) {
|
||||||
for (; sect; sect = sect->nextu.get()) {
|
for (Section const &piece : sect->pieces()) {
|
||||||
callback(*sect);
|
callback(piece);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -414,8 +415,8 @@ static void writeSectionName(std::string const &name, FILE *file) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename F>
|
template<typename CallbackFnT>
|
||||||
uint16_t forEachSection(SortedSections const §List, F callback) {
|
uint16_t forEachSection(SortedSections const §List, CallbackFnT callback) {
|
||||||
uint16_t used = 0;
|
uint16_t used = 0;
|
||||||
auto section = sectList.sections.begin();
|
auto section = sectList.sections.begin();
|
||||||
auto zeroLenSection = sectList.zeroLenSections.begin();
|
auto zeroLenSection = sectList.zeroLenSections.begin();
|
||||||
@@ -432,26 +433,32 @@ uint16_t forEachSection(SortedSections const §List, F callback) {
|
|||||||
return used;
|
return used;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeMapSymbols(Section const *sect) {
|
static void writeMapSymbols(Section const §) {
|
||||||
for (uint16_t org = sect->org; sect; sect = sect->nextu.get()) {
|
bool announced = true;
|
||||||
for (Symbol *sym : sect->symbols) {
|
for (Section const &piece : sect.pieces()) {
|
||||||
|
for (Symbol *sym : piece.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) {
|
||||||
|
assume(sect.modifier == piece.modifier);
|
||||||
|
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 + sect.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);
|
||||||
}
|
}
|
||||||
|
announced = false;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +488,7 @@ static void writeMapBank(SortedSections const §List, SectionType type, uint3
|
|||||||
|
|
||||||
if (!options.noSymInMap) {
|
if (!options.noSymInMap) {
|
||||||
// Also print symbols in the following "pieces"
|
// Also print symbols in the following "pieces"
|
||||||
writeMapSymbols(§);
|
writeMapSymbols(sect);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -541,15 +548,16 @@ static void writeSym() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(options.symFileName, "-")) {
|
char const *symFileName = options.symFileName->c_str();
|
||||||
symFile = fopen(options.symFileName, "w");
|
if (*options.symFileName != "-") {
|
||||||
|
symFile = fopen(symFileName, "w");
|
||||||
} else {
|
} else {
|
||||||
options.symFileName = "<stdout>";
|
symFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
||||||
symFile = stdout;
|
symFile = stdout;
|
||||||
}
|
}
|
||||||
if (!symFile) {
|
if (!symFile) {
|
||||||
fatal("Failed to open sym file \"%s\": %s", options.symFileName, strerror(errno));
|
fatal("Failed to open sym file \"%s\": %s", symFileName, strerror(errno));
|
||||||
}
|
}
|
||||||
Defer closeSymFile{[&] { fclose(symFile); }};
|
Defer closeSymFile{[&] { fclose(symFile); }};
|
||||||
|
|
||||||
@@ -591,15 +599,16 @@ static void writeMap() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(options.mapFileName, "-")) {
|
char const *mapFileName = options.mapFileName->c_str();
|
||||||
mapFile = fopen(options.mapFileName, "w");
|
if (*options.mapFileName != "-") {
|
||||||
|
mapFile = fopen(mapFileName, "w");
|
||||||
} else {
|
} else {
|
||||||
options.mapFileName = "<stdout>";
|
mapFileName = "<stdout>";
|
||||||
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
(void)setmode(STDOUT_FILENO, O_TEXT); // May have been set to O_BINARY previously
|
||||||
mapFile = stdout;
|
mapFile = stdout;
|
||||||
}
|
}
|
||||||
if (!mapFile) {
|
if (!mapFile) {
|
||||||
fatal("Failed to open map file \"%s\": %s", options.mapFileName, strerror(errno));
|
fatal("Failed to open map file \"%s\": %s", mapFileName, strerror(errno));
|
||||||
}
|
}
|
||||||
Defer closeMapFile{[&] { fclose(mapFile); }};
|
Defer closeMapFile{[&] { fclose(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
|
||||||
|
if (typeSize < sizeof(int)) {
|
||||||
checkPatchSize(patch, value, typeSize * 8);
|
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 : section.pieces()) {
|
||||||
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);
|
||||||
@@ -153,23 +138,30 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
line.reserve(256);
|
line.reserve(256);
|
||||||
char const *token;
|
char const *token;
|
||||||
|
|
||||||
#define getToken(ptr, ...) \
|
#define expectEol(lineType) \
|
||||||
do { \
|
|
||||||
token = strtok((ptr), delim); \
|
|
||||||
if (!token) { \
|
|
||||||
fatalAt(where, __VA_ARGS__); \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
#define expectEol(...) \
|
|
||||||
do { \
|
do { \
|
||||||
token = strtok(nullptr, delim); \
|
token = strtok(nullptr, delim); \
|
||||||
if (token) { \
|
if (token) { \
|
||||||
fatalAt(where, __VA_ARGS__); \
|
fatalAt(where, "'%c' line is too long", (lineType)); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
#define expectNext(ptr, lineType) \
|
||||||
|
do { \
|
||||||
|
token = strtok((ptr), delim); \
|
||||||
|
if (!token) { \
|
||||||
|
fatalAt(where, "'%c' line is too short", (lineType)); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
#define expectRelocation() \
|
||||||
|
do { \
|
||||||
|
token = strtok(nullptr, delim); \
|
||||||
|
if (!token) { \
|
||||||
|
fatalAt(where, "Incomplete relocation"); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
#define expectToken(expected, lineType) \
|
#define expectToken(expected, lineType) \
|
||||||
do { \
|
do { \
|
||||||
getToken(nullptr, "'%c' line is too short", (lineType)); \
|
expectNext(nullptr, lineType); \
|
||||||
if (strcasecmp(token, (expected)) != 0) { \
|
if (strcasecmp(token, (expected)) != 0) { \
|
||||||
fatalAt( \
|
fatalAt( \
|
||||||
where, \
|
where, \
|
||||||
@@ -184,18 +176,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(
|
||||||
@@ -238,20 +230,23 @@ 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");
|
token = strtok(line.data(), delim);
|
||||||
uint32_t expectedNbAreas = parseNumber(where, token, numberType);
|
if (!token) {
|
||||||
|
fatalAt(where, "Empty 'H' line");
|
||||||
|
}
|
||||||
|
uint32_t expectedNbAreas = readInt(where, token, numberBase);
|
||||||
|
|
||||||
expectToken("areas", 'H');
|
expectToken("areas", 'H');
|
||||||
|
|
||||||
getToken(nullptr, "'H' line is too short");
|
expectNext(nullptr, 'H');
|
||||||
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');
|
||||||
|
|
||||||
expectToken("symbols", 'H');
|
expectToken("symbols", 'H');
|
||||||
|
|
||||||
expectEol("'H' line is too long");
|
expectEol('H');
|
||||||
|
|
||||||
// Now, let's parse the rest of the lines as they come!
|
// Now, let's parse the rest of the lines as they come!
|
||||||
|
|
||||||
@@ -282,7 +277,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
curSection->src = where.src;
|
curSection->src = where.src;
|
||||||
curSection->lineNo = where.lineNo;
|
curSection->lineNo = where.lineNo;
|
||||||
|
|
||||||
getToken(line.data(), "'A' line is too short");
|
expectNext(line.data(), 'A');
|
||||||
assume(strlen(token) != 0); // This should be impossible, tokens are non-empty
|
assume(strlen(token) != 0); // This should be impossible, tokens are non-empty
|
||||||
// The following is required for fragment offsets to be reliably predicted
|
// The following is required for fragment offsets to be reliably predicted
|
||||||
for (FileSection &entry : fileSections) {
|
for (FileSection &entry : fileSections) {
|
||||||
@@ -294,9 +289,9 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
|
|
||||||
expectToken("size", 'A');
|
expectToken("size", 'A');
|
||||||
|
|
||||||
getToken(nullptr, "'A' line is too short");
|
expectNext(nullptr, 'A');
|
||||||
|
|
||||||
uint32_t tmp = parseNumber(where, token, numberType);
|
uint32_t tmp = readInt(where, token, numberBase);
|
||||||
|
|
||||||
if (tmp > UINT16_MAX) {
|
if (tmp > UINT16_MAX) {
|
||||||
fatalAt(
|
fatalAt(
|
||||||
@@ -309,8 +304,8 @@ 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");
|
expectNext(nullptr, 'A');
|
||||||
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");
|
||||||
}
|
}
|
||||||
@@ -328,12 +323,12 @@ 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");
|
expectNext(nullptr, 'A');
|
||||||
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;
|
||||||
|
|
||||||
expectEol("'A' line is too long");
|
expectEol('A');
|
||||||
|
|
||||||
// Init the rest of the members
|
// Init the rest of the members
|
||||||
curSection->offset = 0;
|
curSection->offset = 0;
|
||||||
@@ -364,7 +359,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;
|
||||||
@@ -381,12 +376,12 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
symbol.src = where.src;
|
symbol.src = where.src;
|
||||||
symbol.lineNo = where.lineNo;
|
symbol.lineNo = where.lineNo;
|
||||||
|
|
||||||
getToken(line.data(), "'S' line is too short");
|
expectNext(line.data(), 'S');
|
||||||
symbol.name = token;
|
symbol.name = token;
|
||||||
|
|
||||||
getToken(nullptr, "'S' line is too short");
|
expectNext(nullptr, 'S');
|
||||||
|
|
||||||
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) {
|
||||||
@@ -453,7 +448,7 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
fileSections.back().section->symbols.push_back(&symbol);
|
fileSections.back().section->symbols.push_back(&symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
expectEol("'S' line is too long");
|
expectEol('S');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,7 +460,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) {
|
||||||
@@ -482,14 +477,14 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First two bytes are ignored
|
// First two bytes are ignored
|
||||||
getToken(line.data(), "'R' line is too short");
|
expectNext(line.data(), 'R');
|
||||||
getToken(nullptr, "'R' line is too short");
|
expectNext(nullptr, 'R');
|
||||||
uint16_t areaIdx;
|
uint16_t areaIdx;
|
||||||
|
|
||||||
getToken(nullptr, "'R' line is too short");
|
expectNext(nullptr, 'R');
|
||||||
areaIdx = parseByte(where, token, numberType);
|
areaIdx = readByte(where, token, numberBase);
|
||||||
getToken(nullptr, "'R' line is too short");
|
expectNext(nullptr, 'R');
|
||||||
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 +544,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");
|
expectRelocation();
|
||||||
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");
|
expectRelocation();
|
||||||
uint8_t offset = parseByte(where, token, numberType);
|
uint8_t offset = readByte(where, token, numberBase);
|
||||||
|
|
||||||
if (offset < addrSize) {
|
if (offset < addrSize) {
|
||||||
fatalAt(
|
fatalAt(
|
||||||
@@ -577,11 +572,11 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
expectRelocation();
|
||||||
uint16_t idx = parseByte(where, token, numberType);
|
uint16_t idx = readByte(where, token, numberBase);
|
||||||
|
|
||||||
getToken(nullptr, "Incomplete relocation");
|
expectRelocation();
|
||||||
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)) {
|
||||||
@@ -834,8 +829,9 @@ void sdobj_ReadFile(FileStackNode const &src, FILE *file, std::vector<Symbol> &f
|
|||||||
}
|
}
|
||||||
|
|
||||||
#undef expectEol
|
#undef expectEol
|
||||||
|
#undef expectNext
|
||||||
|
#undef expectRelocation
|
||||||
#undef expectToken
|
#undef expectToken
|
||||||
#undef getToken
|
|
||||||
|
|
||||||
if (!data.empty()) {
|
if (!data.empty()) {
|
||||||
warningAt(where, "Last 'T' line had no 'R' line (ignored)");
|
warningAt(where, "Last 'T' line had no 'R' line (ignored)");
|
||||||
@@ -902,21 +898,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
|||||||
{"truncation", LEVEL_EVERYTHING},
|
{"truncation", LEVEL_EVERYTHING},
|
||||||
},
|
},
|
||||||
.paramWarnings = {
|
.paramWarnings = {
|
||||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
|
||||||
},
|
},
|
||||||
.state = DiagnosticsState<WarningID>(),
|
.state = DiagnosticsState<WarningID>(),
|
||||||
.nbErrors = 0,
|
.nbErrors = 0,
|
||||||
@@ -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
|
|
||||||
|
|||||||
124
src/util.cpp
124
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';
|
||||||
}
|
}
|
||||||
@@ -41,6 +58,14 @@ bool isAlphanumeric(int c) {
|
|||||||
return isLetter(c) || isDigit(c);
|
return isLetter(c) || isDigit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char toLower(char c) {
|
||||||
|
return isUpper(c) ? c - 'A' + 'a' : c;
|
||||||
|
}
|
||||||
|
|
||||||
|
char toUpper(char c) {
|
||||||
|
return isLower(c) ? c - 'a' + 'A' : c;
|
||||||
|
}
|
||||||
|
|
||||||
bool startsIdentifier(int c) {
|
bool startsIdentifier(int c) {
|
||||||
// This returns false for anonymous labels, which internally start with a '!',
|
// This returns false for anonymous labels, which internally start with a '!',
|
||||||
// and for section fragment literal labels, which internally start with a '$'.
|
// and for section fragment literal labels, which internally start with a '$'.
|
||||||
@@ -51,6 +76,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
|
||||||
|
|||||||
3
test/asm/abort-on-missing-incbin-start.asm
Normal file
3
test/asm/abort-on-missing-incbin-start.asm
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
section "test", rom0
|
||||||
|
incbin "incbin-mg-noexist.bin", 2
|
||||||
|
println "never reached"
|
||||||
1
test/asm/abort-on-missing-incbin-start.flags
Normal file
1
test/asm/abort-on-missing-incbin-start.flags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-MG
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
section "test", rom0
|
section "test", rom0
|
||||||
incbin "incbin-mg-noexist.bin", 2
|
incbin "incbin-mg-noexist.bin"
|
||||||
println "never reached"
|
println "never reached"
|
||||||
|
|||||||
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'
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user