Skip to content

Commit

Permalink
Implement [[ inline fragments ]]
Browse files Browse the repository at this point in the history
Fixes gbdev#500
  • Loading branch information
Rangi42 committed Aug 21, 2022
1 parent 0105779 commit 7a5fdd5
Show file tree
Hide file tree
Showing 19 changed files with 245 additions and 9 deletions.
1 change: 1 addition & 0 deletions include/asm/section.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void sect_NewSection(char const *name, uint32_t secttype, uint32_t org,
void sect_SetLoadSection(char const *name, uint32_t secttype, uint32_t org,
struct SectionSpec const *attributes, enum SectionModifier mod);
void sect_EndLoadSection(void);
void sect_PushInlineFragmentSection(void);

struct Section *sect_GetSymbolSection(void);
uint32_t sect_GetSymbolOffset(void);
Expand Down
64 changes: 64 additions & 0 deletions man/rgbasm.5
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,70 @@ first, followed by the one from
and the one from
.Ql bar.o
last.
.Ss Inline Fragments
Inline fragments are useful for short blocks of code or data that are only referenced once.
They are section fragments created by surrounding instructions or directives with
.Ql [[
double brackets
.Ql ]] ,
without a separate
.Ic SECTION FRAGMENT
declaration.
.Pp
The content of an inline fragment becomes a
.Ic SECTION FRAGMENT ,
sharing the same name and bank as its parent ROM section, but without any other constraints.
The parent section also becomes a
.Ic FRAGMENT
if it was not one already, so that it can be merged with its inline fragments.
RGBLINK merges the fragments in no particular order.
.Pp
An inline fragment can take the place of any 16-bit integer constant
.Ql n16
from the
.Xr gbz80 7
documentation, as well as a
.Ic DW
item.
The inline fragment then evaluates to its starting address.
For example, you can
.Ic CALL
or
.Ic JP
to an inline fragment.
.Pp
This code using named labels:
.Bd -literal -offset indent
FortyTwo:
call Sub1
jp Sub2
Sub1:
ld a, [Twenty]
ret
Sub2:
inc a
add a
ret
Twenty: db 20
dw FortyTwo
.Ed
.Pp
is equivalent to this code using inline fragments:
.Bd -literal -offset indent
dw [[
call [[
ld a, [ [[db 20]] ]
ret
]]
jp [[
inc a
add a
ret
]]
]]
.Ed
.Pp
The difference is that the example using inline fragments does not declare a particular order for its pieces.
.Sh SYMBOLS
RGBDS supports several types of symbols:
.Bl -hang
Expand Down
34 changes: 30 additions & 4 deletions src/asm/lexer.c
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ struct LexerState {
uint32_t lineNo;
uint32_t colNo;
int lastToken;
int nextToken;

struct IfStack *ifStack;

Expand All @@ -365,6 +366,7 @@ static void initState(struct LexerState *state)
state->mode = LEXER_NORMAL;
state->atLineStart = true; /* yylex() will init colNo due to this */
state->lastToken = T_EOF;
state->nextToken = 0;

state->ifStack = NULL;

Expand Down Expand Up @@ -1285,6 +1287,7 @@ static uint32_t readGfxConstant(void)
static bool startsIdentifier(int c)
{
// Anonymous labels internally start with '!'
// Section fragment labels internally start with '$'
return (c <= 'Z' && c >= 'A') || (c <= 'z' && c >= 'a') || c == '.' || c == '_';
}

Expand Down Expand Up @@ -1762,6 +1765,13 @@ static int yylex_SKIP_TO_ENDC(void); // forward declaration for yylex_NORMAL

static int yylex_NORMAL(void)
{
if (lexerState->nextToken) {
int token = lexerState->nextToken;

lexerState->nextToken = 0;
return token;
}

for (;;) {
int c = nextChar();
char secondChar;
Expand All @@ -1786,10 +1796,6 @@ static int yylex_NORMAL(void)
yylval.symName[1] = '\0';
return T_ID;

case '[':
return T_LBRACK;
case ']':
return T_RBRACK;
case '(':
return T_LPAREN;
case ')':
Expand All @@ -1799,6 +1805,26 @@ static int yylex_NORMAL(void)

/* Handle ambiguous 1- or 2-char tokens */

case '[': /* Either [ or [[ */
if (peek() == '[') {
shiftChar();
return T_2LBRACK;
}
return T_LBRACK;

case ']': /* Either ] or ]] */
if (peek() == ']') {
shiftChar();
/*
* [[ Inline fragments ]] inject a T_EOL token to
* end their contents even without a newline.
* Retroactively lex the ]] after it.
*/
lexerState->nextToken = T_2RBRACK;
return T_EOL;
}
return T_RBRACK;

case '+': /* Either += or ADD */
if (peek() == '=') {
shiftChar();
Expand Down
17 changes: 16 additions & 1 deletion src/asm/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "platform.h" // strncasecmp, strdup

static struct CaptureBody captureBody; /* Captures a REPT/FOR or MACRO */
static uint32_t inlineFragmentID = 0; /* Incrementing unique ID for inline fragment labels */

static void upperstring(char *dest, char const *src)
{
Expand Down Expand Up @@ -524,6 +525,7 @@ enum {
%token T_COMMA ","
%token T_COLON ":"
%token T_LBRACK "[" T_RBRACK "]"
%token T_2LBRACK "[[" T_2RBRACK "]]"
%token T_LPAREN "(" T_RPAREN ")"
%token T_NEWLINE "newline"

Expand Down Expand Up @@ -586,6 +588,7 @@ enum {
%type <symName> redef_id
%type <symName> scoped_id
%type <symName> scoped_anon_id
%type <symName> inline_fragment
%token T_POP_EQU "EQU"
%token T_POP_SET "SET"
%token T_POP_EQUAL "="
Expand Down Expand Up @@ -682,6 +685,7 @@ enum {
%type <expr> op_mem_ind
%type <assertType> assert_type

%token T_EOL "end of line"
%token T_EOB "end of buffer"
%token T_EOF 0 "end of file"
%start asmfile
Expand All @@ -695,7 +699,7 @@ lines : %empty
| lines line
;

endofline : T_NEWLINE | T_EOB
endofline : T_NEWLINE | T_EOL | T_EOB
;

plain_directive : label
Expand Down Expand Up @@ -1365,14 +1369,25 @@ reloc_16bit : relocexpr {
rpn_CheckNBit(&$1, 16);
$$ = $1;
}
| inline_fragment { rpn_Symbol(&$$, $1); }
;

reloc_16bit_no_str : relocexpr_no_str {
rpn_CheckNBit(&$1, 16);
$$ = $1;
}
| inline_fragment { rpn_Symbol(&$$, $1); }
;

inline_fragment : T_2LBRACK {
sect_PushInlineFragmentSection();
sprintf($<symName>$, "$%" PRIu32, inlineFragmentID++);
sym_AddLabel($<symName>$);
} asmfile T_2RBRACK {
sect_PopSection();
strcpy($$, $<symName>2);
}
;

relocexpr : relocexpr_no_str
| string {
Expand Down
34 changes: 34 additions & 0 deletions src/asm/section.c
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,40 @@ void sect_EndLoadSection(void)
currentLoadSection = NULL;
}

void sect_PushInlineFragmentSection(void)
{
if (!checkcodesection())
return;

if (currentLoadSection)
fatalerror("`LOAD` blocks cannot contain inline fragments\n");

struct Section *sect = currentSection;

// SECTION UNION (RAM-only) is incompatible with SECTION FRAGMENT (ROM-only)
if (sect->modifier == SECTION_UNION)
fatalerror("`SECTION UNION` cannot contain inline fragments\n");

// A section containing an inline fragment has to become a fragment too
sect->modifier = SECTION_FRAGMENT;

sect_PushSection();

// `SECTION "...", ROM0, BANK[0]` is not allowed
uint32_t bank = sect->bank == 0 ? -1 : sect->bank;

struct Section *newSect = createSection(sect->name, sect->type, -1, bank, 0, 0,
SECTION_FRAGMENT);

// Add the new section fragment to the list (after the section containing it)
newSect->next = sect->next;
sect->next = newSect;

changeSection();
curOffset = newSect->size;
currentSection = newSect;
}

struct Section *sect_GetSymbolSection(void)
{
return currentLoadSection ? currentLoadSection : currentSection;
Expand Down
8 changes: 4 additions & 4 deletions test/asm/code-after-endm-endr-endc.err
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
error: code-after-endm-endr-endc.asm(6):
syntax error, unexpected PRINTLN, expecting newline or end of buffer
syntax error, unexpected PRINTLN, expecting newline or end of line or end of buffer
error: code-after-endm-endr-endc.asm(7):
Macro "mac" not defined
error: code-after-endm-endr-endc.asm(12):
syntax error, unexpected PRINTLN, expecting newline or end of buffer
syntax error, unexpected PRINTLN, expecting newline or end of line or end of buffer
error: code-after-endm-endr-endc.asm(17):
syntax error, unexpected PRINTLN, expecting newline
error: code-after-endm-endr-endc.asm(19):
syntax error, unexpected PRINTLN, expecting newline or end of buffer
syntax error, unexpected PRINTLN, expecting newline or end of line or end of buffer
error: code-after-endm-endr-endc.asm(23):
syntax error, unexpected PRINTLN, expecting newline
error: code-after-endm-endr-endc.asm(25):
syntax error, unexpected PRINTLN, expecting newline or end of buffer
syntax error, unexpected PRINTLN, expecting newline or end of line or end of buffer
error: Assembly aborted (7 errors)!
14 changes: 14 additions & 0 deletions test/asm/inline-fragment-in-load.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
SECTION "OAMDMACode", ROM0
OAMDMACode:
LOAD "hOAMDMA", HRAM
hOAMDMA::
ldh [$ff46], a
ld a, 40
jp [[
: dec a
jr nz, :-
ret
]]
.end
ENDL
OAMDMACodeEnd:
2 changes: 2 additions & 0 deletions test/asm/inline-fragment-in-load.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FATAL: inline-fragment-in-load.asm(7):
`LOAD` blocks cannot contain inline fragments
Empty file.
9 changes: 9 additions & 0 deletions test/asm/inline-fragment-in-ram.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
SECTION "RAM", WRAM0

wFoo:: db
wBar:: ds 3
println "ok"
wQux:: dw [[
ds 4
println "inline"
]]
4 changes: 4 additions & 0 deletions test/asm/inline-fragment-in-ram.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ERROR: inline-fragment-in-ram.asm(6):
Section 'RAM' cannot contain code or data (not ROM0 or ROMX)
FATAL: inline-fragment-in-ram.asm(9):
No entries in the section stack
2 changes: 2 additions & 0 deletions test/asm/inline-fragment-in-ram.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ok
inline
5 changes: 5 additions & 0 deletions test/asm/inline-fragment-in-union.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SECTION UNION "U", ROM0
db $11
dw [[ db $22 ]]
SECTION UNION "U", ROM0
db $33
2 changes: 2 additions & 0 deletions test/asm/inline-fragment-in-union.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FATAL: inline-fragment-in-union.asm(3):
`SECTION UNION` cannot contain inline fragments
Empty file.
58 changes: 58 additions & 0 deletions test/asm/inline-fragments.asm
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
SECTION "1", ROM0[0]

VERSION EQU $11
GetVersion::
ld a, [ [[db VERSION]] ]
ret

SECTION "2", ROM0, ALIGN[4]

text: MACRO
db \1, 0
ENDM

text_pointer: MACRO
dw [[ text \1 ]]
ENDM

GetText::
ld hl, [[
dw [[ db "Alpha", 0 ]]
dw [[ text "Beta" ]]
text_pointer "Gamma"
dw 0
]]
ld c, a
ld b, 0
add hl, bc
add hl, bc
ld a, [hli]
ld h, [hl]
ld l, a
ret

SECTION "C", ROM0

Foo::
call [[ jp [[ jp [[ ret ]] ]] ]]
call [[
Label::
call GetVersion
MYTEXT EQU 3
ld a, MYTEXT
call GetText
ld b, h
ld c, l
ret
]]
jp [[
Bar:
inc hl
.loop
halt
: dec l
jr nz, :-
dec h
jr nz, .loop
ret
]]
Empty file added test/asm/inline-fragments.err
Empty file.
Empty file added test/asm/inline-fragments.out
Empty file.
Binary file added test/asm/inline-fragments.out.bin
Binary file not shown.

0 comments on commit 7a5fdd5

Please sign in to comment.