diff --git a/include/asm/section.h b/include/asm/section.h index d92aca67f1..cebea49f4e 100644 --- a/include/asm/section.h +++ b/include/asm/section.h @@ -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); diff --git a/man/rgbasm.5 b/man/rgbasm.5 index 4536629017..ea01148d3b 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -852,6 +852,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 diff --git a/src/asm/lexer.c b/src/asm/lexer.c index fb07bec8bc..ada6f4f014 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -339,6 +339,7 @@ struct LexerState { uint32_t lineNo; uint32_t colNo; int lastToken; + int nextToken; struct IfStack *ifStack; @@ -362,6 +363,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; @@ -1266,6 +1268,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 == '_'; } @@ -1741,6 +1744,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; @@ -1765,10 +1775,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 ')': @@ -1778,6 +1784,24 @@ 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(); diff --git a/src/asm/parser.y b/src/asm/parser.y index 3836bb1569..0aed97dfd8 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -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) { @@ -520,6 +521,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" @@ -583,6 +585,7 @@ enum { %type redef_id %type scoped_id %type scoped_anon_id +%type inline_fragment %token T_POP_EQU "EQU" %token T_POP_EQUAL "=" %token T_POP_EQUS "EQUS" @@ -678,6 +681,7 @@ enum { %type op_mem_ind %type assert_type +%token T_EOL "end of line" %token T_EOB "end of buffer" %token T_EOF 0 "end of file" %start asmfile @@ -691,7 +695,7 @@ lines : %empty | lines line ; -endofline : T_NEWLINE | T_EOB +endofline : T_NEWLINE | T_EOL | T_EOB ; plain_directive : label @@ -1360,14 +1364,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($$, "$%" PRIu32, inlineFragmentID++); + sym_AddLabel($$); + } asmfile T_2RBRACK { + sect_PopSection(); + strcpy($$, $2); + } +; relocexpr : relocexpr_no_str | string { diff --git a/src/asm/section.c b/src/asm/section.c index d33f55cb47..e22fe8889f 100644 --- a/src/asm/section.c +++ b/src/asm/section.c @@ -458,6 +458,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 ? (uint32_t)-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; diff --git a/test/asm/code-after-endm-endr-endc.err b/test/asm/code-after-endm-endr-endc.err index 1152bd1d86..76501efb32 100644 --- a/test/asm/code-after-endm-endr-endc.err +++ b/test/asm/code-after-endm-endr-endc.err @@ -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)! diff --git a/test/asm/inline-fragment-in-load.asm b/test/asm/inline-fragment-in-load.asm new file mode 100644 index 0000000000..87768688c6 --- /dev/null +++ b/test/asm/inline-fragment-in-load.asm @@ -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: diff --git a/test/asm/inline-fragment-in-load.err b/test/asm/inline-fragment-in-load.err new file mode 100644 index 0000000000..28f00e4d03 --- /dev/null +++ b/test/asm/inline-fragment-in-load.err @@ -0,0 +1,2 @@ +FATAL: inline-fragment-in-load.asm(7): + `LOAD` blocks cannot contain inline fragments diff --git a/test/asm/inline-fragment-in-load.out b/test/asm/inline-fragment-in-load.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/asm/inline-fragment-in-ram.asm b/test/asm/inline-fragment-in-ram.asm new file mode 100644 index 0000000000..4b36a16a96 --- /dev/null +++ b/test/asm/inline-fragment-in-ram.asm @@ -0,0 +1,9 @@ +SECTION "RAM", WRAM0 + +wFoo:: db +wBar:: ds 3 + println "ok" +wQux:: dw [[ + ds 4 + println "inline" +]] diff --git a/test/asm/inline-fragment-in-ram.err b/test/asm/inline-fragment-in-ram.err new file mode 100644 index 0000000000..502259f5c4 --- /dev/null +++ b/test/asm/inline-fragment-in-ram.err @@ -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 diff --git a/test/asm/inline-fragment-in-ram.out b/test/asm/inline-fragment-in-ram.out new file mode 100644 index 0000000000..bd36db679c --- /dev/null +++ b/test/asm/inline-fragment-in-ram.out @@ -0,0 +1,2 @@ +ok +inline diff --git a/test/asm/inline-fragment-in-union.asm b/test/asm/inline-fragment-in-union.asm new file mode 100644 index 0000000000..9f2e75cf8f --- /dev/null +++ b/test/asm/inline-fragment-in-union.asm @@ -0,0 +1,5 @@ +SECTION UNION "U", ROM0 + db $11 + dw [[ db $22 ]] +SECTION UNION "U", ROM0 + db $33 diff --git a/test/asm/inline-fragment-in-union.err b/test/asm/inline-fragment-in-union.err new file mode 100644 index 0000000000..9c8184bb47 --- /dev/null +++ b/test/asm/inline-fragment-in-union.err @@ -0,0 +1,2 @@ +FATAL: inline-fragment-in-union.asm(3): + `SECTION UNION` cannot contain inline fragments diff --git a/test/asm/inline-fragment-in-union.out b/test/asm/inline-fragment-in-union.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/asm/inline-fragments.asm b/test/asm/inline-fragments.asm new file mode 100644 index 0000000000..233d5b383c --- /dev/null +++ b/test/asm/inline-fragments.asm @@ -0,0 +1,62 @@ +SECTION "1", ROM0[0] + +VERSION EQU $11 +GetVersion:: + ld a, [ [[db VERSION]] ] + ret + +SECTION "2", ROM0, ALIGN[4] + +MACRO text + db \1, 0 +ENDM + +MACRO text_pointer + 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 + nop +: dec l + jr nz, :- + dec h + jr nz, .loop + ret + ]] diff --git a/test/asm/inline-fragments.err b/test/asm/inline-fragments.err new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/asm/inline-fragments.out b/test/asm/inline-fragments.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/asm/inline-fragments.out.bin b/test/asm/inline-fragments.out.bin new file mode 100644 index 0000000000..ddf72d409a Binary files /dev/null and b/test/asm/inline-fragments.out.bin differ