diff --git a/include/asm/symbol.hpp b/include/asm/symbol.hpp index 779fe9fc..acbdd42a 100644 --- a/include/asm/symbol.hpp +++ b/include/asm/symbol.hpp @@ -68,6 +68,8 @@ struct Symbol { uint32_t getConstantValue() const; }; +bool sym_IsDotScope(std::string const &symName); + void sym_ForEach(void (*callback)(Symbol &)); Symbol *sym_AddLocalLabel(std::string const &symName); diff --git a/src/asm/lexer.cpp b/src/asm/lexer.cpp index 8ba89b2c..0249a552 100644 --- a/src/asm/lexer.cpp +++ b/src/asm/lexer.cpp @@ -1295,7 +1295,7 @@ static Token readIdentifier(char firstChar, bool raw) { } // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot - if (identifier.find_first_not_of('.') == identifier.npos) { + if (sym_IsDotScope(identifier)) { tokenType = T_(SYMBOL); } @@ -1939,11 +1939,12 @@ static Token yylex_NORMAL() { // `token` is either a `SYMBOL` or a `LOCAL`, and both have a `std::string` value. assume(std::holds_alternative(token.value)); + std::string const &identifier = std::get(token.value); // Raw symbols and local symbols cannot be string expansions if (!raw && token.type == T_(SYMBOL) && lexerState->expandStrings) { // Attempt string expansion - if (Symbol const *sym = sym_FindExactSymbol(std::get(token.value)); + if (Symbol const *sym = sym_FindExactSymbol(identifier); sym && sym->type == SYM_EQUS) { beginExpansion(sym->getEqus(), sym->name); continue; // Restart, reading from the new buffer @@ -1954,6 +1955,7 @@ static Token yylex_NORMAL() { // - label definitions (which are followed by a ':' and use the token `LABEL`) // - quiet macro invocations (which are followed by a '?' and use the token `QMACRO`) // - regular macro invocations (which use the token `SYMBOL`) + // - label scopes "." and ".." (which use the token `SYMBOL` no matter what) // // If we had one `IDENTIFIER` token, the parser would need to perform "lookahead" to // determine which rule applies. But since macros need to enter "raw" mode to parse @@ -1964,7 +1966,7 @@ static Token yylex_NORMAL() { // one to lex depending on the character *immediately* following the identifier. // Thus "name:" is a label definition, and "name?" is a quiet macro invocation, but // "name :" and "name ?" and just "name" are all regular macro invocations. - if (token.type == T_(SYMBOL)) { + if (token.type == T_(SYMBOL) && !sym_IsDotScope(identifier)) { c = peek(); token.type = c == ':' ? T_(LABEL) : c == '?' ? T_(QMACRO) : T_(SYMBOL); } diff --git a/src/asm/symbol.cpp b/src/asm/symbol.cpp index 571dd2f3..e6912c9e 100644 --- a/src/asm/symbol.cpp +++ b/src/asm/symbol.cpp @@ -53,6 +53,12 @@ bool sym_IsPC(Symbol const *sym) { return sym == PCSymbol; } +bool sym_IsDotScope(std::string const &symName) { + // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot. + // Three or more dots are considered a nonsensical local label. + return symName == "." || symName == ".."; +} + void sym_ForEach(void (*callback)(Symbol &)) { for (auto &it : symbols) { callback(it.second); @@ -215,8 +221,8 @@ static void redefinedError(Symbol const &sym) { static void assumeAlreadyExpanded(std::string const &symName) { // Either the symbol name is `Global.local` or entirely '.'s (for scopes `.` and `..`), - // but cannot be unqualified `.local` - assume(!symName.starts_with('.') || symName.find_first_not_of('.') == symName.npos); + // but cannot be unqualified `.local` or more than two '.'s + assume(!symName.starts_with('.') || sym_IsDotScope(symName)); } static Symbol &createSymbol(std::string const &symName) { @@ -253,7 +259,7 @@ static bool isAutoScoped(std::string const &symName) { } // Label scopes `.` and `..` are the only nonlocal identifiers that start with a dot - if (dotPos == 0 && symName.find_first_not_of('.') == symName.npos) { + if (sym_IsDotScope(symName)) { return false; } diff --git a/test/asm/dots-constant.asm b/test/asm/dots-constant.asm new file mode 100644 index 00000000..3280b8ae --- /dev/null +++ b/test/asm/dots-constant.asm @@ -0,0 +1,2 @@ +section "test", rom0 +def #... equs "sublocal" diff --git a/test/asm/dots-constant.err b/test/asm/dots-constant.err new file mode 100644 index 00000000..8c64d28d --- /dev/null +++ b/test/asm/dots-constant.err @@ -0,0 +1,3 @@ +error: syntax error, unexpected local label, expecting symbol + at dots-constant.asm(2) +Assembly aborted with 1 error! diff --git a/test/asm/dots-interpolate.asm b/test/asm/dots-interpolate.asm new file mode 100644 index 00000000..865a7ecb --- /dev/null +++ b/test/asm/dots-interpolate.asm @@ -0,0 +1,2 @@ +section "test", rom0 +println "{...}" diff --git a/test/asm/dots-interpolate.err b/test/asm/dots-interpolate.err new file mode 100644 index 00000000..b3c4ca3e --- /dev/null +++ b/test/asm/dots-interpolate.err @@ -0,0 +1,2 @@ +FATAL: `...` is a nonsensical reference to a nested local label + at dots-interpolate.asm(2) diff --git a/test/asm/dots-label.asm b/test/asm/dots-label.asm new file mode 100644 index 00000000..4549b481 --- /dev/null +++ b/test/asm/dots-label.asm @@ -0,0 +1,2 @@ +section "test", rom0 +#...: diff --git a/test/asm/dots-label.err b/test/asm/dots-label.err new file mode 100644 index 00000000..89b948e7 --- /dev/null +++ b/test/asm/dots-label.err @@ -0,0 +1,2 @@ +FATAL: `...` is a nonsensical reference to a nested local label + at dots-label.asm(2) diff --git a/test/asm/dots-macro-arg.asm b/test/asm/dots-macro-arg.asm new file mode 100644 index 00000000..fce8ec1a --- /dev/null +++ b/test/asm/dots-macro-arg.asm @@ -0,0 +1,4 @@ +MACRO test + println "\<...>" +ENDM + test 1, 2, 3, 4 diff --git a/test/asm/dots-macro-arg.err b/test/asm/dots-macro-arg.err new file mode 100644 index 00000000..6fb2b413 --- /dev/null +++ b/test/asm/dots-macro-arg.err @@ -0,0 +1,2 @@ +FATAL: `...` is a nonsensical reference to a nested local label + at dots-macro-arg.asm::test(2) <- dots-macro-arg.asm(4) diff --git a/test/asm/label-scope.asm b/test/asm/label-scope.asm index 6ea225ec..8ee4f2c1 100644 --- a/test/asm/label-scope.asm +++ b/test/asm/label-scope.asm @@ -16,3 +16,17 @@ ASSERT DEF(@) && DEF(.) && DEF(..) && DEF(Foo) && DEF(.bar) PRINTLN "PC: {#05X:@}" PRINTLN "global scope: \"{.}\" ({#05X:{.}})" PRINTLN "local scope: \"{..}\" ({#05X:{..}})" + +SECTION "can't redefine", ROM0 + +#. +#.: +#.? +DEF #. EQUS "global" +jp #. + +#.. +#..: +#..? +DEF #.. EQUS "local" +jp #.. diff --git a/test/asm/label-scope.err b/test/asm/label-scope.err index db4b599d..3075c3d1 100644 --- a/test/asm/label-scope.err +++ b/test/asm/label-scope.err @@ -10,4 +10,32 @@ error: Built-in symbol `.` cannot be purged at label-scope.asm(12) error: Built-in symbol `..` cannot be purged at label-scope.asm(12) -Assembly aborted with 6 errors! +error: `.` is not a macro + at label-scope.asm(22) +error: `.` is not a macro + at label-scope.asm(23) +error: `.` is not a macro + at label-scope.asm(24) +error: `.` is reserved for a built-in symbol + at label-scope.asm(25) +error: `.` has no value outside of a label scope + at label-scope.asm(26) +warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete] + at label-scope.asm(26) +error: Strings as numbers must be a single charmap unit + at label-scope.asm(26) +error: `..` is not a macro + at label-scope.asm(28) +error: `..` is not a macro + at label-scope.asm(29) +error: `..` is not a macro + at label-scope.asm(30) +error: `..` is reserved for a built-in symbol + at label-scope.asm(31) +error: `..` has no value outside of a local label scope + at label-scope.asm(32) +warning: Treating strings as numbers is deprecated; use character literals or `CHARVAL` instead [-Wobsolete] + at label-scope.asm(32) +error: Strings as numbers must be a single charmap unit + at label-scope.asm(32) +Assembly aborted with 18 errors!