diff --git a/include/asm/section.hpp b/include/asm/section.hpp index 17d39945c..aaf04735a 100644 --- a/include/asm/section.hpp +++ b/include/asm/section.hpp @@ -43,6 +43,7 @@ void sect_NewSection(char const *name, enum SectionType type, uint32_t org, void sect_SetLoadSection(char const *name, enum SectionType type, 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 e39452c39..2ac9a4612 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -921,6 +921,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.cpp b/src/asm/lexer.cpp index 9175160ca..87f9abff9 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -336,6 +336,7 @@ struct LexerState { uint32_t lineNo; uint32_t colNo; int lastToken; + int nextToken; struct IfStack *ifStack; @@ -359,6 +360,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; @@ -1290,6 +1292,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 == '_'; } @@ -1767,6 +1770,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(); @@ -1790,10 +1800,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 ')': @@ -1803,6 +1809,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 8b63035eb..2c6e25783 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -31,6 +31,7 @@ #include "platform.hpp" // 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) { @@ -545,6 +546,7 @@ enum { %token T_COMMA "," %token T_COLON ":" T_DOUBLE_COLON "::" %token T_LBRACK "[" T_RBRACK "]" +%token T_2LBRACK "[[" T_2RBRACK "]]" %token T_LPAREN "(" T_RPAREN ")" %token T_NEWLINE "newline" @@ -611,6 +613,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" @@ -709,6 +712,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 @@ -722,7 +726,7 @@ lines : %empty | lines opt_diff_mark line ; -endofline : T_NEWLINE | T_EOB +endofline : T_NEWLINE | T_EOL | T_EOB ; opt_diff_mark : %empty // OK @@ -1459,14 +1463,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.cpp b/src/asm/section.cpp index 1d9428ee4..d35b47ded 100644 --- a/src/asm/section.cpp +++ b/src/asm/section.cpp @@ -455,6 +455,40 @@ void sect_EndLoadSection(void) sym_SetCurrentSymbolScope(currentLoadScope); } +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 1152bd1d8..76501efb3 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 000000000..87768688c --- /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 000000000..28f00e4d0 --- /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 000000000..e69de29bb diff --git a/test/asm/inline-fragment-in-ram.asm b/test/asm/inline-fragment-in-ram.asm new file mode 100644 index 000000000..4b36a16a9 --- /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 000000000..502259f5c --- /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 000000000..bd36db679 --- /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 000000000..9f2e75cf8 --- /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 000000000..9c8184bb4 --- /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 000000000..e69de29bb diff --git a/test/asm/inline-fragments.asm b/test/asm/inline-fragments.asm new file mode 100644 index 000000000..c9ab16b37 --- /dev/null +++ b/test/asm/inline-fragments.asm @@ -0,0 +1,62 @@ +SECTION "1", ROM0[0] + +DEF 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 + DEF 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 000000000..e69de29bb diff --git a/test/asm/inline-fragments.out b/test/asm/inline-fragments.out new file mode 100644 index 000000000..e69de29bb diff --git a/test/asm/inline-fragments.out.bin b/test/asm/inline-fragments.out.bin new file mode 100644 index 000000000..ddf72d409 Binary files /dev/null and b/test/asm/inline-fragments.out.bin differ