diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2021-03-31 13:11:49 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-31 13:11:49 -0400 |
| commit | 5fde038b1a6b3c8b335cd5b380c3ee8d15403052 (patch) | |
| tree | 27975331c960edc405a5294031fd6a4a79eff964 /source/slang/slang-preprocessor.cpp | |
| parent | 5fefb120e0c2469563e937f4ee39b391d7678cdf (diff) | |
Support for __LINE__ and __FILE__ in preprocessor (#1772)
* #include an absolute path didn't work - because paths were taken to always be relative.
* First pass support for __LINE__ and __FILE__.
* Test include handling with __FILE__
Fix diagnostic compare when input is empty.
* Fix some issues in preprocessor handling of special macros like __LINE__
Add a more complex test.
* Use CONCAT2 in tests, because preprocessor doesn't quite get parameter expansion correct.
* Make __FILE__ and __LINE__ behave more like Clang/Gcc.
* A test for preprocessor bug.
* Fix __LINE__ and __FILE__ in macro expansion, should be initiating location.
* Fix some comments.
* Small tidy up around builtin macros.
* Small improvements for macro type names.
Escape found paths.
Diffstat (limited to 'source/slang/slang-preprocessor.cpp')
| -rw-r--r-- | source/slang/slang-preprocessor.cpp | 246 |
1 files changed, 182 insertions, 64 deletions
diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp index f233d46f0..f9d18332d 100644 --- a/source/slang/slang-preprocessor.cpp +++ b/source/slang/slang-preprocessor.cpp @@ -136,7 +136,7 @@ struct MacroExpansion : PretokenizedInputStream // The macro we will expand PreprocessorMacro* macro; - /// State for marking `macro` as busy in thsi expansion + /// State for marking `macro` as busy in this expansion BusyMacro busy; // Environment for macro expansion. @@ -159,14 +159,24 @@ enum class PreprocessorMacroFlavor ObjectLike, FunctionArg, FunctionLike, + BuiltinLine, /// builtin macro __LINE__ + BuiltinFile, /// builtin macro __FILE__ }; +SLANG_FORCE_INLINE bool isBuiltinMacro(PreprocessorMacroFlavor flavor) +{ + return flavor == PreprocessorMacroFlavor::BuiltinLine || flavor == PreprocessorMacroFlavor::BuiltinFile; +} + // In the current design (which we may want to re-consider), // a macro is a specialized flavor of input stream, that // captures the token list in its expansion, and then // can be "played back." struct PreprocessorMacro { + // The flavor of macro + PreprocessorMacroFlavor flavor; + // The name under which the macro was `#define`d NameLoc nameAndLoc; @@ -176,9 +186,6 @@ struct PreprocessorMacro // The tokens that make up the macro body TokenList tokens; - // The flavor of macro - PreprocessorMacroFlavor flavor; - // The environment in which this macro needs to be expanded. // For ordinary macros this will be the global environment, // while for function-like macro arguments, it will be @@ -229,14 +236,18 @@ struct Preprocessor /// File system to use when looking up files ISlangFileSystemExt* fileSystem = nullptr; - /// Source maanger to use when loading source files + /// Source manager to use when loading source files SourceManager* sourceManager = nullptr; + /// Stores the initiating macro source location. + SourceLoc initiatingMacroSourceLoc; + NamePool* getNamePool() { return namePool; } SourceManager* getSourceManager() { return sourceManager; } }; + static Token AdvanceToken(Preprocessor* preprocessor); // Convenience routine to access the diagnostic sink @@ -497,6 +508,7 @@ static PreprocessorMacro* LookupMacro(PreprocessorEnvironment* environment, Name return NULL; } + static PreprocessorEnvironment* GetCurrentEnvironment(Preprocessor* preprocessor) { // The environment we will use for looking up a macro is associated @@ -530,6 +542,12 @@ static PreprocessorEnvironment* GetCurrentEnvironment(Preprocessor* preprocessor } } +static bool _isInMacroExpansion(Preprocessor* preprocessor) +{ + return preprocessor->inputStream->environment->busyMacros != nullptr; +} + + static PreprocessorMacro* LookupMacro(Preprocessor* preprocessor, Name* name) { return LookupMacro(GetCurrentEnvironment(preprocessor), name); @@ -636,8 +654,15 @@ static void initializeMacroExpansion( static void pushMacroExpansion( Preprocessor* preprocessor, - MacroExpansion* expansion) + MacroExpansion* expansion, + SourceLoc initiatingMacroSourceLoc) { + // Only set the initiating if outside of a macro expansion + if (!_isInMacroExpansion(preprocessor)) + { + preprocessor->initiatingMacroSourceLoc = initiatingMacroSourceLoc; + } + // Before pushing a macro as an input stream, // we need to set the appropraite "busy" state // that will be used during expansions of that @@ -901,7 +926,9 @@ static void MaybeBeginMacroExpansion( // Not a macro? Can't be an invocation. if (!macro) + { return; + } // If the macro is busy (already being expanded), // don't try to trigger recursive expansion @@ -916,76 +943,137 @@ static void MaybeBeginMacroExpansion( // A function-style macro invocation should only match // if the token *after* the identifier is `(`. This // requires more lookahead than we usually have/need - if (macro->flavor == PreprocessorMacroFlavor::FunctionLike) - { - // Consume the token that (possibly) triggered macro expansion - AdvanceRawToken(preprocessor); - // Look at the next token, and see if it is an opening `(` - // that indicates we should actually expand a macro. - if(PeekRawTokenType(preprocessor) != TokenType::LParent) + switch (macro->flavor) + { + case PreprocessorMacroFlavor::FunctionLike: { - // In this case, we are in a bit of a mess, because we have - // consumed the token that named the macro, but we need to - // make sure that token (and not whatever came after it) - // gets returned to the user. - // - // To work around this we will construct a short-lived input - // stream just to handle that one token, and also set - // a flag on the token to keep us from doing this logic again. + // Consume the token that (possibly) triggered macro expansion + AdvanceRawToken(preprocessor); - token.flags |= TokenFlag::SuppressMacroExpansion; + // Look at the next token, and see if it is an opening `(` + // that indicates we should actually expand a macro. + if(PeekRawTokenType(preprocessor) != TokenType::LParent) + { + // In this case, we are in a bit of a mess, because we have + // consumed the token that named the macro, but we need to + // make sure that token (and not whatever came after it) + // gets returned to the user. + // + // To work around this we will construct a short-lived input + // stream just to handle that one token, and also set + // a flag on the token to keep us from doing this logic again. + + token.flags |= TokenFlag::SuppressMacroExpansion; + + SimpleTokenInputStream* simpleStream = createSimpleInputStream(preprocessor, token); + PushInputStream(preprocessor, simpleStream); + return; + } - SimpleTokenInputStream* simpleStream = createSimpleInputStream(preprocessor, token); - PushInputStream(preprocessor, simpleStream); - return; - } + MacroExpansion* expansion = new MacroExpansion(); + initializeMacroExpansion(preprocessor, expansion, macro); + + // Consume the opening `(` + Token leftParen = AdvanceRawToken(preprocessor); - MacroExpansion* expansion = new MacroExpansion(); - initializeMacroExpansion(preprocessor, expansion, macro); + // Parse the arguments to the macro invocation + Index argCount = _parseMacroArgs(preprocessor, macro, expansion); - // Consume the opening `(` - Token leftParen = AdvanceRawToken(preprocessor); + // Expect a closing ')' + if(PeekRawTokenType(preprocessor) == TokenType::RParent) + { + AdvanceRawToken(preprocessor); + } + else + { + GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::expectedTokenInMacroArguments, TokenType::RParent, PeekRawTokenType(preprocessor)); + } - // Parse the arguments to the macro invocation - Index argCount = _parseMacroArgs(preprocessor, macro, expansion); + // If we didn't parse the expected number of arguments, + // then diagnose an error and do not attempt expansion. + // + // TODO: This check will need to be updated for variadic macros. + // + const Index paramCount = Index(macro->params.getCount()); + if (argCount != paramCount) + { + GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::wrongNumberOfArgumentsToMacro, paramCount, argCount); + return; + } - // Expect a closing ')' - if(PeekRawTokenType(preprocessor) == TokenType::RParent) + // Now that the arguments have been parsed and validated, + // we are ready to proceed with expansion of the macro body. + // + pushMacroExpansion(preprocessor, expansion, token.loc); + break; + } + case PreprocessorMacroFlavor::FunctionArg: + case PreprocessorMacroFlavor::ObjectLike: { + // Consume the token that triggered macro expansion AdvanceRawToken(preprocessor); + + // Object-like macros are the easy case. + MacroExpansion* expansion = new MacroExpansion(); + initializeMacroExpansion(preprocessor, expansion, macro); + pushMacroExpansion(preprocessor, expansion, token.loc); + break; } - else + case PreprocessorMacroFlavor::BuiltinLine: + case PreprocessorMacroFlavor::BuiltinFile: { - GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::expectedTokenInMacroArguments, TokenType::RParent, PeekRawTokenType(preprocessor)); - } + const SourceLoc loc = _isInMacroExpansion(preprocessor) ? preprocessor->initiatingMacroSourceLoc : token.loc; - // If we didn't parse the expected number of arguments, - // then diagnose an error and do not attempt expansion. - // - // TODO: This check will need to be updated for variadic macros. - // - const Index paramCount = Index(macro->params.getCount()); - if (argCount != paramCount) - { - GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::wrongNumberOfArgumentsToMacro, paramCount, argCount); - return; - } + if (!loc.isValid()) + { + // If we don't have a valid source location, don't expand + return; + } - // Now that the arguments have been parsed and validated, - // we are ready to proceed with expansion of the macro body. - // - pushMacroExpansion(preprocessor, expansion); - } - else - { - // Consume the token that triggered macro expansion - AdvanceRawToken(preprocessor); + AdvanceRawToken(preprocessor); + + SourceManager* sourceManager = preprocessor->getSourceManager(); + + // Since the location can be overridden by #line directives, use the slower path to get the line number + const HumaneSourceLoc humaneSourceLoc = sourceManager->getHumaneLoc(loc); + + Token newToken; + + StringBuilder buf; + if (macro->flavor == PreprocessorMacroFlavor::BuiltinLine) + { + newToken.type = TokenType::IntegerLiteral; + buf << humaneSourceLoc.line; + } + else + { + // We need to escape to a string + newToken.type = TokenType::StringLiteral; + + buf.appendChar('"'); + StringUtil::appendEscaped(humaneSourceLoc.pathInfo.foundPath.getUnownedSlice(), buf); + buf.appendChar('"'); + } + + // We are going to keep the actual text in the slice pool, so it stays in scope + // and if the value appears multiple times, it will shared + auto& pool = sourceManager->getStringSlicePool(); + + auto poolHandle = pool.add(buf.getUnownedSlice()); - // Object-like macros are the easy case. - MacroExpansion* expansion = new MacroExpansion(); - initializeMacroExpansion(preprocessor, expansion, macro); - pushMacroExpansion(preprocessor, expansion); + auto slice = pool.getSlice(poolHandle); + + newToken.setContent(slice); + + // We set the location to be the same as where the original location was + newToken.loc = token.loc; + + // Add to the start of the stream + SimpleTokenInputStream* simpleStream = createSimpleInputStream(preprocessor, newToken); + PushInputStream(preprocessor, simpleStream); + break; + } } } } @@ -1907,8 +1995,17 @@ static void HandleDefineDirective(PreprocessorDirectiveContext* context) PreprocessorMacro* oldMacro = LookupMacro(&context->preprocessor->globalEnv, name); if (oldMacro) { - GetSink(context)->diagnose(nameToken.loc, Diagnostics::macroRedefinition, name); - GetSink(context)->diagnose(oldMacro->getLoc(), Diagnostics::seePreviousDefinitionOf, name); + auto sink = GetSink(context); + + if (isBuiltinMacro(oldMacro->flavor)) + { + sink->diagnose(nameToken.loc, Diagnostics::builtinMacroRedefinition, name); + } + else + { + sink->diagnose(nameToken.loc, Diagnostics::macroRedefinition, name); + sink->diagnose(oldMacro->getLoc(), Diagnostics::seePreviousDefinitionOf, name); + } DestroyMacro(context->preprocessor, oldMacro); } @@ -2475,7 +2572,9 @@ Result findMacroValue( MacroExpansion* expansion = new MacroExpansion(); initializeMacroExpansion(preprocessor, expansion, macro); - pushMacroExpansion(preprocessor, expansion); + + // Don't set macro expansion location + pushMacroExpansion(preprocessor, expansion, SourceLoc()); String value; for(bool first = true;;first = false) @@ -2539,6 +2638,25 @@ TokenList preprocessSource( preprocessor.fileSystem = desc.fileSystem; preprocessor.namePool = desc.namePool; + // Add builtin macros + { + auto namePool = desc.namePool; + + const char*const builtinNames[] = { "__FILE__", "__LINE__" }; + const PreprocessorMacroFlavor builtinFlavors[] = { PreprocessorMacroFlavor::BuiltinFile, PreprocessorMacroFlavor::BuiltinLine }; + + for (Index i = 0; i < SLANG_COUNT_OF(builtinNames); i++) + { + auto name = namePool->getName(builtinNames[i]); + + PreprocessorMacro* macro = CreateMacro(&preprocessor); + macro->flavor = builtinFlavors[i]; + macro->nameAndLoc = NameLoc(name); + + preprocessor.globalEnv.macros[name] = macro; + } + } + auto sourceManager = desc.sourceManager; preprocessor.sourceManager = sourceManager; |
