diff --git a/include/asm/section.h b/include/asm/section.h index 9f0efd92b9..4469144da1 100644 --- a/include/asm/section.h +++ b/include/asm/section.h @@ -45,6 +45,7 @@ void out_NewSection(char const *name, uint32_t secttype, uint32_t org, void out_SetLoadSection(char const *name, uint32_t secttype, uint32_t org, struct SectionSpec const *attributes); void out_EndLoadSection(void); +void out_PushInlineFragmentSection(void); struct Section *sect_GetSymbolSection(void); uint32_t sect_GetSymbolOffset(void); diff --git a/src/asm/lexer.c b/src/asm/lexer.c index c29fc26b2f..dd3e58cf65 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -1251,6 +1251,8 @@ static void 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 == '_'; } @@ -1648,10 +1650,6 @@ static int yylex_NORMAL(void) yylval.tzSym[1] = '\0'; return T_ID; - case '[': - return T_LBRACK; - case ']': - return T_RBRACK; case '(': return T_LPAREN; case ')': @@ -1661,6 +1659,18 @@ static int yylex_NORMAL(void) /* Handle ambiguous 1- or 2-char tokens */ + case '[': /* Either [ or [[ */ + if (peek(0) == '[') { + shiftChars(1); + return T_2LBRACK; + } + return T_LBRACK; + case ']': /* Either ] or ]] */ + if (peek(0) == ']') { + shiftChars(1); + return T_2RBRACK; + } + return T_RBRACK; case '*': /* Either MUL or EXP */ if (peek(0) == '*') { shiftChars(1); diff --git a/src/asm/parser.y b/src/asm/parser.y index c0e2b1419f..263565f8e7 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -38,6 +38,7 @@ static bool executeElseBlock; /* If this is set, ELIFs cannot be executed anymore */ 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) { @@ -436,6 +437,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" @@ -492,6 +494,7 @@ enum { %token T_ANON "anonymous label" %type scoped_id %type scoped_anon_id +%type inline_fragment %token T_POP_EQU "EQU" %token T_POP_SET "SET" %token T_POP_EQUAL "=" @@ -1206,6 +1209,7 @@ reloc_16bit : relocexpr { warning(WARNING_TRUNCATION, "Expression must be 16-bit\n"); $$ = $1; } + | inline_fragment { rpn_Symbol(&$$, $1); } ; reloc_16bit_no_str : relocexpr_no_str { @@ -1214,8 +1218,18 @@ reloc_16bit_no_str : relocexpr_no_str { warning(WARNING_TRUNCATION, "Expression must be 16-bit\n"); $$ = $1; } + | inline_fragment { rpn_Symbol(&$$, $1); } ; +inline_fragment : T_2LBRACK { + out_PushInlineFragmentSection(); + sprintf($$, "$%" PRIu32, inlineFragmentID++); + sym_AddLabel($$); + } asmfile T_2RBRACK { + out_PopSection(); + strcpy($$, $2); + } +; relocexpr : relocexpr_no_str | string { diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index 04402096cc..d4b9085de8 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -812,6 +812,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/section.c b/src/asm/section.c index e77018eacb..710511de44 100644 --- a/src/asm/section.c +++ b/src/asm/section.c @@ -249,7 +249,48 @@ static void mergeSections(struct Section *sect, enum SectionType type, uint32_t #undef fail /* - * Find a section by name and type. If it doesn't exist, create it + * Create a new section, not yet in the list. + */ +static struct Section *createSection(char const *name, enum SectionType type, + uint32_t org, uint32_t bank, uint8_t alignment, + uint16_t alignOffset, enum SectionModifier mod) +{ + struct Section *sect = malloc(sizeof(*sect)); + + if (sect == NULL) + fatalerror("Not enough memory for section: %s\n", strerror(errno)); + + sect->name = strdup(name); + if (sect->name == NULL) + fatalerror("Not enough memory for section name: %s\n", strerror(errno)); + + sect->type = type; + sect->modifier = mod; + sect->size = 0; + sect->org = org; + sect->bank = bank; + sect->align = alignment; + sect->alignOfs = alignOffset; + sect->next = NULL; + sect->patches = NULL; + + /* It is only needed to allocate memory for ROM sections. */ + if (sect_HasData(type)) { + uint32_t sectsize; + + sectsize = maxsize[type]; + sect->data = malloc(sectsize); + if (sect->data == NULL) + fatalerror("Not enough memory for section: %s\n", strerror(errno)); + } else { + sect->data = NULL; + } + + return sect; +} + +/* + * Find a section by name and type. If it doesn't exist, create it. */ static struct Section *getSection(char const *name, enum SectionType type, uint32_t org, struct SectionSpec const *attrs, enum SectionModifier mod) @@ -317,42 +358,13 @@ static struct Section *getSection(char const *name, enum SectionType type, uint3 if (sect) { mergeSections(sect, type, org, bank, alignment, alignOffset, mod); - return sect; - } - - sect = malloc(sizeof(*sect)); - if (sect == NULL) - fatalerror("Not enough memory for section: %s\n", strerror(errno)); - - sect->name = strdup(name); - if (sect->name == NULL) - fatalerror("Not enough memory for section name: %s\n", strerror(errno)); - - sect->type = type; - sect->modifier = mod; - sect->size = 0; - sect->org = org; - sect->bank = bank; - sect->align = alignment; - sect->alignOfs = alignOffset; - sect->patches = NULL; - - /* It is only needed to allocate memory for ROM sections. */ - if (sect_HasData(type)) { - uint32_t sectsize; - - sectsize = maxsize[type]; - sect->data = malloc(sectsize); - if (sect->data == NULL) - fatalerror("Not enough memory for section: %s\n", strerror(errno)); } else { - sect->data = NULL; + sect = createSection(name, type, org, bank, alignment, alignOffset, mod); + // Add the new section to the list (order doesn't matter) + sect->next = pSectionList; + pSectionList = sect; } - // Add the new section to the list (order doesn't matter) - sect->next = pSectionList; - pSectionList = sect; - return sect; } @@ -416,6 +428,39 @@ void out_EndLoadSection(void) loadOffset = 0; } +void out_PushInlineFragmentSection(void) +{ + checkcodesection(); + + if (currentLoadSection) + fatalerror("`LOAD` blocks cannot contain inline fragments\n"); + + struct Section *sect = pCurrentSection; + + // SECTION UNION (RAM-only) is incompatible with SECTION FRAGMENT (ROM-only) + if (sect->modifier == SECTION_UNION) + fatalerror("`SECTION UNION` cannot contain inline fragments"); + + // A section containing an inline fragment has to become a fragment too + sect->modifier = SECTION_FRAGMENT; + + out_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; + pCurrentSection = newSect; +} + struct Section *sect_GetSymbolSection(void) { return currentLoadSection ? currentLoadSection : pCurrentSection; 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..60328b1d74 --- /dev/null +++ b/test/asm/inline-fragment-in-ram.err @@ -0,0 +1,2 @@ +FATAL: inline-fragment-in-ram.asm(6): + Section 'RAM' cannot contain code or data (not ROM0 or ROMX) diff --git a/test/asm/inline-fragment-in-ram.out b/test/asm/inline-fragment-in-ram.out new file mode 100644 index 0000000000..9766475a41 --- /dev/null +++ b/test/asm/inline-fragment-in-ram.out @@ -0,0 +1 @@ +ok 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..7c21859b6d --- /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 \ No newline at end of file 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..b4c5d2ca83 --- /dev/null +++ b/test/asm/inline-fragments.asm @@ -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 + ]] 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..96389c2aaa Binary files /dev/null and b/test/asm/inline-fragments.out.bin differ