ref: 66512ed8d2676f4937b2c64c9fea125aeb76d1d4
parent: 03967bd6238e13acdd91018fc1b27472d28eb39b
parent: fb58166e5d391643906fd0aabd58d367507426fa
author: Eldred Habert <[email protected]>
date: Sat Mar 21 19:06:44 EDT 2020
Merge pull request #488 from rednex/assert Add assertions
--- a/include/asm/output.h
+++ b/include/asm/output.h
@@ -20,6 +20,8 @@
void out_SetFileName(char *s);
void out_CreatePatch(uint32_t type, struct Expression const *expr);
+bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
+ char const *message);
void out_WriteObject(void);
#endif /* RGBDS_ASM_OUTPUT_H */
--- a/include/asm/warning.h
+++ b/include/asm/warning.h
@@ -22,6 +22,7 @@
WARNING_OBSOLETE,
WARNING_SHIFT,
WARNING_USER,
+ WARNING_ASSERT,
WARNING_SHIFT_AMOUNT,
WARNING_TRUNCATION,
--- a/include/link/patch.h
+++ b/include/link/patch.h
@@ -10,6 +10,28 @@
#ifndef RGBDS_LINK_PATCH_H
#define RGBDS_LINK_PATCH_H
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "link/section.h"
+
+#include "linkdefs.h"
+
+struct Assertion {
+ struct Patch patch;
+ // enum AssertionType type; The `patch`'s field is instead re-used
+ struct Section *section;
+ char *message;
+
+ struct Assertion *next;
+};
+
+/**
+ * Checks all assertions
+ * @return true if assertion failed
+ */
+void patch_CheckAssertions(struct Assertion *assertion);
+
/**
* Applies all SECTIONs' patches to them
*/
--- a/include/linkdefs.h
+++ b/include/linkdefs.h
@@ -14,7 +14,13 @@
#define RGBDS_OBJECT_VERSION_STRING "RGB%1hhu"
#define RGBDS_OBJECT_VERSION_NUMBER (uint8_t)9
-#define RGBDS_OBJECT_REV 1
+#define RGBDS_OBJECT_REV 2
+
+enum AssertionType {
+ ASSERT_WARN,
+ ASSERT_ERROR,
+ ASSERT_FATAL
+};
enum RPNCommand {
RPN_ADD = 0x00,
--- a/src/asm/asmy.y
+++ b/src/asm/asmy.y
@@ -23,6 +23,7 @@
#include "asm/macro.h"
#include "asm/main.h"
#include "asm/mymath.h"
+#include "asm/output.h"
#include "asm/rpn.h"
#include "asm/section.h"
#include "asm/symbol.h"
@@ -497,6 +498,7 @@
int32_t nConstValue;
struct SectionSpec sectSpec;
struct MacroArgs *macroArg;
+ enum AssertionType assertType;
}
%type <sVal> relocexpr
@@ -587,6 +589,8 @@
%token T_POP_LOAD T_POP_ENDL
%token T_POP_FAIL
%token T_POP_WARN
+%token T_POP_FATAL
+%token T_POP_ASSERT T_POP_STATIC_ASSERT
%token T_POP_PURGE
%token T_POP_POPS
%token T_POP_PUSHS
@@ -638,6 +642,7 @@
%type <nConstValue> op_a_r
%type <nConstValue> op_hl_ss
%type <sVal> op_mem_ind
+%type <assertType> assert_type
%start asmfile
%%
@@ -759,6 +764,7 @@
| shift
| fail
| warn
+ | assert
| purge
| pops
| pushs
@@ -799,8 +805,97 @@
warn : T_POP_WARN string { warning(WARNING_USER, "%s", $2); }
;
+assert_type : /* empty */ { $$ = ASSERT_ERROR; }
+ | T_POP_WARN ',' { $$ = ASSERT_WARN; }
+ | T_POP_FAIL ',' { $$ = ASSERT_ERROR; }
+ | T_POP_FATAL ',' { $$ = ASSERT_FATAL; }
+;
+
+assert : T_POP_ASSERT assert_type relocexpr
+ {
+ if (rpn_isKnown(&$3) && $3.nVal == 0) {
+ switch ($2) {
+ case ASSERT_FATAL:
+ fatalerror("Assertion failed");
+ case ASSERT_ERROR:
+ yyerror("Assertion failed");
+ break;
+ case ASSERT_WARN:
+ warning(WARNING_ASSERT,
+ "Assertion failed");
+ break;
+ }
+ } else {
+ if (!out_CreateAssert($2, &$3, ""))
+ yyerror("Assertion creation failed: %s",
+ strerror(errno));
+ }
+ rpn_Free(&$3);
+ }
+ | T_POP_ASSERT assert_type relocexpr ',' string
+ {
+ if (rpn_isKnown(&$3) && $3.nVal == 0) {
+ switch ($2) {
+ case ASSERT_FATAL:
+ fatalerror("Assertion failed: %s",
+ $5);
+ case ASSERT_ERROR:
+ yyerror("Assertion failed: %s",
+ $5);
+ break;
+ case ASSERT_WARN:
+ warning(WARNING_ASSERT,
+ "Assertion failed: %s",
+ $5);
+ break;
+ }
+ } else {
+ if (!out_CreateAssert($2, &$3, $5))
+ yyerror("Assertion creation failed: %s",
+ strerror(errno));
+ }
+ rpn_Free(&$3);
+ }
+ | T_POP_STATIC_ASSERT assert_type const
+ {
+ if ($3 == 0) {
+ switch ($2) {
+ case ASSERT_FATAL:
+ fatalerror("Assertion failed");
+ case ASSERT_ERROR:
+ yyerror("Assertion failed");
+ break;
+ case ASSERT_WARN:
+ warning(WARNING_ASSERT,
+ "Assertion failed");
+ break;
+ }
+ }
+ }
+ | T_POP_STATIC_ASSERT assert_type const ',' string
+ {
+ if ($3 == 0) {
+ switch ($2) {
+ case ASSERT_FATAL:
+ fatalerror("Assertion failed: %s",
+ $5);
+ case ASSERT_ERROR:
+ yyerror("Assertion failed: %s",
+ $5);
+ break;
+ case ASSERT_WARN:
+ warning(WARNING_ASSERT,
+ "Assertion failed: %s",
+ $5);
+ break;
+ }
+ }
+ }
+;
+
shift : T_POP_SHIFT { macro_ShiftCurrentArgs(); }
- | T_POP_SHIFT uconst {
+ | T_POP_SHIFT uconst
+ {
int32_t i = $2;
while (i--)
macro_ShiftCurrentArgs();
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -496,6 +496,9 @@
{"fail", T_POP_FAIL},
{"warn", T_POP_WARN},
+ {"fatal", T_POP_FATAL},
+ {"assert", T_POP_ASSERT},
+ {"static_assert", T_POP_STATIC_ASSERT},
{"macro", T_POP_MACRO},
/* Not needed but we have it here just to protect the name */
--- a/src/asm/output.c
+++ b/src/asm/output.c
@@ -47,10 +47,18 @@
struct PatchSymbol *pBucketNext; /* next symbol in hash table bucket */
};
+struct Assertion {
+ struct Patch *patch;
+ struct Section *section;
+ char *message;
+ struct Assertion *next;
+};
+
struct PatchSymbol *tHashedPatchSymbols[HASHSIZE];
struct Section *pSectionList, *pCurrentSection;
struct PatchSymbol *pPatchSymbols;
struct PatchSymbol **ppPatchSymbolsTail = &pPatchSymbols;
+struct Assertion *assertions = NULL;
char *tzObjectname;
/*
@@ -104,6 +112,21 @@
return r;
}
+/**
+ * Count the number of assertions used in this object
+ */
+static uint32_t countasserts(void)
+{
+ struct Assertion *assert = assertions;
+ uint32_t count = 0;
+
+ while (assert) {
+ count++;
+ assert = assert->next;
+ }
+ return count;
+}
+
/*
* Write a long to a file (little-endian)
*/
@@ -287,62 +310,29 @@
}
}
-/*
- * Allocate a new patchstructure and link it into the list
- */
-struct Patch *allocpatch(void)
+static void writerpn(uint8_t *rpnexpr, uint32_t *rpnptr, uint8_t *rpn,
+ uint32_t rpnlen)
{
- struct Patch *pPatch;
-
- pPatch = malloc(sizeof(struct Patch));
-
- if (pPatch == NULL)
- fatalerror("No memory for patch");
-
- pPatch->pNext = pCurrentSection->pPatches;
- pPatch->nRPNSize = 0;
- pPatch->pRPN = NULL;
- pCurrentSection->pPatches = pPatch;
-
- return pPatch;
-}
-
-/*
- * Create a new patch (includes the rpn expr)
- */
-void out_CreatePatch(uint32_t type, struct Expression const *expr)
-{
- struct Patch *pPatch;
- uint8_t *rpnexpr;
char tzSym[512];
- uint32_t rpnptr = 0, symptr;
- rpnexpr = malloc(expr->nRPNPatchSize);
+ for (size_t offset = 0; offset < rpnlen; ) {
+#define popbyte() rpn[offset++]
+#define writebyte(byte) rpnexpr[(*rpnptr)++] = byte
+ uint8_t rpndata = popbyte();
- if (rpnexpr == NULL)
- fatalerror("No memory for patch RPN expression");
-
- pPatch = allocpatch();
- pPatch->nType = type;
- fstk_DumpToStr(pPatch->tzFilename, sizeof(pPatch->tzFilename));
- pPatch->nOffset = pCurrentSection->nPC;
-
- for (size_t offset = 0; offset < expr->nRPNLength; ) {
-#define popbyte(expr) (expr)->tRPN[offset++]
- uint8_t rpndata = popbyte(expr);
-
switch (rpndata) {
case RPN_CONST:
- rpnexpr[rpnptr++] = RPN_CONST;
- rpnexpr[rpnptr++] = popbyte(expr);
- rpnexpr[rpnptr++] = popbyte(expr);
- rpnexpr[rpnptr++] = popbyte(expr);
- rpnexpr[rpnptr++] = popbyte(expr);
+ writebyte(RPN_CONST);
+ writebyte(popbyte());
+ writebyte(popbyte());
+ writebyte(popbyte());
+ writebyte(popbyte());
break;
case RPN_SYM:
{
- symptr = 0;
- while ((tzSym[symptr++] = popbyte(expr)) != 0)
+ uint32_t symptr = 0;
+
+ while ((tzSym[symptr++] = popbyte()) != 0)
;
struct sSymbol const *sym = sym_FindSymbol(tzSym);
@@ -353,18 +343,18 @@
uint32_t value;
value = sym_GetConstantValue(tzSym);
- rpnexpr[rpnptr++] = RPN_CONST;
- rpnexpr[rpnptr++] = value & 0xFF;
- rpnexpr[rpnptr++] = value >> 8;
- rpnexpr[rpnptr++] = value >> 16;
- rpnexpr[rpnptr++] = value >> 24;
+ writebyte(RPN_CONST);
+ writebyte(value & 0xFF);
+ writebyte(value >> 8);
+ writebyte(value >> 16);
+ writebyte(value >> 24);
} else {
symptr = addsymbol(sym);
- rpnexpr[rpnptr++] = RPN_SYM;
- rpnexpr[rpnptr++] = symptr & 0xFF;
- rpnexpr[rpnptr++] = symptr >> 8;
- rpnexpr[rpnptr++] = symptr >> 16;
- rpnexpr[rpnptr++] = symptr >> 24;
+ writebyte(RPN_SYM);
+ writebyte(symptr & 0xFF);
+ writebyte(symptr >> 8);
+ writebyte(symptr >> 16);
+ writebyte(symptr >> 24);
}
break;
}
@@ -371,9 +361,9 @@
case RPN_BANK_SYM:
{
struct sSymbol *sym;
+ uint32_t symptr = 0;
- symptr = 0;
- while ((tzSym[symptr++] = popbyte(expr)) != 0)
+ while ((tzSym[symptr++] = popbyte()) != 0)
;
sym = sym_FindSymbol(tzSym);
@@ -381,11 +371,11 @@
break;
symptr = addsymbol(sym);
- rpnexpr[rpnptr++] = RPN_BANK_SYM;
- rpnexpr[rpnptr++] = symptr & 0xFF;
- rpnexpr[rpnptr++] = symptr >> 8;
- rpnexpr[rpnptr++] = symptr >> 16;
- rpnexpr[rpnptr++] = symptr >> 24;
+ writebyte(RPN_BANK_SYM);
+ writebyte(symptr & 0xFF);
+ writebyte(symptr >> 8);
+ writebyte(symptr >> 16);
+ writebyte(symptr >> 24);
break;
}
case RPN_BANK_SECT:
@@ -392,28 +382,95 @@
{
uint16_t b;
- rpnexpr[rpnptr++] = RPN_BANK_SECT;
+ writebyte(RPN_BANK_SECT);
do {
- b = popbyte(expr);
- rpnexpr[rpnptr++] = b & 0xFF;
+ b = popbyte();
+ writebyte(b & 0xFF);
} while (b != 0);
break;
}
default:
- rpnexpr[rpnptr++] = rpndata;
+ writebyte(rpndata);
break;
}
#undef popbyte
+#undef writebyte
}
+}
- assert(rpnptr == expr->nRPNPatchSize);
+/*
+ * Allocate a new patch structure and link it into the list
+ */
+static struct Patch *allocpatch(uint32_t type, struct Expression const *expr)
+{
+ struct Patch *pPatch;
- pPatch->pRPN = rpnexpr;
- pPatch->nRPNSize = rpnptr;
+ pPatch = malloc(sizeof(struct Patch));
+
+ if (!pPatch)
+ fatalerror("No memory for patch: %s", strerror(errno));
+ pPatch->pRPN = malloc(sizeof(*pPatch->pRPN) * expr->nRPNPatchSize);
+
+ if (!pPatch->pRPN)
+ fatalerror("No memory for patch's RPN expression: %s",
+ strerror(errno));
+
+ pPatch->nRPNSize = 0;
+ pPatch->nType = type;
+ pPatch->nOffset = pCurrentSection->nPC;
+ fstk_DumpToStr(pPatch->tzFilename, sizeof(pPatch->tzFilename));
+
+ writerpn(pPatch->pRPN, &pPatch->nRPNSize, expr->tRPN, expr->nRPNLength);
+ assert(pPatch->nRPNSize == expr->nRPNPatchSize);
+
+ return pPatch;
}
/*
+ * Create a new patch (includes the rpn expr)
+ */
+void out_CreatePatch(uint32_t type, struct Expression const *expr)
+{
+ struct Patch *pPatch = allocpatch(type, expr);
+
+ pPatch->pNext = pCurrentSection->pPatches;
+ pCurrentSection->pPatches = pPatch;
+}
+
+/**
+ * Creates an assert that will be written to the object file
+ */
+bool out_CreateAssert(enum AssertionType type, struct Expression const *expr,
+ char const *message)
+{
+ struct Assertion *assertion = malloc(sizeof(*assertion));
+
+ if (!assertion)
+ return false;
+
+ assertion->patch = allocpatch(type, expr);
+ assertion->section = pCurrentSection;
+ assertion->message = strdup(message);
+ if (!assertion->message) {
+ free(assertion);
+ return false;
+ }
+
+ assertion->next = assertions;
+ assertions = assertion;
+
+ return true;
+}
+
+static void writeassert(struct Assertion *assert, FILE *f)
+{
+ writepatch(assert->patch, f);
+ fputlong(getsectid(assert->section), f);
+ fputstring(assert->message, f);
+}
+
+/*
* Write an objectfile
*/
void out_WriteObject(void)
@@ -421,6 +478,7 @@
FILE *f;
struct PatchSymbol *pSym;
struct Section *pSect;
+ struct Assertion *assert = assertions;
addexports();
@@ -444,6 +502,12 @@
while (pSect) {
writesection(pSect, f);
pSect = pSect->pNext;
+ }
+
+ fputlong(countasserts(), f);
+ while (assert) {
+ writeassert(assert, f);
+ assert = assert->next;
}
fclose(f);
--- a/src/asm/rgbasm.1
+++ b/src/asm/rgbasm.1
@@ -141,7 +141,8 @@
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 Wempty-entry
-enables the warning that Fl Wno-empty-entry
+enables the warning that
+.Fl Wno-empty-entry
disables).
Only the non-default flag is listed here.
Ignoring the
@@ -148,6 +149,13 @@
.Dq no-
prefix, entries are listed alphabetically.
.Bl -tag -width Ds
+.It Fl Wno-assert
+Warns when
+.Ic WARN Ns No -type
+assertions fail. (See
+.Xr rgbasm 5 "Aborting the assembly process"
+for
+.Ic ASSERT ) .
.It Fl Wbuiltin-args
Warn about incorrect arguments to built-in functions, such as
.Fn STRSUB
@@ -182,9 +190,12 @@
Warn when shifting triggers C undefined behavior, potentially causing unpredictable behavior.
Shfting behavior will be changed and this warning removed before next release.
.It Fl Wno-user
-Warns when the built-in function
-.Fn WARN
-is executed.
+Warns when the
+.Ic WARN
+built-in is executed. (See
+.Xr rgbasm 5 "Aborting the assembly process"
+for
+.Ic WARN ) .
.El
.Sh EXAMPLES
You can assemble a source file in two ways.
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -980,6 +980,62 @@
stops assembling immediately while
.Ic WARN
shows the message but continues afterwards.
+.Pp
+If you need to ensure some assumption is correct when compiling, you can use
+.Ic ASSERT
+and
+.Ic STATIC_ASSERT .
+Syntax examples are given below:
+.Pp
+.Bd -literal -offset indent
+Function:
+ xor a
+ASSERT LOW(Variable) == 0
+ ld h, HIGH(Variable)
+ ld l, a
+ ld a, [hli]
+ ; You can also indent this!
+ ASSERT BANK(OtherFunction) == BANK(Function)
+ call OtherFunction
+; Lowercase also works
+assert Variable + 1 == OtherVariable
+ ld c, [hl]
+ ret
+\&.end
+ ; If you specify one, a message will be printed
+ STATIC_ASSERT .end - Function < 256, "Function is too large!"
+.Ed
+.Pp
+First, the difference between
+.Ic ASSERT
+and
+.Ic STATIC_ASSERT
+is that the former is evaluated by RGBASM if it can, otherwise by RGBLINK; but the latter is only ever evaluated by RGBASM.
+If RGBASM cannot compute the value of the argument to
+.Ic STATIC_ASSERT ,
+it will produce an error.
+.Pp
+Second, as shown above, a string can be optionally added at the end, to give insight into what the assertion is checking.
+.Pp
+Finally, you can add one of
+.Ic WARN , FAIL
+or
+.Ic FATAL
+as the first optional argument to either
+.Ic ASSERT
+or
+.Ic STATIC_ASSERT .
+If the assertion fails,
+.Ic WARN
+will cause a simple warning (controlled by
+.Xr rgbasm 1 DIAGNOSTICS
+flag
+.Fl Wassert )
+to be emitted;
+.Ic FAIL
+(the default) will cause a non-fatal error; and
+.Ic FATAL
+immediately aborts.
.Ss Including other source files
Use
.Ic INCLUDE
--- a/src/asm/warning.c
+++ b/src/asm/warning.c
@@ -36,6 +36,7 @@
WARNING_DISABLED, /* Obsolete things */
WARNING_DISABLED, /* Shifting undefined behavior */
WARNING_ENABLED, /* User warnings */
+ WARNING_ENABLED, /* Assertions */
WARNING_DISABLED, /* Strange shift amount */
WARNING_ENABLED, /* Implicit truncation loses some bits */
};
@@ -72,6 +73,7 @@
"obsolete",
"shift",
"user",
+ "assert",
"shift-amount",
"truncation",
--- a/src/link/object.c
+++ b/src/link/object.c
@@ -13,13 +13,15 @@
#include <errno.h>
#include <limits.h>
-#include "link/object.h"
+#include "link/assign.h"
#include "link/main.h"
-#include "link/symbol.h"
+#include "link/object.h"
+#include "link/patch.h"
#include "link/section.h"
-#include "link/assign.h"
+#include "link/symbol.h"
#include "extern/err.h"
+#include "helpers.h"
#include "linkdefs.h"
static struct SymbolList {
@@ -28,6 +30,8 @@
struct SymbolList *next;
} *symbolLists;
+static struct Assertion *assertions;
+
/***** Helper functions for reading object files *****/
/*
@@ -232,7 +236,7 @@
}
/**
- * Reads a RGB6 section from a file.
+ * Reads a section from a file.
* @param file The file to read from
* @param section The struct to fill
* @param fileName The filename to report in errors
@@ -332,6 +336,29 @@
}
/**
+ * Reads an assertion from a file
+ * @param file The file to read from
+ * @param assert The struct to fill
+ * @param fileName The filename to report in errors
+ */
+static void readAssertion(FILE *file, struct Assertion *assert,
+ char const *fileName, struct Section *fileSections[],
+ uint32_t i)
+{
+ char assertName[sizeof("Assertion #" EXPAND_AND_STR(UINT32_MAX))];
+ uint32_t sectionID;
+
+ snprintf(assertName, sizeof(assertName), "Assertion #%u", i);
+
+ readPatch(file, &assert->patch, fileName, assertName, 0);
+ tryReadlong(sectionID, file, "%s: Cannot read assertion's section ID: %s",
+ fileName);
+ assert->section = fileSections[sectionID];
+ tryReadstr(assert->message, file, "%s: Cannot read assertion's message: %s",
+ fileName);
+}
+
+/**
* Reads an object file of any supported format
* @param fileName The filename to report for errors
*/
@@ -451,6 +478,21 @@
}
}
+ uint32_t nbAsserts;
+
+ tryReadlong(nbAsserts, file, "%s: Cannot read number of assertions: %s",
+ fileName);
+ verbosePrint("Reading %u assertions...\n", nbAsserts);
+ for (uint32_t i = 0; i < nbAsserts; i++) {
+ struct Assertion *assertion = malloc(sizeof(*assertion));
+
+ if (!assertion)
+ err(1, "%s: Couldn't create new assertion", fileName);
+ readAssertion(file, assertion, fileName, fileSections, i);
+ assertion->next = assertions;
+ assertions = assertion;
+ }
+
fclose(file);
}
@@ -457,6 +499,8 @@
void obj_DoSanityChecks(void)
{
sect_DoSanityChecks();
+
+ patch_CheckAssertions(assertions);
}
static void freeSection(struct Section *section, void *arg)
--- a/src/link/patch.c
+++ b/src/link/patch.c
@@ -340,6 +340,48 @@
#undef popRPN
}
+void patch_CheckAssertions(struct Assertion *assert)
+{
+ verbosePrint("Checking assertions...");
+ initRPNStack();
+
+ uint8_t failures = 0;
+
+ while (assert) {
+ if (!computeRPNExpr(&assert->patch, assert->section)) {
+ switch ((enum AssertionType)assert->patch.type) {
+ case ASSERT_FATAL:
+ errx(1, "%s: %s", assert->patch.fileName,
+ assert->message[0] ? assert->message
+ : "assert failure");
+ /* Not reached */
+ break; /* Here so checkpatch doesn't complain */
+ case ASSERT_ERROR:
+ fprintf(stderr, "%s: %s\n",
+ assert->patch.fileName,
+ assert->message[0] ? assert->message
+ : "assert failure");
+ failures++;
+ break;
+ case ASSERT_WARN:
+ warnx("%s: %s", assert->patch.fileName,
+ assert->message[0] ? assert->message
+ : "assert failure");
+ break;
+ }
+ }
+ struct Assertion *next = assert->next;
+
+ free(assert);
+ assert = next;
+ }
+
+ freeRPNStack();
+
+ if (failures)
+ errx(1, "%u assertions failed!", failures);
+}
+
/**
* Applies all of a section's patches
* @param section The section to patch
@@ -399,3 +441,4 @@
sect_ForEach(applyPatches, NULL);
freeRPNStack();
}
+
--- a/src/rgbds.5
+++ b/src/rgbds.5
@@ -124,6 +124,34 @@
ENDC
ENDR
+
+; Assertions
+
+LONG NumberOfAssertions
+
+REPT NumberOfAssertions
+
+ STRING SourceFile ; Name of the source file (for printing the failure).
+
+ LONG Offset ; Offset into the section where the assertion is located.
+
+ BYTE Type ; 0 = Prints the message but allows linking to continue
+ ; 1 = Prints the message and evaluates other assertions,
+ ; but linking fails afterwards
+ ; 2 = Prints the message and immediately fails linking
+
+ LONG RPNSize ; Size of the RPN expression's buffer.
+
+ BYTE RPN[RPNSize] ; RPN expression, same as patches. Assert fails if == 0.
+
+ LONG SectionID ; The section number (of this object file) in which this
+ ; assert is defined. If it doesn't belong to any specific
+ ; section (like a constant), this field has the value -1.
+
+ STRING Message ; A message displayed when the assert fails. If set to
+ ; the empty string, a generic message is printed instead.
+
+ENDR
.Ed
.Ss RPN DATA
Expressions in the object file are stored as RPN.
--- /dev/null
+++ b/test/asm/assert.asm
@@ -1,0 +1,23 @@
+SECTION "fixed", ROM0[0]
+
+FixedBase:
+ assert FixedBase ; This should eval (and fail) at compile time
+
+ ds 0
+ static_assert @ == 0, "@ ain't 0 now? (Hint: it's {@})"
+
+ ds 42
+ assert WARN, @ - FixedBase != 42 ; This should also eval at compile time
+
+SECTION "floating", ROM0
+
+FloatingBase:
+ assert FAIL, FloatingBase == 0 ; This shouldn't eval at compile time
+
+ ds 4
+ static_assert FAIL, FloatingBase != 0 ; This is not constant!
+
+ ds 69
+ static_assert FATAL, FixedBase != 0 ; This will fail... ↓
+ ; The point of `FATAL` is for stuff that can, say, cause division by 0!
+ static_assert FAIL, 1 / FixedBase, "You dun goofed, son" ; Won't be read
--- /dev/null
+++ b/test/asm/assert.err
@@ -1,0 +1,10 @@
+ERROR: assert.asm(4):
+ Assertion failed
+warning: assert.asm(10): [-Wassert]
+ Assertion failed
+ERROR: assert.asm(18):
+ Expected constant expression: 'FloatingBase' is not constant at assembly time
+ERROR: assert.asm(18):
+ Assertion failed
+ERROR: assert.asm(21):
+ Assertion failed
--- /dev/null
+++ b/test/link/assert.asm
@@ -1,0 +1,11 @@
+
+SECTION "test", ROM0
+
+ ds 123
+
+FloatingBase:
+ assert WARN, FloatingBase & 0, "Worry about me, but not too much."
+ assert FAIL, FloatingBase & 0, "Okay, this is getting serious!"
+ assert FATAL, FloatingBase & 0, "It all ends now."
+ assert FAIL, FloatingBase & 0, "Not even time to roll credits!"
+ assert WARN, 0, "Still can finish the film, though!"
--- /dev/null
+++ b/test/link/assert.out
@@ -1,0 +1,3 @@
+warning: assert.asm(7): Worry about me, but not too much.
+assert.asm(8): Okay, this is getting serious!
+error: assert.asm(9): It all ends now.