mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
Compare commits
154 Commits
v0.9.4
...
8bedd710d7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bedd710d7 | ||
|
|
efb5a88edb | ||
|
|
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 | ||
|
|
8df88f92ba | ||
|
|
534a4efee4 | ||
|
|
cc96b4d517 | ||
|
|
0ccdbf509a | ||
|
|
531278961f | ||
|
|
85176ef10a | ||
|
|
02b880e1b0 | ||
|
|
c6997fe73c | ||
|
|
c578a7b761 | ||
|
|
8564df51e5 | ||
|
|
62d3b44768 | ||
|
|
ead5337fe0 | ||
|
|
0d509aa65c | ||
|
|
fcfc931867 | ||
|
|
3d155d5695 | ||
|
|
92ed6ece53 | ||
|
|
e0e9ef190a | ||
|
|
386fb5f398 | ||
|
|
94e9ef5213 | ||
|
|
0c4c25b2d2 | ||
|
|
f7167d8115 | ||
|
|
b7e0783ae7 | ||
|
|
77a105e189 | ||
|
|
a16dcc0ea5 | ||
|
|
9f373d49ac | ||
|
|
272019beb0 | ||
|
|
db6793f444 | ||
|
|
ea1358bbe6 | ||
|
|
2bdf61da70 | ||
|
|
92826a726a | ||
|
|
9c3ce69180 | ||
|
|
50d0b101c3 | ||
|
|
1bf1219e07 | ||
|
|
7b405513d9 | ||
|
|
7df9c12a6c | ||
|
|
30a8503dcd | ||
|
|
02310489c6 | ||
|
|
5f8b7474b4 | ||
|
|
92a9c73ee7 | ||
|
|
7ade3e74b3 | ||
|
|
978e832914 | ||
|
|
2130a5ba1f | ||
|
|
9fc83efe06 | ||
|
|
e41ce49698 | ||
|
|
1574b5b1f7 | ||
|
|
3f6db080b4 | ||
|
|
f9a55bd5cd | ||
|
|
a4a830776b | ||
|
|
34d99b273c | ||
|
|
feb8365812 | ||
|
|
bf66e346f0 | ||
|
|
3a0a4b7f90 | ||
|
|
39f0f9edc0 | ||
|
|
a3983b7b0f | ||
|
|
504a45a4ed | ||
|
|
98c5c7f776 | ||
|
|
7020cf7188 | ||
|
|
70d129fcd7 | ||
|
|
2d5f4d8910 | ||
|
|
26c6911c8f | ||
|
|
f1fd3abcff | ||
|
|
2cae47a5a2 | ||
|
|
ac75a085fa | ||
|
|
0c1b422c36 | ||
|
|
23ce888d65 | ||
|
|
d992b21141 | ||
|
|
903492cce2 | ||
|
|
fc9b614225 | ||
|
|
543b7fa6c2 | ||
|
|
5a7ffd19b0 | ||
|
|
7be7483571 | ||
|
|
2eeb333be0 | ||
|
|
fe8baaec50 | ||
|
|
752b273aec | ||
|
|
b51056f743 | ||
|
|
ee1aed51d8 | ||
|
|
db2cda790c | ||
|
|
86b43ea14f |
@@ -71,6 +71,7 @@ Language: Cpp
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PPIndentWidth: -1
|
||||
PenaltyBreakScopeResolution: 1000
|
||||
PointerAlignment: Right
|
||||
QualifierAlignment: Right
|
||||
ReflowComments: true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
root = true
|
||||
indent_style = tab
|
||||
indent_size = tab
|
||||
tab_width = 8
|
||||
tab_width = 4
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
body: |
|
||||
Please ensure that the packages below work properly.
|
||||
Once that's done, replace this text with the changelog, un-draft the release, and update the `release` branch.
|
||||
By the way, if you forgot to update `include/version.hpp`, RGBASM's version test is gonna fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
|
||||
By the way, if you forgot to update `include/version.hpp`, RGBASM's version test is going to fail in the tag's regression testing! (Use `git push --delete origin <tag>` to delete it)
|
||||
draft: true # Don't publish the release quite yet...
|
||||
prerelease: ${{ contains(github.ref, '-rc') }}
|
||||
files: |
|
||||
|
||||
2
.github/workflows/testing.yml
vendored
2
.github/workflows/testing.yml
vendored
@@ -373,7 +373,7 @@ jobs:
|
||||
- name: Build & test using CMake on FreeBSD
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
release: "15.0"
|
||||
release: "14.3"
|
||||
usesh: true
|
||||
prepare: |
|
||||
pkg install -y \
|
||||
|
||||
279
ARCHITECTURE.md
Normal file
279
ARCHITECTURE.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# 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
|
||||
|
||||
The RGBDS source code file structure is as follows:
|
||||
|
||||
```
|
||||
rgbds/
|
||||
├── .github/
|
||||
│ ├── scripts/
|
||||
│ │ └── ...
|
||||
│ └── workflows/
|
||||
│ └── ...
|
||||
├── contrib/
|
||||
│ ├── bash_compl/
|
||||
│ ├── zsh_compl/
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
├── include/
|
||||
│ └── ...
|
||||
├── man/
|
||||
│ └── ...
|
||||
├── src/
|
||||
│ ├── asm/
|
||||
│ │ └── ...
|
||||
│ ├── extern/
|
||||
│ │ └── ...
|
||||
│ ├── fix/
|
||||
│ │ └── ...
|
||||
│ ├── gfx/
|
||||
│ │ └── ...
|
||||
│ ├── link/
|
||||
│ │ └── ...
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── bison.sh
|
||||
│ └── ...
|
||||
├── test/
|
||||
│ ├── fetch-test-deps.sh
|
||||
│ ├── run-tests.sh
|
||||
│ └── ...
|
||||
├── .clang-format
|
||||
├── .clang-tidy
|
||||
├── CMakeLists.txt
|
||||
├── compile_flags.txt
|
||||
├── Dockerfile
|
||||
└── Makefile
|
||||
```
|
||||
|
||||
- **`.github/`:**
|
||||
Files related to the integration of the RGBDS codebase with GitHub features.
|
||||
* **`scripts/`:**
|
||||
Scripts used by GitHub Actions workflow files.
|
||||
* **`workflows/`:**
|
||||
GitHub Actions CI workflow description files. Used for automated testing, deployment, etc.
|
||||
- **`contrib/`:**
|
||||
Scripts and other resources which may be useful to RGBDS users and developers.
|
||||
* **`bash_compl/`:**
|
||||
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.
|
||||
* **`zsh_compl/`:**
|
||||
Tab completion scripts for use with `zsh`. Put them somewhere in your `fpath`, and they should auto-load when you open a shell.
|
||||
- **`include/`:**
|
||||
Header files for the respective source files in `src`.
|
||||
- **`man/`:**
|
||||
Manual pages to be read with `man`, written in the [`mandoc`](https://mandoc.bsd.lv) dialect.
|
||||
- **`src/`:**
|
||||
Source code of RGBDS.
|
||||
* **`asm/`:**
|
||||
Source code of RGBASM.
|
||||
* **`extern/`:**
|
||||
Source code copied from external sources.
|
||||
* **`fix/`:**
|
||||
Source code of RGBFIX.
|
||||
* **`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
|
||||
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`
|
||||
header.
|
||||
years). If you are adding new files, you need to use the
|
||||
`SPDX-License-Identifier: MIT` header.
|
||||
|
||||
1. Fork this repository.
|
||||
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
|
||||
the code much easier).
|
||||
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
|
||||
coding style according to our standards defined in `.clang-format`.
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
There are two kinds of test.
|
||||
|
||||
#### Simple tests
|
||||
|
||||
Each `.asm` file corresponds to one test.
|
||||
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 `.err` file exists, RGBASM's error output (`warn`, errors, etc.) must match its contents.
|
||||
If a `.flags` file exists, its first line contains flags to pass to RGBASM.
|
||||
(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.
|
||||
|
||||
#### 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
Each `.flags` file corresponds to one test.
|
||||
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 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 one *does* exist, RGBFIX's return status is ignored, but its error output **must** match the `.err` file's contents.
|
||||
If no `.err` file exists, RGBFIX is simply expected to be able to process the
|
||||
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`.
|
||||
|
||||
@@ -139,20 +185,27 @@ There are three kinds of test.
|
||||
|
||||
Each `.png` file corresponds to one test.
|
||||
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.
|
||||
|
||||
If no `.err` file exists, RGBGFX is simply expected to be able to process the file normally.
|
||||
If one *does* exist, RGBGFX's return status is ignored, but its output **must** match the `.err` file's contents.
|
||||
If no `.err` file exists, RGBGFX is simply expected to be able to process the
|
||||
file normally.
|
||||
If one *does* exist, RGBGFX's return status is ignored, but its output **must**
|
||||
match the `.err` file's contents.
|
||||
|
||||
#### Reverse tests
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -161,18 +214,24 @@ Each one is a binary RNG file which is passed to the `rgbgfx_test` program.
|
||||
|
||||
### Downstream projects
|
||||
|
||||
1. Make sure the downstream project supports <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.
|
||||
1. Make sure the downstream project supports
|
||||
<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.
|
||||
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
|
||||
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.)
|
||||
3. Add the project to `test/run-tests.sh`: add a new `test_downstream` line at the bottom, following the existing pattern:
|
||||
(The date is used to avoid fetching too much history when cloning the
|
||||
repositories.)
|
||||
3. Add the project to `test/run-tests.sh`: add a new `test_downstream` line at
|
||||
the bottom, following the existing pattern:
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
# e.g. to build and tag as 'master'
|
||||
|
||||
@@ -32,3 +32,17 @@
|
||||
- yenatch <yenatch@gmail.com>
|
||||
- phs <phil@philhsmith.com>
|
||||
- jidoc01 <jidoc01@naver.com>
|
||||
|
||||
[<img src="https://contrib.rocks/image?repo=gbdev/rgbds">](https://github.com/gbdev/rgbds/graphs/contributors)
|
||||
|
||||
Contributor image made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
RGBGFX generates palettes using algorithms found in the paper
|
||||
["Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"](https://arxiv.org/abs/1605.00558)
|
||||
([GitHub](https://github.com/pagination-problem/pagination), MIT license),
|
||||
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 by [LIJI](https://github.com/LIJI32).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM debian:12-slim
|
||||
LABEL org.opencontainers.image.source=https://github.com/gbdev/rgbds
|
||||
ARG version=0.9.4
|
||||
ARG version=1.0.0-rc2
|
||||
WORKDIR /rgbds
|
||||
|
||||
COPY . .
|
||||
|
||||
24
Makefile
24
Makefile
@@ -3,7 +3,7 @@
|
||||
.SUFFIXES:
|
||||
.SUFFIXES: .cpp .y .o
|
||||
|
||||
.PHONY: all clean install checkdiff develop debug profile coverage tidy iwyu mingw32 mingw64 wine-shim dist
|
||||
.PHONY: all clean install checkdiff develop debug profile coverage format tidy iwyu mingw32 mingw64 wine-shim dist
|
||||
|
||||
# User-defined variables
|
||||
|
||||
@@ -51,8 +51,11 @@ all: rgbasm rgblink rgbfix rgbgfx
|
||||
|
||||
common_obj := \
|
||||
src/extern/getopt.o \
|
||||
src/cli.o \
|
||||
src/diagnostics.o \
|
||||
src/usage.o
|
||||
src/style.o \
|
||||
src/usage.o \
|
||||
src/util.o
|
||||
|
||||
rgbasm_obj := \
|
||||
${common_obj} \
|
||||
@@ -72,15 +75,17 @@ rgbasm_obj := \
|
||||
src/asm/symbol.o \
|
||||
src/asm/warning.o \
|
||||
src/extern/utf8decoder.o \
|
||||
src/backtrace.o \
|
||||
src/linkdefs.o \
|
||||
src/opmath.o \
|
||||
src/util.o
|
||||
src/verbosity.o
|
||||
|
||||
src/asm/lexer.o src/asm/main.o: src/asm/parser.hpp
|
||||
|
||||
rgblink_obj := \
|
||||
${common_obj} \
|
||||
src/link/assign.o \
|
||||
src/link/fstack.o \
|
||||
src/link/lexer.o \
|
||||
src/link/layout.o \
|
||||
src/link/main.o \
|
||||
@@ -93,14 +98,16 @@ rgblink_obj := \
|
||||
src/link/symbol.o \
|
||||
src/link/warning.o \
|
||||
src/extern/utf8decoder.o \
|
||||
src/backtrace.o \
|
||||
src/linkdefs.o \
|
||||
src/opmath.o \
|
||||
src/util.o
|
||||
src/verbosity.o
|
||||
|
||||
src/link/lexer.o src/link/main.o: src/link/script.hpp
|
||||
|
||||
rgbfix_obj := \
|
||||
${common_obj} \
|
||||
src/fix/fix.o \
|
||||
src/fix/main.o \
|
||||
src/fix/mbc.o \
|
||||
src/fix/warning.o
|
||||
@@ -112,12 +119,13 @@ rgbgfx_obj := \
|
||||
src/gfx/pal_packing.o \
|
||||
src/gfx/pal_sorting.o \
|
||||
src/gfx/pal_spec.o \
|
||||
src/gfx/palette.o \
|
||||
src/gfx/png.o \
|
||||
src/gfx/process.o \
|
||||
src/gfx/reverse.o \
|
||||
src/gfx/rgba.o \
|
||||
src/gfx/warning.o \
|
||||
src/util.o
|
||||
src/verbosity.o
|
||||
|
||||
rgbasm: ${rgbasm_obj}
|
||||
$Q${CXX} ${REALLDFLAGS} -o $@ ${rgbasm_obj} ${REALCXXFLAGS} src/version.cpp
|
||||
@@ -233,10 +241,14 @@ coverage:
|
||||
$Qenv ${MAKE} \
|
||||
CXXFLAGS="-ggdb3 -Og --coverage -fno-omit-frame-pointer -fno-optimize-sibling-calls"
|
||||
|
||||
# Target used in development to format source code with clang-format.
|
||||
format:
|
||||
$Qclang-format -i $$(git ls-files '*.hpp' '*.cpp')
|
||||
|
||||
# Target used in development to check code with clang-tidy.
|
||||
# Requires Bison-generated header files to exist.
|
||||
tidy: src/asm/parser.hpp src/link/script.hpp
|
||||
$Qclang-tidy -p . $$(find src -name '*.cpp')
|
||||
$Qclang-tidy -p . $$(git ls-files '*.hpp' '*.cpp')
|
||||
|
||||
# Target used in development to remove unused `#include` headers.
|
||||
iwyu:
|
||||
|
||||
122
README.md
122
README.md
@@ -11,19 +11,19 @@ for the Game Boy and Game Boy Color. It consists of:
|
||||
This is a fork of the original RGBDS which aims to make the programs more like
|
||||
other UNIX tools.
|
||||
|
||||
|
||||
This toolchain is maintained [on GitHub](https://github.com/gbdev/rgbds).
|
||||
|
||||
The documentation of this toolchain can be [viewed online](https://rgbds.gbdev.io/docs/).
|
||||
The documentation of this toolchain can be [viewed online](https://rgbds.gbdev.io/docs/),
|
||||
including its [basic usage and development history](https://rgbds.gbdev.io/docs/rgbds.7).
|
||||
It is generated from the man pages found in this repository.
|
||||
The source code of the website itself is on GitHub as well under the repository
|
||||
[rgbds-www](https://github.com/gbdev/rgbds-www).
|
||||
|
||||
If you want to contribute or maintain RGBDS, or you have questions regarding the code, its
|
||||
organization, etc. you can find the maintainers [on the gbdev community channels](https://gbdev.io/chat)
|
||||
or via mail at `rgbds at gbdev dot io`.
|
||||
If you want to contribute or maintain RGBDS, read [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
If you have questions regarding the code, its organization, etc. you can find the maintainers
|
||||
[on the GBDev community channels](https://gbdev.io/chat) or via mail at `rgbds at gbdev dot io`.
|
||||
|
||||
## 1. Installing RGBDS
|
||||
## Installing RGBDS
|
||||
|
||||
The [installation procedure](https://rgbds.gbdev.io/install) is available
|
||||
online for various platforms. [Building from source](https://rgbds.gbdev.io/install/source)
|
||||
@@ -55,113 +55,3 @@ cmake --install build --prefix install_dir
|
||||
```
|
||||
|
||||
(If you set a `SUFFIX`, it should include the `.exe` extension on Windows.)
|
||||
|
||||
## 2. RGBDS Folder Organization
|
||||
|
||||
The RGBDS source code file structure is as follows:
|
||||
|
||||
```
|
||||
.
|
||||
├── .github/
|
||||
│ ├── scripts/
|
||||
│ │ └── ...
|
||||
│ └── workflows/
|
||||
│ └── ...
|
||||
├── contrib/
|
||||
│ ├── zsh_compl/
|
||||
│ │ └── ...
|
||||
│ └── ...
|
||||
├── include/
|
||||
│ └── ...
|
||||
├── man/
|
||||
│ └── ...
|
||||
├── src/
|
||||
│ ├── asm/
|
||||
│ │ └── ...
|
||||
│ ├── extern/
|
||||
│ │ └── ...
|
||||
│ ├── fix/
|
||||
│ │ └── ...
|
||||
│ ├── gfx/
|
||||
│ │ └── ...
|
||||
│ ├── link/
|
||||
│ │ └── ...
|
||||
│ ├── CMakeLists.txt
|
||||
│ └── ...
|
||||
├── test/
|
||||
│ ├── ...
|
||||
│ └── run-tests.sh
|
||||
├── .clang-format
|
||||
├── CMakeLists.txt
|
||||
├── compile_flags.txt
|
||||
├── Dockerfile
|
||||
├── Makefile
|
||||
└── README.md
|
||||
```
|
||||
|
||||
- `.github/` - files and scripts related to the integration of the RGBDS codebase with
|
||||
GitHub.
|
||||
* `scripts/` - scripts used by workflow files.
|
||||
* `workflows/` - CI workflow description files.
|
||||
- `contrib/` - scripts and other resources which may be useful to users and developers of
|
||||
RGBDS.
|
||||
* `zsh_compl` contains tab completion scripts for use with zsh. Put them somewhere in
|
||||
your `fpath`, and they should auto-load.
|
||||
* `bash_compl` contains tab completion scripts for use with bash. Run them with `source`
|
||||
somewhere in your `.bashrc`, and they should load every time you open a shell.
|
||||
- `include/` - header files for the respective source files in `src`.
|
||||
- `man/` - manual pages.
|
||||
- `src/` - source code of RGBDS.
|
||||
* Note that the code unique to each RGBDS tool is stored in its respective subdirectory
|
||||
(RGBASM's code is in `src/asm/`, for example). `src/extern/` contains code imported from
|
||||
external sources.
|
||||
- `test/` - testing framework used to verify that changes to the code don't break or
|
||||
modify the behavior of RGBDS.
|
||||
- `.clang-format` - code style for automated C++ formatting with
|
||||
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
|
||||
- `compile_flags.txt` - compiler flags for C++ static analysis with
|
||||
[`clang-tidy`](https://clang.llvm.org/extra/clang-tidy/).
|
||||
- `Dockerfile` - defines how to build RGBDS with Docker.
|
||||
|
||||
## 3. History
|
||||
|
||||
- 1996-10-01: Carsten Sørensen (a.k.a. SurfSmurf) releases
|
||||
[xAsm](http://otakunozoku.com/RGBDSdocs/asm.htm),
|
||||
[xLink](http://otakunozoku.com/RGBDSdocs/link.htm), and
|
||||
[RGBFix](http://otakunozoku.com/RGBDSdocs/fix.htm),
|
||||
a Game Boy SM83 (GBZ80) assembler/linker system for DOS/Win32.
|
||||
- 1997-07-03: Sørensen releases [ASMotor](http://otakunozoku.com/RGBDSdocs/geninfo.htm),
|
||||
packaging the three programs together and moving towards making them a
|
||||
general-purpose target-independent system.
|
||||
- 1999-08-01: Justin Lloyd (a.k.a. Otaku no Zoku) adapts ASMotor to re-focus
|
||||
on SM83 assembly/machine code, and releases this version as
|
||||
[RGBDS](http://otakunozoku.com/rednex-gameboy-development-system/).
|
||||
- 2009-06-11: Vegard Nossum adapts the code to be more UNIX-like and releases
|
||||
this version as [rgbds-linux](https://github.com/vegard/rgbds-linux).
|
||||
- 2010-01-12: Anthony J. Bentley [forks](https://github.com/bentley) Nossum's
|
||||
repository. The fork becomes the reference implementation of RGBDS.
|
||||
- 2010-09-25: Sørensen continues development of
|
||||
[ASMotor](https://github.com/asmotor/asmotor) to this day.
|
||||
- 2015-01-18: stag019 begins implementing [RGBGFX](https://github.com/stag019/rgbgfx),
|
||||
a PNG-to-Game Boy graphics converter, for eventual integration into RGBDS.
|
||||
- 2016-09-05: RGBGFX is
|
||||
[integrated](https://github.com/gbdev/rgbds/commit/c3c31138ddbd8680d4e67957e387f2816798a71b)
|
||||
into Bentley's repository.
|
||||
- 2017-02-23: Bentley's repository is moved to the [rednex](https://github.com/rednex)
|
||||
organization.
|
||||
- 2018-01-26: The codebase is [relicensed](https://github.com/gbdev/rgbds/issues/128)
|
||||
under the MIT license.
|
||||
- 2020-09-15: The repository is [moved](https://github.com/gbdev/rgbds/issues/567)
|
||||
to the [gbdev](https://github.com/gbdev) organization.
|
||||
- 2022-05-17: The [rgbds.gbdev.io](https://rgbds.gbdev.io) website for RGBDS
|
||||
documentation and downloads is published.
|
||||
|
||||
## 4. Acknowledgements
|
||||
|
||||
RGBGFX generates palettes using algorithms found in the paper
|
||||
["Algorithms for the Pagination Problem, a Bin Packing with Overlapping Items"](https://arxiv.org/abs/1605.00558)
|
||||
([GitHub](https://github.com/pagination-problem/pagination), MIT license),
|
||||
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
|
||||
by [LIJI](https://github.com/LIJI32).
|
||||
|
||||
@@ -28,6 +28,7 @@ _rgbasm_completions() {
|
||||
[V]="version:normal"
|
||||
[W]="warning:warning"
|
||||
[w]=":normal"
|
||||
[B]="backtrace:unk"
|
||||
[b]="binary-digits:unk"
|
||||
[D]="define:unk"
|
||||
[E]="export-all:normal"
|
||||
@@ -61,7 +62,7 @@ _rgbasm_completions() {
|
||||
parse_short_opt() {
|
||||
# These options act like a long option (= takes up the entire word), but only use a single dash
|
||||
# So, they need some special handling
|
||||
if [[ "$1" = "-M"[GP] ]]; then
|
||||
if [[ "$1" = "-M"[CGP] ]]; then
|
||||
state=normal
|
||||
optlen=${#1}
|
||||
return;
|
||||
@@ -146,7 +147,7 @@ _rgbasm_completions() {
|
||||
# It is, try to complete one
|
||||
mapfile -t COMPREPLY < <(compgen -W "${opts[*]%%:*}" -P '--' -- "${cur_word#--}")
|
||||
return 0
|
||||
elif [[ "$cur_word" = '-M'[GPQT] ]]; then
|
||||
elif [[ "$cur_word" = '-M'[CGPQT] ]]; then
|
||||
# These options act like long opts with no arguments, so return them and exactly them
|
||||
COMPREPLY=( "$cur_word" )
|
||||
return 0
|
||||
@@ -183,6 +184,7 @@ _rgbasm_completions() {
|
||||
empty-data-directive
|
||||
empty-macro-arg
|
||||
empty-strrpl
|
||||
export-undefined
|
||||
large-constant
|
||||
macro-shift
|
||||
nested-comment
|
||||
|
||||
@@ -145,6 +145,7 @@ _rgbfix_completions() {
|
||||
warning)
|
||||
mapfile -t COMPREPLY < <(compgen -W "
|
||||
mbc
|
||||
obsolete
|
||||
overwrite
|
||||
sgb
|
||||
truncation
|
||||
|
||||
@@ -157,6 +157,7 @@ _rgbgfx_completions() {
|
||||
warning)
|
||||
mapfile -t COMPREPLY < <(compgen -W "
|
||||
embedded
|
||||
obsolete
|
||||
trim-nonempty
|
||||
all
|
||||
everything
|
||||
|
||||
@@ -12,6 +12,7 @@ _rgblink_completions() {
|
||||
[W]="warning:warning"
|
||||
[M]="no-sym-in-map:normal"
|
||||
[d]="dmg:normal"
|
||||
[B]="backtrace:unk"
|
||||
[l]="linkerscript:glob-*"
|
||||
[m]="map:glob-*.map"
|
||||
[n]="sym:glob-*.sym"
|
||||
|
||||
@@ -16,6 +16,7 @@ _rgbasm_warnings() {
|
||||
'empty-data-directive:Warn on arg-less d[bwl] in ROM'
|
||||
'empty-macro-arg:Warn on empty macro arg'
|
||||
'empty-strrpl:Warn on calling STRRPL with empty pattern'
|
||||
'export-undefined:Warn on EXPORT of an undefined symbol'
|
||||
'large-constant:Warn on constants too large for a signed 32-bit int'
|
||||
'macro-shift:Warn when shifting macro args part their limits'
|
||||
'nested-comment:Warn on "/*" inside block comments'
|
||||
@@ -39,10 +40,12 @@ local args=(
|
||||
'(- : * options)'{-h,--help}'[Print help text and exit]'
|
||||
|
||||
'(-E --export-all)'{-E,--export-all}'[Export all symbols]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Print additional messages regarding progression]'
|
||||
'(-v --verbose)'{-v,--verbose}'[Enable verbose output]'
|
||||
-w'[Disable all warnings]'
|
||||
|
||||
'(-B --backtrace)'{-B,--backtrace}'+[Set backtrace depth or style]:param:'
|
||||
'(-b --binary-digits)'{-b,--binary-digits}'+[Change chars for binary constants]:digit spec:'
|
||||
--color'[Whether to use color in output]:color:(auto always never)'
|
||||
'*'{-D,--define}'+[Define a string symbol]:name + value (default 1):'
|
||||
'(-g --gfx-chars)'{-g,--gfx-chars}'+[Change chars for gfx constants]:chars spec:'
|
||||
'(-I --include)'{-I,--include}'+[Add an include directory]:include path:_files -/'
|
||||
|
||||
@@ -42,6 +42,7 @@ _rgbfix_warnings() {
|
||||
'everything:Enable literally everything'
|
||||
|
||||
'mbc:Warn about issues with MBC specs'
|
||||
'obsolete:Warn when using deprecated features'
|
||||
'overwrite:Warn when overwriting non-zero bytes'
|
||||
'sgb:Warn when SGB flag conflicts with old licensee code'
|
||||
'truncation:Warn when values are truncated to fit'
|
||||
@@ -62,6 +63,7 @@ local args=(
|
||||
'(-f --fix-spec -v --validate)'{-v,--validate}'[Shorthand for -f lhg]'
|
||||
-w'[Disable all warnings]'
|
||||
|
||||
--color'[Whether to use color in output]:color:(auto always never)'
|
||||
'(-f --fix-spec -v --validate)'{-f,--fix-spec}'+[Fix or trash some header values]:fix spec:'
|
||||
'(-i --game-id)'{-i,--game-id}'+[Set game ID string]:4-char game ID:'
|
||||
'(-k --new-licensee)'{-k,--new-licensee}'+[Set new licensee string]:2-char licensee ID:'
|
||||
|
||||
@@ -17,6 +17,7 @@ _rgbgfx_warnings() {
|
||||
'everything:Enable literally everything'
|
||||
|
||||
'embedded:Warn when using embedded PLTE without "-c embedded"'
|
||||
'obsolete:Warn when using deprecated features'
|
||||
'trim-nonempty:Warn when "-x" trims nonempty tiles'
|
||||
)
|
||||
_describe warning warnings
|
||||
@@ -44,6 +45,7 @@ local args=(
|
||||
'(-a --attr-map -A --auto-attr-map)'{-a,--attr-map}'+[Generate a map of tile attributes (mirroring)]:attrmap file:_files'
|
||||
'(-B --background-color)'{-B,--background-color}'+[Ignore tiles containing only specified color]:color:'
|
||||
'(-b --base-tiles)'{-b,--base-tiles}'+[Base tile IDs for tile map output]:base tile IDs:'
|
||||
--color'[Whether to use color in output]:color:(auto always never)'
|
||||
'(-c --colors)'{-c,--colors}'+[Specify color palettes]:palette spec:'
|
||||
'(-d --depth)'{-d,--depth}'+[Set bit depth]:bit depth:_depths'
|
||||
'(-i --input-tileset)'{-i,--input-tileset}'+[Use specific tiles]:tileset file:_files -g "*.2bpp"'
|
||||
|
||||
@@ -28,6 +28,8 @@ local args=(
|
||||
'(-w --wramx)'{-w,--wramx}'[Disable WRAM banking]'
|
||||
'(-x --nopad)'{-x,--nopad}'[Disable padding the end of the final file]'
|
||||
|
||||
'(-B --backtrace)'{-B,--backtrace}'+[Set backtrace depth or style]:param:'
|
||||
--color'[Whether to use color in output]:color:(auto always never)'
|
||||
'(-l --linkerscript)'{-l,--linkerscript}"+[Use a linker script]:linker script:_files -g '*.link'"
|
||||
'(-M --no-sym-in-map)'{-M,--no-sym-in-map}'[Do not output symbol names in map file]'
|
||||
'(-m --map)'{-m,--map}"+[Produce a map file]:map file:_files -g '*.map'"
|
||||
|
||||
@@ -4,39 +4,58 @@
|
||||
#define RGBDS_ASM_ACTIONS_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "asm/output.hpp" // AssertionType
|
||||
#include "asm/rpn.hpp" // RPNCommand
|
||||
#include "linkdefs.hpp" // AssertionType, RPNCommand
|
||||
|
||||
#include "asm/rpn.hpp" // Expression
|
||||
|
||||
struct AlignmentSpec {
|
||||
uint8_t alignment;
|
||||
uint16_t alignOfs;
|
||||
};
|
||||
|
||||
void act_If(int32_t condition);
|
||||
void act_Elif(int32_t condition);
|
||||
void act_Else();
|
||||
void act_Endc();
|
||||
|
||||
AlignmentSpec act_Alignment(int32_t alignment, int32_t alignOfs);
|
||||
|
||||
void act_Assert(AssertionType type, Expression const &expr, std::string const &message);
|
||||
void act_StaticAssert(AssertionType type, int32_t condition, std::string const &message);
|
||||
|
||||
std::optional<std::string> act_ReadFile(std::string const &name, uint32_t maxLen);
|
||||
|
||||
uint32_t act_StringToNum(std::vector<int32_t> const &str);
|
||||
uint32_t act_CharToNum(std::string const &str);
|
||||
uint32_t act_StringToNum(std::string const &str);
|
||||
|
||||
int32_t act_CharVal(std::string const &str);
|
||||
int32_t act_CharVal(std::string const &str, int32_t negIdx);
|
||||
uint8_t act_StringByte(std::string const &str, int32_t negIdx);
|
||||
|
||||
size_t act_StringLen(std::string const &str, bool printErrors);
|
||||
std::string act_StringSlice(std::string const &str, uint32_t start, uint32_t stop);
|
||||
std::string act_StringSub(std::string const &str, uint32_t pos, uint32_t len);
|
||||
std::string
|
||||
act_StringSlice(std::string const &str, int32_t negStart, std::optional<int32_t> negStop);
|
||||
std::string act_StringSub(std::string const &str, int32_t negPos, std::optional<uint32_t> optLen);
|
||||
|
||||
size_t act_CharLen(std::string const &str);
|
||||
std::string act_StringChar(std::string const &str, uint32_t idx);
|
||||
std::string act_CharSub(std::string const &str, uint32_t pos);
|
||||
std::string act_StringChar(std::string const &str, int32_t negIdx);
|
||||
std::string act_CharSub(std::string const &str, int32_t negPos);
|
||||
int32_t act_CharCmp(std::string_view str1, std::string_view str2);
|
||||
|
||||
uint32_t act_AdjustNegativeIndex(int32_t idx, size_t len, char const *functionName);
|
||||
uint32_t act_AdjustNegativePos(int32_t pos, size_t len, char const *functionName);
|
||||
|
||||
std::string act_StringReplace(std::string_view str, std::string const &old, std::string const &rep);
|
||||
std::string act_StringFormat(
|
||||
std::string const &spec, std::vector<std::variant<uint32_t, std::string>> const &args
|
||||
);
|
||||
|
||||
std::string act_SectionName(std::string const &symName);
|
||||
|
||||
void act_CompoundAssignment(std::string const &symName, RPNCommand op, int32_t constValue);
|
||||
|
||||
void act_FailAssert(AssertionType type);
|
||||
void act_FailAssertMsg(AssertionType type, std::string const &message);
|
||||
|
||||
#endif // RGBDS_ASM_ACTIONS_HPP
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#define RGBDS_ASM_CHARMAP_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -7,19 +7,7 @@
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
enum FormatState {
|
||||
FORMAT_SIGN, // expects '+' or ' ' (optional)
|
||||
FORMAT_EXACT, // expects '#' (optional)
|
||||
FORMAT_ALIGN, // expects '-' (optional)
|
||||
FORMAT_WIDTH, // expects '0'-'9', max 255 (optional) (leading '0' indicates pad)
|
||||
FORMAT_FRAC, // got '.', expects '0'-'9', max 255 (optional)
|
||||
FORMAT_PREC, // got 'q', expects '0'-'9', range 1-31 (optional)
|
||||
FORMAT_DONE, // got [duXxbofs] (required)
|
||||
FORMAT_INVALID, // got unexpected character
|
||||
};
|
||||
|
||||
class FormatSpec {
|
||||
FormatState state;
|
||||
int sign;
|
||||
bool exact;
|
||||
bool alignLeft;
|
||||
@@ -30,15 +18,13 @@ class FormatSpec {
|
||||
bool hasPrec;
|
||||
size_t precision;
|
||||
int type;
|
||||
bool valid;
|
||||
bool parsed;
|
||||
|
||||
public:
|
||||
bool isEmpty() const { return !state; }
|
||||
bool isValid() const { return valid || state == FORMAT_DONE; }
|
||||
bool isFinished() const { return state >= FORMAT_DONE; }
|
||||
bool isValid() const { return !!type; }
|
||||
bool isParsed() const { return parsed; }
|
||||
|
||||
void useCharacter(int c);
|
||||
void finishCharacters();
|
||||
size_t parseSpec(char const *spec);
|
||||
|
||||
void appendString(std::string &str, std::string const &value) const;
|
||||
void appendNumber(std::string &str, uint32_t value) const;
|
||||
|
||||
@@ -24,6 +24,7 @@ struct FileStackNode {
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
data;
|
||||
bool isQuiet; // Whether to omit this node from error reporting
|
||||
|
||||
std::shared_ptr<FileStackNode> parent; // Pointer to parent node, for error reporting
|
||||
// Line at which the parent context was exited
|
||||
@@ -40,16 +41,21 @@ struct FileStackNode {
|
||||
std::string &name() { return std::get<std::string>(data); }
|
||||
std::string const &name() const { return std::get<std::string>(data); }
|
||||
|
||||
FileStackNode(FileStackNodeType type_, std::variant<std::vector<uint32_t>, std::string> data_)
|
||||
: type(type_), data(data_) {}
|
||||
FileStackNode(
|
||||
FileStackNodeType type_,
|
||||
std::variant<std::vector<uint32_t>, std::string> data_,
|
||||
bool isQuiet_
|
||||
)
|
||||
: type(type_), data(data_), isQuiet(isQuiet_) {}
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
std::string reptChain() const;
|
||||
void printBacktrace(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
struct MacroArgs;
|
||||
|
||||
bool fstk_DumpCurrent();
|
||||
void fstk_VerboseOutputConfig();
|
||||
|
||||
void fstk_TraceCurrent();
|
||||
std::shared_ptr<FileStackNode> fstk_GetFileStack();
|
||||
std::shared_ptr<std::string> fstk_GetUniqueIDStr();
|
||||
MacroArgs *fstk_GetCurrentMacroArgs();
|
||||
@@ -61,16 +67,19 @@ bool fstk_FileError(std::string const &path, char const *functionName);
|
||||
bool fstk_FailedOnMissingInclude();
|
||||
|
||||
bool yywrap();
|
||||
bool fstk_RunInclude(std::string const &path);
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs);
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span);
|
||||
bool fstk_RunInclude(std::string const &path, bool isQuiet);
|
||||
void fstk_RunMacro(
|
||||
std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet
|
||||
);
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet);
|
||||
void fstk_RunFor(
|
||||
std::string const &symName,
|
||||
int32_t start,
|
||||
int32_t stop,
|
||||
int32_t step,
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
ContentSpan const &span,
|
||||
bool isQuiet
|
||||
);
|
||||
bool fstk_Break();
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
@@ -133,7 +134,7 @@ void lexer_ReachELSEBlock();
|
||||
|
||||
void lexer_CheckRecursionDepth();
|
||||
uint32_t lexer_GetLineNo();
|
||||
void lexer_DumpStringExpansions();
|
||||
void lexer_TraceStringExpansions();
|
||||
|
||||
struct Capture {
|
||||
uint32_t lineNo;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#ifndef RGBDS_ASM_MAIN_HPP
|
||||
#define RGBDS_ASM_MAIN_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
@@ -14,16 +15,16 @@ enum MissingInclude {
|
||||
};
|
||||
|
||||
struct Options {
|
||||
bool exportAll = false; // -E
|
||||
uint8_t fixPrecision = 16; // -Q
|
||||
size_t maxRecursionDepth = 64; // -r
|
||||
char binDigits[2] = {'0', '1'}; // -b
|
||||
char gfxDigits[4] = {'0', '1', '2', '3'}; // -g
|
||||
bool verbose = false; // -v
|
||||
FILE *dependFile = nullptr; // -M
|
||||
std::string targetFileName; // -MQ, -MT
|
||||
std::optional<std::string> targetFileName{}; // -MQ, -MT
|
||||
MissingInclude missingIncludeState = INC_ERROR; // -MC, -MG
|
||||
bool generatePhonyDeps = false; // -MP
|
||||
std::string objectFileName; // -o
|
||||
std::optional<std::string> objectFileName{}; // -o
|
||||
uint8_t padByte = 0; // -p
|
||||
uint64_t maxErrors = 0; // -X
|
||||
|
||||
@@ -35,18 +36,11 @@ struct Options {
|
||||
|
||||
void printDep(std::string const &depName) {
|
||||
if (dependFile) {
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName.c_str(), depName.c_str());
|
||||
fprintf(dependFile, "%s: %s\n", targetFileName->c_str(), depName.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (options.verbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif // RGBDS_ASM_MAIN_HPP
|
||||
|
||||
@@ -6,17 +6,18 @@
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Expression;
|
||||
struct FileStackNode;
|
||||
struct Symbol;
|
||||
|
||||
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_RegisterSymbol(Symbol &sym);
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift);
|
||||
void out_CreateAssert(
|
||||
AssertionType type, Expression const &expr, std::string const &message, uint32_t ofs
|
||||
|
||||
@@ -12,15 +12,25 @@
|
||||
|
||||
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 {
|
||||
std::variant<
|
||||
int32_t, // If the expression's value is known, it's here
|
||||
std::string // Why the expression is not known, if it isn't
|
||||
>
|
||||
data = 0;
|
||||
bool isSymbol = false; // Whether the expression represents a symbol suitable for const diffing
|
||||
std::vector<uint8_t> rpn{}; // Bytes serializing the RPN expression
|
||||
uint32_t rpnPatchSize = 0; // Size the expression will take in the object file
|
||||
std::vector<RPNValue> rpn{}; // Values to be serialized into the RPN expression
|
||||
|
||||
bool isKnown() const { return std::holds_alternative<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 makeBinaryOp(RPNCommand op, Expression &&src1, Expression const &src2);
|
||||
|
||||
bool makeCheckHRAM();
|
||||
void makeCheckRST();
|
||||
void makeCheckBitIndex(uint8_t mask);
|
||||
void addCheckHRAM();
|
||||
void addCheckRST();
|
||||
void addCheckBitIndex(uint8_t mask);
|
||||
|
||||
void checkNBit(uint8_t n) const;
|
||||
|
||||
private:
|
||||
void clear();
|
||||
uint8_t *reserveSpace(uint32_t size);
|
||||
uint8_t *reserveSpace(uint32_t size, uint32_t patchSize);
|
||||
void encode(std::vector<uint8_t> &buffer) const;
|
||||
};
|
||||
|
||||
bool checkNBit(int32_t v, uint8_t n, char const *name);
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <time.h>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@@ -30,8 +28,9 @@ bool sym_IsPC(Symbol const *sym); // Forward declaration for `getSection`
|
||||
struct Symbol {
|
||||
std::string name;
|
||||
SymbolType type;
|
||||
bool isExported; // Whether the symbol is to be exported
|
||||
bool isBuiltin; // Whether the symbol is a built-in
|
||||
bool isBuiltin;
|
||||
bool isExported; // Not relevant for SYM_MACRO or SYM_EQUS
|
||||
bool isQuiet; // Only relevant for SYM_MACRO
|
||||
Section *section;
|
||||
std::shared_ptr<FileStackNode> src; // Where the symbol was defined
|
||||
uint32_t fileLine; // Line where the symbol was defined
|
||||
@@ -71,7 +70,6 @@ struct Symbol {
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &));
|
||||
|
||||
void sym_SetExportAll(bool set);
|
||||
Symbol *sym_AddLocalLabel(std::string const &symName);
|
||||
Symbol *sym_AddLabel(std::string const &symName);
|
||||
Symbol *sym_AddAnonLabel();
|
||||
@@ -89,7 +87,9 @@ Symbol *sym_FindScopedSymbol(std::string const &symName);
|
||||
// Find a scoped symbol by name; do not return `@` or `_NARG` when they have no value
|
||||
Symbol *sym_FindScopedValidSymbol(std::string const &symName);
|
||||
Symbol const *sym_GetPC();
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span);
|
||||
Symbol *sym_AddMacro(
|
||||
std::string const &symName, int32_t defLineNo, ContentSpan const &span, bool isQuiet
|
||||
);
|
||||
Symbol *sym_Ref(std::string const &symName);
|
||||
Symbol *sym_AddString(std::string const &symName, std::shared_ptr<std::string> value);
|
||||
Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string> value);
|
||||
|
||||
@@ -23,6 +23,7 @@ enum WarningID {
|
||||
WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
|
||||
WARNING_EMPTY_MACRO_ARG, // Empty macro argument
|
||||
WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
|
||||
WARNING_EXPORT_UNDEFINED, // `EXPORT` of an undefined symbol
|
||||
WARNING_LARGE_CONSTANT, // Constants too large
|
||||
WARNING_MACRO_SHIFT, // `SHIFT` past available arguments in macro
|
||||
WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
|
||||
@@ -74,11 +75,10 @@ void fatal(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
|
||||
// Used for errors that make it impossible to assemble correctly, but don't
|
||||
// affect the following code. The code will fail to assemble but the user will
|
||||
// get a list of all errors at the end, making it easier to fix all of them at
|
||||
// once.
|
||||
void error(std::function<void()> callback);
|
||||
// Used for errors that handle their own backtrace output. The code will fail
|
||||
// to assemble but the user will get a list of all errors at the end, making it
|
||||
// easier to fix all of them at once.
|
||||
void errorNoTrace(std::function<void()> callback);
|
||||
|
||||
void requireZeroErrors();
|
||||
|
||||
|
||||
89
include/backtrace.hpp
Normal file
89
include/backtrace.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_BACKTRACE_HPP
|
||||
#define RGBDS_BACKTRACE_HPP
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
#define TRACE_SEPARATOR "<-"
|
||||
#define NODE_SEPARATOR "::"
|
||||
#define REPT_NODE_PREFIX "REPT~"
|
||||
|
||||
struct Tracing {
|
||||
uint64_t depth = 0;
|
||||
bool collapse = false;
|
||||
bool loud = false;
|
||||
};
|
||||
|
||||
extern Tracing tracing;
|
||||
|
||||
bool trace_ParseTraceDepth(char const *arg);
|
||||
|
||||
template<typename NodeT, typename NameFnT, typename LineNoFnT>
|
||||
void trace_PrintBacktrace(std::vector<NodeT> const &stack, NameFnT getName, LineNoFnT getLineNo) {
|
||||
size_t n = stack.size();
|
||||
if (n == 0) {
|
||||
return; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto printLocation = [&](size_t i) {
|
||||
NodeT const &item = stack[n - i - 1];
|
||||
style_Reset(stderr);
|
||||
if (!tracing.collapse) {
|
||||
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
||||
}
|
||||
fprintf(stderr, " %s ", i == 0 ? "at" : TRACE_SEPARATOR);
|
||||
style_Set(stderr, STYLE_CYAN, true);
|
||||
fputs(getName(item), stderr);
|
||||
style_Set(stderr, STYLE_CYAN, false);
|
||||
fprintf(stderr, "(%" PRIu32 ")", getLineNo(item));
|
||||
if (!tracing.collapse) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
};
|
||||
|
||||
if (tracing.collapse) {
|
||||
fputs(" ", stderr); // Just three spaces; the fourth will be handled by the loop
|
||||
}
|
||||
|
||||
if (tracing.depth == 0 || static_cast<size_t>(tracing.depth) >= n) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
printLocation(i);
|
||||
}
|
||||
} else {
|
||||
size_t last = tracing.depth / 2;
|
||||
size_t first = tracing.depth - last;
|
||||
size_t skipped = n - tracing.depth;
|
||||
for (size_t i = 0; i < first; ++i) {
|
||||
printLocation(i);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
|
||||
if (tracing.collapse) {
|
||||
fputs(" " TRACE_SEPARATOR, stderr);
|
||||
} else {
|
||||
fputs(" ", stderr); // Just three spaces; the fourth will be printed next
|
||||
}
|
||||
fprintf(stderr, " ...%zu more%s", skipped, last ? "..." : "");
|
||||
if (!tracing.collapse) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
for (size_t i = n - last; i < n; ++i) {
|
||||
printLocation(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (tracing.collapse) {
|
||||
putc('\n', stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
}
|
||||
|
||||
#endif // RGBDS_BACKTRACE_HPP
|
||||
21
include/cli.hpp
Normal file
21
include/cli.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_CLI_HPP
|
||||
#define RGBDS_CLI_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
|
||||
#include "extern/getopt.hpp" // option
|
||||
#include "usage.hpp"
|
||||
|
||||
void cli_ParseArgs(
|
||||
int argc,
|
||||
char *argv[],
|
||||
char const *shortOpts,
|
||||
option const *longOpts,
|
||||
void (*parseArg)(int, char *),
|
||||
Usage usage
|
||||
);
|
||||
|
||||
#endif // RGBDS_CLI_HPP
|
||||
@@ -9,8 +9,8 @@
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
@@ -30,35 +30,35 @@ struct WarningState {
|
||||
|
||||
std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::string &flag);
|
||||
|
||||
template<typename L>
|
||||
template<typename LevelEnumT>
|
||||
struct WarningFlag {
|
||||
char const *name;
|
||||
L level;
|
||||
LevelEnumT level;
|
||||
};
|
||||
|
||||
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||
|
||||
template<typename W>
|
||||
template<typename WarningEnumT>
|
||||
struct ParamWarning {
|
||||
W firstID;
|
||||
W lastID;
|
||||
WarningEnumT firstID;
|
||||
WarningEnumT lastID;
|
||||
uint8_t defaultLevel;
|
||||
};
|
||||
|
||||
template<typename W>
|
||||
template<typename WarningEnumT>
|
||||
struct DiagnosticsState {
|
||||
WarningState flagStates[W::NB_WARNINGS];
|
||||
WarningState metaStates[W::NB_WARNINGS];
|
||||
WarningState flagStates[WarningEnumT::NB_WARNINGS];
|
||||
WarningState metaStates[WarningEnumT::NB_WARNINGS];
|
||||
bool warningsEnabled = true;
|
||||
bool warningsAreErrors = false;
|
||||
};
|
||||
|
||||
template<typename L, typename W>
|
||||
template<typename LevelEnumT, typename WarningEnumT>
|
||||
struct Diagnostics {
|
||||
std::vector<WarningFlag<L>> metaWarnings;
|
||||
std::vector<WarningFlag<L>> warningFlags;
|
||||
std::vector<ParamWarning<W>> paramWarnings;
|
||||
DiagnosticsState<W> state;
|
||||
std::vector<WarningFlag<LevelEnumT>> metaWarnings;
|
||||
std::vector<WarningFlag<LevelEnumT>> warningFlags;
|
||||
std::vector<ParamWarning<WarningEnumT>> paramWarnings;
|
||||
DiagnosticsState<WarningEnumT> state;
|
||||
uint64_t nbErrors;
|
||||
|
||||
void incrementErrors() {
|
||||
@@ -67,12 +67,12 @@ struct Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
WarningBehavior getWarningBehavior(W id) const;
|
||||
std::string processWarningFlag(char const *flag);
|
||||
WarningBehavior getWarningBehavior(WarningEnumT id) const;
|
||||
void processWarningFlag(char const *flag);
|
||||
};
|
||||
|
||||
template<typename L, typename W>
|
||||
WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
||||
template<typename LevelEnumT, typename WarningEnumT>
|
||||
WarningBehavior Diagnostics<LevelEnumT, WarningEnumT>::getWarningBehavior(WarningEnumT id) const {
|
||||
// Check if warnings are globally disabled
|
||||
if (!state.warningsEnabled) {
|
||||
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 (warningFlags[id].level == L::LEVEL_DEFAULT) { // enabled by default
|
||||
if (warningFlags[id].level == LevelEnumT::LEVEL_DEFAULT) { // enabled by default
|
||||
return enabledBehavior;
|
||||
}
|
||||
|
||||
@@ -120,19 +120,19 @@ WarningBehavior Diagnostics<L, W>::getWarningBehavior(W id) const {
|
||||
return WarningBehavior::DISABLED;
|
||||
}
|
||||
|
||||
template<typename L, typename W>
|
||||
std::string Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
||||
template<typename LevelEnumT, typename WarningEnumT>
|
||||
void Diagnostics<LevelEnumT, WarningEnumT>::processWarningFlag(char const *flag) {
|
||||
std::string rootFlag = flag;
|
||||
|
||||
// Check for `-Werror` or `-Wno-error` to return early
|
||||
if (rootFlag == "error") {
|
||||
// `-Werror` promotes warnings to errors
|
||||
state.warningsAreErrors = true;
|
||||
return rootFlag;
|
||||
return;
|
||||
} else if (rootFlag == "no-error") {
|
||||
// `-Wno-error` disables promotion of warnings to errors
|
||||
state.warningsAreErrors = false;
|
||||
return rootFlag;
|
||||
return;
|
||||
}
|
||||
|
||||
auto [flagState, param] = getInitialWarningState(rootFlag);
|
||||
@@ -140,8 +140,8 @@ std::string Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
||||
// 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,
|
||||
// which applies to all levels
|
||||
for (ParamWarning<W> const ¶mWarning : paramWarnings) {
|
||||
W baseID = paramWarning.firstID;
|
||||
for (ParamWarning<WarningEnumT> const ¶mWarning : paramWarnings) {
|
||||
WarningEnumT baseID = paramWarning.firstID;
|
||||
uint8_t maxParam = paramWarning.lastID - baseID + 1;
|
||||
assume(paramWarning.defaultLevel <= maxParam);
|
||||
|
||||
@@ -174,39 +174,38 @@ std::string Diagnostics<L, W>::processWarningFlag(char const *flag) {
|
||||
warning.state = WARNING_DISABLED;
|
||||
}
|
||||
}
|
||||
return rootFlag;
|
||||
return;
|
||||
}
|
||||
|
||||
if (param.has_value()) {
|
||||
warnx("Unknown warning flag parameter \"%s=%" PRIu32 "\"", rootFlag.c_str(), *param);
|
||||
return rootFlag;
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to match against a "meta" warning
|
||||
for (WarningFlag<L> const &metaWarning : metaWarnings) {
|
||||
for (WarningFlag<LevelEnumT> const &metaWarning : metaWarnings) {
|
||||
if (rootFlag != metaWarning.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
state.metaStates[id].update(flagState);
|
||||
}
|
||||
}
|
||||
return rootFlag;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
state.flagStates[id].update(flagState);
|
||||
return rootFlag;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
warnx("Unknown warning flag \"%s\"", rootFlag.c_str());
|
||||
return rootFlag;
|
||||
}
|
||||
|
||||
#endif // RGBDS_DIAGNOSTICS_HPP
|
||||
|
||||
6
include/extern/getopt.hpp
vendored
6
include/extern/getopt.hpp
vendored
@@ -12,7 +12,7 @@ static constexpr int optional_argument = 2;
|
||||
// clang-format on
|
||||
|
||||
extern char *musl_optarg;
|
||||
extern int musl_optind, musl_opterr, musl_optopt, musl_optreset;
|
||||
extern int musl_optind, musl_optopt;
|
||||
|
||||
struct option {
|
||||
char const *name;
|
||||
@@ -21,8 +21,6 @@ struct option {
|
||||
int val;
|
||||
};
|
||||
|
||||
int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
||||
);
|
||||
int musl_getopt_long_only(int argc, char **argv, char const *optstring, option const *longopts);
|
||||
|
||||
#endif // RGBDS_EXTERN_GETOPT_HPP
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <ios>
|
||||
#include <iostream>
|
||||
#include <streambuf>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
|
||||
|
||||
8
include/fix/fix.hpp
Normal file
8
include/fix/fix.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FIX_FIX_HPP
|
||||
#define RGBDS_FIX_FIX_HPP
|
||||
|
||||
bool fix_ProcessFile(char const *name, char const *outputName);
|
||||
|
||||
#endif // RGBDS_FIX_FIX_HPP
|
||||
51
include/fix/main.hpp
Normal file
51
include/fix/main.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_FIX_MAIN_HPP
|
||||
#define RGBDS_FIX_MAIN_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
#include "fix/mbc.hpp" // UNSPECIFIED, MbcType
|
||||
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t FIX_LOGO = 1 << 7;
|
||||
static constexpr uint8_t TRASH_LOGO = 1 << 6;
|
||||
static constexpr uint8_t FIX_HEADER_SUM = 1 << 5;
|
||||
static constexpr uint8_t TRASH_HEADER_SUM = 1 << 4;
|
||||
static constexpr uint8_t FIX_GLOBAL_SUM = 1 << 3;
|
||||
static constexpr uint8_t TRASH_GLOBAL_SUM = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
enum Model { DMG, BOTH, CGB };
|
||||
|
||||
struct Options {
|
||||
uint8_t fixSpec = 0; // -f, -v
|
||||
Model model = DMG; // -C, -c
|
||||
bool japanese = true; // -j
|
||||
uint16_t oldLicensee = UNSPECIFIED; // -l
|
||||
uint16_t romVersion = UNSPECIFIED; // -n
|
||||
uint16_t padValue = UNSPECIFIED; // -p
|
||||
uint16_t ramSize = UNSPECIFIED; // -r
|
||||
bool sgb = false; // -s
|
||||
|
||||
std::optional<std::string> gameID; // -i
|
||||
uint8_t gameIDLen;
|
||||
|
||||
std::optional<std::string> newLicensee; // -k
|
||||
uint8_t newLicenseeLen;
|
||||
|
||||
std::optional<std::string> logoFilename; // -L
|
||||
uint8_t logo[48] = {};
|
||||
|
||||
MbcType cartridgeType = MBC_NONE; // -m
|
||||
uint8_t tpp1Rev[2];
|
||||
|
||||
std::optional<std::string> title; // -t
|
||||
uint8_t titleLen;
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
#endif // RGBDS_FIX_MAIN_HPP
|
||||
@@ -3,6 +3,8 @@
|
||||
#ifndef RGBDS_FIX_WARNING_HPP
|
||||
#define RGBDS_FIX_WARNING_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
enum WarningLevel {
|
||||
@@ -13,6 +15,7 @@ enum WarningLevel {
|
||||
|
||||
enum WarningID {
|
||||
WARNING_MBC, // Issues with MBC specs
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_OVERWRITE, // Overwriting non-zero bytes
|
||||
WARNING_SGB, // SGB flag conflicts with old licensee code
|
||||
WARNING_TRUNCATION, // Truncating values to fit
|
||||
|
||||
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
|
||||
@@ -7,10 +7,9 @@
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
@@ -20,7 +19,6 @@ struct Options {
|
||||
bool allowMirroringX = false; // -X, -m
|
||||
bool allowMirroringY = false; // -Y, -m
|
||||
bool columnMajor = false; // -Z
|
||||
uint8_t verbosity = 0; // -v
|
||||
|
||||
std::string attrmap{}; // -a, -A
|
||||
std::optional<Rgba> bgColor{}; // -B
|
||||
@@ -56,18 +54,6 @@ struct Options {
|
||||
|
||||
std::string input{}; // positional arg
|
||||
|
||||
// clang-format off: vertically align values
|
||||
static constexpr uint8_t VERB_NONE = 0; // Normal, no extra output
|
||||
static constexpr uint8_t VERB_CFG = 1; // Print configuration after parsing options
|
||||
static constexpr uint8_t VERB_LOG_ACT = 2; // Log actions before doing them
|
||||
static constexpr uint8_t VERB_INTERM = 3; // Print some intermediate results
|
||||
static constexpr uint8_t VERB_DEBUG = 4; // Internals are logged
|
||||
static constexpr uint8_t VERB_TRACE = 5; // Step-by-step algorithm details
|
||||
static constexpr uint8_t VERB_VVVVVV = 6; // What, can't I have a little fun?
|
||||
// clang-format on
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void verbosePrint(uint8_t level, char const *fmt, ...) const;
|
||||
|
||||
mutable bool hasTransparentPixels = false;
|
||||
uint8_t maxOpaqueColors() const { return nbColorsPerPal - hasTransparentPixels; }
|
||||
|
||||
@@ -82,35 +68,4 @@ struct 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
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#define RGBDS_GFX_PAL_PACKING_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
struct Palette;
|
||||
class ColorSet;
|
||||
|
||||
// Returns which palette each color set maps to, and how many palettes are necessary
|
||||
std::tuple<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets);
|
||||
std::pair<std::vector<size_t>, size_t> overloadAndRemove(std::vector<ColorSet> const &colorSets);
|
||||
|
||||
#endif // RGBDS_GFX_PAL_PACKING_HPP
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
#ifndef RGBDS_GFX_PAL_SPEC_HPP
|
||||
#define RGBDS_GFX_PAL_SPEC_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void parseInlinePalSpec(char const * const rawArg);
|
||||
void parseExternalPalSpec(char const *arg);
|
||||
void parseDmgPalSpec(char const * const rawArg);
|
||||
void parseDmgPalSpec(uint8_t palSpecDmg);
|
||||
|
||||
void parseBackgroundPalSpec(char const *arg);
|
||||
|
||||
|
||||
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
|
||||
@@ -3,8 +3,8 @@
|
||||
#ifndef RGBDS_GFX_PNG_HPP
|
||||
#define RGBDS_GFX_PNG_HPP
|
||||
|
||||
#include <fstream>
|
||||
#include <stdint.h>
|
||||
#include <streambuf>
|
||||
#include <vector>
|
||||
|
||||
#include "gfx/rgba.hpp"
|
||||
|
||||
@@ -18,10 +18,7 @@ struct Rgba {
|
||||
: red(rgba >> 24), green(rgba >> 16), blue(rgba >> 8), alpha(rgba) {}
|
||||
|
||||
static constexpr Rgba fromCGBColor(uint16_t color) {
|
||||
constexpr auto _5to8 = [](uint8_t channel) -> uint8_t {
|
||||
channel &= 0b11111; // For caller's convenience
|
||||
return channel << 3 | channel >> 2;
|
||||
};
|
||||
constexpr auto _5to8 = [](uint8_t c) -> uint8_t { return ((c & 0b11111) * 255 + 15) / 31; };
|
||||
return {
|
||||
_5to8(color),
|
||||
_5to8(color >> 5),
|
||||
|
||||
@@ -13,6 +13,7 @@ enum WarningLevel {
|
||||
|
||||
enum WarningID {
|
||||
WARNING_EMBEDDED, // Using an embedded PNG palette without '-c embedded'
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_TRIM_NONEMPTY, // '-x' trims nonempty tiles
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
@@ -97,18 +97,19 @@ static inline int clz(unsigned int x) {
|
||||
|
||||
// For lack of <ranges>, this adds some more brevity
|
||||
#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
|
||||
template<int N>
|
||||
static constexpr int literal_strlen(char const (&)[N]) {
|
||||
return N - 1;
|
||||
template<int SizeOfString>
|
||||
static constexpr int literal_strlen(char const (&)[SizeOfString]) {
|
||||
return SizeOfString - 1; // Don't count the ending '\0'
|
||||
}
|
||||
|
||||
// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
|
||||
template<typename T>
|
||||
template<typename DeferredFnT>
|
||||
struct Defer {
|
||||
T deferred;
|
||||
Defer(T func) : deferred(func) {}
|
||||
DeferredFnT deferred;
|
||||
Defer(DeferredFnT func) : deferred(func) {}
|
||||
~Defer() { deferred(); }
|
||||
};
|
||||
|
||||
|
||||
@@ -3,46 +3,119 @@
|
||||
#ifndef RGBDS_ITERTOOLS_HPP
|
||||
#define RGBDS_ITERTOOLS_HPP
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
template<typename T>
|
||||
class EnumSeq {
|
||||
T _start;
|
||||
T _stop;
|
||||
// A wrapper around iterables to reverse their iteration order; used in `for`-each loops.
|
||||
template<typename IterableT>
|
||||
struct ReversedIterable {
|
||||
IterableT &_iterable;
|
||||
};
|
||||
|
||||
class Iterator {
|
||||
T _value;
|
||||
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 {
|
||||
std::deque<ItemT> list;
|
||||
std::unordered_map<std::string, size_t> map; // Indexes into `list`
|
||||
|
||||
public:
|
||||
explicit Iterator(T value) : _value(value) {}
|
||||
size_t size() const { return list.size(); }
|
||||
|
||||
bool empty() const { return list.empty(); }
|
||||
|
||||
bool contains(std::string const &name) const { return map.find(name) != map.end(); }
|
||||
|
||||
ItemT &operator[](size_t i) { return list[i]; }
|
||||
|
||||
typename decltype(list)::iterator begin() { return list.begin(); }
|
||||
typename decltype(list)::iterator end() { return list.end(); }
|
||||
typename decltype(list)::const_iterator begin() const { return list.begin(); }
|
||||
typename decltype(list)::const_iterator end() const { return list.end(); }
|
||||
|
||||
ItemT &add(std::string const &name) {
|
||||
map[name] = list.size();
|
||||
return list.emplace_back();
|
||||
}
|
||||
|
||||
ItemT &add(std::string const &name, ItemT &&value) {
|
||||
map[name] = list.size();
|
||||
list.emplace_back(std::move(value));
|
||||
return list.back();
|
||||
}
|
||||
|
||||
ItemT &addAnonymous() {
|
||||
// Add the new item to the list, but do not update the map
|
||||
return list.emplace_back();
|
||||
}
|
||||
|
||||
std::optional<size_t> findIndex(std::string const &name) const {
|
||||
if (auto search = map.find(name); search != map.end()) {
|
||||
return search->second;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
// An iterable of `enum` values in the half-open range [start, stop).
|
||||
template<typename EnumT>
|
||||
class EnumSeq {
|
||||
EnumT _start;
|
||||
EnumT _stop;
|
||||
|
||||
class Iterator {
|
||||
EnumT _value;
|
||||
|
||||
public:
|
||||
explicit Iterator(EnumT value) : _value(value) {}
|
||||
|
||||
Iterator &operator++() {
|
||||
_value = static_cast<T>(_value + 1);
|
||||
_value = static_cast<EnumT>(_value + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T operator*() const { return _value; }
|
||||
EnumT operator*() const { return _value; }
|
||||
|
||||
bool operator==(Iterator const &rhs) const { return _value == rhs._value; }
|
||||
};
|
||||
|
||||
public:
|
||||
explicit EnumSeq(T stop) : _start(static_cast<T>(0)), _stop(stop) {}
|
||||
explicit EnumSeq(T start, T stop) : _start(start), _stop(stop) {}
|
||||
explicit EnumSeq(EnumT stop) : _start(static_cast<EnumT>(0)), _stop(stop) {}
|
||||
explicit EnumSeq(EnumT start, EnumT stop) : _start(start), _stop(stop) {}
|
||||
|
||||
Iterator begin() { return Iterator(_start); }
|
||||
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.
|
||||
// We also assume that all iterators have the same length.
|
||||
template<typename... Ts>
|
||||
template<typename... IteratorTs>
|
||||
class ZipIterator {
|
||||
std::tuple<Ts...> _iters;
|
||||
std::tuple<IteratorTs...> _iters;
|
||||
|
||||
public:
|
||||
explicit ZipIterator(std::tuple<Ts...> &&iters) : _iters(iters) {}
|
||||
explicit ZipIterator(std::tuple<IteratorTs...> &&iters) : _iters(iters) {}
|
||||
|
||||
ZipIterator &operator++() {
|
||||
std::apply([](auto &&...it) { (++it, ...); }, _iters);
|
||||
@@ -60,12 +133,14 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
// Only needed inside `zip` below.
|
||||
template<typename... IterableTs>
|
||||
class ZipContainer {
|
||||
std::tuple<Ts...> _containers;
|
||||
std::tuple<IterableTs...> _containers;
|
||||
|
||||
public:
|
||||
explicit ZipContainer(Ts &&...containers) : _containers(std::forward<Ts>(containers)...) {}
|
||||
explicit ZipContainer(IterableTs &&...containers)
|
||||
: _containers(std::forward<IterableTs>(containers)...) {}
|
||||
|
||||
auto begin() {
|
||||
return ZipIterator(std::apply(
|
||||
@@ -88,15 +163,19 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// Only needed inside `zip` below.
|
||||
// Take ownership of objects and rvalue refs passed to us, but not lvalue refs
|
||||
template<typename T>
|
||||
using Holder = std::
|
||||
conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cv_t<std::remove_reference_t<T>>>;
|
||||
template<typename IterableT>
|
||||
using ZipHolder = std::conditional_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!
|
||||
template<typename... Ts>
|
||||
static constexpr auto zip(Ts &&...cs) {
|
||||
return ZipContainer<Holder<Ts>...>(std::forward<Ts>(cs)...);
|
||||
template<typename... IterableTs>
|
||||
static constexpr auto zip(IterableTs &&...containers) {
|
||||
return ZipContainer<ZipHolder<IterableTs>...>(std::forward<IterableTs>(containers)...);
|
||||
}
|
||||
|
||||
#endif // RGBDS_ITERTOOLS_HPP
|
||||
|
||||
38
include/link/fstack.hpp
Normal file
38
include/link/fstack.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_LINK_FSTACK_HPP
|
||||
#define RGBDS_LINK_FSTACK_HPP
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNodeType type;
|
||||
std::variant<
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
data;
|
||||
bool isQuiet; // Whether to omit this node from error reporting
|
||||
|
||||
FileStackNode *parent;
|
||||
// Line at which the parent context was exited; meaningless for the root level
|
||||
uint32_t lineNo;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters() { return std::get<std::vector<uint32_t>>(data); }
|
||||
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
|
||||
// File name for files, file::macro name for macros
|
||||
std::string &name() { return std::get<std::string>(data); }
|
||||
std::string const &name() const { return std::get<std::string>(data); }
|
||||
|
||||
void printBacktrace(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
#endif // RGBDS_LINK_FSTACK_HPP
|
||||
@@ -3,15 +3,13 @@
|
||||
#ifndef RGBDS_LINK_LEXER_HPP
|
||||
#define RGBDS_LINK_LEXER_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void lexer_Error(char const *fmt, ...);
|
||||
void lexer_TraceCurrent();
|
||||
|
||||
void lexer_IncludeFile(std::string &&path);
|
||||
void lexer_IncLineNo();
|
||||
|
||||
bool lexer_Init(char const *linkerScriptName);
|
||||
bool lexer_Init(std::string const &linkerScriptName);
|
||||
|
||||
#endif // RGBDS_LINK_LEXER_HPP
|
||||
|
||||
@@ -3,21 +3,17 @@
|
||||
#ifndef RGBDS_LINK_MAIN_HPP
|
||||
#define RGBDS_LINK_MAIN_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
struct Options {
|
||||
bool isDmgMode; // -d
|
||||
char const *mapFileName; // -m
|
||||
std::optional<std::string> mapFileName; // -m
|
||||
bool noSymInMap; // -M
|
||||
char const *symFileName; // -n
|
||||
char const *overlayFileName; // -O
|
||||
char const *outputFileName; // -o
|
||||
std::optional<std::string> symFileName; // -n
|
||||
std::optional<std::string> overlayFileName; // -O
|
||||
std::optional<std::string> outputFileName; // -o
|
||||
uint8_t padValue; // -p
|
||||
bool hasPadValue = false;
|
||||
// Setting these three to 0 disables the functionality
|
||||
@@ -25,41 +21,10 @@ struct Options {
|
||||
uint16_t scrambleWRAMX;
|
||||
uint16_t scrambleSRAM;
|
||||
bool is32kMode; // -t
|
||||
bool beVerbose; // -v
|
||||
bool isWRAM0Mode; // -w
|
||||
bool disablePadding; // -x
|
||||
};
|
||||
|
||||
extern Options options;
|
||||
|
||||
#define verbosePrint(...) \
|
||||
do { \
|
||||
if (options.beVerbose) { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
struct FileStackNode {
|
||||
FileStackNodeType type;
|
||||
std::variant<
|
||||
std::monostate, // Default constructed; `.type` and `.data` must be set manually
|
||||
std::vector<uint32_t>, // NODE_REPT
|
||||
std::string // NODE_FILE, NODE_MACRO
|
||||
>
|
||||
data;
|
||||
|
||||
FileStackNode *parent;
|
||||
// Line at which the parent context was exited; meaningless for the root level
|
||||
uint32_t lineNo;
|
||||
|
||||
// REPT iteration counts since last named node, in reverse depth order
|
||||
std::vector<uint32_t> &iters() { return std::get<std::vector<uint32_t>>(data); }
|
||||
std::vector<uint32_t> const &iters() const { return std::get<std::vector<uint32_t>>(data); }
|
||||
// File name for files, file::macro name for macros
|
||||
std::string &name() { return std::get<std::string>(data); }
|
||||
std::string const &name() const { return std::get<std::string>(data); }
|
||||
|
||||
std::string const &dump(uint32_t curLineNo) const;
|
||||
};
|
||||
|
||||
#endif // RGBDS_LINK_MAIN_HPP
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
#ifndef 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.
|
||||
void obj_ReadFile(char const *fileName, unsigned int fileID);
|
||||
void obj_ReadFile(std::string const &filePath, size_t fileID);
|
||||
|
||||
// Sets up object file reading
|
||||
void obj_Setup(unsigned int nbFiles);
|
||||
void obj_Setup(size_t nbFiles);
|
||||
|
||||
#endif // RGBDS_LINK_OBJECT_HPP
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
#ifndef RGBDS_LINK_SECTION_HPP
|
||||
#define RGBDS_LINK_SECTION_HPP
|
||||
|
||||
// GUIDELINE: external code MUST NOT BE AWARE of the data structure used!
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
@@ -13,8 +10,6 @@
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "link/main.hpp"
|
||||
|
||||
struct FileStackNode;
|
||||
struct Section;
|
||||
struct Symbol;
|
||||
@@ -53,7 +48,40 @@ struct Section {
|
||||
// Extra info computed during linking
|
||||
std::vector<Symbol> *fileSymbols;
|
||||
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.
|
||||
|
||||
@@ -33,8 +33,8 @@ struct Symbol {
|
||||
>
|
||||
data;
|
||||
|
||||
Label &label() { return std::get<Label>(data); }
|
||||
Label const &label() const { return std::get<Label>(data); }
|
||||
void linkToSection(Section §ion);
|
||||
void fixSectionOffset();
|
||||
};
|
||||
|
||||
void sym_ForEach(void (*callback)(Symbol &));
|
||||
@@ -44,6 +44,6 @@ void sym_AddSymbol(Symbol &symbol);
|
||||
// Finds a symbol in all the defined symbols.
|
||||
Symbol *sym_GetSymbol(std::string const &name);
|
||||
|
||||
void sym_DumpLocalAliasedSymbols(std::string const &name);
|
||||
void sym_TraceLocalAliasedSymbols(std::string const &name);
|
||||
|
||||
#endif // RGBDS_LINK_SYMBOL_HPP
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
#define warningAt(where, ...) warning(where.src, where.lineNo, __VA_ARGS__)
|
||||
#define errorAt(where, ...) error(where.src, where.lineNo, __VA_ARGS__)
|
||||
#define fatalAt(where, ...) fatal(where.src, where.lineNo, __VA_ARGS__)
|
||||
#define warningAt(where, ...) warning((where).src, (where).lineNo, __VA_ARGS__)
|
||||
#define errorAt(where, ...) error((where).src, (where).lineNo, __VA_ARGS__)
|
||||
#define fatalAt(where, ...) fatal((where).src, (where).lineNo, __VA_ARGS__)
|
||||
|
||||
#define fatalTwoAt(where1, where2, ...) \
|
||||
fatalTwo(*(where1).src, (where1).lineNo, *(where2).src, (where2).lineNo, __VA_ARGS__)
|
||||
|
||||
enum WarningLevel {
|
||||
LEVEL_DEFAULT, // Warnings that are enabled by default
|
||||
@@ -24,11 +27,14 @@ enum WarningID {
|
||||
WARNING_OBSOLETE, // Obsolete/deprecated things
|
||||
WARNING_SHIFT, // Undefined `SHIFT` behavior
|
||||
WARNING_SHIFT_AMOUNT, // Strange `SHIFT` amount
|
||||
WARNING_TRUNCATION, // Implicit truncation loses some bits
|
||||
|
||||
NB_PLAIN_WARNINGS,
|
||||
|
||||
NB_WARNINGS = NB_PLAIN_WARNINGS,
|
||||
// Implicit truncation loses some bits
|
||||
WARNING_TRUNCATION_1 = NB_PLAIN_WARNINGS,
|
||||
WARNING_TRUNCATION_2,
|
||||
|
||||
NB_WARNINGS,
|
||||
};
|
||||
|
||||
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||
@@ -47,15 +53,23 @@ void error(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void error(char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2)]]
|
||||
void errorNoDump(char const *fmt, ...);
|
||||
|
||||
void scriptError(char const *name, uint32_t lineNo, char const *fmt, va_list args);
|
||||
void scriptError(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 3, 4), noreturn]]
|
||||
void fatal(FileStackNode const *src, uint32_t lineNo, char const *fmt, ...);
|
||||
[[gnu::format(printf, 1, 2), noreturn]]
|
||||
void fatal(char const *fmt, ...);
|
||||
|
||||
[[gnu::format(printf, 5, 6), noreturn]]
|
||||
void fatalTwo(
|
||||
FileStackNode const &src1,
|
||||
uint32_t lineNo1,
|
||||
FileStackNode const &src2,
|
||||
uint32_t lineNo2,
|
||||
char const *fmt,
|
||||
...
|
||||
);
|
||||
|
||||
void requireZeroErrors();
|
||||
|
||||
#endif // RGBDS_LINK_WARNING_HPP
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "helpers.hpp" // assume
|
||||
|
||||
#define RGBDS_OBJECT_VERSION_STRING "RGB9"
|
||||
#define RGBDS_OBJECT_REV 12U
|
||||
#define RGBDS_OBJECT_REV 13U
|
||||
|
||||
enum AssertionType { ASSERT_WARN, ASSERT_ERROR, ASSERT_FATAL };
|
||||
|
||||
@@ -78,12 +78,18 @@ enum SectionType {
|
||||
SECTTYPE_INVALID
|
||||
};
|
||||
|
||||
static constexpr uint8_t SECTTYPE_TYPE_MASK = 0b111;
|
||||
static constexpr uint8_t SECTTYPE_UNION_BIT = 7;
|
||||
static constexpr uint8_t SECTTYPE_FRAGMENT_BIT = 6;
|
||||
|
||||
enum FileStackNodeType {
|
||||
NODE_REPT,
|
||||
NODE_FILE,
|
||||
NODE_MACRO,
|
||||
};
|
||||
|
||||
static constexpr uint8_t FSTACKNODE_QUIET_BIT = 7;
|
||||
|
||||
// Nont-`const` members may be patched in RGBLINK depending on CLI flags
|
||||
extern struct SectionTypeInfo {
|
||||
std::string const name;
|
||||
@@ -95,18 +101,18 @@ extern struct SectionTypeInfo {
|
||||
|
||||
// Tells whether a section has data in its object file definition,
|
||||
// depending on type.
|
||||
static inline bool sect_HasData(SectionType type) {
|
||||
static inline bool sectTypeHasData(SectionType type) {
|
||||
assume(type != SECTTYPE_INVALID);
|
||||
return type == SECTTYPE_ROM0 || type == SECTTYPE_ROMX;
|
||||
}
|
||||
|
||||
// Returns a memory region's end address (last byte), e.g. 0x7FFF
|
||||
static inline uint16_t endaddr(SectionType type) {
|
||||
static inline uint16_t sectTypeEndAddr(SectionType type) {
|
||||
return sectionTypeInfo[type].startAddr + sectionTypeInfo[type].size - 1;
|
||||
}
|
||||
|
||||
// Returns a memory region's number of banks, or 1 for regions without banking
|
||||
static inline uint32_t nbbanks(SectionType type) {
|
||||
static inline uint32_t sectTypeBanks(SectionType type) {
|
||||
return sectionTypeInfo[type].lastBank - sectionTypeInfo[type].firstBank + 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,17 @@
|
||||
int32_t op_divide(int32_t dividend, int32_t divisor);
|
||||
int32_t op_modulo(int32_t dividend, int32_t divisor);
|
||||
int32_t op_exponent(int32_t base, uint32_t power);
|
||||
|
||||
int32_t op_shift_left(int32_t value, int32_t amount);
|
||||
int32_t op_shift_right(int32_t value, int32_t amount);
|
||||
int32_t op_shift_right_unsigned(int32_t value, int32_t amount);
|
||||
|
||||
int32_t op_neg(int32_t value);
|
||||
|
||||
int32_t op_high(int32_t value);
|
||||
int32_t op_low(int32_t value);
|
||||
|
||||
int32_t op_bitwidth(int32_t value);
|
||||
int32_t op_tzcount(int32_t value);
|
||||
|
||||
#endif // RGBDS_OP_MATH_HPP
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#define STDERR_FILENO 2
|
||||
#define ssize_t int
|
||||
#define SSIZE_MAX INT_MAX
|
||||
#define isatty _isatty
|
||||
#else
|
||||
#include <fcntl.h> // IWYU pragma: export
|
||||
#include <limits.h> // IWYU pragma: export
|
||||
@@ -54,9 +55,18 @@
|
||||
#define setmode(fd, mode) (0)
|
||||
#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__)
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#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
|
||||
|
||||
42
include/style.hpp
Normal file
42
include/style.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_STYLE_HPP
|
||||
#define RGBDS_STYLE_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
#define STYLE_ANSI 0
|
||||
#else
|
||||
#define STYLE_ANSI 1
|
||||
#endif
|
||||
|
||||
enum StyleColor {
|
||||
#if STYLE_ANSI
|
||||
// Values analogous to ANSI foreground and background SGR colors
|
||||
STYLE_BLACK,
|
||||
STYLE_RED,
|
||||
STYLE_GREEN,
|
||||
STYLE_YELLOW,
|
||||
STYLE_BLUE,
|
||||
STYLE_MAGENTA,
|
||||
STYLE_CYAN,
|
||||
STYLE_GRAY,
|
||||
#else
|
||||
// Values analogous to `FOREGROUND_*` constants from `windows.h`
|
||||
STYLE_BLACK,
|
||||
STYLE_BLUE, // bit 0
|
||||
STYLE_GREEN, // bit 1
|
||||
STYLE_CYAN, // STYLE_BLUE | STYLE_GREEN
|
||||
STYLE_RED, // bit 2
|
||||
STYLE_MAGENTA, // STYLE_BLUE | STYLE_RED
|
||||
STYLE_YELLOW, // STYLE_GREEN | STYLE_RED
|
||||
STYLE_GRAY, // STYLE_BLUE | STYLE_GREEN | STYLE_RED
|
||||
#endif
|
||||
};
|
||||
|
||||
bool style_Parse(char const *arg);
|
||||
void style_Set(FILE *file, StyleColor color, bool bold);
|
||||
void style_Reset(FILE *file);
|
||||
|
||||
#endif // RGBDS_STYLE_HPP
|
||||
@@ -4,12 +4,14 @@
|
||||
#define RGBDS_USAGE_HPP
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class Usage {
|
||||
char const *usage;
|
||||
|
||||
public:
|
||||
Usage(char const *usage_) : usage(usage_) {}
|
||||
struct Usage {
|
||||
std::string name;
|
||||
std::vector<std::string> flags;
|
||||
std::vector<std::pair<std::vector<std::string>, std::vector<std::string>>> options;
|
||||
|
||||
[[noreturn]]
|
||||
void printAndExit(int code) const;
|
||||
|
||||
@@ -3,23 +3,69 @@
|
||||
#ifndef RGBDS_UTIL_HPP
|
||||
#define RGBDS_UTIL_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#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 isBlankSpace(int c);
|
||||
bool isWhitespace(int c);
|
||||
bool isPrintable(int c);
|
||||
bool isUpper(int c);
|
||||
bool isLower(int c);
|
||||
bool isLetter(int c);
|
||||
bool isDigit(int c);
|
||||
bool isBinDigit(int c);
|
||||
bool isOctDigit(int c);
|
||||
bool isHexDigit(int c);
|
||||
bool isAlphanumeric(int c);
|
||||
|
||||
// Locale-independent character transform functions
|
||||
char toLower(char c);
|
||||
char toUpper(char c);
|
||||
|
||||
bool startsIdentifier(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);
|
||||
|
||||
struct Uppercase {
|
||||
size_t operator()(std::string const &str) const;
|
||||
bool operator()(std::string const &str1, std::string const &str2) const;
|
||||
// FNV-1a hash of an uppercased string
|
||||
constexpr size_t operator()(std::string const &str) const {
|
||||
return std::accumulate(RANGE(str), 0x811C9DC5, [](size_t hash, char c) {
|
||||
return (hash ^ toUpper(c)) * 16777619;
|
||||
});
|
||||
}
|
||||
|
||||
// Compare two strings without case-sensitivity (by converting to uppercase)
|
||||
constexpr bool operator()(std::string const &str1, std::string const &str2) const {
|
||||
return std::equal(RANGE(str1), RANGE(str2), [](char c1, char c2) {
|
||||
return toUpper(c1) == toUpper(c2);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using UpperMap = std::unordered_map<std::string, T, Uppercase, Uppercase>;
|
||||
// An unordered map from case-insensitive `std::string` keys to `ItemT` items
|
||||
template<typename ItemT>
|
||||
using UpperMap = std::unordered_map<std::string, ItemT, Uppercase, Uppercase>;
|
||||
|
||||
#endif // RGBDS_UTIL_HPP
|
||||
|
||||
35
include/verbosity.hpp
Normal file
35
include/verbosity.hpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#ifndef RGBDS_VERBOSITY_HPP
|
||||
#define RGBDS_VERBOSITY_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
// This macro does not evaluate its arguments unless the condition is true.
|
||||
#define verbosePrint(level, ...) \
|
||||
do { \
|
||||
if (checkVerbosity(level)) { \
|
||||
style_Set(stderr, STYLE_MAGENTA, false); \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
style_Reset(stderr); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
enum Verbosity {
|
||||
VERB_NONE, // 0. Default, no extra output
|
||||
VERB_CONFIG, // 1. Basic configuration, after parsing CLI options
|
||||
VERB_NOTICE, // 2. Before significant actions
|
||||
VERB_INFO, // 3. Some intermediate action results
|
||||
VERB_DEBUG, // 4. Internals useful for debugging
|
||||
VERB_TRACE, // 5. Step-by-step algorithm details
|
||||
VERB_VVVVVV, // 6. What, can't I have a little fun?
|
||||
};
|
||||
|
||||
void incrementVerbosity();
|
||||
bool checkVerbosity(Verbosity level);
|
||||
|
||||
void printVVVVVVerbosity();
|
||||
|
||||
#endif // RGBDS_VERBOSITY_HPP
|
||||
@@ -3,9 +3,10 @@
|
||||
#ifndef RGBDS_VERSION_HPP
|
||||
#define RGBDS_VERSION_HPP
|
||||
|
||||
#define PACKAGE_VERSION_MAJOR 0
|
||||
#define PACKAGE_VERSION_MINOR 9
|
||||
#define PACKAGE_VERSION_PATCH 4
|
||||
#define PACKAGE_VERSION_MAJOR 1
|
||||
#define PACKAGE_VERSION_MINOR 0
|
||||
#define PACKAGE_VERSION_PATCH 0
|
||||
#define PACKAGE_VERSION_RC 2
|
||||
|
||||
char const *get_package_version_string();
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt GBZ80 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm gbz80
|
||||
.Nd CPU opcode reference
|
||||
.Nd Game Boy CPU instruction reference
|
||||
.Sh DESCRIPTION
|
||||
This is the list of opcodes supported by
|
||||
This is the list of instructions supported by
|
||||
.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.
|
||||
.Pp
|
||||
@@ -657,7 +657,7 @@ Set if result is 0.
|
||||
.It Sy H
|
||||
0
|
||||
.It Sy C
|
||||
Set or reset depending on the operation.
|
||||
Set or unaffected depending on the operation.
|
||||
.El
|
||||
.Ss DEC r8
|
||||
Decrement the value in register
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBASM-OLD 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -129,12 +129,23 @@ Deprecated in 0.5.0, removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql 3.141592653 .
|
||||
.Ss __DATE__ and __TIME__
|
||||
Deprecated in 1.0.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql __ISO_8601_LOCAL__ .
|
||||
.Ss Treating multi-character strings as numbers
|
||||
Deprecated in 0.9.0.
|
||||
Deprecated in 0.9.0, removed in 1.0.0.
|
||||
.Pp
|
||||
Instead, use a multi-value
|
||||
.Ic CHARMAP ,
|
||||
or explicitly combine the values of individual characters.
|
||||
.Ss Treating strings as numbers
|
||||
Deprecated in 1.0.0.
|
||||
.Pp
|
||||
Instead, use character constants or the
|
||||
.Ic CHARVAL
|
||||
function.
|
||||
.Ss rgbgfx -f/--fix and -F/--fix-and-save
|
||||
Removed in 0.6.0.
|
||||
.Pp
|
||||
@@ -209,6 +220,31 @@ typed in column 1.
|
||||
Instead, use
|
||||
.Ql \&;
|
||||
comments.
|
||||
.Ss STRIN, STRRIN, STRSUB, and CHARSUB
|
||||
Deprecated in 1.0.0.
|
||||
.Pp
|
||||
These functions used 1-based indexing of string characters, which was inconsistent with the 0-based indexing used more often in programming.
|
||||
.Pp
|
||||
Instead of
|
||||
.Ic STRIN ,
|
||||
use
|
||||
.Ic STRFIND ;
|
||||
instead of
|
||||
.Ic STRRIN ,
|
||||
use
|
||||
.Ic STRRFIND ;
|
||||
instead of
|
||||
.Ic STRSUB ,
|
||||
use
|
||||
.Ic STRSLICE ;
|
||||
and instead of
|
||||
.Ic CHARSUB ,
|
||||
use
|
||||
.Ic STRCHAR .
|
||||
.Pp
|
||||
Note that
|
||||
.Ic STRSLICE
|
||||
takes a start and end index instead of a start index and a length.
|
||||
.Ss PRINTT, PRINTI, PRINTV, and PRINTF
|
||||
Deprecated in 0.5.0, removed in 0.6.0.
|
||||
.Pp
|
||||
@@ -247,7 +283,7 @@ and
|
||||
.Ic DATA ,
|
||||
use
|
||||
.Ic ROMX ;
|
||||
instead of
|
||||
and instead of
|
||||
.Ic BSS ,
|
||||
use
|
||||
.Ic WRAM0 .
|
||||
@@ -272,12 +308,12 @@ or
|
||||
and
|
||||
.Ql LD A, [HL-] ) .
|
||||
.Ss LDIO
|
||||
Deprecated in 0.9.0.
|
||||
Deprecated in 0.9.0, removed in 1.0.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH .
|
||||
.Ss LD [C], A and LD A, [C]
|
||||
Deprecated in 0.9.0.
|
||||
Deprecated in 0.9.0, removed in 1.0.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Ql LDH [C], A
|
||||
@@ -292,7 +328,7 @@ were also deprecated in 0.9.0, but were
|
||||
.Em undeprecated
|
||||
in 0.9.1.
|
||||
.Ss LDH [n8], A and LDH A, [n8]
|
||||
Deprecated in 0.9.0.
|
||||
Deprecated in 0.9.0, removed in 1.0.0.
|
||||
.Pp
|
||||
.Ql LDH
|
||||
used to treat "addresses" from
|
||||
@@ -329,19 +365,24 @@ Deprecated in 0.6.0, removed in 0.8.0.
|
||||
Instead, use
|
||||
.Fl I
|
||||
or
|
||||
.Fl -include .
|
||||
.Ss rgbgfx -h
|
||||
.Fl \-include .
|
||||
.Ss rgbfix -O/--overwrite
|
||||
Deprecated in 1.0.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Dl -Wno-overwrite .
|
||||
.Ss rgbgfx -h/--horizontal
|
||||
Removed in 0.6.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl Z
|
||||
or
|
||||
.Fl -columns .
|
||||
.Fl \-columns .
|
||||
.Ss rgbgfx --output-*
|
||||
Deprecated in 0.7.0, removed in 0.8.0.
|
||||
.Pp
|
||||
Instead, use
|
||||
.Fl -auto-* .
|
||||
.Fl \-auto-* .
|
||||
.Sh CHANGED
|
||||
These are breaking changes that did not alter syntax, and so could not practically be deprecated.
|
||||
.Ss Trigonometry function units
|
||||
@@ -420,6 +461,24 @@ Previously we had
|
||||
.Pp
|
||||
Instead, now we have
|
||||
.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
|
||||
These are misfeatures that may have been possible by mistake.
|
||||
They do not get deprecated, just fixed.
|
||||
@@ -450,6 +509,13 @@ Instead, use
|
||||
.Ql Label:
|
||||
and
|
||||
.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
|
||||
Fixed in 0.5.0.
|
||||
.Pp
|
||||
|
||||
166
man/rgbasm.1
166
man/rgbasm.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBASM 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -9,7 +9,9 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl EhVvw
|
||||
.Op Fl B Ar param
|
||||
.Op Fl b Ar chars
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl D Ar name Ns Op = Ns Ar value
|
||||
.Op Fl g Ar chars
|
||||
.Op Fl I Ar path
|
||||
@@ -34,14 +36,14 @@ The
|
||||
program creates an RGB object file from an assembly source file.
|
||||
The object file format is documented in
|
||||
.Xr rgbds 5 .
|
||||
.Pp
|
||||
The input
|
||||
.Ar asmfile
|
||||
can be a path to a file, or
|
||||
.Cm \-
|
||||
to read from standard input.
|
||||
.Pp
|
||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||
.Sh ARGUMENTS
|
||||
.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
|
||||
is
|
||||
.Fl \-verbose ,
|
||||
@@ -49,8 +51,76 @@ but
|
||||
.Fl \-ver
|
||||
is invalid because it could also be
|
||||
.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
|
||||
.It Fl B Ar param , Fl \-backtrace Ar param
|
||||
Configures how location backtraces are printed if warnings or errors occur.
|
||||
This flag may be specified multiple times with different parameters that combine meaningfully.
|
||||
If
|
||||
.Ar param
|
||||
is a positive number, it specifies the maximum backtrace depth, abbreviating deeper ones.
|
||||
Other valid parameter values are the following:
|
||||
.Bl -tag -width Ds
|
||||
.It Cm 0
|
||||
Do not limit the maximum backtrace depth; this is the default.
|
||||
.It Cm all
|
||||
Force all locations to be printed, even "quiet" ones (see
|
||||
.Dq Excluding locations from backtraces
|
||||
in
|
||||
.Xr rgbasm 5
|
||||
for details).
|
||||
.It Cm no-all
|
||||
Do not print "quieted" locations in backtraces; this is the default.
|
||||
.It Cm collapse
|
||||
Print all locations on one line.
|
||||
.It Cm no-collapse
|
||||
Print one location per line; this is the default.
|
||||
.El
|
||||
.It Fl b Ar chars , Fl \-binary-digits Ar chars
|
||||
Allow two characters to be used for binary constants in addition to the default
|
||||
.Sq 0
|
||||
@@ -65,6 +135,18 @@ letters,
|
||||
.Sq # ,
|
||||
or
|
||||
.Sq @ .
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl D Ar name Ns Oo = Ns Ar value Oc , Fl \-define Ar name Ns Oo = Ns Ar value Oc
|
||||
Add a string symbol to the compiled source code.
|
||||
This is equivalent to
|
||||
@@ -233,6 +315,24 @@ below).
|
||||
Print the version of the program and exit.
|
||||
.It Fl v , Fl \-verbose
|
||||
Be verbose.
|
||||
The verbosity level is increased by one each time the flag is specified, with each level including the previous:
|
||||
.Bl -enum -compact
|
||||
.It
|
||||
Print the
|
||||
.Nm
|
||||
configuration before taking actions.
|
||||
.It
|
||||
Print a notice before significant actions.
|
||||
.It
|
||||
Print some of the actions' intermediate results.
|
||||
.It
|
||||
Print some internal debug information.
|
||||
.It
|
||||
Print detailed internal information.
|
||||
.El
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||
.It Fl W Ar warning , Fl \-warning Ar warning
|
||||
Set warning flag
|
||||
.Ar warning .
|
||||
@@ -251,6 +351,23 @@ disables this behavior.
|
||||
The default is 100 if
|
||||
.Nm
|
||||
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
|
||||
.Sh DIAGNOSTICS
|
||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the assembling process.
|
||||
@@ -289,9 +406,9 @@ Enables literally every warning.
|
||||
.Pp
|
||||
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,
|
||||
.Fl Wcharmap-redef
|
||||
.Fl Wobsolete
|
||||
enables the warning that
|
||||
.Fl Wno-charmap-redef
|
||||
.Fl Wno-obsolete
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
@@ -329,6 +446,15 @@ This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wdiv
|
||||
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
|
||||
Warn when a macro argument is empty.
|
||||
This warning is enabled by
|
||||
@@ -339,10 +465,12 @@ Warn when
|
||||
is called with an empty string as its second argument (the substring to replace).
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wlarge-constant
|
||||
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
||||
.It Fl Wexport-undefined
|
||||
Warn when exporting an undefined symbol.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wno-large-constant
|
||||
Warn when a constant too large to fit in a signed 32-bit integer is encountered.
|
||||
.It Fl Wmacro-shift
|
||||
Warn when shifting macro arguments past their limits.
|
||||
This warning is enabled by
|
||||
@@ -375,10 +503,10 @@ or
|
||||
.Fl Wno-purge
|
||||
disables this warning.
|
||||
.Fl Wpurge=1
|
||||
or just
|
||||
.Fl Wpurge
|
||||
warns when purging any exported symbol (regardless of type).
|
||||
.Fl Wpurge=2
|
||||
or just
|
||||
.Fl Wpurge
|
||||
also warns when purging any label (even if not exported).
|
||||
.It Fl Wshift
|
||||
Warn when shifting right a negative value.
|
||||
@@ -394,10 +522,10 @@ or
|
||||
.Fl Wno-truncation
|
||||
disables this warning.
|
||||
.Fl Wtruncation=1
|
||||
warns when an N-bit value is 2**N or greater, or less than -2**N.
|
||||
.Fl Wtruncation=2
|
||||
or just
|
||||
.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.
|
||||
.It Fl Wunmapped-char=
|
||||
Warn when a character goes through charmap conversion but has no defined mapping.
|
||||
@@ -461,7 +589,7 @@ Writing the final assembler state to a file:
|
||||
Or to multiple files:
|
||||
.Dl $ rgbasm -s equ,var:numbers.dump.asm -s equs:strings.dump.asm foo.asm
|
||||
.Sh BUGS
|
||||
Please report bugs on
|
||||
Please report bugs or mistakes in this documentation on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 5 ,
|
||||
|
||||
139
man/rgbasm.5
139
man/rgbasm.5
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBASM 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -43,7 +43,9 @@ Labels tie a name to a specific location within a section (see
|
||||
below).
|
||||
.Pp
|
||||
Instructions are assembled into Game Boy opcodes.
|
||||
Multiple instructions on one line can be separated by double colons
|
||||
Multiple instructions on one line, as well as data directives (see
|
||||
.Sx Defining constant data in ROM
|
||||
below), can be separated by double colons
|
||||
.Ql :: .
|
||||
.Pp
|
||||
The available instructions are documented in
|
||||
@@ -194,12 +196,19 @@ If specified, pads right-aligned numbers with zeros instead of spaces.
|
||||
If specified, pads the value to this width, right-aligned with spaces by default.
|
||||
.It Ql <frac> Ta May be
|
||||
.Ql \&.
|
||||
followed by one or more
|
||||
followed by zero or more
|
||||
.Ql 0
|
||||
\[en]
|
||||
.Ql 9 .
|
||||
If specified, prints this many fractional digits of a fixed-point number.
|
||||
Defaults to 5 digits, maximum 255 digits.
|
||||
(A
|
||||
.Ql \&.
|
||||
followed by zero
|
||||
.Ql 0
|
||||
\[en]
|
||||
.Ql 9
|
||||
prints zero fractional digits.)
|
||||
.It Ql <prec> Ta May be
|
||||
.Ql q
|
||||
followed by one or more
|
||||
@@ -577,6 +586,19 @@ is equivalent to
|
||||
or to
|
||||
.Ql STRCAT("str", \&"ing") .
|
||||
.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.
|
||||
.Bl -column "STRSLICE(str, start, stop)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
@@ -615,7 +637,7 @@ The following functions operate on string expressions, but return integers.
|
||||
.It Fn CHARLEN str Ta Returns the number of charmap entries in Ar str No with the current charmap.
|
||||
.It Fn CHARCMP str1 str2 Ta Compares Ar str1 No and Ar str2 No according to their charmap entry values with the current charmap. Returns -1 if Ar str1 No is lower than Ar str2 Ns , 1 if Ar str1 No is greater than Ar str2 Ns , or 0 if they match.
|
||||
.It Fn CHARSIZE char Ta Returns how many values are in the charmap entry for Ar char No with the current charmap.
|
||||
.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char .
|
||||
.It Fn CHARVAL char idx Ta Returns the value at Ar idx No of the charmap entry for Ar char . If Ar idx No is not specified, Ar char No must have a single value, which is returned.
|
||||
.El
|
||||
.Pp
|
||||
Note that indexes count starting from 0 at the beginning, or from -1 at the end.
|
||||
@@ -625,28 +647,16 @@ the charmap entries of a string are counted by
|
||||
.Ql CHARLEN ;
|
||||
and the values of a charmap entry are counted by
|
||||
.Ql CHARSIZE .
|
||||
.Pp
|
||||
The following legacy functions are similar to other functions that operate on string expressions, but for historical reasons, they count starting from
|
||||
.Em position 1 ,
|
||||
not from index 0!
|
||||
(Position -1 still counts from the end.)
|
||||
.Bl -column "STRSUB(str, pos, len)"
|
||||
.It Sy Name Ta Sy Operation
|
||||
.It Fn STRSUB str pos len Ta Returns a substring of Ar str No starting at Ar pos No and Ar len No characters long. If Ar len No is not specified, the substring continues to the end of Ar str No .
|
||||
.It Fn STRIN str sub Ta Returns the first position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn STRRIN str sub Ta Returns the last position of Ar sub No in Ar str Ns , or 0 if it's not present.
|
||||
.It Fn CHARSUB str pos Ta Returns the substring of Ar str No for the charmap entry at Ar pos No with the current charmap . Pq Ar pos No counts charmap entries, not characters.
|
||||
.El
|
||||
.Ss Character maps
|
||||
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.
|
||||
.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
|
||||
CHARMAP "A", 42
|
||||
CHARMAP ":)", 39
|
||||
CHARMAP ':)', 39
|
||||
CHARMAP "<br>", 13, 10
|
||||
CHARMAP "€", $20ac
|
||||
CHARMAP '€', $20ac
|
||||
.Ed
|
||||
.Pp
|
||||
This would result in
|
||||
@@ -1691,7 +1701,8 @@ $ rgbasm -o a.o a.asm
|
||||
$ rgbasm -o b.o b.asm
|
||||
$ rgbasm -o c.o c.asm
|
||||
$ rgblink a.o b.o c.o
|
||||
error: c.asm(2): Unknown symbol "LabelA"
|
||||
error: Undefined symbol "LabelA"
|
||||
at c.asm(2)
|
||||
Linking failed with 1 error
|
||||
.Ed
|
||||
.Pp
|
||||
@@ -1700,15 +1711,21 @@ Note also that only exported symbols will appear in symbol and map files produce
|
||||
.Ss Purging symbols
|
||||
.Ic PURGE
|
||||
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
|
||||
.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).
|
||||
.Bd -literal -offset indent
|
||||
DEF Kamikaze EQUS "I don't want to live anymore"
|
||||
AOLer: DB "Me too lol"
|
||||
PURGE Kamikaze, AOLer
|
||||
ASSERT !DEF(Kamikaze) && !DEF(AOLer)
|
||||
.Ed
|
||||
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).
|
||||
Purging labels at all is
|
||||
.Em not
|
||||
recommended.
|
||||
.Pp
|
||||
String constants are not expanded within the symbol names.
|
||||
.Ss Predeclared symbols
|
||||
@@ -1718,10 +1735,9 @@ 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 EQUS Ta The current global 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 _NARG Ta Ic EQU Ta Number of arguments passed to macro, updated by Ic SHIFT
|
||||
.It Dv __DATE__ Ta Ic EQUS Ta Today's date
|
||||
.It Dv __TIME__ Ta Ic EQUS Ta The current time
|
||||
.It Dv __ISO_8601_LOCAL__ Ta Ic EQUS Ta ISO 8601 timestamp (local)
|
||||
.It Dv __ISO_8601_UTC__ Ta Ic EQUS Ta ISO 8601 timestamp (UTC)
|
||||
.It Dv __UTC_YEAR__ Ta Ic EQU Ta Today's year
|
||||
@@ -2494,7 +2510,8 @@ can be used to change some of the options during assembling from within the sour
|
||||
takes a comma-separated list of options as its argument:
|
||||
.Bd -literal -offset indent
|
||||
PUSHO
|
||||
OPT g.oOX, Wdiv ; acts like command-line -g.oOX -Wdiv
|
||||
OPT g.oOX, Wdiv ; acts like command-line `-g.oOX -Wdiv`
|
||||
OPT -Wdiv ; dashes before the options are optional
|
||||
DW `..ooOOXX ; uses the graphics constant characters from OPT g
|
||||
PRINTLN $80000000/-1 ; prints a warning about division
|
||||
POPO
|
||||
@@ -2527,6 +2544,68 @@ PUSHO b.X, g.oOX
|
||||
DW `..ooOOXX
|
||||
POPO
|
||||
.Ed
|
||||
.Ss Excluding locations from backtraces
|
||||
Errors and warnings print
|
||||
.Em backtraces
|
||||
showing the location in the source file where the problem occurred, tracing the origin of the problem even through a chain of
|
||||
.Ic REPT ,
|
||||
.Ic FOR ,
|
||||
.Ic MACRO ,
|
||||
and
|
||||
.Ic INCLUDE
|
||||
locations.
|
||||
Sometimes there are locations you would like to ignore; for example, a common utility macro when you only care about the line where the macro is used, or an
|
||||
.Ic INCLUDE
|
||||
file that only serves to include other files and is just filler in the backtrace.
|
||||
.Pp
|
||||
In those cases, you can
|
||||
.Em silence
|
||||
a location with a question mark
|
||||
.Sq \&?
|
||||
after the token: all of the locations created by a
|
||||
.Sq REPT? ,
|
||||
.Sq FOR? ,
|
||||
or
|
||||
.Sq MACRO?
|
||||
will not be printed, and any location created by a
|
||||
.Sq INCLUDE? ,
|
||||
or a macro invocation whose name is immediately followed by a
|
||||
.Sq \&? ,
|
||||
will not be printed.
|
||||
For example, if this were assembled as
|
||||
.Ql example.asm :
|
||||
.Bd -literal -offset indent
|
||||
MACRO lb
|
||||
assert -128 <= (\e2) && (\e2) < 256, "\e2 is not a byte"
|
||||
assert -128 <= (\e3) && (\e3) < 256, "\e3 is not a byte"
|
||||
ld \e1, (LOW(\e2) << 8) | LOW(\e3)
|
||||
ENDM
|
||||
SECTION "Code", ROM0
|
||||
lb hl, $123, $45
|
||||
.Ed
|
||||
.Pp
|
||||
This would print an error backtrace:
|
||||
.Bd -literal -offset indent
|
||||
error: Assertion failed: $123 is not a byte
|
||||
at example.asm::lb(2)
|
||||
<- example.asm(7)
|
||||
.Ed
|
||||
.Pp
|
||||
But if
|
||||
.Ql MACRO
|
||||
were changed to
|
||||
.Ql MACRO? ,
|
||||
or
|
||||
.Ql lb hl
|
||||
were changed to
|
||||
.Ql lb? hl ,
|
||||
then the error backtrace would not mention the location within the
|
||||
.Ql lb
|
||||
macro:
|
||||
.Bd -literal -offset indent
|
||||
error: Assertion failed: $123 is not a byte
|
||||
at example.asm(7)
|
||||
.Ed
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr rgblink 1 ,
|
||||
|
||||
12
man/rgbds.5
12
man/rgbds.5
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBDS 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -79,12 +79,16 @@ order, meaning the node with ID 0 is the last one in the list!
|
||||
.It Cm LONG Ar ParentLineNo
|
||||
Line at which the parent node's context was exited; meaningless for the root node.
|
||||
.It Cm BYTE Ar Type
|
||||
Bits 0\(en6 indicate the node's type:
|
||||
.Bl -column "Value" -compact
|
||||
.It Sy Value Ta Sy Meaning
|
||||
.It 0 Ta REPT node
|
||||
.It 1 Ta File node
|
||||
.It 2 Ta Macro node
|
||||
.El
|
||||
.Pp
|
||||
Bit\ 7 being set means that the node is "quieted"
|
||||
.Pq see Do Excluding locations from backtraces Dc in Xr rgbasm 5 .
|
||||
.It Cm IF Ar Type No \(!= 0
|
||||
If the node is not a REPT node...
|
||||
.Pp
|
||||
@@ -401,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 ,
|
||||
that is, from 0 to 7.
|
||||
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
|
||||
byte.
|
||||
.It Li $71 Ta Cm LOW
|
||||
@@ -412,6 +413,9 @@ byte.
|
||||
value.
|
||||
.It Li $73 Ta Cm TZCOUNT
|
||||
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
|
||||
.Cm LONG
|
||||
ID.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBDS 7
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
125
man/rgbfix.1
125
man/rgbfix.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBFIX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -8,8 +8,9 @@
|
||||
.Nd Game Boy header utility and checksum fixer
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl hjOsVvw
|
||||
.Op Fl hjsVvw
|
||||
.Op Fl C | c
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl f Ar fix_spec
|
||||
.Op Fl i Ar game_id
|
||||
.Op Fl k Ar licensee_str
|
||||
@@ -22,7 +23,7 @@
|
||||
.Op Fl r Ar ram_size
|
||||
.Op Fl t Ar title_str
|
||||
.Op Fl W Ar warning
|
||||
.Op Ar
|
||||
.Ar
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
@@ -38,23 +39,67 @@ Developers are advised to fill those fields with 0x00 bytes in their source code
|
||||
.Nm ,
|
||||
and to have already populated whichever fields they don't specify using
|
||||
.Nm .
|
||||
.Pp
|
||||
The input
|
||||
.Ar asmfile
|
||||
can be a path to a file, or
|
||||
.Cm \-
|
||||
to read from standard input.
|
||||
.Pp
|
||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||
.Fl \-color-o
|
||||
.Sh ARGUMENTS
|
||||
.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 \-ver
|
||||
is
|
||||
.Fl \-color-only ,
|
||||
.Fl \-version ,
|
||||
but
|
||||
.Fl \-color
|
||||
.Fl \-v
|
||||
is invalid because it could also be
|
||||
.Fl \-color-compatible .
|
||||
Options later in the command line override those set earlier.
|
||||
Accepted options are as follows:
|
||||
.Fl \-validate .
|
||||
.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
|
||||
.It Fl C , Fl \-color-only
|
||||
Set the Game Boy Color\(enonly flag
|
||||
@@ -70,6 +115,18 @@ to 0x80.
|
||||
This overrides
|
||||
.Fl c
|
||||
if it was set prior.
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl f Ar fix_spec , Fl \-fix-spec Ar fix_spec
|
||||
Fix certain header values that the Game Boy checks for correctness.
|
||||
Alternatively, intentionally trash these values by writing their binary inverse instead.
|
||||
@@ -136,9 +193,6 @@ section below.
|
||||
Set the ROM version
|
||||
.Pq Ad 0x14C
|
||||
to a given value from 0 to 0xFF.
|
||||
.It Fl O , Fl \-overwrite
|
||||
Alias for
|
||||
.Fl Wno-overwrite .
|
||||
.It Fl o Ar out_file , Fl \-output Ar out_file
|
||||
Write the modified ROM image to the given file, or '-' to write to standard output.
|
||||
If not specified, the input files are modified in-place, or written to standard output if read from standard input.
|
||||
@@ -159,7 +213,7 @@ Set the SGB flag
|
||||
.Pq Ad 0x146
|
||||
to 0x03.
|
||||
This flag will be ignored by the SGB unless the old licensee code
|
||||
.Pq Fl -l
|
||||
.Pq Fl l
|
||||
is 0x33!
|
||||
.It Fl t Ar title , Fl \-title Ar title
|
||||
Set the title string
|
||||
@@ -189,6 +243,23 @@ See the
|
||||
section for a list of warnings.
|
||||
.It Fl w
|
||||
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
|
||||
.Sh DIAGNOSTICS
|
||||
Warnings are diagnostic messages that indicate possibly erroneous behavior that does not necessarily compromise the header-fixing process.
|
||||
@@ -202,7 +273,7 @@ to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning or meta warning into an error.
|
||||
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.
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
@@ -224,10 +295,10 @@ Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
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,
|
||||
.Fl Wtruncation
|
||||
Note that each of these flags also has a negation (for example,
|
||||
.Fl Wobsolete
|
||||
enables the warning that
|
||||
.Fl Wno-truncation
|
||||
.Fl Wno-obsolete
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
@@ -240,6 +311,8 @@ prefix, entries are listed alphabetically.
|
||||
.Bl -tag -width Ds
|
||||
.It Fl Wno-mbc
|
||||
Warn when there are inconsistencies with or caveats about the specified MBC type.
|
||||
.It Fl Wno-obsolete
|
||||
Warn when obsolete features are encountered, which have been deprecated and may later be removed.
|
||||
.It Fl Wno-overwrite
|
||||
Warn when overwriting different non-zero bytes in the header.
|
||||
.It Fl Wno-sgb
|
||||
@@ -315,7 +388,7 @@ will warn about and ignore
|
||||
.Fl j
|
||||
if used in combination with TPP1.
|
||||
.Sh BUGS
|
||||
Please report bugs on
|
||||
Please report bugs or mistakes in this documentation on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
|
||||
195
man/rgbgfx.1
195
man/rgbgfx.1
@@ -2,7 +2,7 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBGFX 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -15,6 +15,7 @@
|
||||
.Op Fl a Ar attrmap | Fl A
|
||||
.Op Fl b Ar base_ids
|
||||
.Op Fl c Ar pal_spec
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl d Ar depth
|
||||
.Op Fl i Ar input_tiles
|
||||
.Op Fl L Ar slice
|
||||
@@ -42,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.
|
||||
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
|
||||
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
|
||||
is
|
||||
.Fl \-verbose ,
|
||||
@@ -51,26 +58,6 @@ but
|
||||
is invalid because it could also be
|
||||
.Fl \-version .
|
||||
.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
|
||||
.Ql -
|
||||
(a single dash) as a file name makes
|
||||
@@ -81,7 +68,39 @@ To suppress this behavior, and open a file in the current directory actually cal
|
||||
pass
|
||||
.Ql ./-
|
||||
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
|
||||
The following options are accepted:
|
||||
.Bl -tag -width Ds
|
||||
@@ -177,8 +196,25 @@ the low two bits 0-1 specify which gray shade goes in color index 0,
|
||||
the next two bits 2-3 specify which gray shade goes in color index 1,
|
||||
and so on.
|
||||
Gray shade 0 is the lightest (white), 3 is the darkest (black).
|
||||
If
|
||||
.Ar pal_spec
|
||||
is the case-insensitive word
|
||||
.Cm dmg ,
|
||||
then it acts like
|
||||
.Cm dmg=E4 ,
|
||||
i.e. the darkest gray will end up in color index 0, and so on.
|
||||
The same gray shade cannot go in two color indexes.
|
||||
To specify a DMG palette, the input PNG must have all its colors in shades of gray, without any transparent colors.
|
||||
.It Sy automatic palette generation
|
||||
If
|
||||
.Ar pal_spec
|
||||
is the case-insensitive word
|
||||
.Cm auto ,
|
||||
then a palette is automatically generated using the procedure described in
|
||||
.Sx PALETTE GENERATION .
|
||||
This is the default behavior if
|
||||
.Fl c
|
||||
was not specified.
|
||||
.It Sy external palette spec
|
||||
Otherwise,
|
||||
.Ar pal_spec
|
||||
@@ -195,6 +231,18 @@ See
|
||||
.Sx PALETTE SPECIFICATION FORMATS
|
||||
for a list of formats and their descriptions.
|
||||
.El
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl d Ar depth , Fl \-depth Ar depth
|
||||
Set the bit depth of the output tile data, in bits per pixel (bpp), either 1 or 2 (the default).
|
||||
This changes how tile data is output, and the maximum number of colors per palette (2 and 4 respectively).
|
||||
@@ -317,7 +365,7 @@ Output the image's palette set to this file.
|
||||
Same as
|
||||
.Fl p Ar base_path Ns .pal
|
||||
.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.
|
||||
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
|
||||
@@ -382,14 +430,17 @@ Be verbose.
|
||||
The verbosity level is increased by one each time the flag is specified, with each level including the previous:
|
||||
.Bl -enum -compact
|
||||
.It
|
||||
Print the
|
||||
.Nm
|
||||
prints out its configuration before doing anything.
|
||||
configuration before taking actions.
|
||||
.It
|
||||
A generic message is printed before doing most actions.
|
||||
Print a notice before significant actions.
|
||||
.It
|
||||
Some of the actions' intermediate results are printed.
|
||||
Print some of the actions' intermediate results.
|
||||
.It
|
||||
Some internal debug printing is enabled.
|
||||
Print some internal debug information.
|
||||
.It
|
||||
Print detailed internal information.
|
||||
.El
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
@@ -436,69 +487,53 @@ Implies
|
||||
.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).
|
||||
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
|
||||
.Ss At-files
|
||||
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
|
||||
To avoid these drawbacks,
|
||||
.Nm
|
||||
supports
|
||||
To avoid these drawbacks, you can use
|
||||
.Dq at-files :
|
||||
any command-line argument that begins with an at sign
|
||||
.Pq Ql @
|
||||
is interpreted as one.
|
||||
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.
|
||||
is interpreted as one, as documented above.
|
||||
At-files can be stored right next to the corresponding image, for example:
|
||||
.Pp
|
||||
.Dl $ rgbgfx -o image.2bpp -t image.tilemap @image.flags image.png
|
||||
.Pp
|
||||
This will read additional flags from file
|
||||
This will read additional flags from the file
|
||||
.Ql image.flags ,
|
||||
which could contains for example
|
||||
which could contain, for example,
|
||||
.Ql -b 128
|
||||
to specify a base offset for the image's tiles.
|
||||
The above command could be generated from the following
|
||||
.Xr make 1
|
||||
rule, for example:
|
||||
rule:
|
||||
.Bd -literal -offset indent
|
||||
%.2bpp %.tilemap: %.flags %.png
|
||||
rgbgfx -o $*.2bpp -t $*.tilemap @$*.flags $*.png
|
||||
.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
|
||||
The following formats are supported:
|
||||
.Bl -tag -width Ds
|
||||
@@ -600,9 +635,10 @@ behavior depends on an internal detail of how the PNG is saved, specifically its
|
||||
chunk.
|
||||
Since few image editors (such as GIMP) expose that detail, this behavior is only kept for compatibility and should be considered deprecated.
|
||||
.Pp
|
||||
It turns out that palette generation is an NP-complete problem, so
|
||||
It turns out that palette generation is an NP-complete problem known as "pagination", so
|
||||
.Nm
|
||||
does not attempt to find the optimal solution, but instead to find a good one in a reasonable amount of time.
|
||||
does not attempt to find the optimal solution, but instead uses an "overload-and-remove" heuristic to find a good one in a reasonable amount of time.
|
||||
(There are no guarantees about how this algorithm will generate palettes, apart from the constraints documented above.)
|
||||
It is possible to compute the optimal solution externally (using a solver, for example), and then provide it to
|
||||
.Nm
|
||||
via
|
||||
@@ -735,7 +771,7 @@ to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning or meta warning into an error.
|
||||
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.
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
@@ -757,10 +793,10 @@ Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
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,
|
||||
.Fl Wtrim-nonempty
|
||||
Note that each of these flags also has a negation (for example,
|
||||
.Fl Wobsolete
|
||||
enables the warning that
|
||||
.Fl Wno-trim-nonempty
|
||||
.Fl Wno-obsolete
|
||||
disables; and
|
||||
.Fl Wall
|
||||
enables every warning that
|
||||
@@ -777,6 +813,8 @@ Warn when a generated palette is sorted according to the input PNG's embedded pa
|
||||
was not provided.
|
||||
This warning is enabled by
|
||||
.Fl Weverything .
|
||||
.It Fl Wno-obsolete
|
||||
Warn when obsolete features are encountered, which have been deprecated and may later be removed.
|
||||
.It Fl Wtrim-nonempty
|
||||
Warn when
|
||||
.Fl x
|
||||
@@ -824,9 +862,8 @@ $ rgbgfx level1.png -i tileset.2bpp -c gbc:tileset.pal -t level1.tilemap -a leve
|
||||
$ rgbgfx level2.png -i tileset.2bpp -c gbc:tileset.pal -t level2.tilemap -a level2.attrmap
|
||||
.Ed
|
||||
.Sh BUGS
|
||||
Please report bugs and mistakes in this man page on
|
||||
Please report bugs or mistakes in this documentation on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
Bug reports and feature requests about RGBDS are also welcome!
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
.Xr rgblink 1 ,
|
||||
|
||||
167
man/rgblink.1
167
man/rgblink.1
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBLINK 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
@@ -9,6 +9,8 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dhMtVvwx
|
||||
.Op Fl B Ar param
|
||||
.Op Fl \-color Ar when
|
||||
.Op Fl l Ar linker_script
|
||||
.Op Fl m Ar map_file
|
||||
.Op Fl n Ar sym_file
|
||||
@@ -46,14 +48,14 @@ Also, if your ROM is designed for a monochrome Game Boy, you can make sure that
|
||||
option, which implies
|
||||
.Fl w
|
||||
but also prohibits the use of banked VRAM.
|
||||
.Pp
|
||||
The input
|
||||
.Ar asmfile
|
||||
can be a path to a file, or
|
||||
.Cm \-
|
||||
to read from standard input.
|
||||
.Pp
|
||||
Note that options can be abbreviated as long as the abbreviation is unambiguous:
|
||||
.Sh ARGUMENTS
|
||||
.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
|
||||
is
|
||||
.Fl \-verbose ,
|
||||
@@ -61,8 +63,88 @@ but
|
||||
.Fl \-ver
|
||||
is invalid because it could also be
|
||||
.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
|
||||
.It Fl B Ar param , Fl \-backtrace Ar param
|
||||
Configures how location backtraces are printed if warnings or errors occur.
|
||||
This flag may be specified multiple times with different parameters that combine meaningfully.
|
||||
If
|
||||
.Ar param
|
||||
is a positive number, it specifies the maximum backtrace depth, abbreviating deeper ones.
|
||||
Other valid parameter values are the following:
|
||||
.Bl -tag -width Ds
|
||||
.It Cm 0
|
||||
Do not limit the maximum backtrace depth; this is the default.
|
||||
.It Cm all
|
||||
Force all locations to be printed, even "quiet" ones (see
|
||||
.Dq Excluding locations from backtraces
|
||||
in
|
||||
.Xr rgbasm 5
|
||||
for details).
|
||||
.It Cm no-all
|
||||
Do not print "quieted" locations in backtraces; this is the default.
|
||||
.It Cm collapse
|
||||
Print all locations on one line.
|
||||
.It Cm no-collapse
|
||||
Print one location per line; this is the default.
|
||||
.El
|
||||
.It Fl \-color Ar when
|
||||
Specify when to highlight warning and error messages with color:
|
||||
.Ql always ,
|
||||
.Ql never ,
|
||||
or
|
||||
.Ql auto .
|
||||
.Ql auto
|
||||
determines whether to use colors based on the
|
||||
.Ql Lk https://no-color.org/ NO_COLOR
|
||||
or
|
||||
.Ql Lk https://force-color.org/ FORCE_COLOR
|
||||
environment variables, or whether the output is to a TTY.
|
||||
.It Fl d , Fl \-dmg
|
||||
Enable DMG mode.
|
||||
Prohibit the use of sections that doesn't exist on a DMG, such as VRAM bank 1.
|
||||
@@ -114,7 +196,25 @@ Useful for ROMs that fit in 32 KiB.
|
||||
.It Fl V , Fl \-version
|
||||
Print the version of the program and exit.
|
||||
.It Fl v , Fl \-verbose
|
||||
Verbose: enable printing more information to standard error.
|
||||
Be verbose.
|
||||
The verbosity level is increased by one each time the flag is specified, with each level including the previous:
|
||||
.Bl -enum -compact
|
||||
.It
|
||||
Print the
|
||||
.Nm
|
||||
configuration before taking actions.
|
||||
.It
|
||||
Print a notice before significant actions.
|
||||
.It
|
||||
Print some of the actions' intermediate results.
|
||||
.It
|
||||
Print some internal debug information.
|
||||
.It
|
||||
Print detailed internal information.
|
||||
.El
|
||||
The verbosity level does not go past 6.
|
||||
.Pp
|
||||
Note that verbose output is only intended to be consumed by humans, and may change without notice between RGBDS releases; relying on those for scripts is not advised.
|
||||
.It Fl W Ar warning , Fl \-warning Ar warning
|
||||
Set warning flag
|
||||
.Ar warning .
|
||||
@@ -135,10 +235,32 @@ 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
|
||||
.Xr rgbfix 1 Ap s Fl p
|
||||
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
|
||||
.Ss Scrambling algorithm
|
||||
The default section placement algorithm tries to minimize the number of banks used;
|
||||
.Dq scrambling
|
||||
The default section placement algorithm tries to place sections into as few banks as possible.
|
||||
(It turns out that section placement is an NP-complete problem known as "bin packing", so
|
||||
.Nm
|
||||
does not attempt to find the optimal solution, but instead uses a "first-fit" heuristic to find a good one in a reasonable amount of time.
|
||||
There are no guarantees about where this algorithm will place sections, apart from the bank, address, and alignment constraints manually specified for the sections.)
|
||||
.Pp
|
||||
.Dq Scrambling
|
||||
instead places sections into a given pool of banks, trying to minimize the number of sections sharing a given bank.
|
||||
This is useful to catch broken bank assumptions, such as expecting two different sections to land in the same bank (that is not guaranteed unless both are manually assigned the same bank number).
|
||||
.Pp
|
||||
@@ -198,7 +320,7 @@ to prevent turning all warnings into errors.
|
||||
.It Fl Werror=
|
||||
Make the specified warning or meta warning into an error.
|
||||
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.
|
||||
This can be negated as
|
||||
.Fl Wno-error=
|
||||
@@ -220,7 +342,7 @@ Enables literally every warning.
|
||||
.El
|
||||
.Pp
|
||||
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
|
||||
enables the warning that
|
||||
.Fl Wno-obsolete
|
||||
@@ -258,11 +380,20 @@ This warning is enabled by
|
||||
Warn when a shift's operand is negative or greater than 32.
|
||||
This warning is enabled by
|
||||
.Fl Wall .
|
||||
.It Fl Wno-truncation
|
||||
.It Fl Wtruncation=
|
||||
Warn when an implicit truncation (for example,
|
||||
.Ic db
|
||||
to an 8-bit value) loses some bits.
|
||||
This occurs when an N-bit value is 2**N or greater, or less than -2**N.
|
||||
.Fl Wtruncation=0
|
||||
or
|
||||
.Fl Wno-truncation
|
||||
disables this warning.
|
||||
.Fl Wtruncation=1
|
||||
or just
|
||||
.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.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
All you need for a basic ROM is an object file, which can be made into a ROM image like so:
|
||||
@@ -282,7 +413,7 @@ Here is a more complete example:
|
||||
.Pp
|
||||
.Dl $ rgblink -o bin/game.gb -n bin/game.sym -p 0xFF obj/title.o obj/engine.o
|
||||
.Sh BUGS
|
||||
Please report bugs on
|
||||
Please report bugs or mistakes in this documentation on
|
||||
.Lk https://github.com/gbdev/rgbds/issues GitHub .
|
||||
.Sh SEE ALSO
|
||||
.Xr rgbasm 1 ,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" SPDX-License-Identifier: MIT
|
||||
.\"
|
||||
.Dd July 31, 2025
|
||||
.Dd September 30, 2025
|
||||
.Dt RGBLINK 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
|
||||
@@ -4,8 +4,11 @@ configure_file(version.cpp _version.cpp ESCAPE_QUOTES)
|
||||
|
||||
set(common_src
|
||||
"extern/getopt.cpp"
|
||||
"cli.cpp"
|
||||
"diagnostics.cpp"
|
||||
"style.cpp"
|
||||
"usage.cpp"
|
||||
"util.cpp"
|
||||
"_version.cpp"
|
||||
)
|
||||
|
||||
@@ -50,14 +53,16 @@ set(rgbasm_src
|
||||
"asm/symbol.cpp"
|
||||
"asm/warning.cpp"
|
||||
"extern/utf8decoder.cpp"
|
||||
"backtrace.cpp"
|
||||
"linkdefs.cpp"
|
||||
"opmath.cpp"
|
||||
"util.cpp"
|
||||
"verbosity.cpp"
|
||||
)
|
||||
|
||||
set(rgblink_src
|
||||
"${BISON_LINKER_SCRIPT_PARSER_OUTPUT_SOURCE}"
|
||||
"link/assign.cpp"
|
||||
"link/fstack.cpp"
|
||||
"link/lexer.cpp"
|
||||
"link/layout.cpp"
|
||||
"link/main.cpp"
|
||||
@@ -69,12 +74,14 @@ set(rgblink_src
|
||||
"link/symbol.cpp"
|
||||
"link/warning.cpp"
|
||||
"extern/utf8decoder.cpp"
|
||||
"backtrace.cpp"
|
||||
"linkdefs.cpp"
|
||||
"opmath.cpp"
|
||||
"util.cpp"
|
||||
"verbosity.cpp"
|
||||
)
|
||||
|
||||
set(rgbfix_src
|
||||
"fix/fix.cpp"
|
||||
"fix/main.cpp"
|
||||
"fix/mbc.cpp"
|
||||
"fix/warning.cpp"
|
||||
@@ -86,12 +93,13 @@ set(rgbgfx_src
|
||||
"gfx/pal_packing.cpp"
|
||||
"gfx/pal_sorting.cpp"
|
||||
"gfx/pal_spec.cpp"
|
||||
"gfx/palette.cpp"
|
||||
"gfx/png.cpp"
|
||||
"gfx/process.cpp"
|
||||
"gfx/reverse.cpp"
|
||||
"gfx/rgba.cpp"
|
||||
"gfx/warning.cpp"
|
||||
"util.cpp"
|
||||
"verbosity.cpp"
|
||||
)
|
||||
|
||||
foreach(PROG "asm" "fix" "gfx" "link")
|
||||
|
||||
@@ -2,19 +2,137 @@
|
||||
|
||||
#include "asm/actions.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/format.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/rpn.hpp" // Expression
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void act_If(int32_t condition) {
|
||||
lexer_IncIFDepth();
|
||||
|
||||
if (condition) {
|
||||
lexer_RunIFBlock();
|
||||
} else {
|
||||
lexer_SetMode(LEXER_SKIP_TO_ELIF);
|
||||
}
|
||||
}
|
||||
|
||||
void act_Elif(int32_t condition) {
|
||||
if (lexer_GetIFDepth() == 0) {
|
||||
fatal("Found `ELIF` outside of a conditional (not after an `IF`/`ELIF` block)");
|
||||
}
|
||||
if (lexer_RanIFBlock()) {
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
fatal("Found `ELIF` after an `ELSE` block");
|
||||
}
|
||||
// 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) {
|
||||
lexer_RunIFBlock();
|
||||
} else {
|
||||
lexer_SetMode(LEXER_SKIP_TO_ELIF);
|
||||
}
|
||||
}
|
||||
|
||||
void act_Else() {
|
||||
if (lexer_GetIFDepth() == 0) {
|
||||
fatal("Found `ELSE` outside of a conditional (not after an `IF`/`ELIF` block)");
|
||||
}
|
||||
if (lexer_RanIFBlock()) {
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
// 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);
|
||||
} else {
|
||||
lexer_RunIFBlock();
|
||||
lexer_ReachELSEBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void act_Endc() {
|
||||
lexer_DecIFDepth();
|
||||
}
|
||||
|
||||
AlignmentSpec act_Alignment(int32_t alignment, int32_t alignOfs) {
|
||||
AlignmentSpec spec = {0, 0};
|
||||
if (alignment > 16) {
|
||||
error("Alignment must be between 0 and 16, not %u", alignment);
|
||||
} else if (alignOfs <= -(1 << alignment) || alignOfs >= 1 << alignment) {
|
||||
error(
|
||||
"The absolute alignment offset (%" PRIu32 ") must be less than alignment size (%d)",
|
||||
static_cast<uint32_t>(alignOfs < 0 ? -alignOfs : alignOfs),
|
||||
1 << alignment
|
||||
);
|
||||
} else {
|
||||
spec.alignment = alignment;
|
||||
spec.alignOfs = alignOfs < 0 ? (1 << alignment) + alignOfs : alignOfs;
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
static void failAssert(AssertionType type, std::string const &message) {
|
||||
switch (type) {
|
||||
case ASSERT_FATAL:
|
||||
if (message.empty()) {
|
||||
fatal("Assertion failed");
|
||||
} else {
|
||||
fatal("Assertion failed: %s", message.c_str());
|
||||
}
|
||||
case ASSERT_ERROR:
|
||||
if (message.empty()) {
|
||||
error("Assertion failed");
|
||||
} else {
|
||||
error("Assertion failed: %s", message.c_str());
|
||||
}
|
||||
break;
|
||||
case ASSERT_WARN:
|
||||
if (message.empty()) {
|
||||
warning(WARNING_ASSERT, "Assertion failed");
|
||||
} else {
|
||||
warning(WARNING_ASSERT, "Assertion failed: %s", message.c_str());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void act_Assert(AssertionType type, Expression const &expr, std::string const &message) {
|
||||
if (!expr.isKnown()) {
|
||||
out_CreateAssert(type, expr, message, sect_GetOutputOffset());
|
||||
} else if (expr.value() == 0) {
|
||||
failAssert(type, message);
|
||||
}
|
||||
}
|
||||
|
||||
void act_StaticAssert(AssertionType type, int32_t condition, std::string const &message) {
|
||||
if (!condition) {
|
||||
failAssert(type, message);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> act_ReadFile(std::string const &name, uint32_t maxLen) {
|
||||
FILE *file = nullptr;
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
@@ -38,46 +156,120 @@ std::optional<std::string> act_ReadFile(std::string const &name, uint32_t maxLen
|
||||
readSize = fileSize;
|
||||
}
|
||||
fseek(file, 0, SEEK_SET);
|
||||
// LCOV_EXCL_START
|
||||
} else if (errno != ESPIPE) {
|
||||
error("Error determining size of READFILE file '%s': %s", name.c_str(), strerror(errno));
|
||||
error(
|
||||
"Error determining size of `READFILE` file \"%s\": %s", name.c_str(), strerror(errno)
|
||||
);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
std::string contents;
|
||||
contents.resize(readSize);
|
||||
|
||||
if (fread(&contents[0], 1, readSize, file) < readSize || ferror(file)) {
|
||||
error("Error reading READFILE file '%s': %s", name.c_str(), strerror(errno));
|
||||
// LCOV_EXCL_START
|
||||
error("Error reading `READFILE` file \"%s\": %s", name.c_str(), strerror(errno));
|
||||
return "";
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
uint32_t act_StringToNum(std::vector<int32_t> const &str) {
|
||||
uint32_t length = str.size();
|
||||
uint32_t act_CharToNum(std::string const &str) {
|
||||
if (std::vector<int32_t> units = charmap_Convert(str); units.size() == 1) {
|
||||
// The string is a single character with a single unit value,
|
||||
// which can be used directly as a numeric character.
|
||||
return static_cast<uint32_t>(units[0]);
|
||||
} else {
|
||||
error("Character literals must be a single charmap unit");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (length == 1) {
|
||||
// The string is a single character with a single value,
|
||||
uint32_t act_StringToNum(std::string const &str) {
|
||||
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) {
|
||||
// The string is a single character with a single unit value,
|
||||
// which can be used directly as a number.
|
||||
return static_cast<uint32_t>(str[0]);
|
||||
}
|
||||
|
||||
warning(WARNING_OBSOLETE, "Treating multi-unit strings as numbers is deprecated");
|
||||
|
||||
for (int32_t v : str) {
|
||||
if (!checkNBit(v, 8, "All character units")) {
|
||||
break;
|
||||
return static_cast<uint32_t>(units[0]);
|
||||
} else {
|
||||
error("Strings as numbers must be a single charmap unit");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t r = 0;
|
||||
|
||||
for (uint32_t i = length < 4 ? 0 : length - 4; i < length; ++i) {
|
||||
r <<= 8;
|
||||
r |= static_cast<uint8_t>(str[i]);
|
||||
static uint32_t adjustNegativeIndex(int32_t idx, size_t len, char const *functionName) {
|
||||
// String functions adjust negative index arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (idx < 0) {
|
||||
idx += len;
|
||||
}
|
||||
if (idx < 0) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Index starts at 0", functionName);
|
||||
idx = 0;
|
||||
}
|
||||
return static_cast<uint32_t>(idx);
|
||||
}
|
||||
|
||||
return r;
|
||||
static uint32_t adjustNegativePos(int32_t pos, size_t len, char const *functionName) {
|
||||
// STRSUB and CHARSUB adjust negative position arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (pos < 0) {
|
||||
pos += len + 1;
|
||||
}
|
||||
if (pos < 1) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1", functionName);
|
||||
pos = 1;
|
||||
}
|
||||
return static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
int32_t act_CharVal(std::string const &str) {
|
||||
if (size_t len = charmap_CharSize(str); len == 0) {
|
||||
error("CHARVAL: No character mapping for \"%s\"", str.c_str());
|
||||
return 0;
|
||||
} else if (len != 1) {
|
||||
error("CHARVAL: Character mapping for \"%s\" must have a single value", str.c_str());
|
||||
return 0;
|
||||
} else {
|
||||
return *charmap_CharValue(str, 0);
|
||||
}
|
||||
}
|
||||
|
||||
int32_t act_CharVal(std::string const &str, int32_t negIdx) {
|
||||
if (size_t len = charmap_CharSize(str); len != 0) {
|
||||
uint32_t idx = adjustNegativeIndex(negIdx, len, "CHARVAL");
|
||||
if (std::optional<int32_t> val = charmap_CharValue(str, idx); val.has_value()) {
|
||||
return *val;
|
||||
} else {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"CHARVAL: Index %" PRIu32 " is past the end of the character mapping",
|
||||
idx
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
error("CHARVAL: No character mapping for \"%s\"", str.c_str());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t act_StringByte(std::string const &str, int32_t negIdx) {
|
||||
size_t len = str.length();
|
||||
if (uint32_t idx = adjustNegativeIndex(negIdx, len, "STRBYTE"); idx < len) {
|
||||
return static_cast<uint8_t>(str[idx]);
|
||||
} else {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG, "STRBYTE: Index %" PRIu32 " is past the end of the string", idx
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void errorInvalidUTF8Byte(uint8_t byte, char const *functionName) {
|
||||
@@ -116,7 +308,12 @@ size_t act_StringLen(std::string const &str, bool printErrors) {
|
||||
return len;
|
||||
}
|
||||
|
||||
std::string act_StringSlice(std::string const &str, uint32_t start, uint32_t stop) {
|
||||
std::string
|
||||
act_StringSlice(std::string const &str, int32_t negStart, std::optional<int32_t> negStop) {
|
||||
size_t adjustLen = act_StringLen(str, false);
|
||||
uint32_t start = adjustNegativeIndex(negStart, adjustLen, "STRSLICE");
|
||||
uint32_t stop = negStop ? adjustNegativeIndex(*negStop, adjustLen, "STRSLICE") : adjustLen;
|
||||
|
||||
size_t strLen = str.length();
|
||||
size_t index = 0;
|
||||
uint32_t state = UTF8_ACCEPT;
|
||||
@@ -180,7 +377,13 @@ std::string act_StringSlice(std::string const &str, uint32_t start, uint32_t sto
|
||||
return str.substr(startIndex, index - startIndex);
|
||||
}
|
||||
|
||||
std::string act_StringSub(std::string const &str, uint32_t pos, uint32_t len) {
|
||||
std::string act_StringSub(std::string const &str, int32_t negPos, std::optional<uint32_t> optLen) {
|
||||
warning(WARNING_OBSOLETE, "`STRSUB` is deprecated; use 0-indexed `STRSLICE` instead");
|
||||
|
||||
size_t adjustLen = act_StringLen(str, false);
|
||||
uint32_t pos = adjustNegativePos(negPos, adjustLen, "STRSUB");
|
||||
uint32_t len = optLen ? *optLen : pos > adjustLen ? 0 : adjustLen + 1 - pos;
|
||||
|
||||
size_t strLen = str.length();
|
||||
size_t index = 0;
|
||||
uint32_t state = UTF8_ACCEPT;
|
||||
@@ -248,7 +451,10 @@ size_t act_CharLen(std::string const &str) {
|
||||
return len;
|
||||
}
|
||||
|
||||
std::string act_StringChar(std::string const &str, uint32_t idx) {
|
||||
std::string act_StringChar(std::string const &str, int32_t negIdx) {
|
||||
size_t adjustLen = act_CharLen(str);
|
||||
uint32_t idx = adjustNegativeIndex(negIdx, adjustLen, "STRCHAR");
|
||||
|
||||
std::string_view view = str;
|
||||
size_t charLen = 1;
|
||||
|
||||
@@ -269,7 +475,12 @@ std::string act_StringChar(std::string const &str, uint32_t idx) {
|
||||
return std::string(start);
|
||||
}
|
||||
|
||||
std::string act_CharSub(std::string const &str, uint32_t pos) {
|
||||
std::string act_CharSub(std::string const &str, int32_t negPos) {
|
||||
warning(WARNING_OBSOLETE, "`CHARSUB` is deprecated; use 0-indexed `STRCHAR` instead");
|
||||
|
||||
size_t adjustLen = act_CharLen(str);
|
||||
uint32_t pos = adjustNegativePos(negPos, adjustLen, "CHARSUB");
|
||||
|
||||
std::string_view view = str;
|
||||
size_t charLen = 1;
|
||||
|
||||
@@ -317,32 +528,6 @@ int32_t act_CharCmp(std::string_view str1, std::string_view str2) {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t act_AdjustNegativeIndex(int32_t idx, size_t len, char const *functionName) {
|
||||
// String functions adjust negative index arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (idx < 0) {
|
||||
idx += len;
|
||||
}
|
||||
if (idx < 0) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Index starts at 0", functionName);
|
||||
idx = 0;
|
||||
}
|
||||
return static_cast<uint32_t>(idx);
|
||||
}
|
||||
|
||||
uint32_t act_AdjustNegativePos(int32_t pos, size_t len, char const *functionName) {
|
||||
// STRSUB and CHARSUB adjust negative position arguments the same way,
|
||||
// such that position -1 is the last character of a string.
|
||||
if (pos < 0) {
|
||||
pos += len + 1;
|
||||
}
|
||||
if (pos < 1) {
|
||||
warning(WARNING_BUILTIN_ARG, "%s: Position starts at 1", functionName);
|
||||
pos = 1;
|
||||
}
|
||||
return static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
std::string
|
||||
act_StringReplace(std::string_view str, std::string const &old, std::string const &rep) {
|
||||
if (old.empty()) {
|
||||
@@ -372,40 +557,30 @@ std::string act_StringFormat(
|
||||
std::string str;
|
||||
size_t argIndex = 0;
|
||||
|
||||
for (size_t i = 0; spec[i] != '\0'; ++i) {
|
||||
int c = spec[i];
|
||||
|
||||
if (c != '%') {
|
||||
for (size_t i = 0; spec[i] != '\0';) {
|
||||
if (int c = spec[i]; c != '%') {
|
||||
str += c;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
c = spec[++i];
|
||||
|
||||
if (c == '%') {
|
||||
if (int c = spec[++i]; c == '%') {
|
||||
str += c;
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
FormatSpec fmt{};
|
||||
|
||||
while (c != '\0') {
|
||||
fmt.useCharacter(c);
|
||||
if (fmt.isFinished()) {
|
||||
break;
|
||||
}
|
||||
c = spec[++i];
|
||||
}
|
||||
|
||||
if (fmt.isEmpty()) {
|
||||
} else if (c == '\0') {
|
||||
error("STRFMT: Illegal '%%' at end of format string");
|
||||
str += '%';
|
||||
break;
|
||||
}
|
||||
|
||||
FormatSpec fmt{};
|
||||
size_t n = fmt.parseSpec(spec.c_str() + i);
|
||||
i += n;
|
||||
|
||||
if (!fmt.isValid()) {
|
||||
error("STRFMT: Invalid format spec for argument %zu", argIndex + 1);
|
||||
str += '%';
|
||||
str += spec.substr(i - n - 1, n + 1); // include the '%'
|
||||
} else if (argIndex >= args.size()) {
|
||||
// Will warn after formatting is done.
|
||||
str += '%';
|
||||
@@ -419,51 +594,43 @@ std::string act_StringFormat(
|
||||
}
|
||||
|
||||
if (argIndex < args.size()) {
|
||||
error("STRFMT: %zu unformatted argument(s)", args.size() - argIndex);
|
||||
size_t extra = args.size() - argIndex;
|
||||
error("STRFMT: %zu unformatted argument%s", extra, extra == 1 ? "" : "s");
|
||||
} else if (argIndex > args.size()) {
|
||||
error(
|
||||
"STRFMT: Not enough arguments for format spec, got: %zu, need: %zu",
|
||||
args.size(),
|
||||
argIndex
|
||||
"STRFMT: Not enough arguments for format spec (expected %zu, got %zu)",
|
||||
argIndex,
|
||||
args.size()
|
||||
);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
std::string act_SectionName(std::string const &symName) {
|
||||
Symbol *sym = sym_FindScopedValidSymbol(symName);
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
fatal("Undefined symbol `%s`; it was purged", symName.c_str());
|
||||
} else {
|
||||
fatal("Undefined symbol `%s`", symName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Section const *section = sym->getSection();
|
||||
if (!section) {
|
||||
fatal("`%s` does not belong to any section", sym->name.c_str());
|
||||
}
|
||||
|
||||
return section->name;
|
||||
}
|
||||
|
||||
void act_CompoundAssignment(std::string const &symName, RPNCommand op, int32_t constValue) {
|
||||
Expression oldExpr, constExpr, newExpr;
|
||||
int32_t newValue;
|
||||
|
||||
oldExpr.makeSymbol(symName);
|
||||
constExpr.makeNumber(constValue);
|
||||
newExpr.makeBinaryOp(op, std::move(oldExpr), constExpr);
|
||||
newValue = newExpr.getConstVal();
|
||||
|
||||
int32_t newValue = newExpr.getConstVal();
|
||||
sym_AddVar(symName, newValue);
|
||||
}
|
||||
|
||||
void act_FailAssert(AssertionType type) {
|
||||
switch (type) {
|
||||
case ASSERT_FATAL:
|
||||
fatal("Assertion failed");
|
||||
case ASSERT_ERROR:
|
||||
error("Assertion failed");
|
||||
break;
|
||||
case ASSERT_WARN:
|
||||
warning(WARNING_ASSERT, "Assertion failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void act_FailAssertMsg(AssertionType type, std::string const &message) {
|
||||
switch (type) {
|
||||
case ASSERT_FATAL:
|
||||
fatal("Assertion failed: %s", message.c_str());
|
||||
case ASSERT_ERROR:
|
||||
error("Assertion failed: %s", message.c_str());
|
||||
break;
|
||||
case ASSERT_WARN:
|
||||
warning(WARNING_ASSERT, "Assertion failed: %s", message.c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,21 @@
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp" // InsertionOrderedMap
|
||||
#include "util.hpp"
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
@@ -34,8 +38,8 @@ struct Charmap {
|
||||
};
|
||||
|
||||
// Traverse the trie depth-first to derive the character mappings in definition order
|
||||
template<typename F>
|
||||
bool forEachChar(Charmap const &charmap, F callback) {
|
||||
template<typename CallbackFnT>
|
||||
bool forEachChar(Charmap const &charmap, CallbackFnT callback) {
|
||||
// clang-format off: nested initializers
|
||||
for (std::stack<std::pair<size_t, std::string>> prefixes({{0, ""}}); !prefixes.empty();) {
|
||||
// clang-format on
|
||||
@@ -54,8 +58,7 @@ bool forEachChar(Charmap const &charmap, F callback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::deque<Charmap> charmapList;
|
||||
static std::unordered_map<std::string, size_t> charmapMap; // Indexes into `charmapList`
|
||||
static InsertionOrderedMap<Charmap> charmaps;
|
||||
|
||||
static Charmap *currentCharmap;
|
||||
static std::stack<Charmap *> charmapStack;
|
||||
@@ -64,7 +67,7 @@ bool charmap_ForEach(
|
||||
void (*mapFunc)(std::string const &),
|
||||
void (*charFunc)(std::string const &, std::vector<int32_t>)
|
||||
) {
|
||||
for (Charmap const &charmap : charmapList) {
|
||||
for (Charmap const &charmap : charmaps) {
|
||||
std::map<size_t, std::string> mappings;
|
||||
forEachChar(charmap, [&mappings](size_t nodeIdx, std::string const &mapping) {
|
||||
mappings[nodeIdx] = mapping;
|
||||
@@ -72,49 +75,45 @@ bool charmap_ForEach(
|
||||
});
|
||||
|
||||
mapFunc(charmap.name);
|
||||
for (auto [nodeIdx, mapping] : mappings) {
|
||||
for (auto const &[nodeIdx, mapping] : mappings) {
|
||||
charFunc(mapping, charmap.nodes[nodeIdx].value);
|
||||
}
|
||||
}
|
||||
return !charmapList.empty();
|
||||
return !charmaps.empty();
|
||||
}
|
||||
|
||||
void charmap_New(std::string const &name, std::string const *baseName) {
|
||||
size_t baseIdx = SIZE_MAX;
|
||||
std::optional<size_t> baseIdx = std::nullopt;
|
||||
|
||||
if (baseName != nullptr) {
|
||||
if (auto search = charmapMap.find(*baseName); search == charmapMap.end()) {
|
||||
error("Base charmap '%s' doesn't exist", baseName->c_str());
|
||||
} else {
|
||||
baseIdx = search->second;
|
||||
baseIdx = charmaps.findIndex(*baseName);
|
||||
if (!baseIdx) {
|
||||
error("Undefined base charmap `%s`", baseName->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (charmapMap.find(name) != charmapMap.end()) {
|
||||
error("Charmap '%s' already exists", name.c_str());
|
||||
if (charmaps.contains(name)) {
|
||||
error("Charmap `%s` is already defined", name.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Init the new charmap's fields
|
||||
charmapMap[name] = charmapList.size();
|
||||
Charmap &charmap = charmapList.emplace_back();
|
||||
|
||||
if (baseIdx != SIZE_MAX) {
|
||||
charmap.nodes = charmapList[baseIdx].nodes; // Copies `charmapList[baseIdx].nodes`
|
||||
Charmap &charmap = charmaps.add(name);
|
||||
charmap.name = name;
|
||||
if (baseIdx) {
|
||||
charmap.nodes = charmaps[*baseIdx].nodes; // Copies `charmaps[*baseIdx].nodes`
|
||||
} else {
|
||||
charmap.nodes.emplace_back(); // Zero-init the root node
|
||||
}
|
||||
|
||||
charmap.name = name;
|
||||
|
||||
currentCharmap = &charmap;
|
||||
}
|
||||
|
||||
void charmap_Set(std::string const &name) {
|
||||
if (auto search = charmapMap.find(name); search == charmapMap.end()) {
|
||||
error("Charmap '%s' doesn't exist", name.c_str());
|
||||
if (auto index = charmaps.findIndex(name); index) {
|
||||
currentCharmap = &charmaps[*index];
|
||||
} else {
|
||||
currentCharmap = &charmapList[search->second];
|
||||
error("Undefined charmap `%s`", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +288,7 @@ size_t charmap_ConvertNext(std::string_view &input, std::vector<int32_t> *output
|
||||
} else if (charmap.name != DEFAULT_CHARMAP_NAME) {
|
||||
warning(
|
||||
WARNING_UNMAPPED_CHAR_2,
|
||||
"Unmapped character %s not in " DEFAULT_CHARMAP_NAME " charmap",
|
||||
"Unmapped character %s not in `" DEFAULT_CHARMAP_NAME "` charmap",
|
||||
printChar(firstChar)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#include "asm/fixpoint.hpp"
|
||||
|
||||
#include <math.h>
|
||||
#include <numbers>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
#endif
|
||||
static constexpr double tau = std::numbers::pi * 2;
|
||||
|
||||
static double fix2double(int32_t i, int32_t q) {
|
||||
return i / pow(2.0, q);
|
||||
@@ -25,11 +25,11 @@ static int32_t double2fix(double d, int32_t q) {
|
||||
}
|
||||
|
||||
static double turn2rad(double t) {
|
||||
return t * (M_PI * 2);
|
||||
return t * tau;
|
||||
}
|
||||
|
||||
static double rad2turn(double r) {
|
||||
return r / (M_PI * 2);
|
||||
return r / tau;
|
||||
}
|
||||
|
||||
int32_t fix_Sin(int32_t i, int32_t q) {
|
||||
|
||||
@@ -5,96 +5,65 @@
|
||||
#include <algorithm>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
#include "util.hpp" // parseNumber
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/main.hpp" // options
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
void FormatSpec::useCharacter(int c) {
|
||||
if (state == FORMAT_INVALID) {
|
||||
return;
|
||||
}
|
||||
size_t FormatSpec::parseSpec(char const *spec) {
|
||||
size_t i = 0;
|
||||
|
||||
switch (c) {
|
||||
// sign
|
||||
case ' ':
|
||||
case '+':
|
||||
if (state > FORMAT_SIGN) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_EXACT;
|
||||
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>
|
||||
if (char c = spec[i]; c == ' ' || c == '+') {
|
||||
++i;
|
||||
sign = c;
|
||||
return;
|
||||
|
||||
// exact
|
||||
case '#':
|
||||
if (state > FORMAT_EXACT) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_ALIGN;
|
||||
// <exact>
|
||||
if (spec[i] == '#') {
|
||||
++i;
|
||||
exact = true;
|
||||
return;
|
||||
|
||||
// align
|
||||
case '-':
|
||||
if (state > FORMAT_ALIGN) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_WIDTH;
|
||||
// <align>
|
||||
if (spec[i] == '-') {
|
||||
++i;
|
||||
alignLeft = true;
|
||||
return;
|
||||
|
||||
// pad, width, and prec values
|
||||
case '0':
|
||||
if (state < FORMAT_WIDTH) {
|
||||
}
|
||||
// <pad>
|
||||
if (spec[i] == '0') {
|
||||
++i;
|
||||
padZero = true;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
if (state < FORMAT_WIDTH) {
|
||||
state = FORMAT_WIDTH;
|
||||
width = c - '0';
|
||||
} else if (state == FORMAT_WIDTH) {
|
||||
width = width * 10 + (c - '0');
|
||||
} else if (state == FORMAT_FRAC) {
|
||||
fracWidth = fracWidth * 10 + (c - '0');
|
||||
} else if (state == FORMAT_PREC) {
|
||||
precision = precision * 10 + (c - '0');
|
||||
} else {
|
||||
break;
|
||||
// <width>
|
||||
if (isDigit(spec[i])) {
|
||||
width = parseSpecNumber();
|
||||
}
|
||||
return;
|
||||
|
||||
// width
|
||||
case '.':
|
||||
if (state > FORMAT_WIDTH) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_FRAC;
|
||||
// <frac>
|
||||
if (spec[i] == '.') {
|
||||
++i;
|
||||
hasFrac = true;
|
||||
return;
|
||||
|
||||
// prec
|
||||
case 'q':
|
||||
if (state > FORMAT_PREC) {
|
||||
break;
|
||||
fracWidth = parseSpecNumber();
|
||||
}
|
||||
state = FORMAT_PREC;
|
||||
// <prec>
|
||||
if (spec[i] == 'q') {
|
||||
++i;
|
||||
hasPrec = true;
|
||||
return;
|
||||
|
||||
// type
|
||||
precision = parseSpecNumber();
|
||||
}
|
||||
// <type>
|
||||
switch (char c = spec[i]; c) {
|
||||
case 'd':
|
||||
case 'u':
|
||||
case 'X':
|
||||
@@ -103,26 +72,13 @@ void FormatSpec::useCharacter(int c) {
|
||||
case 'o':
|
||||
case 'f':
|
||||
case 's':
|
||||
if (state >= FORMAT_DONE) {
|
||||
break;
|
||||
}
|
||||
state = FORMAT_DONE;
|
||||
valid = true;
|
||||
++i;
|
||||
type = c;
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
state = FORMAT_INVALID;
|
||||
valid = false;
|
||||
}
|
||||
|
||||
void FormatSpec::finishCharacters() {
|
||||
if (!isValid()) {
|
||||
state = FORMAT_INVALID;
|
||||
}
|
||||
// Done parsing
|
||||
parsed = true;
|
||||
return i;
|
||||
}
|
||||
|
||||
static std::string escapeString(std::string const &str) {
|
||||
@@ -157,7 +113,7 @@ static std::string escapeString(std::string const &str) {
|
||||
|
||||
void FormatSpec::appendString(std::string &str, std::string const &value) const {
|
||||
int useType = type;
|
||||
if (isEmpty()) {
|
||||
if (!useType) {
|
||||
// No format was specified
|
||||
useType = 's';
|
||||
}
|
||||
@@ -196,7 +152,7 @@ void FormatSpec::appendString(std::string &str, std::string const &value) const
|
||||
void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
int useType = type;
|
||||
bool useExact = exact;
|
||||
if (isEmpty()) {
|
||||
if (!useType) {
|
||||
// No format was specified; default to uppercase $hex
|
||||
useType = 'X';
|
||||
useExact = true;
|
||||
@@ -221,36 +177,32 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
if (useType == 'd' || useType == 'f') {
|
||||
if (int32_t v = value; v < 0) {
|
||||
signChar = '-';
|
||||
if (v != INT32_MIN) {
|
||||
if (v != INT32_MIN) { // -INT32_MIN is UB
|
||||
value = -v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char prefixChar = !useExact ? 0
|
||||
: useType == 'X' ? '$'
|
||||
: useType == 'x' ? '$'
|
||||
: useType == 'b' ? '%'
|
||||
: useType == 'o' ? '&'
|
||||
: 0;
|
||||
|
||||
char valueBuf[262]; // Max 5 digits + decimal + 255 fraction digits + terminator
|
||||
|
||||
// The longest possible formatted number is fixed-point with 10 digits, 255 fractional digits,
|
||||
// and a precision suffix, for 270 total bytes (counting the NUL terminator).
|
||||
// (Actually 269 since a 2-digit precision cannot reach 10 integer digits.)
|
||||
// Make the buffer somewhat larger just in case.
|
||||
char valueBuf[300];
|
||||
if (useType == 'b') {
|
||||
// Special case for binary
|
||||
char *ptr = valueBuf;
|
||||
// Special case for binary (since `snprintf` doesn't support it)
|
||||
|
||||
// Buffer the digits from least to greatest
|
||||
char *ptr = valueBuf;
|
||||
do {
|
||||
*ptr++ = (value & 1) + '0';
|
||||
value >>= 1;
|
||||
} while (value);
|
||||
|
||||
// Reverse the digits
|
||||
// Reverse the digits and terminate the string
|
||||
std::reverse(valueBuf, ptr);
|
||||
|
||||
*ptr = '\0';
|
||||
} 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)
|
||||
size_t useFracWidth = hasFrac ? fracWidth : 5;
|
||||
@@ -259,6 +211,7 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
useFracWidth = 255;
|
||||
}
|
||||
|
||||
// Default precision taken from default `-Q` option
|
||||
size_t defaultPrec = options.fixPrecision;
|
||||
size_t usePrec = hasPrec ? precision : defaultPrec;
|
||||
if (usePrec < 1 || usePrec > 31) {
|
||||
@@ -270,29 +223,30 @@ void FormatSpec::appendNumber(std::string &str, uint32_t value) const {
|
||||
usePrec = defaultPrec;
|
||||
}
|
||||
|
||||
// Floating-point formatting works for all fixed-point values
|
||||
double fval = fabs(value / pow(2.0, usePrec));
|
||||
if (int fracWidthArg = static_cast<int>(useFracWidth); useExact) {
|
||||
snprintf(valueBuf, sizeof(valueBuf), "%.*fq%zu", fracWidthArg, fval, usePrec);
|
||||
} else {
|
||||
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 {
|
||||
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 == 'o' ? "%" PRIo32
|
||||
: "%" PRIu32;
|
||||
|
||||
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 numLen = (signChar != 0) + (prefixChar != 0) + valueLen;
|
||||
size_t totalLen = width > numLen ? width : numLen;
|
||||
|
||||
@@ -7,14 +7,23 @@
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
#include "backtrace.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp" // reversed
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp" // S_ISDIR (stat macro)
|
||||
#include "platform.hpp" // strncasecmp
|
||||
#include "verbosity.hpp"
|
||||
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/macro.hpp"
|
||||
@@ -48,45 +57,78 @@ static std::vector<std::string> includePaths = {""}; // -I
|
||||
static std::deque<std::string> preIncludeNames; // -P
|
||||
static bool failedOnMissingInclude = false;
|
||||
|
||||
std::string FileStackNode::reptChain() const {
|
||||
std::string chain;
|
||||
std::vector<uint32_t> const &nodeIters = iters();
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
chain.append("::REPT~");
|
||||
chain.append(std::to_string(nodeIters[i]));
|
||||
using TraceNode = std::pair<std::string, uint32_t>;
|
||||
|
||||
static std::vector<TraceNode> backtrace(FileStackNode const &node, uint32_t curLineNo) {
|
||||
if (node.isQuiet && !tracing.loud) {
|
||||
if (node.parent) {
|
||||
// Quiet REPT nodes will pass their interior line number up to their parent,
|
||||
// which is more precise than the parent's own line number (since that will be
|
||||
// the line number of the "REPT?" or "FOR?" itself).
|
||||
return backtrace(*node.parent, node.type == NODE_REPT ? curLineNo : node.lineNo);
|
||||
}
|
||||
return chain;
|
||||
return {}; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
std::string const &FileStackNode::dump(uint32_t curLineNo) const {
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(data)) {
|
||||
assume(parent); // REPT nodes use their parent's name
|
||||
std::string const &lastName = parent->dump(lineNo);
|
||||
fputs(" -> ", stderr);
|
||||
fputs(lastName.c_str(), stderr);
|
||||
fputs(reptChain().c_str(), stderr);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
return lastName;
|
||||
if (!node.parent) {
|
||||
assume(node.type != NODE_REPT && std::holds_alternative<std::string>(node.data));
|
||||
return {
|
||||
{node.name(), curLineNo}
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<TraceNode> traceNodes = backtrace(*node.parent, node.lineNo);
|
||||
if (std::holds_alternative<std::vector<uint32_t>>(node.data)) {
|
||||
assume(!traceNodes.empty()); // REPT nodes use their parent's name
|
||||
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 {
|
||||
if (parent) {
|
||||
parent->dump(lineNo);
|
||||
fputs(" -> ", stderr);
|
||||
}
|
||||
std::string const &nodeName = name();
|
||||
fputs(nodeName.c_str(), stderr);
|
||||
fprintf(stderr, "(%" PRIu32 ")", curLineNo);
|
||||
return nodeName;
|
||||
traceNodes.emplace_back(node.name(), curLineNo);
|
||||
}
|
||||
return traceNodes;
|
||||
}
|
||||
|
||||
bool fstk_DumpCurrent() {
|
||||
if (lexer_AtTopLevel()) {
|
||||
return false;
|
||||
void FileStackNode::printBacktrace(uint32_t curLineNo) const {
|
||||
trace_PrintBacktrace(
|
||||
backtrace(*this, curLineNo),
|
||||
[](TraceNode const &node) { return node.first.c_str(); },
|
||||
[](TraceNode const &node) { return node.second; }
|
||||
);
|
||||
}
|
||||
|
||||
void fstk_TraceCurrent() {
|
||||
if (!lexer_AtTopLevel()) {
|
||||
assume(!contextStack.empty());
|
||||
contextStack.top().fileInfo->dump(lexer_GetLineNo());
|
||||
return true;
|
||||
contextStack.top().fileInfo->printBacktrace(lexer_GetLineNo());
|
||||
}
|
||||
lexer_TraceStringExpansions();
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
void fstk_VerboseOutputConfig() {
|
||||
assume(checkVerbosity(VERB_CONFIG));
|
||||
// -I/--include
|
||||
if (includePaths.size() > 1) {
|
||||
fputs("\tInclude file paths:\n", stderr);
|
||||
for (std::string const &path : includePaths) {
|
||||
if (!path.empty()) {
|
||||
fprintf(stderr, "\t - %s\n", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
// -P/--preinclude
|
||||
if (!preIncludeNames.empty()) {
|
||||
fputs("\tPreincluded files:\n", stderr);
|
||||
for (std::string const &name : preIncludeNames) {
|
||||
fprintf(stderr, "\t - %s\n", name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
std::shared_ptr<FileStackNode> fstk_GetFileStack() {
|
||||
return contextStack.empty() ? nullptr : contextStack.top().fileInfo;
|
||||
@@ -124,7 +166,6 @@ void fstk_AddIncludePath(std::string const &path) {
|
||||
|
||||
void fstk_AddPreIncludeFile(std::string const &path) {
|
||||
preIncludeNames.emplace_front(path);
|
||||
verbosePrint("Pre-included filename %s\n", path.c_str()); // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
static bool isValidFilePath(std::string const &path) {
|
||||
@@ -159,8 +200,9 @@ bool yywrap() {
|
||||
|
||||
if (ifDepth != 0) {
|
||||
fatal(
|
||||
"Ended block with %" PRIu32 " unterminated IF construct%s",
|
||||
"Ended block with %" PRIu32 " unterminated conditional%s (`IF`/`ELIF`/`ELSE` block%s)",
|
||||
ifDepth,
|
||||
ifDepth == 1 ? "" : "s",
|
||||
ifDepth == 1 ? "" : "s"
|
||||
);
|
||||
}
|
||||
@@ -187,7 +229,7 @@ bool yywrap() {
|
||||
|
||||
// This error message will refer to the current iteration
|
||||
if (sym->type != SYM_VAR) {
|
||||
fatal("Failed to update FOR symbol value");
|
||||
fatal("Failed to update `FOR` symbol value");
|
||||
}
|
||||
}
|
||||
// Advance to the next iteration
|
||||
@@ -214,14 +256,14 @@ static void checkRecursionDepth() {
|
||||
}
|
||||
}
|
||||
|
||||
static void newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
static void newFileContext(std::string const &filePath, bool isQuiet, bool updateStateNow) {
|
||||
checkRecursionDepth();
|
||||
|
||||
std::shared_ptr<std::string> uniqueIDStr = nullptr;
|
||||
std::shared_ptr<MacroArgs> macroArgs = nullptr;
|
||||
|
||||
auto fileInfo =
|
||||
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath);
|
||||
std::make_shared<FileStackNode>(NODE_FILE, filePath == "-" ? "<stdin>" : filePath, isQuiet);
|
||||
if (!contextStack.empty()) {
|
||||
Context &oldContext = contextStack.top();
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
@@ -239,7 +281,8 @@ static void newFileContext(std::string const &filePath, bool updateStateNow) {
|
||||
context.lexerState.setFileAsNextState(filePath, updateStateNow);
|
||||
}
|
||||
|
||||
static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
static void
|
||||
newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macroArgs, bool isQuiet) {
|
||||
checkRecursionDepth();
|
||||
|
||||
Context &oldContext = contextStack.top();
|
||||
@@ -252,12 +295,16 @@ static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macr
|
||||
}
|
||||
}
|
||||
if (macro.src->type == NODE_REPT) {
|
||||
fileInfoName.append(macro.src->reptChain());
|
||||
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);
|
||||
|
||||
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName);
|
||||
auto fileInfo = std::make_shared<FileStackNode>(NODE_MACRO, fileInfoName, isQuiet);
|
||||
assume(!contextStack.empty()); // The top level context cannot be a MACRO
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
fileInfo->lineNo = lexer_GetLineNo();
|
||||
@@ -271,7 +318,8 @@ static void newMacroContext(Symbol const ¯o, std::shared_ptr<MacroArgs> macr
|
||||
context.lexerState.setViewAsNextState("MACRO", macro.getMacro(), macro.fileLine);
|
||||
}
|
||||
|
||||
static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint32_t count) {
|
||||
static Context &
|
||||
newReptContext(int32_t reptLineNo, ContentSpan const &span, uint32_t count, bool isQuiet) {
|
||||
checkRecursionDepth();
|
||||
|
||||
Context &oldContext = contextStack.top();
|
||||
@@ -282,7 +330,7 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint
|
||||
fileInfoIters.insert(fileInfoIters.end(), RANGE(oldContext.fileInfo->iters()));
|
||||
}
|
||||
|
||||
auto fileInfo = std::make_shared<FileStackNode>(NODE_REPT, fileInfoIters);
|
||||
auto fileInfo = std::make_shared<FileStackNode>(NODE_REPT, fileInfoIters, isQuiet);
|
||||
assume(!contextStack.empty()); // The top level context cannot be a REPT
|
||||
fileInfo->parent = oldContext.fileInfo;
|
||||
fileInfo->lineNo = reptLineNo;
|
||||
@@ -302,13 +350,17 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint
|
||||
|
||||
bool fstk_FileError(std::string const &path, char const *functionName) {
|
||||
if (options.missingIncludeState == INC_ERROR) {
|
||||
error("Error opening %s file '%s': %s", functionName, path.c_str(), strerror(errno));
|
||||
error("Error opening `%s` file \"%s\": %s", functionName, path.c_str(), strerror(errno));
|
||||
} else {
|
||||
failedOnMissingInclude = true;
|
||||
// LCOV_EXCL_START
|
||||
if (options.missingIncludeState == GEN_EXIT) {
|
||||
verbosePrint(
|
||||
"Aborting (-MG) on %s file '%s' (%s)\n", functionName, path.c_str(), strerror(errno)
|
||||
VERB_NOTICE,
|
||||
"Aborting due to '-MG' on `%s` file \"%s\": %s\n",
|
||||
functionName,
|
||||
path.c_str(),
|
||||
strerror(errno)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -322,39 +374,61 @@ bool fstk_FailedOnMissingInclude() {
|
||||
return failedOnMissingInclude;
|
||||
}
|
||||
|
||||
bool fstk_RunInclude(std::string const &path) {
|
||||
bool fstk_RunInclude(std::string const &path, bool isQuiet) {
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(path); fullPath) {
|
||||
newFileContext(*fullPath, false);
|
||||
newFileContext(*fullPath, isQuiet, false);
|
||||
return false;
|
||||
}
|
||||
return fstk_FileError(path, "INCLUDE");
|
||||
}
|
||||
|
||||
void fstk_RunMacro(std::string const ¯oName, std::shared_ptr<MacroArgs> macroArgs) {
|
||||
Symbol *macro = sym_FindExactSymbol(macroName);
|
||||
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);
|
||||
if (!arg) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!macro) {
|
||||
char const *str = arg->c_str();
|
||||
static char const *types[] = {"EQUS", "EQU", "RB", "RW", "RL", "="};
|
||||
for (char const *type : types) {
|
||||
if (strncasecmp(str, type, strlen(type)) == 0) {
|
||||
return "\"DEF "s + macroName + " " + type + " ...\"";
|
||||
}
|
||||
}
|
||||
if (strncasecmp(str, "SET", literal_strlen("SET")) == 0) {
|
||||
return "\"DEF "s + macroName + " = ...\"";
|
||||
}
|
||||
if (str[0] == ':') {
|
||||
return "a label \""s + macroName + (str[1] == ':' ? "::" : ":") + "\"";
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (Symbol *macro = sym_FindExactSymbol(macroName); !macro) {
|
||||
if (sym_IsPurgedExact(macroName)) {
|
||||
error("Macro \"%s\" not defined; it was purged", macroName.c_str());
|
||||
error("Undefined macro `%s`; it was purged", macroName.c_str());
|
||||
} else if (std::optional<std::string> suggestion = makeSuggestion(); suggestion) {
|
||||
error(
|
||||
"Undefined macro `%s` (did you mean %s?)", macroName.c_str(), suggestion->c_str()
|
||||
);
|
||||
} else {
|
||||
error("Macro \"%s\" not defined", macroName.c_str());
|
||||
error("Undefined macro `%s`", macroName.c_str());
|
||||
}
|
||||
return;
|
||||
} else if (macro->type != SYM_MACRO) {
|
||||
error("`%s` is not a macro", macroName.c_str());
|
||||
} else {
|
||||
newMacroContext(*macro, macroArgs, isQuiet || macro->isQuiet);
|
||||
}
|
||||
if (macro->type != SYM_MACRO) {
|
||||
error("\"%s\" is not a macro", macroName.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
newMacroContext(*macro, macroArgs);
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span, bool isQuiet) {
|
||||
if (count) {
|
||||
newReptContext(reptLineNo, span, count, isQuiet);
|
||||
}
|
||||
|
||||
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span) {
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
newReptContext(reptLineNo, span, count);
|
||||
}
|
||||
|
||||
void fstk_RunFor(
|
||||
@@ -363,7 +437,8 @@ void fstk_RunFor(
|
||||
int32_t stop,
|
||||
int32_t step,
|
||||
int32_t reptLineNo,
|
||||
ContentSpan const &span
|
||||
ContentSpan const &span,
|
||||
bool isQuiet
|
||||
) {
|
||||
if (Symbol *sym = sym_AddVar(symName, start); sym->type != SYM_VAR) {
|
||||
return;
|
||||
@@ -375,18 +450,20 @@ void fstk_RunFor(
|
||||
} else if (step < 0 && stop < start) {
|
||||
count = (static_cast<int64_t>(start) - stop - 1) / -static_cast<int64_t>(step) + 1;
|
||||
} else if (step == 0) {
|
||||
error("FOR cannot have a step value of 0");
|
||||
error("`FOR` cannot have a step value of 0");
|
||||
}
|
||||
|
||||
if ((step > 0 && start > stop) || (step < 0 && start < stop)) {
|
||||
warning(WARNING_BACKWARDS_FOR, "FOR goes backwards from %d to %d by %d", start, stop, step);
|
||||
warning(
|
||||
WARNING_BACKWARDS_FOR, "`FOR` goes backwards from %d to %d by %d", start, stop, step
|
||||
);
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Context &context = newReptContext(reptLineNo, span, count);
|
||||
Context &context = newReptContext(reptLineNo, span, count, isQuiet);
|
||||
context.isForLoop = true;
|
||||
context.forValue = start;
|
||||
context.forStep = step;
|
||||
@@ -395,7 +472,7 @@ void fstk_RunFor(
|
||||
|
||||
bool fstk_Break() {
|
||||
if (contextStack.top().fileInfo->type != NODE_REPT) {
|
||||
error("BREAK can only be used inside a REPT/FOR block");
|
||||
error("`BREAK` can only be used inside a loop (`REPT`/`FOR` block)");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -411,13 +488,13 @@ void fstk_NewRecursionDepth(size_t newDepth) {
|
||||
}
|
||||
|
||||
void fstk_Init(std::string const &mainPath) {
|
||||
newFileContext(mainPath, true);
|
||||
newFileContext(mainPath, false, true);
|
||||
|
||||
for (std::string const &name : preIncludeNames) {
|
||||
if (std::optional<std::string> fullPath = fstk_FindFile(name); fullPath) {
|
||||
newFileContext(*fullPath, false);
|
||||
newFileContext(*fullPath, false, false);
|
||||
} else {
|
||||
error("Error reading pre-included file '%s': %s", name.c_str(), strerror(errno));
|
||||
error("Error reading pre-included file \"%s\": %s", name.c_str(), strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "asm/macro.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
|
||||
572
src/asm/main.cpp
572
src/asm/main.cpp
@@ -4,32 +4,112 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "backtrace.hpp"
|
||||
#include "cli.hpp"
|
||||
#include "diagnostics.hpp"
|
||||
#include "extern/getopt.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "parser.hpp" // Generated from parser.y
|
||||
#include "platform.hpp"
|
||||
#include "style.hpp"
|
||||
#include "usage.hpp"
|
||||
#include "util.hpp" // UpperMap
|
||||
#include "verbosity.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/opt.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/symbol.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
Options options;
|
||||
|
||||
// Escapes Make-special chars from a string
|
||||
static std::string make_escape(std::string &str) {
|
||||
// Flags which must be processed after the option parsing finishes
|
||||
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
|
||||
static char const *optstring = "B:b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Long-only option variable
|
||||
static int longOpt; // `--color` and variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"backtrace", required_argument, nullptr, 'B'},
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"q-precision", required_argument, nullptr, 'Q'},
|
||||
{"recursion-depth", required_argument, nullptr, 'r'},
|
||||
{"state", required_argument, nullptr, 's'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"max-errors", required_argument, nullptr, 'X'},
|
||||
{"color", required_argument, &longOpt, 'c'},
|
||||
{"MC", no_argument, &longOpt, 'C'},
|
||||
{"MG", no_argument, &longOpt, 'G'},
|
||||
{"MP", no_argument, &longOpt, 'P'},
|
||||
{"MQ", required_argument, &longOpt, 'Q'},
|
||||
{"MT", required_argument, &longOpt, 'T'},
|
||||
{nullptr, no_argument, nullptr, 0 },
|
||||
};
|
||||
|
||||
// clang-format off: nested initializers
|
||||
static Usage usage = {
|
||||
.name = "rgbasm",
|
||||
.flags = {
|
||||
"[-EhVvw]", "[-B depth]", "[-b chars]", "[-D name[=value]]", "[-g chars]", "[-I path]",
|
||||
"[-M depend_file]", "[-MC]", "[-MG]", "[-MP]", "[-MT target_file]", "[-MQ target_file]",
|
||||
"[-o out_file]", "[-P include_file]", "[-p pad_value]", "[-Q precision]", "[-r depth]",
|
||||
"[-s features:state_file]", "[-W warning]", "[-X max_errors]", "<file>",
|
||||
},
|
||||
.options = {
|
||||
{{"-E", "--export-all"}, {"export all labels"}},
|
||||
{{"-M", "--dependfile <path>"}, {"set the output dependency file"}},
|
||||
{{"-o", "--output <path>"}, {"set the output object file"}},
|
||||
{{"-p", "--pad-value <value>"}, {"set the value to use for `DS`"}},
|
||||
{{"-s", "--state <features>:<path>"}, {"set an output state file"}},
|
||||
{{"-V", "--version"}, {"print RGBASM version and exit"}},
|
||||
{{"-W", "--warning <warning>"}, {"enable or disable warnings"}},
|
||||
},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
static std::string escapeMakeChars(std::string &str) {
|
||||
std::string escaped;
|
||||
size_t pos = 0;
|
||||
for (;;) {
|
||||
@@ -46,65 +126,6 @@ static std::string make_escape(std::string &str) {
|
||||
return escaped;
|
||||
}
|
||||
|
||||
// Short options
|
||||
static char const *optstring = "b:D:Eg:hI:M:o:P:p:Q:r:s:VvW:wX:";
|
||||
|
||||
// Variables for the long-only options
|
||||
static int depType; // Variants of `-M`
|
||||
|
||||
// Equivalent long options
|
||||
// Please keep in the same order as short opts.
|
||||
// Also, make sure long opts don't create ambiguity:
|
||||
// A long opt's name should start with the same letter as its short opt,
|
||||
// except if it doesn't create any ambiguity (`verbose` versus `version`).
|
||||
// This is because long opt matching, even to a single char, is prioritized
|
||||
// over short opt matching.
|
||||
static option const longopts[] = {
|
||||
{"binary-digits", required_argument, nullptr, 'b'},
|
||||
{"define", required_argument, nullptr, 'D'},
|
||||
{"export-all", no_argument, nullptr, 'E'},
|
||||
{"gfx-chars", required_argument, nullptr, 'g'},
|
||||
{"help", no_argument, nullptr, 'h'},
|
||||
{"include", required_argument, nullptr, 'I'},
|
||||
{"dependfile", required_argument, nullptr, 'M'},
|
||||
{"MC", no_argument, &depType, 'C'},
|
||||
{"MG", no_argument, &depType, 'G'},
|
||||
{"MP", no_argument, &depType, 'P'},
|
||||
{"MQ", required_argument, &depType, 'Q'},
|
||||
{"MT", required_argument, &depType, 'T'},
|
||||
{"output", required_argument, nullptr, 'o'},
|
||||
{"preinclude", required_argument, nullptr, 'P'},
|
||||
{"pad-value", required_argument, nullptr, 'p'},
|
||||
{"q-precision", required_argument, nullptr, 'Q'},
|
||||
{"recursion-depth", required_argument, nullptr, 'r'},
|
||||
{"state", required_argument, nullptr, 's'},
|
||||
{"version", no_argument, nullptr, 'V'},
|
||||
{"verbose", no_argument, nullptr, 'v'},
|
||||
{"warning", required_argument, nullptr, 'W'},
|
||||
{"max-errors", required_argument, nullptr, 'X'},
|
||||
{nullptr, no_argument, nullptr, 0 }
|
||||
};
|
||||
|
||||
// clang-format off: long string literal
|
||||
static Usage usage(
|
||||
"Usage: rgbasm [-EhVvw] [-b chars] [-D name[=value]] [-g chars] [-I path]\n"
|
||||
" [-M depend_file] [-MC] [-MG] [-MP] [-MT target_file] [-MQ target_file]\n"
|
||||
" [-o out_file] [-P include_file] [-p pad_value] [-Q precision]\n"
|
||||
" [-r depth] [-s features:state_file] [-W warning] [-X max_errors]\n"
|
||||
" <file>\n"
|
||||
"Useful options:\n"
|
||||
" -E, --export-all export all labels\n"
|
||||
" -M, --dependfile <path> set the output dependency file\n"
|
||||
" -o, --output <path> set the output object file\n"
|
||||
" -p, --pad-value <value> set the value to use for `ds'\n"
|
||||
" -s, --state <features>:<path> set an output state file\n"
|
||||
" -V, --version print RGBASM version and exit\n"
|
||||
" -W, --warning <warning> enable or disable warnings\n"
|
||||
"\n"
|
||||
"For help, use `man rgbasm' or go to https://rgbds.gbdev.io/docs/\n"
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
// Parse a comma-separated string of '-s/--state' features
|
||||
static std::vector<StateFeature> parseStateFeatures(char *str) {
|
||||
std::vector<StateFeature> features;
|
||||
@@ -114,7 +135,7 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
||||
if (next) {
|
||||
*next++ = '\0';
|
||||
}
|
||||
// Trim whitespace from the beginning of `feature`...
|
||||
// Trim blank spaces from the beginning of `feature`...
|
||||
feature += strspn(feature, " \t");
|
||||
// ...and from the end
|
||||
if (char *end = strpbrk(feature, " \t"); end) {
|
||||
@@ -122,7 +143,7 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
||||
}
|
||||
// A feature must be specified
|
||||
if (*feature == '\0') {
|
||||
fatal("Empty feature for option 's'");
|
||||
fatal("Empty feature for option '-s'");
|
||||
}
|
||||
// Parse the `feature` and update the `features` list
|
||||
static UpperMap<StateFeature> const featureNames{
|
||||
@@ -134,14 +155,14 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
||||
};
|
||||
if (!strcasecmp(feature, "all")) {
|
||||
if (!features.empty()) {
|
||||
warnx("Redundant feature before \"%s\" for option 's'", feature);
|
||||
warnx("Redundant feature before \"%s\" for option '-s'", feature);
|
||||
}
|
||||
features.assign({STATE_EQU, STATE_VAR, STATE_EQUS, STATE_CHAR, STATE_MACRO});
|
||||
} else if (auto search = featureNames.find(feature); search == featureNames.end()) {
|
||||
fatal("Invalid feature for option 's': \"%s\"", feature);
|
||||
fatal("Invalid feature for option '-s': \"%s\"", feature);
|
||||
} else if (StateFeature value = search->second;
|
||||
std::find(RANGE(features), value) != features.end()) {
|
||||
warnx("Ignoring duplicate feature for option 's': \"%s\"", feature);
|
||||
warnx("Ignoring duplicate feature for option '-s': \"%s\"", feature);
|
||||
} else {
|
||||
features.push_back(value);
|
||||
}
|
||||
@@ -150,192 +171,165 @@ static std::vector<StateFeature> parseStateFeatures(char *str) {
|
||||
return features;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Local options
|
||||
char const *dependFileName = nullptr; // -M
|
||||
std::unordered_map<std::string, std::vector<StateFeature>> stateFileSpecs; // -s
|
||||
|
||||
for (int ch; (ch = musl_getopt_long_only(argc, argv, optstring, longopts, nullptr)) != -1;) {
|
||||
static void parseArg(int ch, char *arg) {
|
||||
switch (ch) {
|
||||
char *endptr;
|
||||
case 'B':
|
||||
if (!trace_ParseTraceDepth(arg)) {
|
||||
fatal("Invalid argument for option '-B'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
if (strlen(musl_optarg) == 2) {
|
||||
opt_B(musl_optarg);
|
||||
if (strlen(arg) == 2) {
|
||||
opt_B(arg);
|
||||
} else {
|
||||
fatal("Must specify exactly 2 characters for option 'b'");
|
||||
fatal("Must specify exactly 2 characters for option '-b'");
|
||||
}
|
||||
break;
|
||||
|
||||
char *equals;
|
||||
case 'D':
|
||||
equals = strchr(musl_optarg, '=');
|
||||
case 'D': {
|
||||
char *equals = strchr(arg, '=');
|
||||
if (equals) {
|
||||
*equals = '\0';
|
||||
sym_AddString(musl_optarg, std::make_shared<std::string>(equals + 1));
|
||||
sym_AddString(arg, std::make_shared<std::string>(equals + 1));
|
||||
} else {
|
||||
sym_AddString(musl_optarg, std::make_shared<std::string>("1"));
|
||||
sym_AddString(arg, std::make_shared<std::string>("1"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'E':
|
||||
sym_SetExportAll(true);
|
||||
options.exportAll = true;
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(musl_optarg) == 4) {
|
||||
opt_G(musl_optarg);
|
||||
if (strlen(arg) == 4) {
|
||||
opt_G(arg);
|
||||
} else {
|
||||
fatal("Must specify exactly 4 characters for option 'g'");
|
||||
fatal("Must specify exactly 4 characters for option '-g'");
|
||||
}
|
||||
break;
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case 'h':
|
||||
usage.printAndExit(0); // LCOV_EXCL_LINE
|
||||
usage.printAndExit(0);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'I':
|
||||
fstk_AddIncludePath(musl_optarg);
|
||||
fstk_AddIncludePath(arg);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
if (options.dependFile) {
|
||||
warnx("Overriding dependfile %s", dependFileName);
|
||||
}
|
||||
if (strcmp("-", musl_optarg)) {
|
||||
options.dependFile = fopen(musl_optarg, "w");
|
||||
dependFileName = musl_optarg;
|
||||
} else {
|
||||
options.dependFile = stdout;
|
||||
dependFileName = "<stdout>";
|
||||
}
|
||||
if (options.dependFile == nullptr) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to open dependfile \"%s\": %s", dependFileName, strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
if (localOptions.dependFileName) {
|
||||
warnx(
|
||||
"Overriding dependency file \"%s\"",
|
||||
*localOptions.dependFileName == "-" ? "<stdout>"
|
||||
: localOptions.dependFileName->c_str()
|
||||
);
|
||||
}
|
||||
localOptions.dependFileName = arg;
|
||||
break;
|
||||
|
||||
case 'o':
|
||||
if (!options.objectFileName.empty()) {
|
||||
warnx("Overriding output filename %s", options.objectFileName.c_str());
|
||||
if (options.objectFileName) {
|
||||
warnx("Overriding output file \"%s\"", options.objectFileName->c_str());
|
||||
}
|
||||
options.objectFileName = musl_optarg;
|
||||
verbosePrint("Output filename %s\n", options.objectFileName.c_str()); // LCOV_EXCL_LINE
|
||||
options.objectFileName = arg;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
fstk_AddPreIncludeFile(musl_optarg);
|
||||
fstk_AddPreIncludeFile(arg);
|
||||
break;
|
||||
|
||||
unsigned long padByte;
|
||||
case 'p':
|
||||
padByte = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
fatal("Invalid argument for option 'p'");
|
||||
if (std::optional<uint64_t> padByte = parseWholeNumber(arg); !padByte) {
|
||||
fatal("Invalid argument for option '-p'");
|
||||
} else if (*padByte > 0xFF) {
|
||||
fatal("Argument for option '-p' must be between 0 and 0xFF");
|
||||
} else {
|
||||
opt_P(*padByte);
|
||||
}
|
||||
|
||||
if (padByte > 0xFF) {
|
||||
fatal("Argument for option 'p' must be between 0 and 0xFF");
|
||||
}
|
||||
|
||||
opt_P(padByte);
|
||||
break;
|
||||
|
||||
case 'Q': {
|
||||
char const *precisionArg = musl_optarg;
|
||||
char const *precisionArg = arg;
|
||||
if (precisionArg[0] == '.') {
|
||||
++precisionArg;
|
||||
}
|
||||
unsigned long precision = strtoul(precisionArg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
fatal("Invalid argument for option 'Q'");
|
||||
if (std::optional<uint64_t> precision = parseWholeNumber(precisionArg); !precision) {
|
||||
fatal("Invalid argument for option '-Q'");
|
||||
} else if (*precision < 1 || *precision > 31) {
|
||||
fatal("Argument for option '-Q' must be between 1 and 31");
|
||||
} else {
|
||||
opt_Q(*precision);
|
||||
}
|
||||
|
||||
if (precision < 1 || precision > 31) {
|
||||
fatal("Argument for option 'Q' must be between 1 and 31");
|
||||
}
|
||||
|
||||
opt_Q(precision);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'r':
|
||||
options.maxRecursionDepth = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
fatal("Invalid argument for option 'r'");
|
||||
if (std::optional<uint64_t> maxDepth = parseWholeNumber(arg); !maxDepth) {
|
||||
fatal("Invalid argument for option '-r'");
|
||||
} else if (errno == ERANGE) {
|
||||
fatal("Argument for option '-r' is out of range");
|
||||
} else {
|
||||
options.maxRecursionDepth = *maxDepth;
|
||||
}
|
||||
break;
|
||||
|
||||
case 's': {
|
||||
// Split "<features>:<name>" so `musl_optarg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(musl_optarg, ':');
|
||||
// Split "<features>:<name>" so `arg` is "<features>" and `name` is "<name>"
|
||||
char *name = strchr(arg, ':');
|
||||
if (!name) {
|
||||
fatal("Invalid argument for option 's'");
|
||||
fatal("Invalid argument for option '-s'");
|
||||
}
|
||||
*name++ = '\0';
|
||||
|
||||
std::vector<StateFeature> features = parseStateFeatures(musl_optarg);
|
||||
std::vector<StateFeature> features = parseStateFeatures(arg);
|
||||
|
||||
if (stateFileSpecs.find(name) != stateFileSpecs.end()) {
|
||||
warnx("Overriding state filename %s", name);
|
||||
if (localOptions.stateFileSpecs.find(name) != localOptions.stateFileSpecs.end()) {
|
||||
warnx("Overriding state file \"%s\"", name);
|
||||
}
|
||||
verbosePrint("State filename %s\n", name); // LCOV_EXCL_LINE
|
||||
stateFileSpecs.emplace(name, std::move(features));
|
||||
localOptions.stateFileSpecs.emplace(name, std::move(features));
|
||||
break;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
case 'V':
|
||||
printf("rgbasm %s\n", get_package_version_string());
|
||||
exit(0);
|
||||
|
||||
case 'v':
|
||||
// LCOV_EXCL_START
|
||||
options.verbose = true;
|
||||
incrementVerbosity();
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
case 'W':
|
||||
opt_W(musl_optarg);
|
||||
opt_W(arg);
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
warnings.state.warningsEnabled = false;
|
||||
break;
|
||||
|
||||
case 'X': {
|
||||
uint64_t maxErrors = strtoul(musl_optarg, &endptr, 0);
|
||||
|
||||
if (musl_optarg[0] == '\0' || *endptr != '\0') {
|
||||
fatal("Invalid argument for option 'X'");
|
||||
case 'X':
|
||||
if (std::optional<uint64_t> maxErrors = parseWholeNumber(arg); !maxErrors) {
|
||||
fatal("Invalid argument for option '-X'");
|
||||
} else if (*maxErrors > UINT64_MAX) {
|
||||
fatal("Argument for option '-X' must be between 0 and %" PRIu64, UINT64_MAX);
|
||||
} else {
|
||||
options.maxErrors = *maxErrors;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0: // Long-only options
|
||||
switch (longOpt) {
|
||||
case 'c':
|
||||
if (!style_Parse(arg)) {
|
||||
fatal("Invalid argument for option '--color'");
|
||||
}
|
||||
|
||||
if (maxErrors > UINT64_MAX) {
|
||||
fatal("Argument for option 'X' must be between 0 and %" PRIu64, UINT64_MAX);
|
||||
}
|
||||
|
||||
options.maxErrors = maxErrors;
|
||||
break;
|
||||
}
|
||||
|
||||
// Long-only options
|
||||
case 0:
|
||||
switch (depType) {
|
||||
case 'C':
|
||||
options.missingIncludeState = GEN_CONTINUE;
|
||||
break;
|
||||
@@ -350,58 +344,236 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
case 'Q':
|
||||
case 'T': {
|
||||
std::string newTarget = musl_optarg;
|
||||
if (depType == 'Q') {
|
||||
newTarget = make_escape(newTarget);
|
||||
std::string newTarget = arg;
|
||||
if (longOpt == 'Q') {
|
||||
newTarget = escapeMakeChars(newTarget);
|
||||
}
|
||||
if (!options.targetFileName.empty()) {
|
||||
options.targetFileName += ' ';
|
||||
if (options.targetFileName) {
|
||||
*options.targetFileName += ' ';
|
||||
*options.targetFileName += newTarget;
|
||||
} else {
|
||||
options.targetFileName = newTarget;
|
||||
}
|
||||
options.targetFileName += newTarget;
|
||||
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:
|
||||
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, usage);
|
||||
|
||||
if (!options.targetFileName && options.objectFileName) {
|
||||
options.targetFileName = options.objectFileName;
|
||||
}
|
||||
|
||||
if (argc == musl_optind) {
|
||||
usage.printAndExit("Please specify an input file (pass `-` to read from standard input)");
|
||||
} else if (argc != musl_optind + 1) {
|
||||
usage.printAndExit("More than one input file specified");
|
||||
verboseOutputConfig();
|
||||
|
||||
if (!localOptions.inputFileName) {
|
||||
usage.printAndExit("No input file specified (pass \"-\" to read from standard input)");
|
||||
}
|
||||
|
||||
std::string mainFileName = argv[musl_optind];
|
||||
// LCOV_EXCL_START
|
||||
verbosePrint(
|
||||
VERB_NOTICE,
|
||||
"Assembling \"%s\"\n",
|
||||
*localOptions.inputFileName == "-" ? "<stdin>" : localOptions.inputFileName->c_str()
|
||||
);
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
verbosePrint("Assembling %s\n", mainFileName.c_str()); // LCOV_EXCL_LINE
|
||||
|
||||
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 "
|
||||
"-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);
|
||||
|
||||
// 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`)
|
||||
if (yy::parser parser; parser.parse() != 0) {
|
||||
if (warnings.nbErrors == 0) {
|
||||
warnings.nbErrors = 1;
|
||||
}
|
||||
// Exited due to YYABORT or YYNOMEM
|
||||
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_CheckLoadClosed();
|
||||
sect_CheckSizes();
|
||||
@@ -409,18 +581,12 @@ int main(int argc, char *argv[]) {
|
||||
charmap_CheckStack();
|
||||
opt_CheckStack();
|
||||
sect_CheckStack();
|
||||
}
|
||||
|
||||
requireZeroErrors();
|
||||
|
||||
// If parse aborted due to missing an include, and `-MG` was given, exit normally
|
||||
if (fstk_FailedOnMissingInclude()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
out_WriteObject();
|
||||
|
||||
for (auto [name, features] : stateFileSpecs) {
|
||||
for (auto const &[name, features] : localOptions.stateFileSpecs) {
|
||||
out_WriteState(name, features);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <iterator> // std::size
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "helpers.hpp" // assume
|
||||
#include "diagnostics.hpp"
|
||||
#include "util.hpp"
|
||||
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/main.hpp" // options
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
struct OptStackEntry {
|
||||
@@ -49,104 +50,80 @@ void opt_R(size_t maxRecursionDepth) {
|
||||
}
|
||||
|
||||
void opt_W(char const *flag) {
|
||||
if (warnings.processWarningFlag(flag) == "numeric-string") {
|
||||
warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated");
|
||||
}
|
||||
warnings.processWarningFlag(flag);
|
||||
}
|
||||
|
||||
void opt_Parse(char const *s) {
|
||||
switch (s[0]) {
|
||||
if (s[0] == '-') {
|
||||
++s; // Skip a leading '-'
|
||||
}
|
||||
|
||||
char c = *s++;
|
||||
|
||||
while (isBlankSpace(*s)) {
|
||||
++s; // Skip leading blank spaces
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 'b':
|
||||
if (strlen(&s[1]) == 2) {
|
||||
opt_B(&s[1]);
|
||||
if (strlen(s) == 2) {
|
||||
opt_B(s);
|
||||
} else {
|
||||
error("Must specify exactly 2 characters for option 'b'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
if (strlen(&s[1]) == 4) {
|
||||
opt_G(&s[1]);
|
||||
if (strlen(s) == 4) {
|
||||
opt_G(s);
|
||||
} else {
|
||||
error("Must specify exactly 4 characters for option 'g'");
|
||||
}
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (strlen(&s[1]) <= 2) {
|
||||
int result;
|
||||
unsigned int padByte;
|
||||
|
||||
result = sscanf(&s[1], "%x", &padByte);
|
||||
if (result != 1) {
|
||||
if (std::optional<uint64_t> padByte = parseWholeNumber(s); !padByte) {
|
||||
error("Invalid argument for option 'p'");
|
||||
} else if (*padByte > 0xFF) {
|
||||
error("Argument for option 'p' must be between 0 and 0xFF");
|
||||
} else {
|
||||
// Two characters cannot be scanned as a hex number greater than 0xFF
|
||||
assume(padByte <= 0xFF);
|
||||
opt_P(padByte);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'p'");
|
||||
opt_P(*padByte);
|
||||
}
|
||||
break;
|
||||
|
||||
char const *precisionArg;
|
||||
case 'Q':
|
||||
precisionArg = &s[1];
|
||||
if (precisionArg[0] == '.') {
|
||||
++precisionArg;
|
||||
if (s[0] == '.') {
|
||||
++s; // Skip leading '.'
|
||||
}
|
||||
if (strlen(precisionArg) <= 2) {
|
||||
int result;
|
||||
unsigned int fixPrecision;
|
||||
|
||||
result = sscanf(precisionArg, "%u", &fixPrecision);
|
||||
if (result != 1) {
|
||||
if (std::optional<uint64_t> precision = parseWholeNumber(s); !precision) {
|
||||
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");
|
||||
} else {
|
||||
opt_Q(fixPrecision);
|
||||
}
|
||||
} else {
|
||||
error("Invalid argument for option 'Q'");
|
||||
opt_Q(*precision);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'r': {
|
||||
++s; // Skip 'r'
|
||||
while (isblank(*s)) {
|
||||
++s; // Skip leading whitespace
|
||||
}
|
||||
|
||||
if (s[0] == '\0') {
|
||||
error("Missing argument to option 'r'");
|
||||
break;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
unsigned long maxRecursionDepth = strtoul(s, &endptr, 10);
|
||||
|
||||
if (*endptr != '\0') {
|
||||
error("Invalid argument to option 'r' (\"%s\")", s);
|
||||
case 'r':
|
||||
if (std::optional<uint64_t> maxRecursionDepth = parseWholeNumber(s); !maxRecursionDepth) {
|
||||
error("Invalid argument for option 'r'");
|
||||
} else if (errno == ERANGE) {
|
||||
error("Argument to 'r' is out of range (\"%s\")", s);
|
||||
error("Argument for option 'r' is out of range");
|
||||
} else {
|
||||
opt_R(maxRecursionDepth);
|
||||
opt_R(*maxRecursionDepth);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'W':
|
||||
if (strlen(&s[1]) > 0) {
|
||||
opt_W(&s[1]);
|
||||
if (strlen(s) > 0) {
|
||||
opt_W(s);
|
||||
} else {
|
||||
error("Must specify an argument for option 'W'");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error("Unknown option '%c'", s[0]);
|
||||
error("Unknown option '%c'", c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp" // assume, Defer
|
||||
#include "linkdefs.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "asm/charmap.hpp"
|
||||
@@ -83,17 +86,17 @@ static void writeSection(Section const §, FILE *file) {
|
||||
|
||||
putLong(sect.size, file);
|
||||
|
||||
assume((sect.type & SECTTYPE_TYPE_MASK) == sect.type);
|
||||
bool isUnion = sect.modifier == SECTION_UNION;
|
||||
bool isFragment = sect.modifier == SECTION_FRAGMENT;
|
||||
|
||||
putc(sect.type | isUnion << 7 | isFragment << 6, file);
|
||||
putc(sect.type | isUnion << SECTTYPE_UNION_BIT | isFragment << SECTTYPE_FRAGMENT_BIT, file);
|
||||
|
||||
putLong(sect.org, file);
|
||||
putLong(sect.bank, file);
|
||||
putc(sect.align, file);
|
||||
putLong(sect.alignOfs, file);
|
||||
|
||||
if (sect_HasData(sect.type)) {
|
||||
if (sectTypeHasData(sect.type)) {
|
||||
fwrite(sect.data.data(), 1, sect.size, file);
|
||||
putLong(sect.patches.size(), file);
|
||||
|
||||
@@ -120,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
|
||||
if (sym.src && sym.ID == UINT32_MAX && !sym_IsPC(&sym)) {
|
||||
sym.ID = objectSymbols.size(); // Set the symbol's ID within the object file
|
||||
@@ -129,95 +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());
|
||||
if (sym->isConstant()) {
|
||||
rpnexpr[rpnptr++] = RPN_CONST;
|
||||
value = sym->getConstantValue();
|
||||
} else {
|
||||
rpnexpr[rpnptr++] = RPN_SYM;
|
||||
registerUnregisteredSymbol(*sym); // Ensure that `sym->ID` is set
|
||||
value = sym->ID;
|
||||
}
|
||||
|
||||
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) {
|
||||
patch.type = type;
|
||||
patch.src = fstk_GetFileStack();
|
||||
@@ -227,20 +141,7 @@ static void initPatch(Patch &patch, uint32_t type, Expression const &expr, uint3
|
||||
patch.offset = ofs;
|
||||
patch.pcSection = sect_GetSymbolSection();
|
||||
patch.pcOffset = sect_GetSymbolOffset();
|
||||
|
||||
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);
|
||||
}
|
||||
expr.encode(patch.rpn);
|
||||
}
|
||||
|
||||
void out_CreatePatch(uint32_t type, Expression const &expr, uint32_t ofs, uint32_t pcShift) {
|
||||
@@ -273,7 +174,9 @@ static void writeAssert(Assertion const &assert, FILE *file) {
|
||||
static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
putLong(node.parent ? node.parent->ID : UINT32_MAX, file);
|
||||
putLong(node.lineNo, file);
|
||||
putc(node.type, file);
|
||||
|
||||
putc(node.type | node.isQuiet << FSTACKNODE_QUIET_BIT, file);
|
||||
|
||||
if (node.type != NODE_REPT) {
|
||||
putString(node.name(), file);
|
||||
} else {
|
||||
@@ -281,36 +184,35 @@ static void writeFileStackNode(FileStackNode const &node, FILE *file) {
|
||||
|
||||
putLong(nodeIters.size(), file);
|
||||
// Iters are stored by decreasing depth, so reverse the order for output
|
||||
for (uint32_t i = nodeIters.size(); i--;) {
|
||||
putLong(nodeIters[i], file);
|
||||
for (uint32_t iter : reversed(nodeIters)) {
|
||||
putLong(iter, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void out_WriteObject() {
|
||||
if (options.objectFileName.empty()) {
|
||||
if (!options.objectFileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
static FILE *file; // `static` so `sect_ForEach` callback can see it
|
||||
if (options.objectFileName != "-") {
|
||||
file = fopen(options.objectFileName.c_str(), "wb");
|
||||
char const *objectFileName = options.objectFileName->c_str();
|
||||
if (*options.objectFileName != "-") {
|
||||
file = fopen(objectFileName, "wb");
|
||||
} else {
|
||||
options.objectFileName = "<stdout>";
|
||||
objectFileName = "<stdout>";
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
file = stdout;
|
||||
}
|
||||
if (!file) {
|
||||
// LCOV_EXCL_START
|
||||
fatal(
|
||||
"Failed to open object file '%s': %s", options.objectFileName.c_str(), strerror(errno)
|
||||
);
|
||||
fatal("Failed to open object file \"%s\": %s", objectFileName, strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
// Also write symbols that weren't written above
|
||||
sym_ForEach(registerUnregisteredSymbol);
|
||||
sym_ForEach(out_RegisterSymbol);
|
||||
|
||||
fputs(RGBDS_OBJECT_VERSION_STRING, file);
|
||||
putLong(RGBDS_OBJECT_REV, file);
|
||||
@@ -320,12 +222,10 @@ void out_WriteObject() {
|
||||
|
||||
putLong(fileStackNodes.size(), file);
|
||||
for (auto it = fileStackNodes.begin(); it != fileStackNodes.end(); ++it) {
|
||||
FileStackNode const &node = **it;
|
||||
|
||||
writeFileStackNode(node, file);
|
||||
writeFileStackNode(**it, file);
|
||||
|
||||
// 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) {
|
||||
@@ -485,7 +385,7 @@ void out_WriteState(std::string name, std::vector<StateFeature> const &features)
|
||||
}
|
||||
if (!file) {
|
||||
// LCOV_EXCL_START
|
||||
fatal("Failed to open state file '%s': %s", name.c_str(), strerror(errno));
|
||||
fatal("Failed to open state file \"%s\": %s", name.c_str(), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
Defer closeFile{[&] { fclose(file); }};
|
||||
|
||||
423
src/asm/parser.y
423
src/asm/parser.y
@@ -12,16 +12,12 @@
|
||||
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/actions.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/macro.hpp"
|
||||
#include "asm/rpn.hpp"
|
||||
#include "asm/section.hpp"
|
||||
|
||||
struct AlignmentSpec {
|
||||
uint8_t alignment;
|
||||
uint16_t alignOfs;
|
||||
};
|
||||
|
||||
struct ForArgs {
|
||||
int32_t start;
|
||||
int32_t stop;
|
||||
@@ -36,7 +32,6 @@
|
||||
|
||||
%code {
|
||||
#include <algorithm>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <optional>
|
||||
#include <stdio.h>
|
||||
@@ -46,8 +41,8 @@
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "util.hpp" // toLower, toUpper
|
||||
|
||||
#include "asm/actions.hpp"
|
||||
#include "asm/charmap.hpp"
|
||||
#include "asm/fixpoint.hpp"
|
||||
#include "asm/fstack.hpp"
|
||||
@@ -62,8 +57,10 @@
|
||||
|
||||
yy::parser::symbol_type yylex(); // Provided by lexer.cpp
|
||||
|
||||
template <typename N, typename S>
|
||||
static auto handleSymbolByType(std::string const &symName, N numCallback, S strCallback) {
|
||||
template<typename NumCallbackFnT, typename StrCallbackFnT>
|
||||
static auto handleSymbolByType(
|
||||
std::string const &symName, NumCallbackFnT numCallback, StrCallbackFnT strCallback
|
||||
) {
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym && sym->type == SYM_EQUS) {
|
||||
return strCallback(*sym->getEqus());
|
||||
} else {
|
||||
@@ -103,6 +100,7 @@
|
||||
%token LBRACK "[" RBRACK "]"
|
||||
%token LBRACKS "[[" RBRACKS "]]"
|
||||
%token LPAREN "(" RPAREN ")"
|
||||
%token QUESTIONMARK "?"
|
||||
|
||||
// Arithmetic operators
|
||||
%token OP_ADD "+" OP_SUB "-"
|
||||
@@ -111,6 +109,7 @@
|
||||
|
||||
// String operators
|
||||
%token OP_CAT "++"
|
||||
%token OP_STREQU "===" OP_STRNE "!=="
|
||||
|
||||
// Comparison operators
|
||||
%token OP_LOGICEQU "==" OP_LOGICNE "!="
|
||||
@@ -327,6 +326,7 @@
|
||||
%token <std::string> LABEL "label"
|
||||
%token <std::string> LOCAL "local label"
|
||||
%token <std::string> ANON "anonymous label"
|
||||
%token <std::string> QMACRO "quiet macro"
|
||||
|
||||
/******************** Data types ********************/
|
||||
|
||||
@@ -403,6 +403,7 @@
|
||||
%type <SectionType> sect_type
|
||||
%type <StrFmtArgList> strfmt_args
|
||||
%type <StrFmtArgList> strfmt_va_args
|
||||
%type <bool> maybe_quiet
|
||||
|
||||
%%
|
||||
|
||||
@@ -428,12 +429,14 @@ diff_mark:
|
||||
%empty // OK
|
||||
| OP_ADD {
|
||||
::error(
|
||||
"syntax error, unexpected + at the beginning of the line (is it a leftover diff mark?)"
|
||||
"syntax error, unexpected '+' at the beginning of the line (is it a leftover diff "
|
||||
"mark?)"
|
||||
);
|
||||
}
|
||||
| OP_SUB {
|
||||
::error(
|
||||
"syntax error, unexpected - at the beginning of the line (is it a leftover diff mark?)"
|
||||
"syntax error, unexpected '-' at the beginning of the line (is it a leftover diff "
|
||||
"mark?)"
|
||||
);
|
||||
}
|
||||
;
|
||||
@@ -465,48 +468,19 @@ line_directive:
|
||||
|
||||
if:
|
||||
POP_IF iconst NEWLINE {
|
||||
lexer_IncIFDepth();
|
||||
|
||||
if ($2) {
|
||||
lexer_RunIFBlock();
|
||||
} else {
|
||||
lexer_SetMode(LEXER_SKIP_TO_ELIF);
|
||||
}
|
||||
act_If($2);
|
||||
}
|
||||
;
|
||||
|
||||
elif:
|
||||
POP_ELIF iconst NEWLINE {
|
||||
if (lexer_GetIFDepth() == 0) {
|
||||
fatal("Found ELIF outside of an IF construct");
|
||||
}
|
||||
if (lexer_RanIFBlock()) {
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
fatal("Found ELIF after an ELSE block");
|
||||
}
|
||||
lexer_SetMode(LEXER_SKIP_TO_ENDC);
|
||||
} else if ($2) {
|
||||
lexer_RunIFBlock();
|
||||
} else {
|
||||
lexer_SetMode(LEXER_SKIP_TO_ELIF);
|
||||
}
|
||||
act_Elif($2);
|
||||
}
|
||||
;
|
||||
|
||||
else:
|
||||
POP_ELSE NEWLINE {
|
||||
if (lexer_GetIFDepth() == 0) {
|
||||
fatal("Found ELSE outside of an IF construct");
|
||||
}
|
||||
if (lexer_RanIFBlock()) {
|
||||
if (lexer_ReachedELSEBlock()) {
|
||||
fatal("Found ELSE after an ELSE block");
|
||||
}
|
||||
lexer_SetMode(LEXER_SKIP_TO_ENDC);
|
||||
} else {
|
||||
lexer_RunIFBlock();
|
||||
lexer_ReachELSEBlock();
|
||||
}
|
||||
act_Else();
|
||||
}
|
||||
;
|
||||
|
||||
@@ -514,14 +488,14 @@ else:
|
||||
|
||||
plain_directive:
|
||||
label
|
||||
| label cpu_commands
|
||||
| label data
|
||||
| label macro
|
||||
| label directive
|
||||
;
|
||||
|
||||
endc:
|
||||
POP_ENDC {
|
||||
lexer_DecIFDepth();
|
||||
act_Endc();
|
||||
}
|
||||
;
|
||||
|
||||
@@ -576,7 +550,13 @@ macro:
|
||||
// Parsing 'macro_args' will restore the lexer's normal mode
|
||||
lexer_SetMode(LEXER_RAW);
|
||||
} macro_args {
|
||||
fstk_RunMacro($1, $3);
|
||||
fstk_RunMacro($1, $3, false);
|
||||
}
|
||||
| QMACRO {
|
||||
// Parsing 'macro_args' will restore the lexer's normal mode
|
||||
lexer_SetMode(LEXER_RAW);
|
||||
} macro_args {
|
||||
fstk_RunMacro($1, $3, true);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -596,10 +576,6 @@ directive:
|
||||
| println
|
||||
| export
|
||||
| export_def
|
||||
| db
|
||||
| dw
|
||||
| dl
|
||||
| ds
|
||||
| section
|
||||
| rsreset
|
||||
| rsset
|
||||
@@ -684,29 +660,10 @@ align:
|
||||
|
||||
align_spec:
|
||||
uconst {
|
||||
if ($1 > 16) {
|
||||
::error("Alignment must be between 0 and 16, not %u", $1);
|
||||
$$.alignment = $$.alignOfs = 0;
|
||||
} else {
|
||||
$$.alignment = $1;
|
||||
$$.alignOfs = 0;
|
||||
}
|
||||
$$ = act_Alignment($1, 0);
|
||||
}
|
||||
| uconst COMMA iconst {
|
||||
if ($1 > 16) {
|
||||
::error("Alignment must be between 0 and 16, not %u", $1);
|
||||
$$.alignment = $$.alignOfs = 0;
|
||||
} else if ($3 <= -(1 << $1) || $3 >= 1 << $1) {
|
||||
::error(
|
||||
"The absolute alignment offset (%" PRIu32 ") must be less than alignment size (%d)",
|
||||
static_cast<uint32_t>($3 < 0 ? -$3 : $3),
|
||||
1 << $1
|
||||
);
|
||||
$$.alignment = $$.alignOfs = 0;
|
||||
} else {
|
||||
$$.alignment = $1;
|
||||
$$.alignOfs = $3 < 0 ? (1 << $1) + $3 : $3;
|
||||
}
|
||||
$$ = act_Alignment($1, $3);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -796,28 +753,16 @@ assert_type:
|
||||
|
||||
assert:
|
||||
POP_ASSERT assert_type relocexpr {
|
||||
if (!$3.isKnown()) {
|
||||
out_CreateAssert($2, $3, "", sect_GetOutputOffset());
|
||||
} else if ($3.value() == 0) {
|
||||
act_FailAssert($2);
|
||||
}
|
||||
act_Assert($2, $3, "");
|
||||
}
|
||||
| POP_ASSERT assert_type relocexpr COMMA string {
|
||||
if (!$3.isKnown()) {
|
||||
out_CreateAssert($2, $3, $5, sect_GetOutputOffset());
|
||||
} else if ($3.value() == 0) {
|
||||
act_FailAssertMsg($2, $5);
|
||||
}
|
||||
act_Assert($2, $3, $5);
|
||||
}
|
||||
| POP_STATIC_ASSERT assert_type iconst {
|
||||
if ($3 == 0) {
|
||||
act_FailAssert($2);
|
||||
}
|
||||
act_StaticAssert($2, $3, "");
|
||||
}
|
||||
| POP_STATIC_ASSERT assert_type iconst COMMA string {
|
||||
if ($3 == 0) {
|
||||
act_FailAssertMsg($2, $5);
|
||||
}
|
||||
act_StaticAssert($2, $3, $5);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -847,10 +792,19 @@ load:
|
||||
}
|
||||
;
|
||||
|
||||
maybe_quiet:
|
||||
%empty {
|
||||
$$ = false;
|
||||
}
|
||||
| QUESTIONMARK {
|
||||
$$ = true;
|
||||
}
|
||||
;
|
||||
|
||||
rept:
|
||||
POP_REPT uconst NEWLINE capture_rept endofline {
|
||||
if ($4.span.ptr) {
|
||||
fstk_RunRept($2, $4.lineNo, $4.span);
|
||||
POP_REPT maybe_quiet uconst NEWLINE capture_rept endofline {
|
||||
if ($5.span.ptr) {
|
||||
fstk_RunRept($3, $5.lineNo, $5.span, $2);
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -858,11 +812,11 @@ rept:
|
||||
for:
|
||||
POP_FOR {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} SYMBOL {
|
||||
} maybe_quiet SYMBOL {
|
||||
lexer_ToggleStringExpansion(true);
|
||||
} COMMA for_args NEWLINE capture_rept endofline {
|
||||
if ($8.span.ptr) {
|
||||
fstk_RunFor($3, $6.start, $6.stop, $6.step, $8.lineNo, $8.span);
|
||||
if ($9.span.ptr) {
|
||||
fstk_RunFor($4, $7.start, $7.stop, $7.step, $9.lineNo, $9.span, $3);
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -902,11 +856,11 @@ break:
|
||||
def_macro:
|
||||
POP_MACRO {
|
||||
lexer_ToggleStringExpansion(false);
|
||||
} SYMBOL {
|
||||
} maybe_quiet SYMBOL {
|
||||
lexer_ToggleStringExpansion(true);
|
||||
} NEWLINE capture_macro endofline {
|
||||
if ($6.span.ptr) {
|
||||
sym_AddMacro($3, $6.lineNo, $6.span);
|
||||
if ($7.span.ptr) {
|
||||
sym_AddMacro($4, $7.lineNo, $7.span, $3);
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -954,58 +908,6 @@ endu:
|
||||
}
|
||||
;
|
||||
|
||||
ds:
|
||||
POP_DS uconst {
|
||||
sect_Skip($2, true);
|
||||
}
|
||||
| POP_DS uconst COMMA ds_args trailing_comma {
|
||||
sect_RelBytes($2, $4);
|
||||
}
|
||||
| POP_DS POP_ALIGN LBRACK align_spec RBRACK trailing_comma {
|
||||
uint32_t n = sect_GetAlignBytes($4.alignment, $4.alignOfs);
|
||||
|
||||
sect_Skip(n, true);
|
||||
sect_AlignPC($4.alignment, $4.alignOfs);
|
||||
}
|
||||
| POP_DS POP_ALIGN LBRACK align_spec RBRACK COMMA ds_args trailing_comma {
|
||||
uint32_t n = sect_GetAlignBytes($4.alignment, $4.alignOfs);
|
||||
|
||||
sect_RelBytes(n, $7);
|
||||
sect_AlignPC($4.alignment, $4.alignOfs);
|
||||
}
|
||||
;
|
||||
|
||||
ds_args:
|
||||
reloc_8bit {
|
||||
$$.push_back(std::move($1));
|
||||
}
|
||||
| ds_args COMMA reloc_8bit {
|
||||
$$ = std::move($1);
|
||||
$$.push_back(std::move($3));
|
||||
}
|
||||
;
|
||||
|
||||
db:
|
||||
POP_DB {
|
||||
sect_Skip(1, false);
|
||||
}
|
||||
| POP_DB constlist_8bit trailing_comma
|
||||
;
|
||||
|
||||
dw:
|
||||
POP_DW {
|
||||
sect_Skip(2, false);
|
||||
}
|
||||
| POP_DW constlist_16bit trailing_comma
|
||||
;
|
||||
|
||||
dl:
|
||||
POP_DL {
|
||||
sect_Skip(4, false);
|
||||
}
|
||||
| POP_DL constlist_32bit trailing_comma
|
||||
;
|
||||
|
||||
def_equ:
|
||||
def_id POP_EQU iconst {
|
||||
$$ = std::move($1);
|
||||
@@ -1121,8 +1023,8 @@ export_def:
|
||||
;
|
||||
|
||||
include:
|
||||
label POP_INCLUDE string endofline {
|
||||
if (fstk_RunInclude($3)) {
|
||||
label POP_INCLUDE maybe_quiet string endofline {
|
||||
if (fstk_RunInclude($4, $3)) {
|
||||
YYACCEPT;
|
||||
}
|
||||
}
|
||||
@@ -1150,6 +1052,9 @@ charmap:
|
||||
POP_CHARMAP string COMMA charmap_args trailing_comma {
|
||||
charmap_Add($2, std::move($4));
|
||||
}
|
||||
| POP_CHARMAP CHARACTER COMMA charmap_args trailing_comma {
|
||||
charmap_Add($2, std::move($4));
|
||||
}
|
||||
;
|
||||
|
||||
charmap_args:
|
||||
@@ -1363,17 +1268,15 @@ relocexpr:
|
||||
$$ = std::move($1);
|
||||
}
|
||||
| string_literal {
|
||||
std::vector<int32_t> output = charmap_Convert($1);
|
||||
$$.makeNumber(act_StringToNum(output));
|
||||
$$.makeNumber(act_StringToNum($1));
|
||||
}
|
||||
| scoped_sym {
|
||||
$$ = handleSymbolByType(
|
||||
$1,
|
||||
[](Expression const &expr) { return expr; },
|
||||
[](std::string const &str) {
|
||||
std::vector<int32_t> output = charmap_Convert(str);
|
||||
Expression expr;
|
||||
expr.makeNumber(act_StringToNum(output));
|
||||
expr.makeNumber(act_StringToNum(str));
|
||||
return expr;
|
||||
}
|
||||
);
|
||||
@@ -1385,13 +1288,13 @@ relocexpr_no_str:
|
||||
$$.makeNumber($1);
|
||||
}
|
||||
| CHARACTER {
|
||||
std::vector<int32_t> output = charmap_Convert($1);
|
||||
if (output.size() == 1) {
|
||||
$$.makeNumber(static_cast<uint32_t>(output[0]));
|
||||
} else {
|
||||
::error("Character literals must be a single charmap unit");
|
||||
$$.makeNumber(0);
|
||||
$$.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 {
|
||||
$$.makeUnaryOp(RPN_LOGNOT, std::move($2));
|
||||
@@ -1568,10 +1471,12 @@ relocexpr_no_str:
|
||||
$$.makeNumber(pos != std::string::npos ? pos : -1);
|
||||
}
|
||||
| OP_STRIN LPAREN string COMMA string RPAREN {
|
||||
warning(WARNING_OBSOLETE, "`STRIN` is deprecated; use 0-indexed `STRFIND` instead");
|
||||
size_t pos = $3.find($5);
|
||||
$$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
|
||||
}
|
||||
| OP_STRRIN LPAREN string COMMA string RPAREN {
|
||||
warning(WARNING_OBSOLETE, "`STRRIN` is deprecated; use 0-indexed `STRRFIND` instead");
|
||||
size_t pos = $3.rfind($5);
|
||||
$$.makeNumber(pos != std::string::npos ? pos + 1 : 0);
|
||||
}
|
||||
@@ -1598,36 +1503,13 @@ relocexpr_no_str:
|
||||
$$.makeNumber(charSize);
|
||||
}
|
||||
| OP_CHARVAL LPAREN string COMMA iconst RPAREN {
|
||||
if (size_t len = charmap_CharSize($3); len != 0) {
|
||||
uint32_t idx = act_AdjustNegativeIndex($5, len, "CHARVAL");
|
||||
if (std::optional<int32_t> val = charmap_CharValue($3, idx); val.has_value()) {
|
||||
$$.makeNumber(*val);
|
||||
} else {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"CHARVAL: Index %" PRIu32 " is past the end of the character mapping",
|
||||
idx
|
||||
);
|
||||
$$.makeNumber(0);
|
||||
}
|
||||
} else {
|
||||
::error("CHARVAL: No character mapping for \"%s\"", $3.c_str());
|
||||
$$.makeNumber(0);
|
||||
$$.makeNumber(act_CharVal($3, $5));
|
||||
}
|
||||
| OP_CHARVAL LPAREN string RPAREN {
|
||||
$$.makeNumber(act_CharVal($3));
|
||||
}
|
||||
| OP_STRBYTE LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = $3.length();
|
||||
uint32_t idx = act_AdjustNegativeIndex($5, len, "STRBYTE");
|
||||
if (idx < len) {
|
||||
$$.makeNumber(static_cast<uint8_t>($3[idx]));
|
||||
} else {
|
||||
warning(
|
||||
WARNING_BUILTIN_ARG,
|
||||
"STRBYTE: Index %" PRIu32 " is past the end of the string",
|
||||
idx
|
||||
);
|
||||
$$.makeNumber(0);
|
||||
}
|
||||
$$.makeNumber(act_StringByte($3, $5));
|
||||
}
|
||||
| LPAREN relocexpr RPAREN {
|
||||
$$ = std::move($2);
|
||||
@@ -1685,35 +1567,22 @@ string_literal:
|
||||
}
|
||||
}
|
||||
| OP_STRSLICE LPAREN string COMMA iconst COMMA iconst RPAREN {
|
||||
size_t len = act_StringLen($3, false);
|
||||
uint32_t start = act_AdjustNegativeIndex($5, len, "STRSLICE");
|
||||
uint32_t stop = act_AdjustNegativeIndex($7, len, "STRSLICE");
|
||||
$$ = act_StringSlice($3, start, stop);
|
||||
$$ = act_StringSlice($3, $5, $7);
|
||||
}
|
||||
| OP_STRSLICE LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = act_StringLen($3, false);
|
||||
uint32_t start = act_AdjustNegativeIndex($5, len, "STRSLICE");
|
||||
$$ = act_StringSlice($3, start, len);
|
||||
$$ = act_StringSlice($3, $5, std::nullopt);
|
||||
}
|
||||
| OP_STRSUB LPAREN string COMMA iconst COMMA uconst RPAREN {
|
||||
size_t len = act_StringLen($3, false);
|
||||
uint32_t pos = act_AdjustNegativePos($5, len, "STRSUB");
|
||||
$$ = act_StringSub($3, pos, $7);
|
||||
$$ = act_StringSub($3, $5, $7);
|
||||
}
|
||||
| OP_STRSUB LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = act_StringLen($3, false);
|
||||
uint32_t pos = act_AdjustNegativePos($5, len, "STRSUB");
|
||||
$$ = act_StringSub($3, pos, pos > len ? 0 : len + 1 - pos);
|
||||
$$ = act_StringSub($3, $5, std::nullopt);
|
||||
}
|
||||
| OP_STRCHAR LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = act_CharLen($3);
|
||||
uint32_t idx = act_AdjustNegativeIndex($5, len, "STRCHAR");
|
||||
$$ = act_StringChar($3, idx);
|
||||
$$ = act_StringChar($3, $5);
|
||||
}
|
||||
| OP_CHARSUB LPAREN string COMMA iconst RPAREN {
|
||||
size_t len = act_CharLen($3);
|
||||
uint32_t pos = act_AdjustNegativePos($5, len, "CHARSUB");
|
||||
$$ = act_CharSub($3, pos);
|
||||
$$ = act_CharSub($3, $5);
|
||||
}
|
||||
| OP_REVCHAR LPAREN charmap_args RPAREN {
|
||||
bool unique;
|
||||
@@ -1732,11 +1601,11 @@ string_literal:
|
||||
}
|
||||
| OP_STRUPR LPAREN string RPAREN {
|
||||
$$ = std::move($3);
|
||||
std::transform(RANGE($$), $$.begin(), [](char c) { return toupper(c); });
|
||||
std::transform(RANGE($$), $$.begin(), toUpper);
|
||||
}
|
||||
| OP_STRLWR LPAREN string RPAREN {
|
||||
$$ = 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 {
|
||||
$$ = act_StringReplace($3, $5, $7);
|
||||
@@ -1745,23 +1614,7 @@ string_literal:
|
||||
$$ = act_StringFormat($3.format, $3.args);
|
||||
}
|
||||
| POP_SECTION LPAREN scoped_sym RPAREN {
|
||||
Symbol *sym = sym_FindScopedValidSymbol($3);
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped($3)) {
|
||||
fatal("Unknown symbol \"%s\"; it was purged", $3.c_str());
|
||||
} else {
|
||||
fatal("Unknown symbol \"%s\"", $3.c_str());
|
||||
}
|
||||
}
|
||||
Section const *section = sym->getSection();
|
||||
|
||||
if (!section) {
|
||||
fatal("\"%s\" does not belong to any section", sym->name.c_str());
|
||||
}
|
||||
// Section names are capped by rgbasm's maximum string length,
|
||||
// so this currently can't overflow.
|
||||
$$ = section->name;
|
||||
$$ = act_SectionName($3);
|
||||
}
|
||||
;
|
||||
|
||||
@@ -1773,7 +1626,7 @@ string:
|
||||
if (Symbol *sym = sym_FindScopedSymbol($1); sym && sym->type == SYM_EQUS) {
|
||||
$$ = *sym->getEqus();
|
||||
} else {
|
||||
::error("'%s' is not a string symbol", $1.c_str());
|
||||
::error("`%s` is not a string symbol", $1.c_str());
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -1900,15 +1753,19 @@ sect_attrs:
|
||||
}
|
||||
;
|
||||
|
||||
// CPU commands.
|
||||
// CPU instructions and data declarations
|
||||
|
||||
cpu_commands:
|
||||
cpu_command
|
||||
| cpu_command DOUBLE_COLON cpu_commands
|
||||
data:
|
||||
datum
|
||||
| datum DOUBLE_COLON data
|
||||
;
|
||||
|
||||
cpu_command:
|
||||
sm83_adc
|
||||
datum:
|
||||
db
|
||||
| dw
|
||||
| dl
|
||||
| ds
|
||||
| sm83_adc
|
||||
| sm83_add
|
||||
| sm83_and
|
||||
| sm83_bit
|
||||
@@ -1956,6 +1813,56 @@ cpu_command:
|
||||
| sm83_xor
|
||||
;
|
||||
|
||||
ds:
|
||||
POP_DS uconst {
|
||||
sect_Skip($2, true);
|
||||
}
|
||||
| POP_DS uconst COMMA ds_args trailing_comma {
|
||||
sect_RelBytes($2, $4);
|
||||
}
|
||||
| POP_DS POP_ALIGN LBRACK align_spec RBRACK trailing_comma {
|
||||
uint32_t n = sect_GetAlignBytes($4.alignment, $4.alignOfs);
|
||||
sect_Skip(n, true);
|
||||
sect_AlignPC($4.alignment, $4.alignOfs);
|
||||
}
|
||||
| POP_DS POP_ALIGN LBRACK align_spec RBRACK COMMA ds_args trailing_comma {
|
||||
uint32_t n = sect_GetAlignBytes($4.alignment, $4.alignOfs);
|
||||
sect_RelBytes(n, $7);
|
||||
sect_AlignPC($4.alignment, $4.alignOfs);
|
||||
}
|
||||
;
|
||||
|
||||
ds_args:
|
||||
reloc_8bit {
|
||||
$$.push_back(std::move($1));
|
||||
}
|
||||
| ds_args COMMA reloc_8bit {
|
||||
$$ = std::move($1);
|
||||
$$.push_back(std::move($3));
|
||||
}
|
||||
;
|
||||
|
||||
db:
|
||||
POP_DB {
|
||||
sect_Skip(1, false);
|
||||
}
|
||||
| POP_DB constlist_8bit trailing_comma
|
||||
;
|
||||
|
||||
dw:
|
||||
POP_DW {
|
||||
sect_Skip(2, false);
|
||||
}
|
||||
| POP_DW constlist_16bit trailing_comma
|
||||
;
|
||||
|
||||
dl:
|
||||
POP_DL {
|
||||
sect_Skip(4, false);
|
||||
}
|
||||
| POP_DL constlist_32bit trailing_comma
|
||||
;
|
||||
|
||||
sm83_adc:
|
||||
SM83_ADC op_a_n {
|
||||
sect_ConstByte(0xCE);
|
||||
@@ -1996,7 +1903,7 @@ sm83_and:
|
||||
sm83_bit:
|
||||
SM83_BIT reloc_3bit COMMA reg_r {
|
||||
uint8_t mask = static_cast<uint8_t>(0x40 | $4);
|
||||
$2.makeCheckBitIndex(mask);
|
||||
$2.addCheckBitIndex(mask);
|
||||
sect_ConstByte(0xCB);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
@@ -2129,26 +2036,22 @@ sm83_ldd:
|
||||
|
||||
sm83_ldh:
|
||||
SM83_LDH MODE_A COMMA op_mem_ind {
|
||||
if ($4.makeCheckHRAM()) {
|
||||
warning(
|
||||
WARNING_OBSOLETE,
|
||||
"LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF"
|
||||
);
|
||||
}
|
||||
|
||||
$4.addCheckHRAM();
|
||||
sect_ConstByte(0xF0);
|
||||
if (!$4.isKnown()) {
|
||||
sect_RelByte($4, 1);
|
||||
} else {
|
||||
sect_ConstByte($4.value());
|
||||
}
|
||||
}
|
||||
| SM83_LDH op_mem_ind COMMA MODE_A {
|
||||
if ($2.makeCheckHRAM()) {
|
||||
warning(
|
||||
WARNING_OBSOLETE,
|
||||
"LDH is deprecated with values from $00 to $FF; use $FF00 to $FFFF"
|
||||
);
|
||||
}
|
||||
|
||||
$2.addCheckHRAM();
|
||||
sect_ConstByte(0xE0);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 1);
|
||||
} else {
|
||||
sect_ConstByte($2.value());
|
||||
}
|
||||
}
|
||||
| SM83_LDH MODE_A COMMA c_ind {
|
||||
sect_ConstByte(0xF2);
|
||||
@@ -2170,7 +2073,7 @@ ff00_c_ind:
|
||||
LBRACK relocexpr OP_ADD MODE_C RBRACK {
|
||||
// This has to use `relocexpr`, not `iconst`, to avoid a shift/reduce conflict
|
||||
if ($2.getConstVal() != 0xFF00) {
|
||||
::error("Base value must be equal to $FF00 for $FF00+C");
|
||||
::error("Base value must be equal to $FF00 for [$FF00+C]");
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -2197,7 +2100,7 @@ sm83_ld_hl:
|
||||
}
|
||||
| SM83_LD MODE_HL COMMA reg_tt_no_af {
|
||||
::error(
|
||||
"LD HL, %s is not a valid instruction; use LD H, %s and LD L, %s",
|
||||
"\"LD HL, %s\" is not a valid instruction; use \"LD H, %s\" and \"LD L, %s\"",
|
||||
reg_tt_names[$4],
|
||||
reg_tt_high_names[$4],
|
||||
reg_tt_low_names[$4]
|
||||
@@ -2210,7 +2113,7 @@ sm83_ld_sp:
|
||||
sect_ConstByte(0xF9);
|
||||
}
|
||||
| SM83_LD MODE_SP COMMA reg_bc_or_de {
|
||||
::error("LD SP, %s is not a valid instruction", reg_tt_names[$4]);
|
||||
::error("\"LD SP, %s\" is not a valid instruction", reg_tt_names[$4]);
|
||||
}
|
||||
| SM83_LD MODE_SP COMMA reloc_16bit {
|
||||
sect_ConstByte(0x01 | (REG_SP << 4));
|
||||
@@ -2233,10 +2136,6 @@ sm83_ld_c_ind:
|
||||
SM83_LD ff00_c_ind COMMA MODE_A {
|
||||
sect_ConstByte(0xE2);
|
||||
}
|
||||
| SM83_LD c_ind COMMA MODE_A {
|
||||
warning(WARNING_OBSOLETE, "LD [C], A is deprecated; use LDH [C], A");
|
||||
sect_ConstByte(0xE2);
|
||||
}
|
||||
;
|
||||
|
||||
sm83_ld_rr:
|
||||
@@ -2252,7 +2151,7 @@ sm83_ld_r_no_a:
|
||||
}
|
||||
| SM83_LD reg_r_no_a COMMA reg_r {
|
||||
if ($2 == REG_HL_IND && $4 == REG_HL_IND) {
|
||||
::error("LD [HL], [HL] is not a valid instruction");
|
||||
::error("\"LD [HL], [HL]\" is not a valid instruction");
|
||||
} else {
|
||||
sect_ConstByte(0x40 | ($2 << 3) | $4);
|
||||
}
|
||||
@@ -2270,10 +2169,6 @@ sm83_ld_a:
|
||||
| SM83_LD reg_a COMMA ff00_c_ind {
|
||||
sect_ConstByte(0xF2);
|
||||
}
|
||||
| SM83_LD reg_a COMMA c_ind {
|
||||
warning(WARNING_OBSOLETE, "LD A, [C] is deprecated; use LDH A, [C]");
|
||||
sect_ConstByte(0xF2);
|
||||
}
|
||||
| SM83_LD reg_a COMMA reg_rr {
|
||||
sect_ConstByte(0x0A | ($4 << 4));
|
||||
}
|
||||
@@ -2290,7 +2185,7 @@ sm83_ld_ss:
|
||||
}
|
||||
| SM83_LD reg_bc_or_de COMMA reg_tt_no_af {
|
||||
::error(
|
||||
"LD %s, %s is not a valid instruction; use LD %s, %s and LD %s, %s",
|
||||
"\"LD %s, %s\" is not a valid instruction; use \"LD %s, %s\" and \"LD %s, %s\"",
|
||||
reg_tt_names[$2],
|
||||
reg_tt_names[$4],
|
||||
reg_tt_high_names[$2],
|
||||
@@ -2334,7 +2229,7 @@ sm83_push:
|
||||
sm83_res:
|
||||
SM83_RES reloc_3bit COMMA reg_r {
|
||||
uint8_t mask = static_cast<uint8_t>(0x80 | $4);
|
||||
$2.makeCheckBitIndex(mask);
|
||||
$2.addCheckBitIndex(mask);
|
||||
sect_ConstByte(0xCB);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
@@ -2413,7 +2308,7 @@ sm83_rrca:
|
||||
|
||||
sm83_rst:
|
||||
SM83_RST reloc_8bit {
|
||||
$2.makeCheckRST();
|
||||
$2.addCheckRST();
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
} else {
|
||||
@@ -2441,7 +2336,7 @@ sm83_scf:
|
||||
sm83_set:
|
||||
SM83_SET reloc_3bit COMMA reg_r {
|
||||
uint8_t mask = static_cast<uint8_t>(0xC0 | $4);
|
||||
$2.makeCheckBitIndex(mask);
|
||||
$2.addCheckBitIndex(mask);
|
||||
sect_ConstByte(0xCB);
|
||||
if (!$2.isKnown()) {
|
||||
sect_RelByte($2, 0);
|
||||
@@ -2544,7 +2439,7 @@ op_sp_offset:
|
||||
$$.checkNBit(8);
|
||||
}
|
||||
| %empty {
|
||||
::error("LD HL, SP is not a valid instruction; use LD HL, SP + 0");
|
||||
::error("\"LD HL, SP\" is not a valid instruction; use \"LD HL, SP + 0\"");
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
327
src/asm/rpn.cpp
327
src/asm/rpn.cpp
@@ -5,12 +5,17 @@
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp" // assume, clz, ctz
|
||||
#include "helpers.hpp" // assume
|
||||
#include "linkdefs.hpp"
|
||||
#include "opmath.hpp"
|
||||
|
||||
#include "asm/output.hpp"
|
||||
@@ -20,24 +25,6 @@
|
||||
|
||||
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 {
|
||||
if (!isKnown()) {
|
||||
error("Expected constant expression: %s", std::get<std::string>(data).c_str());
|
||||
@@ -47,10 +34,10 @@ int32_t Expression::getConstVal() const {
|
||||
}
|
||||
|
||||
Symbol const *Expression::symbolOf() const {
|
||||
if (!isSymbol) {
|
||||
if (rpn.size() != 1 || rpn[0].command != RPN_SYM) {
|
||||
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 {
|
||||
@@ -67,40 +54,33 @@ bool Expression::isDiffConstant(Symbol const *sym) const {
|
||||
}
|
||||
|
||||
void Expression::makeNumber(uint32_t value) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
data = static_cast<int32_t>(value);
|
||||
}
|
||||
|
||||
void Expression::makeSymbol(std::string const &symName) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
if (Symbol *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym) && !sect_GetSymbolSection()) {
|
||||
error("PC has no value outside of a section");
|
||||
data = 0;
|
||||
} else if (sym && !sym->isNumeric() && !sym->isLabel()) {
|
||||
error("'%s' is not a numeric symbol", symName.c_str());
|
||||
error("`%s` is not a numeric symbol", symName.c_str());
|
||||
data = 0;
|
||||
} else if (!sym || !sym->isConstant()) {
|
||||
isSymbol = true;
|
||||
|
||||
data = sym_IsPC(sym) ? "PC is not constant at assembly time"
|
||||
: sym_IsPurgedScoped(symName)
|
||||
? "'"s + symName + "' is not constant at assembly time; it was purged"
|
||||
: "'"s + symName + "' is not constant at assembly time";
|
||||
: (sym && sym->isDefined()
|
||||
? "`"s + symName + "` is not constant at assembly time"
|
||||
: "undefined symbol `"s + symName + "`")
|
||||
+ (sym_IsPurgedScoped(symName) ? "; it was purged" : "");
|
||||
sym = sym_Ref(symName);
|
||||
|
||||
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);
|
||||
rpn.emplace_back(RPN_SYM, sym->name);
|
||||
} else {
|
||||
data = static_cast<int32_t>(sym->getConstantValue());
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeBankSymbol(std::string const &symName) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
if (Symbol const *sym = sym_FindScopedSymbol(symName); sym_IsPC(sym)) {
|
||||
// The @ symbol is treated differently.
|
||||
if (std::optional<uint32_t> outputBank = sect_GetOutputBank(); !outputBank) {
|
||||
@@ -108,98 +88,68 @@ void Expression::makeBankSymbol(std::string const &symName) {
|
||||
data = 1;
|
||||
} else if (*outputBank == UINT32_MAX) {
|
||||
data = "Current section's bank is not known";
|
||||
|
||||
*reserveSpace(1) = RPN_BANK_SELF;
|
||||
rpn.emplace_back(RPN_BANK_SELF);
|
||||
} else {
|
||||
data = static_cast<int32_t>(*outputBank);
|
||||
}
|
||||
return;
|
||||
} else if (sym && !sym->isLabel()) {
|
||||
error("BANK argument must be a label");
|
||||
error("`BANK` argument must be a label");
|
||||
data = 1;
|
||||
} else {
|
||||
sym = sym_Ref(symName);
|
||||
assume(sym); // If the symbol didn't exist, it should have been created
|
||||
|
||||
if (sym->getSection() && sym->getSection()->bank != UINT32_MAX) {
|
||||
// Symbol's section is known and bank is fixed
|
||||
data = static_cast<int32_t>(sym->getSection()->bank);
|
||||
} else {
|
||||
data = sym_IsPurgedScoped(symName)
|
||||
? "\""s + symName + "\"'s bank is not known; it was purged"
|
||||
: "\""s + symName + "\"'s bank is not known";
|
||||
|
||||
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);
|
||||
? "`"s + symName + "`'s bank is not known; it was purged"
|
||||
: "`"s + symName + "`'s bank is not known";
|
||||
rpn.emplace_back(RPN_BANK_SYM, sym->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeBankSection(std::string const §Name) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->bank != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->bank);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s bank is not known";
|
||||
|
||||
size_t nameLen = sectName.length() + 1; // Room for NUL!
|
||||
|
||||
uint8_t *ptr = reserveSpace(nameLen + 1);
|
||||
*ptr++ = RPN_BANK_SECT;
|
||||
memcpy(ptr, sectName.data(), nameLen);
|
||||
rpn.emplace_back(RPN_BANK_SECT, sectName);
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeSizeOfSection(std::string const §Name) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->isSizeKnown()) {
|
||||
data = static_cast<int32_t>(sect->size);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s size is not known";
|
||||
|
||||
size_t nameLen = sectName.length() + 1; // Room for NUL!
|
||||
|
||||
uint8_t *ptr = reserveSpace(nameLen + 1);
|
||||
*ptr++ = RPN_SIZEOF_SECT;
|
||||
memcpy(ptr, sectName.data(), nameLen);
|
||||
rpn.emplace_back(RPN_SIZEOF_SECT, sectName);
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeStartOfSection(std::string const §Name) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
if (Section *sect = sect_FindSectionByName(sectName); sect && sect->org != UINT32_MAX) {
|
||||
data = static_cast<int32_t>(sect->org);
|
||||
} else {
|
||||
data = "Section \""s + sectName + "\"'s start is not known";
|
||||
|
||||
size_t nameLen = sectName.length() + 1; // Room for NUL!
|
||||
|
||||
uint8_t *ptr = reserveSpace(nameLen + 1);
|
||||
*ptr++ = RPN_STARTOF_SECT;
|
||||
memcpy(ptr, sectName.data(), nameLen);
|
||||
rpn.emplace_back(RPN_STARTOF_SECT, sectName);
|
||||
}
|
||||
}
|
||||
|
||||
void Expression::makeSizeOfSectionType(SectionType type) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
data = "Section type's size is not known";
|
||||
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_SIZEOF_SECTTYPE;
|
||||
*ptr = type;
|
||||
rpn.emplace_back(RPN_SIZEOF_SECTTYPE, static_cast<uint8_t>(type));
|
||||
}
|
||||
|
||||
void Expression::makeStartOfSectionType(SectionType type) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
data = "Section type's start is not known";
|
||||
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_STARTOF_SECTTYPE;
|
||||
*ptr = type;
|
||||
rpn.emplace_back(RPN_STARTOF_SECTTYPE, static_cast<uint8_t>(type));
|
||||
}
|
||||
|
||||
static bool tryConstZero(Expression const &lhs, Expression const &rhs) {
|
||||
@@ -253,7 +203,7 @@ static int32_t tryConstLow(Expression const &expr) {
|
||||
assume(sect.org == UINT32_MAX);
|
||||
int32_t symbolOfs = sym->getValue() + 1;
|
||||
|
||||
return (symbolOfs + sect.alignOfs) & 0xFF;
|
||||
return op_low(symbolOfs + sect.alignOfs);
|
||||
}
|
||||
|
||||
// Returns a constant binary AND with one non-constant operand, or -1 if it cannot be computed.
|
||||
@@ -297,16 +247,13 @@ static int32_t tryConstMask(Expression const &lhs, Expression const &rhs) {
|
||||
}
|
||||
|
||||
void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
// First, check if the expression is known
|
||||
if (src.isKnown()) {
|
||||
// If the expressions is known, just compute the value
|
||||
int32_t val = src.value();
|
||||
uint32_t uval = static_cast<uint32_t>(val);
|
||||
|
||||
switch (op) {
|
||||
switch (int32_t val = src.value(); op) {
|
||||
case RPN_NEG:
|
||||
data = static_cast<int32_t>(-uval);
|
||||
data = op_neg(val);
|
||||
break;
|
||||
case RPN_NOT:
|
||||
data = ~val;
|
||||
@@ -315,16 +262,16 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
data = !val;
|
||||
break;
|
||||
case RPN_HIGH:
|
||||
data = static_cast<int32_t>(uval >> 8 & 0xFF);
|
||||
data = op_high(val);
|
||||
break;
|
||||
case RPN_LOW:
|
||||
data = val & 0xFF;
|
||||
data = op_low(val);
|
||||
break;
|
||||
case RPN_BITWIDTH:
|
||||
data = val != 0 ? 32 - clz(uval) : 0;
|
||||
data = op_bitwidth(val);
|
||||
break;
|
||||
case RPN_TZCOUNT:
|
||||
data = val != 0 ? ctz(uval) : 32;
|
||||
data = op_tzcount(val);
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
default:
|
||||
@@ -337,16 +284,15 @@ void Expression::makeUnaryOp(RPNCommand op, Expression &&src) {
|
||||
} else if (int32_t constVal; op == RPN_LOW && (constVal = tryConstLow(src)) != -1) {
|
||||
data = constVal;
|
||||
} else {
|
||||
// If it's not known, just reuse its RPN buffer and append the operator
|
||||
rpnPatchSize = src.rpnPatchSize;
|
||||
std::swap(rpn, src.rpn);
|
||||
// If it's not known, just reuse its RPN vector and append the operator
|
||||
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) {
|
||||
clear();
|
||||
assume(rpn.empty());
|
||||
// First, check if the expressions are known
|
||||
if (src1.isKnown() && src2.isKnown()) {
|
||||
// If both expressions are known, just compute the value
|
||||
@@ -397,37 +343,30 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (rval < 0) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
data = op_shift_left(lval, rval);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
if (lval < 0) {
|
||||
warning(WARNING_SHIFT, "Shifting right negative value %" PRId32, lval);
|
||||
}
|
||||
|
||||
if (rval < 0) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
data = op_shift_right(lval, rval);
|
||||
break;
|
||||
case RPN_USHR:
|
||||
if (rval < 0) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
if (rval >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %" PRId32, rval);
|
||||
}
|
||||
|
||||
data = op_shift_right_unsigned(lval, rval);
|
||||
break;
|
||||
case RPN_MUL:
|
||||
@@ -437,7 +376,6 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (rval == 0) {
|
||||
fatal("Division by zero");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
warning(
|
||||
WARNING_DIV,
|
||||
@@ -454,7 +392,6 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (rval == 0) {
|
||||
fatal("Modulo by zero");
|
||||
}
|
||||
|
||||
if (lval == INT32_MIN && rval == -1) {
|
||||
data = 0;
|
||||
} else {
|
||||
@@ -465,7 +402,6 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
if (rval < 0) {
|
||||
fatal("Exponentiation by negative power");
|
||||
}
|
||||
|
||||
data = op_exponent(lval, rval);
|
||||
break;
|
||||
// LCOV_EXCL_START
|
||||
@@ -488,88 +424,56 @@ void Expression::makeBinaryOp(RPNCommand op, Expression &&src1, Expression const
|
||||
// Convert the left-hand expression if it's constant
|
||||
if (src1.isKnown()) {
|
||||
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
|
||||
data = std::move(src2.data);
|
||||
rpn.emplace_back(RPN_CONST, lval);
|
||||
} else {
|
||||
// Otherwise just reuse its RPN buffer
|
||||
rpnPatchSize = src1.rpnPatchSize;
|
||||
std::swap(rpn, src1.rpn);
|
||||
// Otherwise just reuse its RPN vector
|
||||
data = std::move(src1.data);
|
||||
std::swap(rpn, src1.rpn);
|
||||
}
|
||||
|
||||
// Now, merge the right expression into the left one
|
||||
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();
|
||||
uint8_t bytes[] = {
|
||||
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;
|
||||
rpn.emplace_back(RPN_CONST, rval);
|
||||
} else {
|
||||
// Copy the right RPN and append the operator
|
||||
uint32_t rightRpnSize = src2.rpn.size();
|
||||
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;
|
||||
// Otherwise just extend with its RPN vector
|
||||
rpn.insert(rpn.end(), RANGE(src2.rpn));
|
||||
}
|
||||
// Append the operator
|
||||
rpn.emplace_back(op);
|
||||
}
|
||||
}
|
||||
|
||||
bool Expression::makeCheckHRAM() {
|
||||
isSymbol = false;
|
||||
void Expression::addCheckHRAM() {
|
||||
if (!isKnown()) {
|
||||
*reserveSpace(1) = RPN_HRAM;
|
||||
rpn.emplace_back(RPN_HRAM);
|
||||
} else if (int32_t val = value(); val >= 0xFF00 && val <= 0xFFFF) {
|
||||
// That range is valid, but only keep the lower byte
|
||||
// That range is valid; only keep the lower byte
|
||||
data = val & 0xFF;
|
||||
} else if (val >= 0 && val <= 0xFF) {
|
||||
// That range is valid, but deprecated
|
||||
return true;
|
||||
} else {
|
||||
error("Source address $%" PRIx32 " not between $FF00 to $FFFF", val);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Expression::makeCheckRST() {
|
||||
void Expression::addCheckRST() {
|
||||
if (!isKnown()) {
|
||||
*reserveSpace(1) = RPN_RST;
|
||||
rpn.emplace_back(RPN_RST);
|
||||
} else if (int32_t val = value(); val & ~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
|
||||
|
||||
if (!isKnown()) {
|
||||
uint8_t *ptr = reserveSpace(2);
|
||||
*ptr++ = RPN_BIT_INDEX;
|
||||
*ptr = mask;
|
||||
rpn.emplace_back(RPN_BIT_INDEX, mask);
|
||||
} else if (int32_t val = value(); val & ~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`"};
|
||||
error("Invalid bit index %" PRId32 " for %s", val, instructions[mask >> 6]);
|
||||
}
|
||||
}
|
||||
@@ -591,7 +495,7 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
"%s must be %u-bit%s",
|
||||
name ? name : "Expression",
|
||||
n,
|
||||
n == 8 && !name ? "; use LOW() to force 8-bit" : ""
|
||||
n == 8 && !name ? "; use `LOW()` to force 8-bit" : ""
|
||||
);
|
||||
return false;
|
||||
}
|
||||
@@ -601,10 +505,111 @@ bool checkNBit(int32_t v, uint8_t n, char const *name) {
|
||||
"%s must be %u-bit%s",
|
||||
name ? name : "Expression",
|
||||
n,
|
||||
n == 8 && !name ? "; use LOW() to force 8-bit" : ""
|
||||
n == 8 && !name ? "; use `LOW()` to force 8-bit" : ""
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,15 +3,23 @@
|
||||
#include "asm/section.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <deque>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <stack>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp" // InsertionOrderedMap
|
||||
#include "linkdefs.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
@@ -38,8 +46,7 @@ struct SectionStackEntry {
|
||||
};
|
||||
|
||||
static Section *currentSection = nullptr;
|
||||
static std::deque<Section> sectionList;
|
||||
static std::unordered_map<std::string, size_t> sectionMap; // Indexes into `sectionList`
|
||||
static InsertionOrderedMap<Section> sections;
|
||||
|
||||
static uint32_t curOffset; // Offset into the current section (see `sect_GetSymbolOffset`)
|
||||
|
||||
@@ -57,7 +64,7 @@ static bool requireSection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
error("Cannot output data outside of a SECTION");
|
||||
error("Cannot output data outside of a `SECTION`");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -67,31 +74,33 @@ static bool requireCodeSection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
if (sectTypeHasData(currentSection->type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
error(
|
||||
"Section '%s' cannot contain code or data (not ROM0 or ROMX)", currentSection->name.c_str()
|
||||
"Section \"%s\" cannot contain code or data (not `ROM0` or `ROMX`)",
|
||||
currentSection->name.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t sect_CountSections() {
|
||||
return sectionList.size();
|
||||
return sections.size();
|
||||
}
|
||||
|
||||
void sect_ForEach(void (*callback)(Section &)) {
|
||||
for (Section § : sectionList) {
|
||||
for (Section § : sections) {
|
||||
callback(sect);
|
||||
}
|
||||
}
|
||||
|
||||
void sect_CheckSizes() {
|
||||
for (Section const § : sectionList) {
|
||||
for (Section const § : sections) {
|
||||
if (uint32_t maxSize = sectionTypeInfo[sect.type].size; sect.size > maxSize) {
|
||||
error(
|
||||
"Section '%s' grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32 ")",
|
||||
"Section \"%s\" grew too big (max size = 0x%" PRIX32 " bytes, reached 0x%" PRIX32
|
||||
")",
|
||||
sect.name.c_str(),
|
||||
maxSize,
|
||||
sect.size
|
||||
@@ -101,11 +110,10 @@ void sect_CheckSizes() {
|
||||
}
|
||||
|
||||
Section *sect_FindSectionByName(std::string const &name) {
|
||||
auto search = sectionMap.find(name);
|
||||
return search != sectionMap.end() ? §ionList[search->second] : nullptr;
|
||||
auto index = sections.findIndex(name);
|
||||
return index ? §ions[*index] : nullptr;
|
||||
}
|
||||
|
||||
#define mask(align) ((1U << (align)) - 1)
|
||||
#define sectError(...) \
|
||||
do { \
|
||||
error(__VA_ARGS__); \
|
||||
@@ -115,13 +123,20 @@ Section *sect_FindSectionByName(std::string const &name) {
|
||||
static unsigned int mergeSectUnion(
|
||||
Section §, SectionType type, uint32_t org, uint8_t alignment, uint16_t alignOffset
|
||||
) {
|
||||
assume(alignment < 16); // Should be ensured by the caller
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
assume(alignment < 16); // Should be ensured by the caller
|
||||
uint32_t alignSize = 1u << alignment;
|
||||
uint32_t alignMask = alignSize - 1;
|
||||
|
||||
assume(sect.align <= 16); // Left-shifting by 32 or more would be UB
|
||||
uint32_t sectAlignSize = 1u << sect.align;
|
||||
uint32_t sectAlignMask = sectAlignSize - 1;
|
||||
|
||||
// Unionized sections only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
if (sect_HasData(type)) {
|
||||
sectError("Cannot declare ROM sections as UNION");
|
||||
if (sectTypeHasData(type)) {
|
||||
sectError("Cannot declare ROM sections as `UNION`");
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
@@ -130,10 +145,10 @@ static unsigned int mergeSectUnion(
|
||||
sectError(
|
||||
"Section already declared as fixed at different address $%04" PRIx32, sect.org
|
||||
);
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (org - sect.alignOfs))) {
|
||||
} else if (sect.align != 0 && ((org - sect.alignOfs) & sectAlignMask)) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
"Section already declared as aligned to %" PRIu32 " bytes (offset %" PRIu16 ")",
|
||||
sectAlignSize,
|
||||
sect.alignOfs
|
||||
);
|
||||
} else {
|
||||
@@ -144,17 +159,18 @@ static unsigned int mergeSectUnion(
|
||||
} else if (alignment != 0) {
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - alignOffset) & mask(alignment)) {
|
||||
if ((sect.org - alignOffset) & alignMask) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((alignOffset & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
} else if ((alignOffset & sectAlignMask) != (sect.alignOfs & alignMask)) {
|
||||
sectError(
|
||||
"Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
"Section already declared with incompatible %" PRIu32
|
||||
"-byte alignment (offset %" PRIu16 ")",
|
||||
sectAlignSize,
|
||||
sect.alignOfs
|
||||
);
|
||||
} else if (alignment > sect.align) {
|
||||
@@ -169,9 +185,16 @@ static unsigned int mergeSectUnion(
|
||||
|
||||
static unsigned int
|
||||
mergeFragments(Section §, uint32_t org, uint8_t alignment, uint16_t alignOffset) {
|
||||
assume(alignment < 16); // Should be ensured by the caller
|
||||
unsigned int nbSectErrors = 0;
|
||||
|
||||
assume(alignment < 16); // Should be ensured by the caller
|
||||
uint32_t alignSize = 1u << alignment;
|
||||
uint32_t alignMask = alignSize - 1;
|
||||
|
||||
assume(sect.align <= 16); // Left-shifting by 32 or more would be UB
|
||||
uint32_t sectAlignSize = 1u << sect.align;
|
||||
uint32_t sectAlignMask = sectAlignSize - 1;
|
||||
|
||||
// Fragments only need "compatible" constraints, and they end up with the strictest
|
||||
// combination of both.
|
||||
// The merging is however performed at the *end* of the original section!
|
||||
@@ -183,10 +206,10 @@ static unsigned int
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32, sect.org
|
||||
);
|
||||
} else if (sect.align != 0 && (mask(sect.align) & (curOrg - sect.alignOfs))) {
|
||||
} else if (sect.align != 0 && ((curOrg - sect.alignOfs) & sectAlignMask)) {
|
||||
sectError(
|
||||
"Section already declared as aligned to %u bytes (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
"Section already declared as aligned to %" PRIu32 " bytes (offset %" PRIu16 ")",
|
||||
sectAlignSize,
|
||||
sect.alignOfs
|
||||
);
|
||||
} else {
|
||||
@@ -195,25 +218,20 @@ static unsigned int
|
||||
}
|
||||
|
||||
} else if (alignment != 0) {
|
||||
int32_t curOfs = (alignOffset - sect.size) % (1U << alignment);
|
||||
|
||||
if (curOfs < 0) {
|
||||
curOfs += 1U << alignment;
|
||||
}
|
||||
|
||||
// Make sure any fixed address given is compatible
|
||||
if (sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & mask(alignment)) {
|
||||
if (uint32_t curOfs = (alignOffset - sect.size) & alignMask; sect.org != UINT32_MAX) {
|
||||
if ((sect.org - curOfs) & alignMask) {
|
||||
sectError(
|
||||
"Section already declared as fixed at incompatible address $%04" PRIx32,
|
||||
sect.org
|
||||
);
|
||||
}
|
||||
// Check if alignment offsets are compatible
|
||||
} else if ((curOfs & mask(sect.align)) != (sect.alignOfs & mask(alignment))) {
|
||||
} else if ((curOfs & sectAlignMask) != (sect.alignOfs & alignMask)) {
|
||||
sectError(
|
||||
"Section already declared with incompatible %u-byte alignment (offset %" PRIu16 ")",
|
||||
1U << sect.align,
|
||||
"Section already declared with incompatible %" PRIu32
|
||||
"-byte alignment (offset %" PRIu16 ")",
|
||||
sectAlignSize,
|
||||
sect.alignOfs
|
||||
);
|
||||
} else if (alignment > sect.align) {
|
||||
@@ -239,12 +257,12 @@ static void mergeSections(
|
||||
|
||||
if (type != sect.type) {
|
||||
sectError(
|
||||
"Section already exists but with type %s", sectionTypeInfo[sect.type].name.c_str()
|
||||
"Section already exists but with type `%s`", sectionTypeInfo[sect.type].name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
if (sect.modifier != mod) {
|
||||
sectError("Section already declared as SECTION %s", sectionModNames[sect.modifier]);
|
||||
sectError("Section already declared as `SECTION %s`", sectionModNames[sect.modifier]);
|
||||
} else {
|
||||
switch (mod) {
|
||||
case SECTION_UNION:
|
||||
@@ -266,9 +284,12 @@ static void mergeSections(
|
||||
break;
|
||||
|
||||
case SECTION_NORMAL:
|
||||
sectError([&]() {
|
||||
fputs("Section already defined previously at ", stderr);
|
||||
sect.src->dump(sect.fileLine);
|
||||
errorNoTrace([&]() {
|
||||
fputs("Section already defined\n", stderr);
|
||||
fstk_TraceCurrent();
|
||||
fputs(" and also:\n", stderr);
|
||||
sect.src->printBacktrace(sect.fileLine);
|
||||
++nbSectErrors;
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -296,8 +317,7 @@ static Section *createSection(
|
||||
SectionModifier mod
|
||||
) {
|
||||
// Add the new section to the list
|
||||
Section § = sectionList.emplace_back();
|
||||
sectionMap.emplace(name, sectionMap.size());
|
||||
Section § = sections.add(name);
|
||||
|
||||
sect.name = name;
|
||||
sect.type = type;
|
||||
@@ -313,7 +333,7 @@ static Section *createSection(
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// It is only needed to allocate memory for ROM sections.
|
||||
if (sect_HasData(type)) {
|
||||
if (sectTypeHasData(type)) {
|
||||
sect.data.resize(sectionTypeInfo[type].size);
|
||||
}
|
||||
|
||||
@@ -321,9 +341,8 @@ static Section *createSection(
|
||||
}
|
||||
|
||||
static Section *createSectionFragmentLiteral(Section const &parent) {
|
||||
// Add the new section to the list, but do not update the map
|
||||
Section § = sectionList.emplace_back();
|
||||
assume(sectionMap.find(parent.name) != sectionMap.end());
|
||||
assume(sections.contains(parent.name));
|
||||
Section § = sections.addAnonymous();
|
||||
|
||||
sect.name = parent.name;
|
||||
sect.type = parent.type;
|
||||
@@ -339,7 +358,7 @@ static Section *createSectionFragmentLiteral(Section const &parent) {
|
||||
out_RegisterNode(sect.src);
|
||||
|
||||
// Section fragment literals must be ROM sections.
|
||||
assume(sect_HasData(sect.type));
|
||||
assume(sectTypeHasData(sect.type));
|
||||
sect.data.resize(sectionTypeInfo[sect.type].size);
|
||||
|
||||
return §
|
||||
@@ -356,12 +375,16 @@ static Section *getSection(
|
||||
uint8_t alignment = attrs.alignment;
|
||||
uint16_t alignOffset = attrs.alignOfs;
|
||||
|
||||
assume(alignment <= 16); // Should be ensured by the caller
|
||||
uint32_t alignSize = 1u << alignment;
|
||||
uint32_t alignMask = alignSize - 1;
|
||||
|
||||
// First, validate parameters, and normalize them if applicable
|
||||
|
||||
if (bank != UINT32_MAX) {
|
||||
if (type != SECTTYPE_ROMX && type != SECTTYPE_VRAM && type != SECTTYPE_SRAM
|
||||
&& type != SECTTYPE_WRAMX) {
|
||||
error("BANK only allowed for ROMX, WRAMX, SRAM, or VRAM sections");
|
||||
error("`BANK` only allowed for `ROMX`, `WRAMX`, `SRAM`, or `VRAM` sections");
|
||||
} else if (bank < sectionTypeInfo[type].firstBank
|
||||
|| bank > sectionTypeInfo[type].lastBank) {
|
||||
error(
|
||||
@@ -372,47 +395,44 @@ static Section *getSection(
|
||||
sectionTypeInfo[type].lastBank
|
||||
);
|
||||
}
|
||||
} else if (nbbanks(type) == 1) {
|
||||
} else if (sectTypeBanks(type) == 1) {
|
||||
// If the section type only has a single bank, implicitly force it
|
||||
bank = sectionTypeInfo[type].firstBank;
|
||||
}
|
||||
|
||||
if (alignOffset >= 1 << alignment) {
|
||||
// This should be redundant, as the parser guarantees that `AlignmentSpec` will be valid.
|
||||
if (alignOffset >= alignSize) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%u)",
|
||||
"Alignment offset (%" PRIu16 ") must be smaller than alignment size (%" PRIu32 ")",
|
||||
alignOffset,
|
||||
1U << alignment
|
||||
alignSize
|
||||
);
|
||||
alignOffset = 0;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > endaddr(type)) {
|
||||
if (org < sectionTypeInfo[type].startAddr || org > sectTypeEndAddr(type)) {
|
||||
error(
|
||||
"Section \"%s\"'s fixed address $%04" PRIx32 " is outside of range [$%04" PRIx16
|
||||
"; $%04" PRIx16 "]",
|
||||
name.c_str(),
|
||||
org,
|
||||
sectionTypeInfo[type].startAddr,
|
||||
endaddr(type)
|
||||
sectTypeEndAddr(type)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (alignment != 0) {
|
||||
if (alignment > 16) {
|
||||
error("Alignment must be between 0 and 16, not %u", alignment);
|
||||
alignment = 16;
|
||||
}
|
||||
// It doesn't make sense to have both alignment and org set
|
||||
uint32_t mask = mask(alignment);
|
||||
|
||||
if (org != UINT32_MAX) {
|
||||
if ((org - alignOffset) & mask) {
|
||||
error("Section \"%s\"'s fixed address doesn't match its alignment", name.c_str());
|
||||
if ((org - alignOffset) & alignMask) {
|
||||
error("Section \"%s\"'s fixed address does not match its alignment", name.c_str());
|
||||
}
|
||||
alignment = 0; // Ignore it if it's satisfied
|
||||
} else if (sectionTypeInfo[type].startAddr & mask) {
|
||||
} else if (sectionTypeInfo[type].startAddr & alignMask) {
|
||||
error(
|
||||
"Section \"%s\"'s alignment cannot be attained in %s",
|
||||
name.c_str(),
|
||||
@@ -443,7 +463,7 @@ static Section *getSection(
|
||||
|
||||
static void changeSection() {
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatal("Cannot change the section within a UNION");
|
||||
fatal("Cannot change the section within a `UNION`");
|
||||
}
|
||||
|
||||
sym_ResetCurrentLabelScopes();
|
||||
@@ -452,9 +472,9 @@ static void changeSection() {
|
||||
uint32_t Section::getID() const {
|
||||
// Section fragments share the same name but have different IDs, so search by identity
|
||||
if (auto search =
|
||||
std::find_if(RANGE(sectionList), [this](Section const &s) { return &s == this; });
|
||||
search != sectionList.end()) {
|
||||
return static_cast<uint32_t>(std::distance(sectionList.begin(), search));
|
||||
std::find_if(RANGE(sections), [this](Section const &s) { return &s == this; });
|
||||
search != sections.end()) {
|
||||
return static_cast<uint32_t>(std::distance(sections.begin(), search));
|
||||
}
|
||||
return UINT32_MAX; // LCOV_EXCL_LINE
|
||||
}
|
||||
@@ -489,7 +509,7 @@ void sect_NewSection(
|
||||
) {
|
||||
for (SectionStackEntry &entry : sectionStack) {
|
||||
if (entry.section && entry.section->name == name) {
|
||||
fatal("Section '%s' is already on the stack", name.c_str());
|
||||
fatal("Section \"%s\" is already on the stack", name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +541,7 @@ void sect_SetLoadSection(
|
||||
return;
|
||||
}
|
||||
|
||||
if (sect_HasData(type)) {
|
||||
if (sectTypeHasData(type)) {
|
||||
error("`LOAD` blocks cannot create a ROM section");
|
||||
return;
|
||||
}
|
||||
@@ -598,10 +618,10 @@ uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) {
|
||||
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;
|
||||
return static_cast<uint16_t>(offset - curOffset - pcValue)
|
||||
% (1u << std::min(alignment, curAlignment));
|
||||
uint32_t minAlignMask = (1u << std::min(alignment, curAlignment)) - 1;
|
||||
return static_cast<uint16_t>(offset - curOffset - pcValue) & minAlignMask;
|
||||
}
|
||||
|
||||
void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
@@ -609,11 +629,17 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
return;
|
||||
}
|
||||
|
||||
assume(alignment <= 16); // Should be ensured by the caller
|
||||
uint32_t alignSize = 1u << alignment;
|
||||
uint32_t alignMask = alignSize - 1;
|
||||
|
||||
Section *sect = sect_GetSymbolSection();
|
||||
uint32_t alignSize = 1 << alignment; // Size of an aligned "block"
|
||||
assume(sect->align <= 16); // Left-shifting by 32 or more would be UB
|
||||
uint32_t sectAlignSize = 1u << sect->align;
|
||||
uint32_t sectAlignMask = sectAlignSize - 1;
|
||||
|
||||
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(
|
||||
"Section is misaligned (at PC = $%04" PRIx32 ", expected ALIGN[%" PRIu32
|
||||
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||
@@ -624,33 +650,26 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) {
|
||||
actualOffset
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (uint32_t actualOffset = (sect->alignOfs + curOffset) % alignSize,
|
||||
sectAlignSize = 1 << sect->align;
|
||||
sect->align != 0 && actualOffset % sectAlignSize != offset % sectAlignSize) {
|
||||
} else if (uint32_t actualOffset = (sect->alignOfs + curOffset) & alignMask;
|
||||
sect->align != 0 && (actualOffset & sectAlignMask) != (offset & sectAlignMask)) {
|
||||
error(
|
||||
"Section is misaligned ($%04" PRIx32
|
||||
" bytes into the section, expected ALIGN[%" PRIu32 ", %" PRIu32
|
||||
"], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||
"Section is misaligned ($%04" PRIx32 " bytes into the section, expected ALIGN[%" PRIu32
|
||||
", %" PRIu32 "], got ALIGN[%" PRIu32 ", %" PRIu32 "])",
|
||||
curOffset,
|
||||
alignment,
|
||||
offset,
|
||||
alignment,
|
||||
actualOffset
|
||||
);
|
||||
} else if (alignment >= 16) {
|
||||
} else if (alignment == 16) {
|
||||
// Treat an alignment large enough as fixing the address.
|
||||
// Note that this also ensures that a section's alignment never becomes 16 or greater.
|
||||
if (alignment > 16) {
|
||||
error("Alignment must be between 0 and 16, not %u", alignment);
|
||||
}
|
||||
sect->align = 0; // Reset the alignment, since we're fixing the address.
|
||||
sect->org = offset - curOffset;
|
||||
} else if (alignment > sect->align) {
|
||||
sect->align = alignment;
|
||||
// We need `(sect->alignOfs + curOffset) % alignSize == offset`
|
||||
sect->alignOfs = (offset - curOffset) % alignSize;
|
||||
}
|
||||
// We need `(sect->alignOfs + curOffset) & alignMask == offset`
|
||||
sect->alignOfs = (offset - curOffset) & alignMask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,11 +716,11 @@ void sect_StartUnion() {
|
||||
// your own peril! ^^
|
||||
|
||||
if (!currentSection) {
|
||||
error("UNIONs must be inside a SECTION");
|
||||
error("`UNION`s must be inside a `SECTION`");
|
||||
return;
|
||||
}
|
||||
if (sect_HasData(currentSection->type)) {
|
||||
error("Cannot use UNION inside of ROM0 or ROMX sections");
|
||||
if (sectTypeHasData(currentSection->type)) {
|
||||
error("Cannot use `UNION` inside of `ROM0` or `ROMX` sections");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -720,7 +739,7 @@ static void endUnionMember() {
|
||||
|
||||
void sect_NextUnionMember() {
|
||||
if (currentUnionStack.empty()) {
|
||||
error("Found NEXTU outside of a UNION construct");
|
||||
error("Found `NEXTU` outside of a `UNION` construct");
|
||||
return;
|
||||
}
|
||||
endUnionMember();
|
||||
@@ -728,7 +747,7 @@ void sect_NextUnionMember() {
|
||||
|
||||
void sect_EndUnion() {
|
||||
if (currentUnionStack.empty()) {
|
||||
error("Found ENDU outside of a UNION construct");
|
||||
error("Found `ENDU` outside of a `UNION` construct");
|
||||
return;
|
||||
}
|
||||
endUnionMember();
|
||||
@@ -738,7 +757,7 @@ void sect_EndUnion() {
|
||||
|
||||
void sect_CheckUnionClosed() {
|
||||
if (!currentUnionStack.empty()) {
|
||||
error("Unterminated UNION construct");
|
||||
error("Unterminated `UNION` construct");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,13 +816,13 @@ void sect_Skip(uint32_t skip, bool ds) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
if (!sectTypeHasData(currentSection->type)) {
|
||||
growSection(skip);
|
||||
} else {
|
||||
if (!ds) {
|
||||
warning(
|
||||
WARNING_EMPTY_DATA_DIRECTIVE,
|
||||
"%s directive without data in ROM",
|
||||
"`%s` directive without data in ROM",
|
||||
(skip == 4) ? "DL"
|
||||
: (skip == 2) ? "DW"
|
||||
: "DB"
|
||||
@@ -892,8 +911,8 @@ void sect_PCRelByte(Expression const &expr, uint32_t pcShift) {
|
||||
|
||||
if (offset < -128 || offset > 127) {
|
||||
error(
|
||||
"JR target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use JP instead",
|
||||
"`JR` target must be between -128 and 127 bytes away, not %" PRId16
|
||||
"; use `JP` instead",
|
||||
offset
|
||||
);
|
||||
writeByte(0);
|
||||
@@ -919,22 +938,28 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
|
||||
|
||||
if (fseek(file, 0, SEEK_END) == 0) {
|
||||
if (startPos > ftell(file)) {
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
error("Specified start position is greater than length of file \"%s\"", name.c_str());
|
||||
return false;
|
||||
}
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
// LCOV_EXCL_START
|
||||
if (errno != ESPIPE) {
|
||||
error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
error(
|
||||
"Error determining size of `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
error(
|
||||
"Specified start position is greater than length of file \"%s\"", name.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
for (int byte; (byte = fgetc(file)) != EOF;) {
|
||||
@@ -942,7 +967,9 @@ bool sect_BinaryFile(std::string const &name, uint32_t startPos) {
|
||||
}
|
||||
|
||||
if (ferror(file)) {
|
||||
error("Error reading INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
// LCOV_EXCL_START
|
||||
error("Error reading `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno));
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -966,11 +993,11 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
|
||||
|
||||
if (fseek(file, 0, SEEK_END) == 0) {
|
||||
if (long fsize = ftell(file); startPos > fsize) {
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
error("Specified start position is greater than length of file \"%s\"", name.c_str());
|
||||
return false;
|
||||
} else if (startPos + length > fsize) {
|
||||
error(
|
||||
"Specified range in INCBIN file '%s' is out of bounds (%" PRIu32 " + %" PRIu32
|
||||
"Specified range in `INCBIN` file \"%s\" is out of bounds (%" PRIu32 " + %" PRIu32
|
||||
" > %ld)",
|
||||
name.c_str(),
|
||||
startPos,
|
||||
@@ -982,29 +1009,37 @@ bool sect_BinaryFileSlice(std::string const &name, uint32_t startPos, uint32_t l
|
||||
// The file is seekable; skip to the specified start position
|
||||
fseek(file, startPos, SEEK_SET);
|
||||
} else {
|
||||
// LCOV_EXCL_START
|
||||
if (errno != ESPIPE) {
|
||||
error("Error determining size of INCBIN file '%s': %s", name.c_str(), strerror(errno));
|
||||
error(
|
||||
"Error determining size of `INCBIN` file \"%s\": %s", name.c_str(), strerror(errno)
|
||||
);
|
||||
}
|
||||
// The file isn't seekable, so we'll just skip bytes one at a time
|
||||
while (startPos--) {
|
||||
if (fgetc(file) == EOF) {
|
||||
error("Specified start position is greater than length of file '%s'", name.c_str());
|
||||
error(
|
||||
"Specified start position is greater than length of file \"%s\"", name.c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
while (length--) {
|
||||
if (int byte = fgetc(file); byte != EOF) {
|
||||
writeByte(byte);
|
||||
// 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));
|
||||
} else {
|
||||
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(),
|
||||
length + 1
|
||||
);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -1056,11 +1091,11 @@ void sect_CheckStack() {
|
||||
|
||||
void sect_EndSection() {
|
||||
if (!currentSection) {
|
||||
fatal("Cannot end the section outside of a SECTION");
|
||||
fatal("Cannot end the section outside of a `SECTION`");
|
||||
}
|
||||
|
||||
if (!currentUnionStack.empty()) {
|
||||
fatal("Cannot end the section within a UNION");
|
||||
fatal("Cannot end the section within a `UNION`");
|
||||
}
|
||||
|
||||
if (currentLoadSection) {
|
||||
@@ -1077,11 +1112,11 @@ std::string sect_PushSectionFragmentLiteral() {
|
||||
|
||||
// Like `requireCodeSection` but fatal
|
||||
if (!currentSection) {
|
||||
fatal("Cannot output fragment literals outside of a SECTION");
|
||||
fatal("Cannot output fragment literals outside of a `SECTION`");
|
||||
}
|
||||
if (!sect_HasData(currentSection->type)) {
|
||||
if (!sectTypeHasData(currentSection->type)) {
|
||||
fatal(
|
||||
"Section '%s' cannot contain fragment literals (not ROM0 or ROMX)",
|
||||
"Section \"%s\" cannot contain fragment literals (not `ROM0` or `ROMX`)",
|
||||
currentSection->name.c_str()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,16 @@
|
||||
#include <algorithm>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp" // assume
|
||||
@@ -17,7 +24,9 @@
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/macro.hpp"
|
||||
#include "asm/main.hpp"
|
||||
#include "asm/output.hpp"
|
||||
#include "asm/section.hpp"
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
using namespace std::literals;
|
||||
@@ -30,6 +39,7 @@ static Symbol const *localScope = nullptr; // Current section's local label sco
|
||||
|
||||
static Symbol *PCSymbol;
|
||||
static Symbol *NARGSymbol;
|
||||
static Symbol *SCOPESymbol;
|
||||
static Symbol *globalScopeSymbol;
|
||||
static Symbol *localScopeSymbol;
|
||||
static Symbol *RSSymbol;
|
||||
@@ -39,8 +49,6 @@ static char savedDATE[256];
|
||||
static char savedTIMESTAMP_ISO8601_LOCAL[256];
|
||||
static char savedTIMESTAMP_ISO8601_UTC[256];
|
||||
|
||||
static bool exportAll = false; // -E
|
||||
|
||||
bool sym_IsPC(Symbol const *sym) {
|
||||
return sym == PCSymbol;
|
||||
}
|
||||
@@ -55,14 +63,27 @@ static int32_t NARGCallback() {
|
||||
if (MacroArgs const *macroArgs = fstk_GetCurrentMacroArgs(); macroArgs) {
|
||||
return macroArgs->nbArgs();
|
||||
} else {
|
||||
error("_NARG has no value outside of a macro");
|
||||
error("`_NARG` has no value outside of a macro");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
if (!globalScope) {
|
||||
error("\".\" has no value outside of a label scope");
|
||||
error("`.` has no value outside of a label scope");
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
return std::make_shared<std::string>(globalScope->name);
|
||||
@@ -70,7 +91,7 @@ static std::shared_ptr<std::string> globalScopeCallback() {
|
||||
|
||||
static std::shared_ptr<std::string> localScopeCallback() {
|
||||
if (!localScope) {
|
||||
error("\"..\" has no value outside of a local label scope");
|
||||
error("`..` has no value outside of a local label scope");
|
||||
return std::make_shared<std::string>("");
|
||||
}
|
||||
return std::make_shared<std::string>(localScope->name);
|
||||
@@ -114,14 +135,15 @@ std::shared_ptr<std::string> Symbol::getEqus() const {
|
||||
return std::get<std::shared_ptr<std::string>>(data);
|
||||
}
|
||||
|
||||
static void dumpFilename(Symbol const &sym) {
|
||||
fputs(" at ", stderr);
|
||||
// Meant to be called last in an `errorNoTrace` callback
|
||||
static void printBacktraces(Symbol const &sym) {
|
||||
putc('\n', stderr);
|
||||
fstk_TraceCurrent();
|
||||
fputs(" and also:\n", stderr);
|
||||
if (sym.src) {
|
||||
sym.src->dump(sym.fileLine);
|
||||
} else if (sym.isBuiltin) {
|
||||
fputs("<builtin>", stderr);
|
||||
sym.src->printBacktrace(sym.fileLine);
|
||||
} else {
|
||||
fputs("<command-line>", stderr);
|
||||
fprintf(stderr, " at <%s>\n", sym.isBuiltin ? "builtin" : "command-line");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,37 +164,52 @@ static bool isValidIdentifier(std::string const &s) {
|
||||
}
|
||||
|
||||
static void alreadyDefinedError(Symbol const &sym, char const *asType) {
|
||||
if (sym.isBuiltin && !sym_FindScopedValidSymbol(sym.name)) {
|
||||
// `DEF()` would return false, so we should not claim the symbol is already defined
|
||||
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||
auto suggestion = [&]() {
|
||||
std::string s;
|
||||
if (auto const &contents = sym.type == SYM_EQUS ? sym.getEqus() : nullptr;
|
||||
contents && isValidIdentifier(*contents)) {
|
||||
s.append(" (should it be {interpolated} to define its contents \"");
|
||||
s.append(*contents);
|
||||
s.append("\"?)");
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
if (sym.isBuiltin) {
|
||||
if (sym_FindScopedValidSymbol(sym.name)) {
|
||||
if (std::string s = suggestion(); asType) {
|
||||
error("`%s` already defined as built-in %s%s", sym.name.c_str(), asType, s.c_str());
|
||||
} else {
|
||||
error([&]() {
|
||||
fprintf(stderr, "'%s' already defined", sym.name.c_str());
|
||||
error("`%s` already defined as built-in%s", sym.name.c_str(), s.c_str());
|
||||
}
|
||||
} else {
|
||||
// `DEF()` would return false, so we should not claim the symbol is already defined,
|
||||
// nor suggest to interpolate it
|
||||
if (asType) {
|
||||
error("`%s` is reserved for a built-in %s symbol", sym.name.c_str(), asType);
|
||||
} else {
|
||||
error("`%s` is reserved for a built-in symbol", sym.name.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorNoTrace([&]() {
|
||||
fprintf(stderr, "`%s` already defined", sym.name.c_str());
|
||||
if (asType) {
|
||||
fprintf(stderr, " as %s", asType);
|
||||
}
|
||||
dumpFilename(sym);
|
||||
if (sym.type != SYM_EQUS) {
|
||||
return;
|
||||
}
|
||||
if (std::string const &contents = *sym.getEqus(); isValidIdentifier(contents)) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"\n (should it be {interpolated} to define its contents \"%s\"?)",
|
||||
contents.c_str()
|
||||
);
|
||||
}
|
||||
fputs(suggestion().c_str(), stderr);
|
||||
printBacktraces(sym);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void redefinedError(Symbol const &sym) {
|
||||
assume(sym.isBuiltin);
|
||||
if (!sym_FindScopedValidSymbol(sym.name)) {
|
||||
// `DEF()` would return false, so we should not imply the symbol is already defined
|
||||
error("'%s' is reserved for a built-in symbol", sym.name.c_str());
|
||||
if (sym_FindScopedValidSymbol(sym.name)) {
|
||||
error("Built-in symbol `%s` cannot be redefined", sym.name.c_str());
|
||||
} else {
|
||||
error("Built-in symbol '%s' cannot be redefined", sym.name.c_str());
|
||||
// `DEF()` would return false, so we should not imply the symbol is already defined
|
||||
error("`%s` is reserved for a built-in symbol", sym.name.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,8 +227,9 @@ static Symbol &createSymbol(std::string const &symName) {
|
||||
Symbol &sym = symbols[symName];
|
||||
|
||||
sym.name = symName;
|
||||
sym.isExported = false;
|
||||
sym.isBuiltin = false;
|
||||
sym.isExported = false;
|
||||
sym.isQuiet = false;
|
||||
sym.section = nullptr;
|
||||
sym.src = fstk_GetFileStack();
|
||||
sym.fileLine = sym.src ? lexer_GetLineNo() : 0;
|
||||
@@ -221,12 +259,12 @@ static bool isAutoScoped(std::string const &symName) {
|
||||
|
||||
// Check for nothing after the dot
|
||||
if (dotPos == symName.length() - 1) {
|
||||
fatal("'%s' is a nonsensical reference to an empty local label", symName.c_str());
|
||||
fatal("`%s` is a nonsensical reference to an empty local label", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for more than one dot
|
||||
if (symName.find('.', dotPos + 1) != std::string::npos) {
|
||||
fatal("'%s' is a nonsensical reference to a nested local label", symName.c_str());
|
||||
fatal("`%s` is a nonsensical reference to a nested local label", symName.c_str());
|
||||
}
|
||||
|
||||
// Check for already-qualified local label
|
||||
@@ -236,7 +274,7 @@ static bool isAutoScoped(std::string const &symName) {
|
||||
|
||||
// Check for unqualifiable local label
|
||||
if (!globalScope) {
|
||||
fatal("Unqualified local label '%s' in main scope", symName.c_str());
|
||||
fatal("Unqualified local label `%s` in main scope", symName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -272,6 +310,10 @@ Symbol *sym_FindScopedValidSymbol(std::string const &symName) {
|
||||
if (sym == localScopeSymbol && !localScope) {
|
||||
return nullptr;
|
||||
}
|
||||
// `__SCOPE__` has no value outside of a section
|
||||
if (sym == SCOPESymbol && !sect_GetSymbolSection()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return sym;
|
||||
}
|
||||
@@ -285,19 +327,19 @@ void sym_Purge(std::string const &symName) {
|
||||
|
||||
if (!sym) {
|
||||
if (sym_IsPurgedScoped(symName)) {
|
||||
error("'%s' was already purged", symName.c_str());
|
||||
error("Undefined symbol `%s` was already purged", symName.c_str());
|
||||
} else {
|
||||
error("'%s' not defined", symName.c_str());
|
||||
error("Undefined symbol `%s`", symName.c_str());
|
||||
}
|
||||
} else if (sym->isBuiltin) {
|
||||
error("Built-in symbol '%s' cannot be purged", symName.c_str());
|
||||
error("Built-in symbol `%s` cannot be purged", symName.c_str());
|
||||
} else if (sym->ID != UINT32_MAX) {
|
||||
error("Symbol \"%s\" is referenced and thus cannot be purged", symName.c_str());
|
||||
error("Symbol `%s` is referenced and thus cannot be purged", symName.c_str());
|
||||
} else {
|
||||
if (sym->isExported) {
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol \"%s\"", symName.c_str());
|
||||
warning(WARNING_PURGE_1, "Purging an exported symbol `%s`", symName.c_str());
|
||||
} else if (sym->isLabel()) {
|
||||
warning(WARNING_PURGE_2, "Purging a label \"%s\"", symName.c_str());
|
||||
warning(WARNING_PURGE_2, "Purging a label `%s`", symName.c_str());
|
||||
}
|
||||
// Do not keep a reference to the label after purging it
|
||||
if (sym == globalScope) {
|
||||
@@ -336,13 +378,10 @@ uint32_t Symbol::getConstantValue() const {
|
||||
}
|
||||
|
||||
if (sym_IsPC(this)) {
|
||||
if (!getSection()) {
|
||||
error("PC has no value outside of a section");
|
||||
} else {
|
||||
assume(getSection()); // There's no way to reach here from outside of a section
|
||||
error("PC does not have a constant value; the current section is not fixed");
|
||||
}
|
||||
} else {
|
||||
error("\"%s\" does not have a constant value", name.c_str());
|
||||
error("`%s` does not have a constant value", name.c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -377,9 +416,9 @@ static Symbol *createNonrelocSymbol(std::string const &symName, bool numeric) {
|
||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||
} else if (!numeric) {
|
||||
// The symbol has already been referenced, but it's not allowed
|
||||
error([&]() {
|
||||
fprintf(stderr, "'%s' already referenced", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
errorNoTrace([&]() {
|
||||
fprintf(stderr, "`%s` already referenced", symName.c_str());
|
||||
printBacktraces(*sym);
|
||||
});
|
||||
return nullptr; // Don't allow overriding the symbol, that'd be bad!
|
||||
}
|
||||
@@ -408,7 +447,7 @@ Symbol *sym_RedefEqu(std::string const &symName, int32_t value) {
|
||||
}
|
||||
|
||||
if (sym->isDefined() && sym->type != SYM_EQU) {
|
||||
alreadyDefinedError(*sym, "non-EQU");
|
||||
alreadyDefinedError(*sym, "non-`EQU`");
|
||||
return nullptr;
|
||||
} else if (sym->isBuiltin) {
|
||||
redefinedError(*sym);
|
||||
@@ -443,11 +482,11 @@ Symbol *sym_RedefString(std::string const &symName, std::shared_ptr<std::string>
|
||||
|
||||
if (sym->type != SYM_EQUS) {
|
||||
if (sym->isDefined()) {
|
||||
alreadyDefinedError(*sym, "non-EQUS");
|
||||
alreadyDefinedError(*sym, "non-`EQUS`");
|
||||
} else {
|
||||
error([&]() {
|
||||
fprintf(stderr, "'%s' already referenced", symName.c_str());
|
||||
dumpFilename(*sym);
|
||||
errorNoTrace([&]() {
|
||||
fprintf(stderr, "`%s` already referenced", symName.c_str());
|
||||
printBacktraces(*sym);
|
||||
});
|
||||
}
|
||||
return nullptr;
|
||||
@@ -497,13 +536,13 @@ static Symbol *addLabel(std::string const &symName) {
|
||||
sym->type = SYM_LABEL;
|
||||
sym->data = static_cast<int32_t>(sect_GetSymbolOffset());
|
||||
// Don't export anonymous labels
|
||||
if (exportAll && !symName.starts_with('!')) {
|
||||
if (options.exportAll && !symName.starts_with('!')) {
|
||||
sym->isExported = true;
|
||||
}
|
||||
sym->section = sect_GetSymbolSection();
|
||||
|
||||
if (sym && !sym->section) {
|
||||
error("Label \"%s\" created outside of a SECTION", symName.c_str());
|
||||
error("Label `%s` created outside of a `SECTION`", symName.c_str());
|
||||
}
|
||||
|
||||
return sym;
|
||||
@@ -573,7 +612,7 @@ std::string sym_MakeAnonLabelName(uint32_t ofs, bool neg) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Reference to anonymous label %" PRIu32 " after, when only %" PRIu32
|
||||
" may still be created",
|
||||
" can still be created",
|
||||
ofs + 1,
|
||||
UINT32_MAX - anonLabelID
|
||||
);
|
||||
@@ -590,7 +629,7 @@ void sym_Export(std::string const &symName) {
|
||||
if (symName.starts_with('!')) {
|
||||
// LCOV_EXCL_START
|
||||
// The parser does not accept anonymous labels for an `EXPORT` directive
|
||||
error("Anonymous labels cannot be exported");
|
||||
error("Cannot export anonymous label");
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
@@ -599,12 +638,16 @@ void sym_Export(std::string const &symName) {
|
||||
|
||||
// If the symbol doesn't exist, create a ref that can be purged
|
||||
if (!sym) {
|
||||
warning(WARNING_EXPORT_UNDEFINED, "Exporting an undefined symbol `%s`", symName.c_str());
|
||||
sym = sym_Ref(symName);
|
||||
}
|
||||
|
||||
sym->isExported = true;
|
||||
}
|
||||
|
||||
Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan const &span) {
|
||||
Symbol *sym_AddMacro(
|
||||
std::string const &symName, int32_t defLineNo, ContentSpan const &span, bool isQuiet
|
||||
) {
|
||||
Symbol *sym = createNonrelocSymbol(symName, false);
|
||||
|
||||
if (!sym) {
|
||||
@@ -613,6 +656,7 @@ Symbol *sym_AddMacro(std::string const &symName, int32_t defLineNo, ContentSpan
|
||||
|
||||
sym->type = SYM_MACRO;
|
||||
sym->data = span;
|
||||
sym->isQuiet = isQuiet;
|
||||
|
||||
sym->src = fstk_GetFileStack();
|
||||
// The symbol is created at the line after the `ENDM`,
|
||||
@@ -635,11 +679,6 @@ Symbol *sym_Ref(std::string const &symName) {
|
||||
return sym;
|
||||
}
|
||||
|
||||
// Set whether to export all relocatable symbols by default
|
||||
void sym_SetExportAll(bool set) {
|
||||
exportAll = set;
|
||||
}
|
||||
|
||||
// Define the built-in symbols
|
||||
void sym_Init(time_t now) {
|
||||
PCSymbol = &createSymbol("@"s);
|
||||
@@ -662,6 +701,11 @@ void sym_Init(time_t now) {
|
||||
localScopeSymbol->data = localScopeCallback;
|
||||
localScopeSymbol->isBuiltin = true;
|
||||
|
||||
SCOPESymbol = &createSymbol("__SCOPE__"s);
|
||||
SCOPESymbol->type = SYM_EQUS;
|
||||
SCOPESymbol->data = SCOPECallback;
|
||||
SCOPESymbol->isBuiltin = true;
|
||||
|
||||
RSSymbol = sym_AddVar("_RS"s, 0);
|
||||
RSSymbol->isBuiltin = true;
|
||||
|
||||
@@ -702,8 +746,22 @@ void sym_Init(time_t now) {
|
||||
time_utc
|
||||
);
|
||||
|
||||
sym_AddString("__TIME__"s, std::make_shared<std::string>(savedTIME))->isBuiltin = true;
|
||||
sym_AddString("__DATE__"s, std::make_shared<std::string>(savedDATE))->isBuiltin = true;
|
||||
Symbol *timeSymbol = &createSymbol("__TIME__"s);
|
||||
timeSymbol->type = SYM_EQUS;
|
||||
timeSymbol->data = []() {
|
||||
warning(WARNING_OBSOLETE, "`__TIME__` is deprecated; use `__ISO_8601_LOCAL__`");
|
||||
return std::make_shared<std::string>(savedTIME);
|
||||
};
|
||||
timeSymbol->isBuiltin = true;
|
||||
|
||||
Symbol *dateSymbol = &createSymbol("__DATE__"s);
|
||||
dateSymbol->type = SYM_EQUS;
|
||||
dateSymbol->data = []() {
|
||||
warning(WARNING_OBSOLETE, "`__DATE__` is deprecated; use `__ISO_8601_LOCAL__`");
|
||||
return std::make_shared<std::string>(savedDATE);
|
||||
};
|
||||
dateSymbol->isBuiltin = true;
|
||||
|
||||
sym_AddString(
|
||||
"__ISO_8601_LOCAL__"s, std::make_shared<std::string>(savedTIMESTAMP_ISO8601_LOCAL)
|
||||
)
|
||||
|
||||
@@ -2,19 +2,16 @@
|
||||
|
||||
#include "asm/warning.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "itertools.hpp"
|
||||
#include "style.hpp"
|
||||
|
||||
#include "asm/fstack.hpp"
|
||||
#include "asm/lexer.hpp"
|
||||
#include "asm/main.hpp"
|
||||
|
||||
// clang-format off: nested initializers
|
||||
@@ -33,7 +30,8 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
{"empty-data-directive", LEVEL_ALL },
|
||||
{"empty-macro-arg", LEVEL_EXTRA },
|
||||
{"empty-strrpl", LEVEL_ALL },
|
||||
{"large-constant", LEVEL_ALL },
|
||||
{"export-undefined", LEVEL_ALL },
|
||||
{"large-constant", LEVEL_DEFAULT },
|
||||
{"macro-shift", LEVEL_EXTRA },
|
||||
{"nested-comment", LEVEL_DEFAULT },
|
||||
{"obsolete", LEVEL_DEFAULT },
|
||||
@@ -54,8 +52,8 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
},
|
||||
.paramWarnings = {
|
||||
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
||||
{WARNING_PURGE_1, WARNING_PURGE_2, 2},
|
||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 1},
|
||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||
},
|
||||
.state = DiagnosticsState<WarningID>(),
|
||||
@@ -64,31 +62,41 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
// clang-format on
|
||||
|
||||
static void printDiag(
|
||||
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
|
||||
char const *fmt,
|
||||
va_list args,
|
||||
char const *type,
|
||||
StyleColor color,
|
||||
char const *flagfmt,
|
||||
char const *flag
|
||||
) {
|
||||
fputs(type, stderr);
|
||||
fputs(": ", stderr);
|
||||
if (fstk_DumpCurrent()) {
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
fputs("\n ", stderr);
|
||||
}
|
||||
style_Set(stderr, color, true);
|
||||
fprintf(stderr, "%s: ", type);
|
||||
style_Reset(stderr);
|
||||
vfprintf(stderr, fmt, args);
|
||||
if (flagfmt) {
|
||||
style_Set(stderr, color, true);
|
||||
putc(' ', stderr);
|
||||
fprintf(stderr, flagfmt, flag);
|
||||
}
|
||||
putc('\n', stderr);
|
||||
|
||||
lexer_DumpStringExpansions();
|
||||
fstk_TraceCurrent();
|
||||
}
|
||||
|
||||
static void incrementErrors() {
|
||||
// This intentionally makes 0 act as "unlimited"
|
||||
warnings.incrementErrors();
|
||||
if (warnings.nbErrors == options.maxErrors) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Assembly aborted after the maximum of %" PRIu64 " error%s! (configure with "
|
||||
"'-X/--max-errors')\n",
|
||||
"Assembly aborted after the maximum of %" PRIu64 " error%s!",
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Set(stderr, STYLE_RED, false);
|
||||
fputs(" (configure with '-X/--max-errors')\n", stderr);
|
||||
style_Reset(stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -97,20 +105,17 @@ void error(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
printDiag(fmt, args, "error", ":", nullptr);
|
||||
printDiag(fmt, args, "error", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
incrementErrors();
|
||||
}
|
||||
|
||||
void error(std::function<void()> callback) {
|
||||
void errorNoTrace(std::function<void()> callback) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
if (fstk_DumpCurrent()) {
|
||||
fputs(":\n ", stderr);
|
||||
}
|
||||
style_Reset(stderr);
|
||||
callback();
|
||||
putc('\n', stderr);
|
||||
lexer_DumpStringExpansions();
|
||||
|
||||
incrementErrors();
|
||||
}
|
||||
@@ -120,7 +125,7 @@ void fatal(char const *fmt, ...) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
printDiag(fmt, args, "FATAL", ":", nullptr);
|
||||
printDiag(fmt, args, "FATAL", STYLE_RED, nullptr, nullptr);
|
||||
va_end(args);
|
||||
|
||||
exit(1);
|
||||
@@ -128,12 +133,14 @@ void fatal(char const *fmt, ...) {
|
||||
|
||||
void requireZeroErrors() {
|
||||
if (warnings.nbErrors != 0) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Assembly aborted with %" PRIu64 " error%s!\n",
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Reset(stderr);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -149,11 +156,11 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
printDiag(fmt, args, "warning", ": [-W%s]", flag);
|
||||
printDiag(fmt, args, "warning", STYLE_YELLOW, "[-W%s]", flag);
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
printDiag(fmt, args, "error", ": [-Werror=%s]", flag);
|
||||
printDiag(fmt, args, "error", STYLE_RED, "[-Werror=%s]", flag);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
33
src/backtrace.cpp
Normal file
33
src/backtrace.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "backtrace.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "platform.hpp" // strcasecmp
|
||||
#include "util.hpp" // parseWholeNumber
|
||||
|
||||
Tracing tracing;
|
||||
|
||||
bool trace_ParseTraceDepth(char const *arg) {
|
||||
if (!strcasecmp(arg, "collapse")) {
|
||||
tracing.collapse = true;
|
||||
return true;
|
||||
} else if (!strcasecmp(arg, "no-collapse")) {
|
||||
tracing.collapse = false;
|
||||
return true;
|
||||
} else if (!strcasecmp(arg, "all")) {
|
||||
tracing.loud = true;
|
||||
return true;
|
||||
} else if (!strcasecmp(arg, "no-all")) {
|
||||
tracing.loud = false;
|
||||
return true;
|
||||
} else {
|
||||
std::optional<uint64_t> depth = parseWholeNumber(arg);
|
||||
if (depth) {
|
||||
tracing.depth = *depth;
|
||||
}
|
||||
return depth.has_value();
|
||||
}
|
||||
}
|
||||
156
src/cli.cpp
Normal file
156
src/cli.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "cli.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fstream>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "extern/getopt.hpp"
|
||||
#include "style.hpp"
|
||||
#include "usage.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, Usage usage) {
|
||||
std::vector<size_t> argvOfs;
|
||||
|
||||
std::filebuf file;
|
||||
if (!file.open(path, std::ios_base::in)) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("FATAL: ", stderr);
|
||||
style_Reset(stderr);
|
||||
fprintf(stderr, "Failed to open at-file \"%s\": %s\n", path.c_str(), strerror(errno));
|
||||
usage.printAndExit(1);
|
||||
}
|
||||
|
||||
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 *),
|
||||
Usage usage
|
||||
) {
|
||||
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)) != -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, usage);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "style.hpp"
|
||||
#include "util.hpp" // isDigit
|
||||
|
||||
void warnx(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_YELLOW, true);
|
||||
fputs("warning: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -48,25 +63,9 @@ std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::str
|
||||
return {state, std::nullopt};
|
||||
}
|
||||
|
||||
// Is the rest of the string a decimal number?
|
||||
// We want to avoid `strtoul`'s whitespace and sign, so we parse manually
|
||||
// If the rest of the string is a decimal number, it's the parameter value
|
||||
char const *ptr = flag.c_str() + equals + 1;
|
||||
uint32_t param = 0;
|
||||
bool overflowed = false;
|
||||
|
||||
for (; *ptr >= '0' && *ptr <= '9'; ++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;
|
||||
}
|
||||
uint64_t param = parseNumber(ptr, BASE_10).value_or(0);
|
||||
|
||||
// If we reached the end of the string, truncate it at the '='
|
||||
if (*ptr == '\0') {
|
||||
@@ -77,5 +76,5 @@ std::pair<WarningState, std::optional<uint32_t>> getInitialWarningState(std::str
|
||||
}
|
||||
}
|
||||
|
||||
return {state, param};
|
||||
return {state, param > UINT32_MAX ? UINT32_MAX : param};
|
||||
}
|
||||
|
||||
203
src/extern/getopt.cpp
vendored
203
src/extern/getopt.cpp
vendored
@@ -11,27 +11,24 @@
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "style.hpp"
|
||||
|
||||
char *musl_optarg;
|
||||
int musl_optind = 1, musl_opterr = 1, musl_optopt;
|
||||
int musl_optreset = 0;
|
||||
int musl_optind = 1, musl_optopt;
|
||||
|
||||
static int musl_optpos;
|
||||
|
||||
static void musl_getopt_msg(char const *a, char const *b, char const *c, size_t l) {
|
||||
FILE *f = stderr;
|
||||
|
||||
if (fputs(a, f) >= 0 && fwrite(b, strlen(b), 1, f) && fwrite(c, 1, l, f) == l) {
|
||||
putc('\n', f);
|
||||
}
|
||||
static void musl_getopt_msg(char const *msg, char const *param) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
fputs(msg, stderr);
|
||||
fputs(param, stderr);
|
||||
putc('\n', stderr);
|
||||
}
|
||||
|
||||
static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
int i;
|
||||
wchar_t c, d;
|
||||
int k, l;
|
||||
char *optchar;
|
||||
|
||||
if (!musl_optind || musl_optreset) {
|
||||
musl_optreset = 0;
|
||||
static int musl_getopt(int argc, char *argv[], char const *optstring) {
|
||||
if (!musl_optind) {
|
||||
musl_optpos = 0;
|
||||
musl_optind = 1;
|
||||
}
|
||||
@@ -40,7 +37,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][0] != '-') {
|
||||
char *argi = argv[musl_optind];
|
||||
|
||||
if (argi[0] != '-') {
|
||||
if (optstring[0] == '-') {
|
||||
musl_optarg = argv[musl_optind++];
|
||||
return 1;
|
||||
@@ -48,26 +47,28 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!argv[musl_optind][1]) {
|
||||
if (!argi[1]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (argv[musl_optind][1] == '-' && !argv[musl_optind][2]) {
|
||||
return musl_optind++, -1;
|
||||
if (argi[1] == '-' && !argi[2]) {
|
||||
++musl_optind;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!musl_optpos) {
|
||||
++musl_optpos;
|
||||
}
|
||||
k = mbtowc(&c, argv[musl_optind] + musl_optpos, MB_LEN_MAX);
|
||||
wchar_t c;
|
||||
int k = mbtowc(&c, argi + musl_optpos, MB_LEN_MAX);
|
||||
if (k < 0) {
|
||||
k = 1;
|
||||
c = 0xFFFD; // replacement char
|
||||
}
|
||||
optchar = argv[musl_optind] + musl_optpos;
|
||||
char *optchar = argi + musl_optpos;
|
||||
musl_optpos += k;
|
||||
|
||||
if (!argv[musl_optind][musl_optpos]) {
|
||||
if (!argi[musl_optpos]) {
|
||||
++musl_optind;
|
||||
musl_optpos = 0;
|
||||
}
|
||||
@@ -76,8 +77,9 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
++optstring;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
d = 0;
|
||||
int i = 0;
|
||||
wchar_t d = 0;
|
||||
int l;
|
||||
do {
|
||||
l = mbtowc(&d, optstring + i, MB_LEN_MAX);
|
||||
if (l > 0) {
|
||||
@@ -89,8 +91,8 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
|
||||
if (d != c || c == ':') {
|
||||
musl_optopt = c;
|
||||
if (optstring[0] != ':' && musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": unrecognized option: ", optchar, k);
|
||||
if (optstring[0] != ':') {
|
||||
musl_getopt_msg("unrecognized option: ", optchar);
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
@@ -105,9 +107,7 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
if (optstring[0] == ':') {
|
||||
return ':';
|
||||
}
|
||||
if (musl_opterr) {
|
||||
musl_getopt_msg(argv[0], ": option requires an argument: ", optchar, k);
|
||||
}
|
||||
musl_getopt_msg("option requires an argument: ", optchar);
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
@@ -116,73 +116,27 @@ static int getopt(int argc, char *argv[], char const *optstring) {
|
||||
|
||||
static void permute(char **argv, int dest, int src) {
|
||||
char *tmp = argv[src];
|
||||
int i;
|
||||
|
||||
for (i = src; i > dest; --i) {
|
||||
for (int i = src; i > dest; --i) {
|
||||
argv[i] = argv[i - 1];
|
||||
}
|
||||
argv[dest] = tmp;
|
||||
}
|
||||
|
||||
static int musl_getopt_long_core(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
|
||||
);
|
||||
|
||||
static int musl_getopt_long(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
|
||||
) {
|
||||
int ret, skipped, resumed;
|
||||
|
||||
if (!musl_optind || musl_optreset) {
|
||||
musl_optreset = 0;
|
||||
musl_optpos = 0;
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i;
|
||||
for (i = musl_optind;; ++i) {
|
||||
if (i >= argc || !argv[i]) {
|
||||
return -1;
|
||||
}
|
||||
if (argv[i][0] == '-' && argv[i][1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
musl_optind = i;
|
||||
}
|
||||
resumed = musl_optind;
|
||||
ret = musl_getopt_long_core(argc, argv, optstring, longopts, idx, longonly);
|
||||
if (resumed > skipped) {
|
||||
int i, cnt = musl_optind - resumed;
|
||||
|
||||
for (i = 0; i < cnt; ++i) {
|
||||
permute(argv, skipped, musl_optind - 1);
|
||||
}
|
||||
musl_optind = skipped + cnt;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int musl_getopt_long_core(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx, int longonly
|
||||
) {
|
||||
static int
|
||||
musl_getopt_long_core(int argc, char **argv, char const *optstring, option const *longopts) {
|
||||
musl_optarg = 0;
|
||||
if (longopts && argv[musl_optind][0] == '-'
|
||||
&& ((longonly && argv[musl_optind][1] && argv[musl_optind][1] != '-')
|
||||
|| (argv[musl_optind][1] == '-' && argv[musl_optind][2]))) {
|
||||
int colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
|
||||
int i, cnt, match = 0;
|
||||
if (char *argi = argv[musl_optind];
|
||||
!longopts || argi[0] != '-'
|
||||
|| ((!argi[1] || argi[1] == '-') && (argi[1] != '-' || !argi[2]))) {
|
||||
return musl_getopt(argc, argv, optstring);
|
||||
}
|
||||
|
||||
bool colon = optstring[optstring[0] == '+' || optstring[0] == '-'] == ':';
|
||||
int i = 0, cnt = 0, match = 0;
|
||||
char *arg = 0, *opt, *start = argv[musl_optind] + 1;
|
||||
|
||||
for (cnt = i = 0; longopts[i].name; ++i) {
|
||||
for (; longopts[i].name; ++i) {
|
||||
char const *name = longopts[i].name;
|
||||
|
||||
opt = start;
|
||||
if (*opt == '-') {
|
||||
++opt;
|
||||
@@ -202,12 +156,10 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
++cnt;
|
||||
}
|
||||
if (cnt == 1 && longonly && arg - start == mblen(start, MB_LEN_MAX)) {
|
||||
if (cnt == 1 && arg - start == mblen(start, MB_LEN_MAX)) {
|
||||
int l = arg - start;
|
||||
|
||||
for (i = 0; optstring[i]; ++i) {
|
||||
int j = 0;
|
||||
|
||||
while (j < l && start[j] == optstring[i + j]) {
|
||||
++j;
|
||||
}
|
||||
@@ -224,15 +176,10 @@ static int musl_getopt_long_core(
|
||||
if (*opt == '=') {
|
||||
if (!longopts[i].has_arg) {
|
||||
musl_optopt = longopts[i].val;
|
||||
if (colon || !musl_opterr) {
|
||||
if (colon) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option does not take an argument: ",
|
||||
longopts[i].name,
|
||||
strlen(longopts[i].name)
|
||||
);
|
||||
musl_getopt_msg("option does not take an argument: ", longopts[i].name);
|
||||
return '?';
|
||||
}
|
||||
musl_optarg = opt + 1;
|
||||
@@ -243,22 +190,11 @@ static int musl_getopt_long_core(
|
||||
if (colon) {
|
||||
return ':';
|
||||
}
|
||||
if (!musl_opterr) {
|
||||
return '?';
|
||||
}
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
": option requires an argument: ",
|
||||
longopts[i].name,
|
||||
strlen(longopts[i].name)
|
||||
);
|
||||
musl_getopt_msg("option requires an argument: ", longopts[i].name);
|
||||
return '?';
|
||||
}
|
||||
++musl_optind;
|
||||
}
|
||||
if (idx) {
|
||||
*idx = i;
|
||||
}
|
||||
if (longopts[i].flag) {
|
||||
*longopts[i].flag = longopts[i].val;
|
||||
return 0;
|
||||
@@ -267,23 +203,48 @@ static int musl_getopt_long_core(
|
||||
}
|
||||
if (argv[musl_optind][1] == '-') {
|
||||
musl_optopt = 0;
|
||||
if (!colon && musl_opterr) {
|
||||
if (!colon) {
|
||||
musl_getopt_msg(
|
||||
argv[0],
|
||||
cnt ? ": option is ambiguous: " : ": unrecognized option: ",
|
||||
argv[musl_optind] + 2,
|
||||
strlen(argv[musl_optind] + 2)
|
||||
cnt ? "option is ambiguous: " : "unrecognized option: ", argv[musl_optind] + 2
|
||||
);
|
||||
}
|
||||
++musl_optind;
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
return getopt(argc, argv, optstring);
|
||||
return musl_getopt(argc, argv, optstring);
|
||||
}
|
||||
|
||||
int musl_getopt_long_only(
|
||||
int argc, char **argv, char const *optstring, option const *longopts, int *idx
|
||||
) {
|
||||
return musl_getopt_long(argc, argv, optstring, longopts, idx, 1);
|
||||
int musl_getopt_long_only(int argc, char **argv, char const *optstring, option const *longopts) {
|
||||
if (!musl_optind) {
|
||||
musl_optpos = 0;
|
||||
musl_optind = 1;
|
||||
}
|
||||
|
||||
if (musl_optind >= argc || !argv[musl_optind]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int skipped = musl_optind;
|
||||
if (optstring[0] != '+' && optstring[0] != '-') {
|
||||
int i = musl_optind;
|
||||
for (;; ++i) {
|
||||
if (i >= argc || !argv[i]) {
|
||||
return -1;
|
||||
}
|
||||
if (argv[i][0] == '-' && argv[i][1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
musl_optind = i;
|
||||
}
|
||||
int resumed = musl_optind;
|
||||
int ret = musl_getopt_long_core(argc, argv, optstring, longopts);
|
||||
if (resumed > skipped) {
|
||||
int cnt = musl_optind - resumed;
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
permute(argv, skipped, musl_optind - 1);
|
||||
}
|
||||
musl_optind = skipped + cnt;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
2
src/extern/utf8decoder.cpp
vendored
2
src/extern/utf8decoder.cpp
vendored
@@ -6,6 +6,8 @@
|
||||
|
||||
#include "extern/utf8decoder.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// clang-format off: vertically align values
|
||||
static uint8_t const utf8d[] = {
|
||||
// The first part of the table maps bytes to character classes that
|
||||
|
||||
525
src/fix/fix.cpp
Normal file
525
src/fix/fix.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "fix/fix.hpp"
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
#include "helpers.hpp"
|
||||
#include "platform.hpp"
|
||||
|
||||
#include "fix/main.hpp"
|
||||
#include "fix/mbc.hpp"
|
||||
#include "fix/warning.hpp"
|
||||
|
||||
static constexpr off_t BANK_SIZE = 0x4000;
|
||||
|
||||
static ssize_t readBytes(int fd, uint8_t *buf, size_t len) {
|
||||
// POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results
|
||||
assume(len <= SSIZE_MAX);
|
||||
|
||||
ssize_t total = 0;
|
||||
|
||||
while (len) {
|
||||
ssize_t ret = read(fd, buf, len);
|
||||
|
||||
// Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) {
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
// EOF reached
|
||||
if (ret == 0) {
|
||||
return total;
|
||||
}
|
||||
// If anything was read, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
len -= ret;
|
||||
buf += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static ssize_t writeBytes(int fd, uint8_t *buf, size_t len) {
|
||||
// POSIX specifies that lengths greater than SSIZE_MAX yield implementation-defined results
|
||||
assume(len <= SSIZE_MAX);
|
||||
|
||||
ssize_t total = 0;
|
||||
|
||||
while (len) {
|
||||
ssize_t ret = write(fd, buf, len);
|
||||
|
||||
// Return errors, unless we only were interrupted
|
||||
if (ret == -1 && errno != EINTR) {
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
}
|
||||
// If anything was written, accumulate it, and continue
|
||||
if (ret != -1) {
|
||||
total += ret;
|
||||
len -= ret;
|
||||
buf += ret;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static void overwriteByte(uint8_t *rom0, uint16_t addr, uint8_t fixedByte, char const *areaName) {
|
||||
uint8_t origByte = rom0[addr];
|
||||
|
||||
if (origByte != 0 && origByte != fixedByte) {
|
||||
warning(WARNING_OVERWRITE, "Overwrote a non-zero byte in the %s", areaName);
|
||||
}
|
||||
|
||||
rom0[addr] = fixedByte;
|
||||
}
|
||||
|
||||
static void overwriteBytes(
|
||||
uint8_t *rom0, uint16_t startAddr, uint8_t const *fixed, uint8_t size, char const *areaName
|
||||
) {
|
||||
for (uint8_t i = 0; i < size; ++i) {
|
||||
uint8_t origByte = rom0[i + startAddr];
|
||||
|
||||
if (origByte != 0 && origByte != fixed[i]) {
|
||||
warning(WARNING_OVERWRITE, "Overwrote a non-zero byte in the %s", areaName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(&rom0[startAddr], fixed, size);
|
||||
}
|
||||
|
||||
static void
|
||||
processFile(int input, int output, char const *name, off_t fileSize, bool expectFileSize) {
|
||||
if (expectFileSize) {
|
||||
assume(fileSize != 0);
|
||||
} else {
|
||||
assume(fileSize == 0);
|
||||
}
|
||||
|
||||
uint8_t rom0[BANK_SIZE];
|
||||
ssize_t rom0Len = readBytes(input, rom0, sizeof(rom0));
|
||||
// Also used as how many bytes to write back when fixing in-place
|
||||
ssize_t headerSize = (options.cartridgeType & 0xFF00) == TPP1 ? 0x154 : 0x150;
|
||||
|
||||
if (rom0Len == -1) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to read \"%s\"'s header: %s", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (rom0Len < headerSize) {
|
||||
error(
|
||||
"\"%s\" too short, expected at least %jd ($%jx) bytes, got only %jd",
|
||||
name,
|
||||
static_cast<intmax_t>(headerSize),
|
||||
static_cast<intmax_t>(headerSize),
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Accept partial reads if the file contains at least the header
|
||||
|
||||
if (options.fixSpec & (FIX_LOGO | TRASH_LOGO)) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x0104,
|
||||
options.logo,
|
||||
sizeof(options.logo),
|
||||
options.logoFilename ? "logo" : "Nintendo logo"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.title) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x134,
|
||||
reinterpret_cast<uint8_t const *>(options.title->c_str()),
|
||||
options.titleLen,
|
||||
"title"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.gameID) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x13F,
|
||||
reinterpret_cast<uint8_t const *>(options.gameID->c_str()),
|
||||
options.gameIDLen,
|
||||
"manufacturer code"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.model != DMG) {
|
||||
overwriteByte(rom0, 0x143, options.model == BOTH ? 0x80 : 0xC0, "CGB flag");
|
||||
}
|
||||
|
||||
if (options.newLicensee) {
|
||||
overwriteBytes(
|
||||
rom0,
|
||||
0x144,
|
||||
reinterpret_cast<uint8_t const *>(options.newLicensee->c_str()),
|
||||
options.newLicenseeLen,
|
||||
"new licensee code"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.sgb) {
|
||||
overwriteByte(rom0, 0x146, 0x03, "SGB flag");
|
||||
}
|
||||
|
||||
// If a valid MBC was specified...
|
||||
if (options.cartridgeType < MBC_NONE) {
|
||||
uint8_t byte = options.cartridgeType;
|
||||
|
||||
if ((options.cartridgeType & 0xFF00) == TPP1) {
|
||||
// Cartridge type isn't directly actionable, translate it
|
||||
byte = 0xBC;
|
||||
// The other TPP1 identification bytes will be written below
|
||||
}
|
||||
overwriteByte(rom0, 0x147, byte, "cartridge type");
|
||||
}
|
||||
|
||||
// ROM size will be written last, after evaluating the file's size
|
||||
|
||||
if ((options.cartridgeType & 0xFF00) == TPP1) {
|
||||
uint8_t const tpp1Code[2] = {0xC1, 0x65};
|
||||
|
||||
overwriteBytes(rom0, 0x149, tpp1Code, sizeof(tpp1Code), "TPP1 identification code");
|
||||
|
||||
overwriteBytes(
|
||||
rom0, 0x150, options.tpp1Rev, sizeof(options.tpp1Rev), "TPP1 revision number"
|
||||
);
|
||||
|
||||
if (options.ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x152, options.ramSize, "RAM size");
|
||||
}
|
||||
|
||||
overwriteByte(rom0, 0x153, options.cartridgeType & 0xFF, "TPP1 feature flags");
|
||||
} else {
|
||||
// Regular mappers
|
||||
|
||||
if (options.ramSize != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x149, options.ramSize, "RAM size");
|
||||
}
|
||||
|
||||
if (!options.japanese) {
|
||||
overwriteByte(rom0, 0x14A, 0x01, "destination code");
|
||||
}
|
||||
}
|
||||
|
||||
if (options.oldLicensee != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14B, options.oldLicensee, "old licensee code");
|
||||
} else if (options.sgb && rom0[0x14B] != 0x33) {
|
||||
warning(
|
||||
WARNING_SGB,
|
||||
"SGB compatibility enabled, but old licensee was 0x%02x, not 0x33",
|
||||
rom0[0x14B]
|
||||
);
|
||||
}
|
||||
|
||||
if (options.romVersion != UNSPECIFIED) {
|
||||
overwriteByte(rom0, 0x14C, options.romVersion, "mask ROM version number");
|
||||
}
|
||||
|
||||
// Remain to be handled the ROM size, and header checksum.
|
||||
// The latter depends on the former, and so will be handled after it.
|
||||
// The former requires knowledge of the file's total size, so read that first.
|
||||
|
||||
uint16_t globalSum = 0;
|
||||
|
||||
// To keep file sizes fairly reasonable, we'll cap the amount of banks at 65536.
|
||||
// Official mappers only go up to 512 banks, but at least the TPP1 spec allows up to
|
||||
// 65536 banks = 1 GiB.
|
||||
// This should be reasonable for the time being, and may be extended later.
|
||||
std::vector<uint8_t> romx; // Buffer of ROMX bank data
|
||||
uint32_t nbBanks = 1; // Number of banks *targeted*, including ROM0
|
||||
size_t totalRomxLen = 0; // *Actual* size of ROMX data
|
||||
|
||||
// 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 (fileSize >= NB_BANKS_LIMIT * BANK_SIZE) {
|
||||
return errorTooLarge(); // LCOV_EXCL_LINE
|
||||
}
|
||||
// Compute number of banks and ROMX len from file size
|
||||
nbBanks = (fileSize + (BANK_SIZE - 1)) / BANK_SIZE; // ceil(fileSize / BANK_SIZE)
|
||||
totalRomxLen = fileSize >= BANK_SIZE ? fileSize - BANK_SIZE : 0;
|
||||
} else if (rom0Len == BANK_SIZE) {
|
||||
// Copy ROMX when reading a pipe, and we're not at EOF yet
|
||||
for (;;) {
|
||||
romx.resize(nbBanks * 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
|
||||
if (bankLen) {
|
||||
// We're going to read another bank, check that it won't be too much
|
||||
if (nbBanks == NB_BANKS_LIMIT) {
|
||||
return errorTooLarge(); // LCOV_EXCL_LINE
|
||||
}
|
||||
++nbBanks;
|
||||
// Update global checksum, too
|
||||
for (uint16_t i = 0; i < bankLen; ++i) {
|
||||
globalSum += romx[totalRomxLen + i];
|
||||
}
|
||||
totalRomxLen += bankLen;
|
||||
}
|
||||
// Stop when an incomplete bank has been read
|
||||
if (bankLen != BANK_SIZE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle setting the ROM size if padding was requested
|
||||
// Pad to the next valid power of 2. This is because padding is required by flashers, which
|
||||
// flash to ROM chips, whose size is always a power of 2... so there'd be no point in
|
||||
// padding to something else.
|
||||
// Additionally, a ROM must be at least 32k, so we guarantee a whole amount of banks...
|
||||
if (options.padValue != UNSPECIFIED) {
|
||||
// We want at least 2 banks
|
||||
if (nbBanks == 1) {
|
||||
if (rom0Len != sizeof(rom0)) {
|
||||
memset(&rom0[rom0Len], options.padValue, sizeof(rom0) - rom0Len);
|
||||
// The global checksum hasn't taken ROM0 into consideration yet!
|
||||
// ROM0 was padded, so treat it as entirely written: update its size
|
||||
// Update how many bytes were read in total, too
|
||||
rom0Len = sizeof(rom0);
|
||||
}
|
||||
nbBanks = 2;
|
||||
} else {
|
||||
assume(rom0Len == sizeof(rom0));
|
||||
}
|
||||
assume(nbBanks >= 2);
|
||||
// Alter number of banks to reflect required value
|
||||
// x&(x-1) is zero iff x is a power of 2, or 0; we know for sure it's non-zero,
|
||||
// so this is true (non-zero) when we don't have a power of 2
|
||||
if (nbBanks & (nbBanks - 1)) {
|
||||
nbBanks = 1 << (CHAR_BIT * sizeof(nbBanks) - clz(nbBanks));
|
||||
}
|
||||
// Write final ROM size
|
||||
rom0[0x148] = ctz(nbBanks / 2);
|
||||
// Alter global checksum based on how many bytes will be added (not counting ROM0)
|
||||
globalSum += options.padValue * ((nbBanks - 1) * BANK_SIZE - totalRomxLen);
|
||||
}
|
||||
|
||||
// Handle the header checksum after the ROM size has been written
|
||||
if (options.fixSpec & (FIX_HEADER_SUM | TRASH_HEADER_SUM)) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (uint16_t i = 0x134; i < 0x14D; ++i) {
|
||||
sum -= rom0[i] + 1;
|
||||
}
|
||||
|
||||
overwriteByte(
|
||||
rom0, 0x14D, options.fixSpec & TRASH_HEADER_SUM ? ~sum : sum, "header checksum"
|
||||
);
|
||||
}
|
||||
|
||||
if (options.fixSpec & (FIX_GLOBAL_SUM | TRASH_GLOBAL_SUM)) {
|
||||
// Computation of the global checksum does not include the checksum bytes
|
||||
assume(rom0Len >= 0x14E);
|
||||
for (uint16_t i = 0; i < 0x14E; ++i) {
|
||||
globalSum += rom0[i];
|
||||
}
|
||||
for (uint16_t i = 0x150; i < rom0Len; ++i) {
|
||||
globalSum += rom0[i];
|
||||
}
|
||||
// Pipes have already read ROMX and updated globalSum, but not regular files
|
||||
if (input == output) {
|
||||
for (;;) {
|
||||
uint8_t bank[BANK_SIZE];
|
||||
ssize_t bankLen = readBytes(input, bank, sizeof(bank));
|
||||
|
||||
for (uint16_t i = 0; i < bankLen; ++i) {
|
||||
globalSum += bank[i];
|
||||
}
|
||||
if (bankLen != sizeof(bank)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (options.fixSpec & TRASH_GLOBAL_SUM) {
|
||||
globalSum = ~globalSum;
|
||||
}
|
||||
|
||||
uint8_t bytes[2] = {
|
||||
static_cast<uint8_t>(globalSum >> 8), static_cast<uint8_t>(globalSum & 0xFF)
|
||||
};
|
||||
|
||||
overwriteBytes(rom0, 0x14E, bytes, sizeof(bytes), "global checksum");
|
||||
}
|
||||
|
||||
ssize_t writeLen;
|
||||
|
||||
// In case the output depends on the input, reset to the beginning of the file, and only
|
||||
// write the header
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_SET) == static_cast<off_t>(-1)) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to rewind \"%s\": %s", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
// If modifying the file in-place, we only need to edit the header
|
||||
// However, padding may have modified ROM0 (added padding), so don't in that case
|
||||
if (options.padValue == UNSPECIFIED) {
|
||||
rom0Len = headerSize;
|
||||
}
|
||||
}
|
||||
writeLen = writeBytes(output, rom0, rom0Len);
|
||||
|
||||
if (writeLen == -1) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to write \"%s\"'s ROM0: %s", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (writeLen < rom0Len) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Could only write %jd of \"%s\"'s %jd ROM0 bytes",
|
||||
static_cast<intmax_t>(writeLen),
|
||||
name,
|
||||
static_cast<intmax_t>(rom0Len)
|
||||
);
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// Output ROMX if it was buffered
|
||||
if (!romx.empty()) {
|
||||
// The value returned is either -1, or smaller than `totalRomxLen`,
|
||||
// so it's fine to cast to `size_t`
|
||||
writeLen = writeBytes(output, romx.data(), totalRomxLen);
|
||||
if (writeLen == -1) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to write \"%s\"'s ROMX: %s", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (static_cast<size_t>(writeLen) < totalRomxLen) {
|
||||
// LCOV_EXCL_START
|
||||
error(
|
||||
"Could only write %jd of \"%s\"'s %zu ROMX bytes",
|
||||
static_cast<intmax_t>(writeLen),
|
||||
name,
|
||||
totalRomxLen
|
||||
);
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Output padding
|
||||
if (options.padValue != UNSPECIFIED) {
|
||||
if (input == output) {
|
||||
if (lseek(output, 0, SEEK_END) == static_cast<off_t>(-1)) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to seek to end of \"%s\": %s", name, strerror(errno));
|
||||
return;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
uint8_t bank[BANK_SIZE];
|
||||
memset(bank, options.padValue, sizeof(bank));
|
||||
size_t len = (nbBanks - 1) * sizeof(bank) - totalRomxLen; // Don't count ROM0!
|
||||
|
||||
while (len) {
|
||||
static_assert(sizeof(bank) <= SSIZE_MAX, "Bank too large for reading");
|
||||
size_t thisLen = len > sizeof(bank) ? sizeof(bank) : len;
|
||||
ssize_t ret = writeBytes(output, bank, thisLen);
|
||||
|
||||
// The return value is either -1, or at most `thisLen`,
|
||||
// so it's fine to cast to `size_t`
|
||||
if (static_cast<size_t>(ret) != thisLen) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to write \"%s\"'s padding: %s", name, strerror(errno));
|
||||
break;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
len -= thisLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool fix_ProcessFile(char const *name, char const *outputName) {
|
||||
warnings.nbErrors = 0;
|
||||
|
||||
bool inputStdin = !strcmp(name, "-");
|
||||
if (inputStdin && !outputName) {
|
||||
outputName = "-";
|
||||
}
|
||||
|
||||
int output = -1;
|
||||
bool openedOutput = false;
|
||||
if (outputName) {
|
||||
if (!strcmp(outputName, "-")) {
|
||||
output = STDOUT_FILENO;
|
||||
(void)setmode(STDOUT_FILENO, O_BINARY);
|
||||
} else {
|
||||
output = open(outputName, O_WRONLY | O_BINARY | O_CREAT, 0600);
|
||||
if (output == -1) {
|
||||
// LCOV_EXCL_START
|
||||
error("Failed to open \"%s\" for writing: %s", outputName, strerror(errno));
|
||||
return true;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
openedOutput = true;
|
||||
}
|
||||
}
|
||||
Defer closeOutput{[&] {
|
||||
if (openedOutput) {
|
||||
close(output);
|
||||
}
|
||||
}};
|
||||
|
||||
if (inputStdin) {
|
||||
name = "<stdin>";
|
||||
(void)setmode(STDIN_FILENO, O_BINARY);
|
||||
processFile(STDIN_FILENO, output, name, 0, false);
|
||||
} else if (int input = open(name, (outputName ? O_RDONLY : O_RDWR) | O_BINARY); input == -1) {
|
||||
// POSIX specifies that the results of O_RDWR on a FIFO are undefined.
|
||||
// However, this is necessary to avoid a TOCTTOU, if the file was changed between
|
||||
// `stat()` and `open(O_RDWR)`, which could trigger the UB anyway.
|
||||
// Thus, we're going to hope that either the `open` fails, or it succeeds but I/O
|
||||
// operations may fail, all of which we handle.
|
||||
error("Failed to open \"%s\" for reading+writing: %s", name, strerror(errno));
|
||||
} else {
|
||||
Defer closeInput{[&] { close(input); }};
|
||||
struct stat stat;
|
||||
if (fstat(input, &stat) == -1) {
|
||||
error("Failed to stat \"%s\": %s", name, strerror(errno)); // LCOV_EXCL_LINE
|
||||
} else if (!S_ISREG(stat.st_mode)) { // We do not support FIFOs or symlinks
|
||||
// LCOV_EXCL_START
|
||||
error("\"%s\" is not a regular file, and thus cannot be modified in-place", name);
|
||||
// LCOV_EXCL_STOP
|
||||
} else if (stat.st_size < 0x150) {
|
||||
// This check is in theory redundant with the one in `processFile`, but it
|
||||
// prevents passing a file size of 0, which usually indicates pipes
|
||||
error(
|
||||
"\"%s\" too short, expected at least 336 ($150) bytes, got only %jd",
|
||||
name,
|
||||
static_cast<intmax_t>(stat.st_size)
|
||||
);
|
||||
} else {
|
||||
if (!outputName) {
|
||||
output = input;
|
||||
}
|
||||
processFile(input, output, name, stat.st_size, true);
|
||||
}
|
||||
}
|
||||
|
||||
return checkErrors(name);
|
||||
}
|
||||
1034
src/fix/main.cpp
1034
src/fix/main.cpp
File diff suppressed because it is too large
Load Diff
202
src/fix/mbc.cpp
202
src/fix/mbc.cpp
@@ -1,11 +1,18 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "fix/mbc.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp" // unreachable_
|
||||
#include "platform.hpp" // strcasecmp
|
||||
#include "util.hpp"
|
||||
|
||||
#include "fix/warning.hpp"
|
||||
|
||||
@@ -91,87 +98,57 @@ bool mbc_HasRAM(MbcType type) {
|
||||
return search != mbcData.end() && search->second.second;
|
||||
}
|
||||
|
||||
static void skipWhitespace(char const *&ptr) {
|
||||
while (*ptr == ' ' || *ptr == '\t') {
|
||||
++ptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void skipMBCSpace(char const *&ptr) {
|
||||
while (*ptr == ' ' || *ptr == '\t' || *ptr == '_') {
|
||||
++ptr;
|
||||
}
|
||||
ptr += strspn(ptr, " \t_");
|
||||
}
|
||||
|
||||
static char normalizeMBCChar(char c) {
|
||||
if (c >= 'a' && c <= 'z') { // Uppercase for comparison with `mbc_Name`s
|
||||
c = c - 'a' + 'A';
|
||||
} else if (c == '_') { // Treat underscores as spaces
|
||||
c = ' ';
|
||||
if (c == '_') {
|
||||
return ' '; // Treat underscores as spaces
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
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;
|
||||
return toUpper(c); // Uppercase for comparison with `mbc_Name`s
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
static void fatalUnknownMBC(char const *fullName) {
|
||||
fatal("Unknown MBC \"%s\"\n%s", fullName, acceptedMBCNames);
|
||||
static void fatalUnknownMBC(char const *name) {
|
||||
fatal("Unknown MBC \"%s\"\n%s", name, acceptedMBCNames);
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
static void fatalWrongMBCFeatures(char const *fullName) {
|
||||
fatal("Features incompatible with MBC (\"%s\")\n%s", fullName, acceptedMBCNames);
|
||||
static void fatalWrongMBCFeatures(char const *name) {
|
||||
fatal("Features incompatible with MBC (\"%s\")\n%s", name, acceptedMBCNames);
|
||||
}
|
||||
|
||||
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
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if ((name[0] >= '0' && name[0] <= '9') || name[0] == '$') {
|
||||
int base = 0;
|
||||
|
||||
if (name[0] == '$') {
|
||||
++name;
|
||||
base = 16;
|
||||
// Parse numeric MBC and return it as-is (unless it's too large)
|
||||
if (char c = *ptr; isDigit(c) || c == '$' || c == '&' || c == '%') {
|
||||
if (std::optional<uint64_t> mbc = parseWholeNumber(ptr); !mbc) {
|
||||
fatalUnknownMBC(name);
|
||||
} else if (*mbc > 0xFF) {
|
||||
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:
|
||||
uint16_t mbc;
|
||||
char const *ptr = name;
|
||||
uint16_t mbc = UINT16_MAX;
|
||||
|
||||
skipWhitespace(ptr); // Trim off leading whitespace
|
||||
|
||||
#define tryReadSlice(expected) \
|
||||
do { \
|
||||
if (!readMBCSlice(ptr, expected)) { \
|
||||
fatalUnknownMBC(fullName); \
|
||||
} \
|
||||
} while (0)
|
||||
auto tryReadSlice = [&ptr, &name](char const *expected) {
|
||||
while (*expected) {
|
||||
// If `name` is too short, the character will be '\0' and this will return `false`
|
||||
if (normalizeMBCChar(*ptr++) != *expected++) {
|
||||
fatalUnknownMBC(name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switch (*ptr++) {
|
||||
case 'R': // ROM / ROM_ONLY
|
||||
@@ -191,13 +168,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
switch (*ptr++) {
|
||||
case 'B':
|
||||
case 'b':
|
||||
switch (*ptr++) {
|
||||
case 'C':
|
||||
case 'c':
|
||||
break;
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
tryReadSlice("C");
|
||||
switch (*ptr++) {
|
||||
case '1':
|
||||
mbc = MBC1;
|
||||
@@ -217,8 +188,6 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
case '7':
|
||||
mbc = MBC7_SENSOR_RUMBLE_RAM_BATTERY;
|
||||
break;
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
break;
|
||||
case 'M':
|
||||
@@ -226,8 +195,6 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
tryReadSlice("M01");
|
||||
mbc = MMM01;
|
||||
break;
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -250,39 +217,30 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
tryReadSlice("MA5");
|
||||
mbc = BANDAI_TAMA5;
|
||||
break;
|
||||
case 'P': {
|
||||
case 'P':
|
||||
tryReadSlice("P1");
|
||||
// Parse version
|
||||
skipMBCSpace(ptr);
|
||||
// Major
|
||||
char *endptr;
|
||||
unsigned long val = strtoul(ptr, &endptr, 10);
|
||||
|
||||
if (endptr == ptr) {
|
||||
if (std::optional<uint64_t> major = parseNumber(ptr, BASE_10); !major) {
|
||||
fatal("Failed to parse TPP1 major revision number");
|
||||
}
|
||||
ptr = endptr;
|
||||
if (val != 1) {
|
||||
} else if (*major != 1) {
|
||||
fatal("RGBFIX only supports TPP1 version 1.0");
|
||||
} else {
|
||||
tpp1Major = *major;
|
||||
}
|
||||
tpp1Major = val;
|
||||
tryReadSlice(".");
|
||||
// Minor
|
||||
val = strtoul(ptr, &endptr, 10);
|
||||
if (endptr == ptr) {
|
||||
if (std::optional<uint64_t> minor = parseNumber(ptr, BASE_10); !minor) {
|
||||
fatal("Failed to parse TPP1 minor revision number");
|
||||
}
|
||||
ptr = endptr;
|
||||
if (val > 0xFF) {
|
||||
} else if (*minor > 0xFF) {
|
||||
fatal("TPP1 minor revision number must be 8-bit");
|
||||
} else {
|
||||
tpp1Minor = *minor;
|
||||
}
|
||||
tpp1Minor = val;
|
||||
mbc = TPP1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'H': // HuC{1, 3}
|
||||
@@ -295,13 +253,12 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
case '3':
|
||||
mbc = HUC3;
|
||||
break;
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
if (mbc == UINT16_MAX) {
|
||||
fatalUnknownMBC(name);
|
||||
}
|
||||
|
||||
// Read "additional features"
|
||||
@@ -315,18 +272,10 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
static constexpr uint8_t MULTIRUMBLE = 1 << 2;
|
||||
// clang-format on
|
||||
|
||||
for (;;) {
|
||||
skipWhitespace(ptr); // Trim off trailing whitespace
|
||||
|
||||
// If done, start processing "features"
|
||||
if (!*ptr) {
|
||||
break;
|
||||
}
|
||||
while (*ptr) {
|
||||
// We expect a '+' at this point
|
||||
skipMBCSpace(ptr);
|
||||
if (*ptr++ != '+') {
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
tryReadSlice("+");
|
||||
skipMBCSpace(ptr);
|
||||
|
||||
switch (*ptr++) {
|
||||
@@ -355,8 +304,6 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
tryReadSlice("M");
|
||||
features |= RAM;
|
||||
break;
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -371,12 +318,8 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
tryReadSlice("IMER");
|
||||
features |= TIMER;
|
||||
break;
|
||||
|
||||
default:
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
}
|
||||
#undef tryReadSlice
|
||||
|
||||
switch (mbc) {
|
||||
case ROM:
|
||||
@@ -397,7 +340,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
} else if (features) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -405,7 +348,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
if (features == BATTERY) {
|
||||
mbc = MBC2_BATTERY;
|
||||
} else if (features) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -413,7 +356,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
// Handle timer, which also requires battery
|
||||
if (features & TIMER) {
|
||||
if (!(features & BATTERY)) {
|
||||
warning(WARNING_MBC, "MBC3+TIMER implies BATTERY");
|
||||
warning(WARNING_MBC, "\"MBC3+TIMER\" implies \"BATTERY\"");
|
||||
}
|
||||
features &= ~(TIMER | BATTERY); // Reset those bits
|
||||
mbc = MBC3_TIMER_BATTERY;
|
||||
@@ -429,7 +372,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
} else if (features) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -447,7 +390,7 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
} else if (features == (RAM | BATTERY)) {
|
||||
mbc += 2;
|
||||
} else if (features) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -457,53 +400,50 @@ MbcType mbc_ParseName(char const *name, uint8_t &tpp1Major, uint8_t &tpp1Minor)
|
||||
case HUC3:
|
||||
// No extra features accepted
|
||||
if (features) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
break;
|
||||
|
||||
case MBC7_SENSOR_RUMBLE_RAM_BATTERY:
|
||||
if (features != (SENSOR | RUMBLE | RAM | BATTERY)) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
break;
|
||||
|
||||
case HUC1_RAM_BATTERY:
|
||||
if (features != (RAM | BATTERY)) { // HuC1 expects RAM+BATTERY
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
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) {
|
||||
warning(WARNING_MBC, "TPP1 requests RAM implicitly if given a non-zero RAM size");
|
||||
}
|
||||
if (features & BATTERY) {
|
||||
mbc |= 0x08;
|
||||
mbc |= BATTERY_TPP1;
|
||||
}
|
||||
if (features & TIMER) {
|
||||
mbc |= 0x04;
|
||||
}
|
||||
if (features & MULTIRUMBLE) {
|
||||
mbc |= 0x03; // Also set the rumble flag
|
||||
mbc |= TIMER_TPP1;
|
||||
}
|
||||
if (features & RUMBLE) {
|
||||
mbc |= 0x01;
|
||||
mbc |= RUMBLE_TPP1;
|
||||
}
|
||||
if (features & SENSOR) {
|
||||
fatalWrongMBCFeatures(fullName);
|
||||
fatalWrongMBCFeatures(name);
|
||||
}
|
||||
// Multiple rumble speeds imply rumble
|
||||
if (mbc & 0x01) {
|
||||
assume(mbc & 0x02);
|
||||
if (features & MULTIRUMBLE) {
|
||||
mbc |= MULTIRUMBLE_TPP1 | RUMBLE_TPP1; // Multiple rumble speeds imply rumble
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
skipWhitespace(ptr); // Trim off trailing whitespace
|
||||
|
||||
// If there is still something past the whitespace, error out
|
||||
if (*ptr) {
|
||||
fatalUnknownMBC(fullName);
|
||||
}
|
||||
|
||||
return static_cast<MbcType>(mbc);
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "fix/warning.hpp"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "diagnostics.hpp"
|
||||
#include "style.hpp"
|
||||
|
||||
// clang-format off: nested initializers
|
||||
Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
.metaWarnings = {
|
||||
@@ -8,6 +19,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
},
|
||||
.warningFlags = {
|
||||
{"mbc", LEVEL_DEFAULT },
|
||||
{"obsolete", LEVEL_DEFAULT },
|
||||
{"overwrite", LEVEL_DEFAULT },
|
||||
{"sgb", LEVEL_DEFAULT },
|
||||
{"truncation", LEVEL_DEFAULT },
|
||||
@@ -20,6 +32,7 @@ Diagnostics<WarningLevel, WarningID> warnings = {
|
||||
|
||||
uint32_t checkErrors(char const *filename) {
|
||||
if (warnings.nbErrors > 0) {
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(
|
||||
stderr,
|
||||
"Fixing \"%s\" failed with %" PRIu64 " error%s\n",
|
||||
@@ -27,13 +40,16 @@ uint32_t checkErrors(char const *filename) {
|
||||
warnings.nbErrors,
|
||||
warnings.nbErrors == 1 ? "" : "s"
|
||||
);
|
||||
style_Reset(stderr);
|
||||
}
|
||||
return warnings.nbErrors;
|
||||
}
|
||||
|
||||
void error(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -44,7 +60,9 @@ void error(char const *fmt, ...) {
|
||||
|
||||
void fatal(char const *fmt, ...) {
|
||||
va_list ap;
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("FATAL: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
@@ -62,19 +80,27 @@ void warning(WarningID id, char const *fmt, ...) {
|
||||
break;
|
||||
|
||||
case WarningBehavior::ENABLED:
|
||||
fprintf(stderr, "warning: [-W%s]\n ", flag);
|
||||
style_Set(stderr, STYLE_YELLOW, true);
|
||||
fputs("warning: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
style_Set(stderr, STYLE_YELLOW, true);
|
||||
fprintf(stderr, " [-W%s]\n", flag);
|
||||
style_Reset(stderr);
|
||||
break;
|
||||
|
||||
case WarningBehavior::ERROR:
|
||||
fprintf(stderr, "error: [-Werror=%s]\n ", flag);
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fputs("error: ", stderr);
|
||||
style_Reset(stderr);
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
putc('\n', stderr);
|
||||
style_Set(stderr, STYLE_RED, true);
|
||||
fprintf(stderr, " [-Werror=%s]\n", flag);
|
||||
style_Reset(stderr);
|
||||
|
||||
warnings.incrementErrors();
|
||||
break;
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include "gfx/color_set.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <utility>
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
|
||||
1094
src/gfx/main.cpp
1094
src/gfx/main.cpp
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user