Add assertions

Closes #292
This commit is contained in:
ISSOtm
2020-03-05 02:58:48 +01:00
parent 03967bd623
commit fb58166e5d
18 changed files with 506 additions and 82 deletions

View File

@@ -20,6 +20,8 @@ extern struct Section *pSectionList, *pCurrentSection;
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 */

View File

@@ -22,6 +22,7 @@ enum WarningID {
WARNING_OBSOLETE,
WARNING_SHIFT,
WARNING_USER,
WARNING_ASSERT,
WARNING_SHIFT_AMOUNT,
WARNING_TRUNCATION,

View File

@@ -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
*/

View File

@@ -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,

View File

@@ -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 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
int32_t nConstValue;
struct SectionSpec sectSpec;
struct MacroArgs *macroArg;
enum AssertionType assertType;
}
%type <sVal> relocexpr
@@ -587,6 +589,8 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
%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 @@ static void strsubUTF8(char *dest, const char *src, uint32_t pos, uint32_t len)
%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 @@ simple_pseudoop : include
| shift
| fail
| warn
| assert
| purge
| pops
| pushs
@@ -799,8 +805,97 @@ fail : T_POP_FAIL string { fatalerror("%s", $2); }
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();

View File

@@ -496,6 +496,9 @@ const struct sLexInitString lexer_strings[] = {
{"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 */

View File

@@ -47,10 +47,18 @@ struct PatchSymbol {
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 @@ static uint32_t countpatches(struct Section *pSect)
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 @@ static void addexports(void)
}
}
/*
* 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);
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);
for (size_t offset = 0; offset < rpnlen; ) {
#define popbyte() rpn[offset++]
#define writebyte(byte) rpnexpr[(*rpnptr)++] = byte
uint8_t rpndata = popbyte();
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,27 +343,27 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr)
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;
}
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,36 +371,103 @@ void out_CreatePatch(uint32_t type, struct Expression const *expr)
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:
{
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
}
}
/*
* 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 = 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;
}
assert(rpnptr == expr->nRPNPatchSize);
assertion->next = assertions;
assertions = assertion;
pPatch->pRPN = rpnexpr;
pPatch->nRPNSize = rpnptr;
return true;
}
static void writeassert(struct Assertion *assert, FILE *f)
{
writepatch(assert->patch, f);
fputlong(getsectid(assert->section), f);
fputstring(assert->message, f);
}
/*
@@ -421,6 +478,7 @@ void out_WriteObject(void)
FILE *f;
struct PatchSymbol *pSym;
struct Section *pSect;
struct Assertion *assert = assertions;
addexports();
@@ -446,6 +504,12 @@ void out_WriteObject(void)
pSect = pSect->pNext;
}
fputlong(countasserts(), f);
while (assert) {
writeassert(assert, f);
assert = assert->next;
}
fclose(f);
}

View File

@@ -141,13 +141,21 @@ Enables literally every warning.
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
.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 @@ This warning is enabled by
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.

View File

@@ -980,6 +980,62 @@ take a string as the only argument and they will print this string out as a norm
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

View File

@@ -36,6 +36,7 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
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 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
"obsolete",
"shift",
"user",
"assert",
"shift-amount",
"truncation",

View File

@@ -13,13 +13,15 @@
#include <errno.h>
#include <limits.h>
#include "link/object.h"
#include "link/main.h"
#include "link/symbol.h"
#include "link/section.h"
#include "link/assign.h"
#include "link/main.h"
#include "link/object.h"
#include "link/patch.h"
#include "link/section.h"
#include "link/symbol.h"
#include "extern/err.h"
#include "helpers.h"
#include "linkdefs.h"
static struct SymbolList {
@@ -28,6 +30,8 @@ static struct SymbolList {
struct SymbolList *next;
} *symbolLists;
static struct Assertion *assertions;
/***** Helper functions for reading object files *****/
/*
@@ -232,7 +236,7 @@ static void readPatch(FILE *file, struct Patch *patch,
}
/**
* 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
@@ -331,6 +335,29 @@ static void linkSymToSect(struct Symbol const *symbol, struct Section *section)
section->nbSymbols++;
}
/**
* 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,12 +478,29 @@ void obj_ReadFile(char const *fileName)
}
}
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);
}
void obj_DoSanityChecks(void)
{
sect_DoSanityChecks();
patch_CheckAssertions(assertions);
}
static void freeSection(struct Section *section, void *arg)

View File

@@ -340,6 +340,48 @@ static int32_t computeRPNExpr(struct Patch const *patch,
#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 @@ void patch_ApplyPatches(void)
sect_ForEach(applyPatches, NULL);
freeRPNStack();
}

View File

@@ -123,6 +123,34 @@ REPT NumberOfSections
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

23
test/asm/assert.asm Normal file
View File

@@ -0,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

10
test/asm/assert.err Normal file
View File

@@ -0,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

0
test/asm/assert.out Normal file
View File

11
test/link/assert.asm Normal file
View File

@@ -0,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!"

3
test/link/assert.out Normal file
View File

@@ -0,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.