From 18f5f92baea322ee8d3107b3d939b36f08de561b Mon Sep 17 00:00:00 2001 From: Daniil Zaikin Date: Sun, 22 Sep 2024 17:34:18 +1000 Subject: [PATCH] implement basic functionality to occasionally parse keywords as identifiers --- CMakePresets.json | 24 ++++++++++++++++++++++++ Caprica/common/CapricaReportingContext.h | 7 +++++++ Caprica/main_options.cpp | 4 ++++ Caprica/papyrus/parser/PapyrusLexer.cpp | 19 +++++++++++++++++++ Caprica/papyrus/parser/PapyrusLexer.h | 1 + Caprica/papyrus/parser/PapyrusParser.cpp | 22 +++++++++++++++++++++- Caprica/papyrus/parser/PapyrusParser.h | 1 + 7 files changed, 77 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 9e3b418..9f1a3b0 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -69,6 +69,24 @@ "value": "RelWithDebInfo" } } + }, + { + "name": "build-debug-msvc", + "inherits": [ + "base", + "vcpkg", + "x64", + "msvc" + ], + "displayName": "Debug", + "description": "Debug build for testing.", + "binaryDir": "${sourceDir}/build/debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": { + "type": "STRING", + "value": "Debug" + } + } } ], "buildPresets": [ @@ -77,6 +95,12 @@ "displayName": "Release (MSVC)", "configurePreset": "build-release-msvc", "description": "Optimized release Build." + }, + { + "name": "debug-msvc", + "displayName": "Debug (MSVC)", + "configurePreset": "build-debug-msvc", + "description": "Debug build for local testing." } ] } \ No newline at end of file diff --git a/Caprica/common/CapricaReportingContext.h b/Caprica/common/CapricaReportingContext.h index 22deaec..c7f9425 100644 --- a/Caprica/common/CapricaReportingContext.h +++ b/Caprica/common/CapricaReportingContext.h @@ -133,6 +133,13 @@ struct CapricaReportingContext final { identifier_ref, parentName) + // Not a base game warning, but some ubiquitous libraries fall prey to this. + DEFINE_WARNING_A1(1006, + Strict_Keyword_Identifiers, + "'{}' is a keyword (e.g. Int, Function) but is used as an identifier.", + identifier_ref, + idName) + // Warnings 2000-2199 are for engine imposed limitations. DEFINE_WARNING_A2(2001, diff --git a/Caprica/main_options.cpp b/Caprica/main_options.cpp index 06efd91..8240165 100644 --- a/Caprica/main_options.cpp +++ b/Caprica/main_options.cpp @@ -206,6 +206,8 @@ bool parseCommandLineArguments(int argc, char* argv[], caprica::CapricaJobManage "Ensure values returned from BetaOnly and DebugOnly functions don't escape, as that will cause invalid code generation.") ("disable-implicit-conversion-from-none", po::bool_switch()->default_value(false), "Disable implicit conversion from None in most situations where the use of None likely wasn't the author's intention.") + ("disable-keywords-as-identifiers", po::bool_switch()->default_value(false), + "Disable the ability to use some keywords (e.g. Switch, Parent) as identifiers in select cases.") ("skyrim-allow-unknown-events-on-non-native-class", po::value(&conf::Skyrim::skyrimAllowUnknownEventsOnNonNativeClass)->default_value(true), "Allow unknown events to be defined on non-native classes. This is encountered with some scripts in the base game having Events that are not present on ObjectReference."); @@ -560,6 +562,8 @@ bool parseCommandLineArguments(int argc, char* argv[], caprica::CapricaJobManage conf::Warnings::warningsToHandleAsErrors.insert(1002); } else if (vm["disable-implicit-conversion-from-none"].as()) { conf::Warnings::warningsToHandleAsErrors.insert(1003); + } else if (vm["disable-keywords-as-identifiers"].as()) { + conf::Warnings::warningsToHandleAsErrors.insert(1006); } if (vm.count("disable-warning")) { diff --git a/Caprica/papyrus/parser/PapyrusLexer.cpp b/Caprica/papyrus/parser/PapyrusLexer.cpp index 7f26afe..134acbe 100644 --- a/Caprica/papyrus/parser/PapyrusLexer.cpp +++ b/Caprica/papyrus/parser/PapyrusLexer.cpp @@ -236,6 +236,25 @@ static const caseless_unordered_identifier_ref_map languageExtensions { "to", TokenType::kTo }, }; +// keywords which can never pass as identifiers +static const std::unordered_set nonIdentifiersSet { + TokenType::kAuto, + TokenType::kAutoReadOnly, + TokenType::kBool, + TokenType::kConst, + TokenType::kFalse, + TokenType::kFloat, + TokenType::kInt, + TokenType::kNone, + TokenType::kString, + TokenType::kStruct, + TokenType::kVar, +}; + +const bool keywordCanBeIdentifier(TokenType tp) { + return keywordIsInGame(tp, conf::Papyrus::game, true) && nonIdentifiersSet.find(tp) == nonIdentifiersSet.end(); +} + ALWAYS_INLINE static bool isAsciiAlphaNumeric(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); diff --git a/Caprica/papyrus/parser/PapyrusLexer.h b/Caprica/papyrus/parser/PapyrusLexer.h index 222abe2..db4e623 100644 --- a/Caprica/papyrus/parser/PapyrusLexer.h +++ b/Caprica/papyrus/parser/PapyrusLexer.h @@ -141,6 +141,7 @@ constexpr TokenType STARFIELD_MAX_KEYWORD = TokenType::kTryGuard; constexpr bool keywordIsLanguageExtension(TokenType tp) { return tp >= TokenType::kBreak && tp <= TokenType::kTo; } +const bool keywordCanBeIdentifier(TokenType tp); constexpr bool keywordIsInGame(TokenType tp, GameID game, bool includeExtensions = false) { if (includeExtensions && keywordIsLanguageExtension(tp)) return true; diff --git a/Caprica/papyrus/parser/PapyrusParser.cpp b/Caprica/papyrus/parser/PapyrusParser.cpp index 48874a0..3962908 100644 --- a/Caprica/papyrus/parser/PapyrusParser.cpp +++ b/Caprica/papyrus/parser/PapyrusParser.cpp @@ -540,7 +540,7 @@ PapyrusFunction* PapyrusParser::parseFunction( auto param = alloc->make(cur.location, func->parameters.size(), expectConsumePapyrusType()); - param->name = expectConsumeIdentRef(); + param->name = expectConsumeKeywordOrIdentRef(); if (maybeConsume(TokenType::Equal)) param->defaultValue = expectConsumePapyrusValue(); func->parameters.push_back(param); @@ -1176,6 +1176,26 @@ expressions::PapyrusExpression* PapyrusParser::parseFuncOrIdExpression(PapyrusFu } } +identifier_ref PapyrusParser::expectConsumeKeywordOrIdentRef() { + if (!keywordCanBeIdentifier(cur.type)) { + reportingContext.fatal(cur.location, + "Syntax error! Expected valid identifier, got '{}'.", + cur.prettyString()); + } + + identifier_ref finalId; + + if (cur.type != TokenType::Identifier) { + const identifier_ref typeIdentifier = identifier_ref(PapyrusLexer::Token::prettyTokenType(cur.type)); + reportingContext.warning_W1006_Strict_Keyword_Identifiers(cur.location, typeIdentifier); + finalId = typeIdentifier; + } else { + finalId = cur.val.s; + } + consume(); + return finalId; +} + PapyrusType PapyrusParser::expectConsumePapyrusType() { PapyrusType tp = PapyrusType::Default(); switch (cur.type) { diff --git a/Caprica/papyrus/parser/PapyrusParser.h b/Caprica/papyrus/parser/PapyrusParser.h index deb021a..24ccc2f 100644 --- a/Caprica/papyrus/parser/PapyrusParser.h +++ b/Caprica/papyrus/parser/PapyrusParser.h @@ -48,6 +48,7 @@ struct PapyrusParser final : private PapyrusLexer { PapyrusType expectConsumePapyrusType(); PapyrusValue expectConsumePapyrusValue(); + identifier_ref expectConsumeKeywordOrIdentRef(); PapyrusUserFlags maybeConsumeUserFlags(CapricaUserFlagsDefinition::ValidLocations location); ALWAYS_INLINE