diff --git a/include/asm/symbol.h b/include/asm/symbol.h index 4974a988..f9005bc2 100644 --- a/include/asm/symbol.h +++ b/include/asm/symbol.h @@ -115,6 +115,8 @@ int32_t sym_GetValue(struct Symbol const *sym); void sym_SetExportAll(bool set); struct Symbol *sym_AddLocalLabel(char const *symName); struct Symbol *sym_AddLabel(char const *symName); +struct Symbol *sym_AddAnonLabel(void); +void sym_WriteAnonLabelName(char name[static MAXSYMLEN + 1], uint32_t ofs, bool neg); void sym_Export(char const *symName); struct Symbol *sym_AddEqu(char const *symName, int32_t value); struct Symbol *sym_AddSet(char const *symName, int32_t value); diff --git a/src/asm/lexer.c b/src/asm/lexer.c index 5c98e266..fa92663e 100644 --- a/src/asm/lexer.c +++ b/src/asm/lexer.c @@ -1045,6 +1045,21 @@ static void readLineContinuation(void) } } +/* Function to read an anonymous label ref */ + +static void readAnonLabelRef(char c) +{ + uint32_t n = 0; + + // We come here having already peeked at one char, so no need to do it again + do { + shiftChars(1); + n++; + } while (peek(0) == c); + + sym_WriteAnonLabelName(yylval.tzSym, n, c == '-'); +} + /* Functions to lex numbers of various radixes */ static void readNumber(int radix, int32_t baseValue) @@ -1568,8 +1583,6 @@ static int yylex_NORMAL(void) yylval.tzSym[1] = '\0'; return T_ID; - /* Handle accepted single chars */ - case '[': return T_LBRACK; case ']': @@ -1580,8 +1593,6 @@ static int yylex_NORMAL(void) return T_RPAREN; case ',': return T_COMMA; - case ':': - return T_COLON; /* Handle ambiguous 1- or 2-char tokens */ char secondChar; @@ -1639,6 +1650,16 @@ static int yylex_NORMAL(void) } return T_OP_LOGICNOT; + /* Handle colon, which may begin an anonymous label ref */ + + case ':': + c = peek(0); + if (c != '+' && c != '-') + return T_COLON; + + readAnonLabelRef(c); + return T_ANON; + /* Handle numbers */ case '$': diff --git a/src/asm/parser.y b/src/asm/parser.y index 06403ad6..7dfb6d75 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -262,7 +262,9 @@ static inline void failAssertMsg(enum AssertionType type, char const *msg) %token T_LABEL %token T_ID %token T_LOCAL_ID +%token T_ANON %type scoped_id +%type scoped_anon_id %token T_POP_EQU %token T_POP_SET %token T_POP_EQUAL @@ -423,9 +425,13 @@ endc : T_POP_ENDC T_NEWLINE { } ; -scoped_id : T_ID | T_LOCAL_ID ; +scoped_id : T_ID | T_LOCAL_ID; +scoped_anon_id : scoped_id | T_ANON; label : /* empty */ + | T_COLON { + sym_AddAnonLabel(); + } | T_LOCAL_ID { sym_AddLocalLabel($1); } @@ -914,7 +920,7 @@ relocexpr : relocexpr_no_str } ; -relocexpr_no_str : scoped_id { rpn_Symbol(&$$, $1); } +relocexpr_no_str : scoped_anon_id { rpn_Symbol(&$$, $1); } | T_NUMBER { rpn_Number(&$$, $1); } | T_OP_LOGICNOT relocexpr %prec NEG { rpn_LOGNOT(&$$, &$2); @@ -979,14 +985,14 @@ relocexpr_no_str : scoped_id { rpn_Symbol(&$$, $1); } | T_OP_HIGH T_LPAREN relocexpr T_RPAREN { rpn_HIGH(&$$, &$3); } | T_OP_LOW T_LPAREN relocexpr T_RPAREN { rpn_LOW(&$$, &$3); } | T_OP_ISCONST T_LPAREN relocexpr T_RPAREN{ rpn_ISCONST(&$$, &$3); } - | T_OP_BANK T_LPAREN scoped_id T_RPAREN { + | T_OP_BANK T_LPAREN scoped_anon_id T_RPAREN { /* '@' is also a T_ID, it is handled here. */ rpn_BankSymbol(&$$, $3); } | T_OP_BANK T_LPAREN string T_RPAREN { rpn_BankSection(&$$, $3); } | T_OP_DEF { lexer_ToggleStringExpansion(false); - } T_LPAREN scoped_id T_RPAREN { + } T_LPAREN scoped_anon_id T_RPAREN { struct Symbol const *sym = sym_FindScopedSymbol($4); rpn_Number(&$$, !!sym); diff --git a/src/asm/rgbasm.5 b/src/asm/rgbasm.5 index 76a6f5d0..502718cc 100644 --- a/src/asm/rgbasm.5 +++ b/src/asm/rgbasm.5 @@ -800,6 +800,33 @@ must be the actual current scope. .Pp Local labels may have whitespace before their declaration as the only exception to the rule. .Pp +.Sy Anonymous labels +are useful for short blocks of code. +They are defined like normal labels, but without a name before the colon. +Defining one does not change the label scope (unlike global labels). +Referencing one is done using a colon +.Ql \&: +followed by pluses +.Ql + +or minuses +.Ql - . +.Ic :+ +references the next one after the expression, +.Ic :++ +the one after it, and so on. +The logic is similar for -, just backwards. +.Bd -literal -offset indent + ld hl, :++ +: ld a, [hli] ; Jumps to here + ldh [c], a + dec c + jr nz, :- + ret + +: ; This address referenced by "ld hl" + dw $7FFF, $1061, $03E0, $58A5 +.Ed +.Pp A label's location (and thus value) is usually not determined until the linking stage, so labels usually cannot be used as constants. However, if the section in which the label is declared has a fixed base address, its value is known at assembly time. .Pp diff --git a/src/asm/symbol.c b/src/asm/symbol.c index 21e73b6a..3ce5c221 100644 --- a/src/asm/symbol.c +++ b/src/asm/symbol.c @@ -515,11 +515,60 @@ struct Symbol *sym_AddLabel(char const *name) return sym; } +static uint32_t anonLabelID; + +/* + * Add an anonymous label + */ +struct Symbol *sym_AddAnonLabel(void) +{ + if (anonLabelID == UINT32_MAX) { + error("Only %" PRIu32 " anonymous labels can be created!"); + return NULL; + } + char name[MAXSYMLEN + 1]; + + sym_WriteAnonLabelName(name, 0, true); // The direction is important!! + anonLabelID++; + return addLabel(name); +} + +/* + * Write an anonymous label's name to a buffer + */ +void sym_WriteAnonLabelName(char buf[static MAXSYMLEN + 1], uint32_t ofs, bool neg) +{ + uint32_t id = 0; + + if (neg) { + if (ofs > anonLabelID) + error("Reference to anonymous label %" PRIu32 " before, when only %" PRIu32 + " ha%s been created so far\n", + ofs, anonLabelID, anonLabelID == 1 ? "s" : "ve"); + else + id = anonLabelID - ofs; + } else { + ofs--; // We're referencing symbols that haven't been created yet... + if (ofs > UINT32_MAX - anonLabelID) + error("Reference to anonymous label %" PRIu32 " after, when only %" PRIu32 + " may still be created\n", ofs + 1, UINT32_MAX - anonLabelID); + else + id = anonLabelID + ofs; + } + + sprintf(buf, "!%u", id); +} + /* * Export a symbol */ void sym_Export(char const *symName) { + if (symName[0] == '!') { + error("Anonymous labels cannot be exported\n"); + return; + } + struct Symbol *sym = sym_FindScopedSymbol(symName); /* If the symbol doesn't exist, create a ref that can be purged */ @@ -671,6 +720,7 @@ void sym_Init(void) #undef addString labelScope = NULL; + anonLabelID = 0; math_DefinePI(); } diff --git a/test/asm/anon-label-bad.asm b/test/asm/anon-label-bad.asm new file mode 100644 index 00000000..4e37519c --- /dev/null +++ b/test/asm/anon-label-bad.asm @@ -0,0 +1,18 @@ + +: ; Outside of section + +SECTION "Anonymous label errors test", ROM0 + + db :-- ; Reference goes too far back + +; Uncomment this if you're a badass with a *lot* of RAM +; REPT 2147483647 +; : +; ENDR +; REPT 2147483647 +; : +; ENDR +; db :+ ; OK +; db :++ ; Reference goes too far + +:: ; Syntax error, can't export this diff --git a/test/asm/anon-label-bad.err b/test/asm/anon-label-bad.err new file mode 100644 index 00000000..74828ed1 --- /dev/null +++ b/test/asm/anon-label-bad.err @@ -0,0 +1,7 @@ +ERROR: anon-label-bad.asm(2): + Label "!0" created outside of a SECTION +ERROR: anon-label-bad.asm(6): + Reference to anonymous label 2 before, when only 1 has been created so far +ERROR: anon-label-bad.asm(18): + syntax error +error: Assembly aborted (3 errors)! diff --git a/test/asm/anon-label-bad.out b/test/asm/anon-label-bad.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/anon-label.asm b/test/asm/anon-label.asm new file mode 100644 index 00000000..6245f637 --- /dev/null +++ b/test/asm/anon-label.asm @@ -0,0 +1,12 @@ + +SECTION "Anonymous label test", ROM0[0] + + ld hl, :++ +: ld a, [hli] + ldh [c], a + dec c + jr nz, :- + ret + +: + dw $7FFF, $1061, $03E0, $58A5 diff --git a/test/asm/anon-label.err b/test/asm/anon-label.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/anon-label.out b/test/asm/anon-label.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/anon-label.out.bin b/test/asm/anon-label.out.bin new file mode 100644 index 00000000..c90314b9 Binary files /dev/null and b/test/asm/anon-label.out.bin differ