summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-preprocessor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/slang-preprocessor.cpp')
-rw-r--r--source/slang/slang-preprocessor.cpp507
1 files changed, 305 insertions, 202 deletions
diff --git a/source/slang/slang-preprocessor.cpp b/source/slang/slang-preprocessor.cpp
index e9b2e5e98..9b7a89ddb 100644
--- a/source/slang/slang-preprocessor.cpp
+++ b/source/slang/slang-preprocessor.cpp
@@ -44,11 +44,29 @@ struct PreprocessorConditional
struct PreprocessorMacro;
+ /// A node in a linked list of macros that are "busy" in an environment.
+ ///
+ /// A macro is "busy" if there is already an open expansion of it in
+ /// the same (or a parent) environment, such that expanding it again
+ /// in the environment would lead to infinite expansion.
+ ///
+struct BusyMacro
+{
+ /// The macro that is busy.
+ PreprocessorMacro* macro = nullptr;
+
+ /// The rest of the list of busy macros.
+ BusyMacro* next = nullptr;
+};
+
struct PreprocessorEnvironment
{
// The "outer" environment, to be used if lookup in this env fails
PreprocessorEnvironment* parent = NULL;
+ /// Macros that should be considered busy in this environment
+ BusyMacro* busyMacros = nullptr;
+
// Macros defined in this environment
Dictionary<Name*, PreprocessorMacro*> macros;
@@ -115,21 +133,24 @@ struct SimpleTokenInputStream : PretokenizedInputStream
struct MacroExpansion : PretokenizedInputStream
{
- // In Dtor we want to mark the macro as no longer being busy
- ~MacroExpansion();
-
// The macro we will expand
PreprocessorMacro* macro;
-};
-struct ObjectLikeMacroExpansion : MacroExpansion
-{
-};
+ /// State for marking `macro` as busy in thsi expansion
+ BusyMacro busy;
-struct FunctionLikeMacroExpansion : MacroExpansion
-{
- // Environment for macro arguments
- PreprocessorEnvironment argumentEnvironment;
+ // Environment for macro expansion.
+ //
+ // For a function-like macro, this will include
+ // the mapping from macro argument names to
+ // their values.
+ //
+ // For both function-like and object-like macros,
+ // this will include a marker that registers
+ // the macro as "busy" during its expansion, so
+ // that it won't be recursively expanded.
+ //
+ PreprocessorEnvironment expansionEnvironment;
};
// An enumeration for the different types of macros
@@ -174,9 +195,6 @@ struct PreprocessorMacro
{
return nameAndLoc.loc;
}
-
- // Set to true during a macros expansion
- bool isBusy = false;
};
// State of the preprocessor
@@ -216,16 +234,6 @@ struct Preprocessor
static Token AdvanceToken(Preprocessor* preprocessor);
-MacroExpansion::~MacroExpansion()
-{
- if (macro)
- {
- SLANG_ASSERT(macro->isBusy);
- macro->isBusy = false;
- }
-}
-
-
// Convenience routine to access the diagnostic sink
static DiagnosticSink* GetSink(Preprocessor* preprocessor)
{
@@ -522,9 +530,12 @@ static PreprocessorMacro* LookupMacro(Preprocessor* preprocessor, Name* name)
return LookupMacro(GetCurrentEnvironment(preprocessor), name);
}
-// A macro is "busy" if it is currently being used for expansion.
-// A macro cannot be expanded again while busy, to avoid infinite recursion.
-static bool _isMacroBusy(PreprocessorMacro* macro)
+ /// Check if `macro` is "busy" in the given `env`.
+ ///
+ /// A macro is "busy" if it is already being used for expansion, such
+ /// that an attempt to expand it again would lead to infinite expansion.
+ ///
+static bool _isMacroBusy(PreprocessorMacro* macro, PreprocessorEnvironment* env)
{
// The challenge here is that we are implementing expansion
// for arguments to function-like macros in a "lazy" fashion.
@@ -535,6 +546,8 @@ static bool _isMacroBusy(PreprocessorMacro* macro)
// can invoke a macro as part of an argument to an
// invocation of the same macro:
//
+ // #define FOO(A,B,C) A + B + C
+ //
// FOO( 1, FOO(22, 2, 2), 333 );
//
// In our implementation, the "inner" invocation of `FOO`
@@ -543,12 +556,30 @@ static bool _isMacroBusy(PreprocessorMacro* macro)
// Doing things this way leads to greatly simplified
// code for handling expansion.
//
- // A proper implementation of `IsMacroBusy` needs to
- // take context into account, so that it bans recursive
- // use of a macro when it occurs (indirectly) through
- // the *body* of the expansion, but not when it occcurs
- // only through an *argument*.
- return macro->isBusy;
+ // We solve this problem by having each `PreprocessorEnvironment`
+ // track an (optional) macro that should be busy in
+ // that environment.
+ //
+ // The environment that we create for the outer expansion
+ // of `FOO` (the one that will map `A => 1, B => FOO(22,2,2), ...`)
+ // will track the `FOO` macro because it should be busy
+ // in the body of `FOO`.
+ //
+ // In contrast, the environment used when expanding the parameter
+ // `B` will just be the environment in place at the macro *invocation*
+ // site, which in this case is the global environment.
+ //
+ // Given the design of putting busy macro state into environments,
+ // we can easily check if a macro is busy in a given environment
+ // by walking through the list of busy macros that was registerd
+ // with that environment.
+ //
+ for(auto busyMacro = env->busyMacros; busyMacro; busyMacro = busyMacro->next)
+ {
+ if(busyMacro->macro == macro)
+ return true;
+ }
+ return false;
}
//
@@ -564,35 +595,92 @@ static void InitializeMacroExpansion(
expansion->parent = preprocessor->inputStream;
expansion->primaryStream = preprocessor->inputStream->primaryStream;
-
- expansion->environment = macro->environment;
expansion->macro = macro;
+
+ // The macro expansion will read from the stored tokens
+ // that were recorded in the macro definition.
+ //
expansion->tokenReader = TokenReader(macro->tokens);
+
+ // A macro expansion will always occur in its own
+ // environment.
+ //
+ // For a function-like macro this environment will
+ // map the names of macro parameters to their argument
+ // token lists.
+ //
+ // For all macros, this environment will be used
+ // to track the "busy" state of the macro itself.
+ //
+ expansion->environment = &expansion->expansionEnvironment;
+
+ // The environment used for expanding a macro is always
+ // a child of the environment where the macro was defined.
+ //
+ PreprocessorEnvironment* parentEnvironment = macro->environment;
+ expansion->expansionEnvironment.parent = parentEnvironment;
+ //
+ // For ordinary function-like and object-like macros, that
+ // environment will always be the global environment.
+ //
+ // For the macros that represent arguments to a function-like
+ // macro, that environment will be the environment where
+ // the function-like macro was *invoked*, which might be
+ // in the context of another macro expansion.
+
+ // A macro is always busy in its own expansion environment,
+ // to prevent recursive expansion. Here we construct a
+ // link for the linked list of busy macros and install it
+ // into the environment.
+ //
+ // Note: this extra link is unnecessary in the case where
+ // `macro` is an argument to a function-like macro, because
+ // there is no way for it to reference itself in its
+ // expansion. We could try to avoid the extra step at
+ // the cost of a bit more code complexity here.
+ //
+ expansion->busy.macro = macro;
+ expansion->expansionEnvironment.busyMacros = &expansion->busy;
+
+ // What goes into the rest of the list of busy macros
+ // depends on what kind of macro is being expanded.
+ //
+ if( macro->flavor == PreprocessorMacroFlavor::FunctionArg )
+ {
+ // For a macro representing an argument to a function-like
+ // macro, the busy macros should be those that were in
+ // place at the invocation site of the function-like macro.
+ // This happens to be what is stored in the parent
+ // environment.
+ //
+ expansion->busy.next = parentEnvironment->busyMacros;
+ }
+ else
+ {
+ // For the other cases (function-like and objet-like
+ // macros), the busy list should include anything
+ // that was already busy in the environment that
+ // is beginning to expand a macro.
+ //
+ expansion->busy.next = preprocessor->inputStream->environment->busyMacros;
+ }
}
static void PushMacroExpansion(
Preprocessor* preprocessor,
MacroExpansion* expansion)
{
- SLANG_ASSERT(expansion->macro->isBusy == false);
-
- // We are now expanding so mark the macro as busy.
- // Will be when the MacroExpansion is release
- expansion->macro->isBusy = true;
-
PushInputStream(preprocessor, expansion);
}
-#if 0
-static void AddEndOfStreamToken(
+static void _addEndOfStreamToken(
Preprocessor* preprocessor,
PreprocessorMacro* macro)
{
Token token = PeekRawToken(preprocessor);
token.type = TokenType::EndOfFile;
- macro->tokens.mTokens.add(token);
+ macro->tokens.add(token);
}
-#endif
static SimpleTokenInputStream* createSimpleInputStream(
Preprocessor* preprocessor,
@@ -614,75 +702,170 @@ static SimpleTokenInputStream* createSimpleInputStream(
return inputStream;
}
-static void _addEndOfStreamToken(Preprocessor* preprocessor, List<Token>& outTokens)
+ /// Parse one macro argument and return it in the form of a macro
+ ///
+ /// Assumes as a precondition that the caller has already checked
+ /// for a closing `)` or end-of-input token.
+ ///
+ /// Does not consume any closing `)` or `,` for the argument.
+ ///
+static PreprocessorMacro* _parseMacroArg(Preprocessor* preprocessor)
{
- Token token = PeekRawToken(preprocessor);
- token.type = TokenType::EndOfFile;
- outTokens.add(token);
-}
-
-static SlangResult _parseArgTokens(Preprocessor* preprocessor, List<Token>& outTokens)
-{
- // Read tokens for the argument
- // We track the nesting depth, since we don't break
- // arguments on a `,` nested in balanced parentheses
+ // Create the argument, represented as a special flavor of macro
//
- // Note: Does not consume the closing ')' or ','
+ PreprocessorMacro* arg = CreateMacro(preprocessor);
+ arg->flavor = PreprocessorMacroFlavor::FunctionArg;
+ arg->environment = GetCurrentEnvironment(preprocessor);
- int nesting = 0;
- for (;;)
+ // We will now read the tokens that make up the argument.
+ //
+ // We need to keep track of the nesting depth of parentheses,
+ // because arguments should only break on a `,` that is
+ // not properly nested in balanced parentheses.
+ //
+ int nestingDepth = 0;
+ for(;;)
{
- switch (PeekRawTokenType(preprocessor))
+ switch(PeekRawTokenType(preprocessor))
{
- case TokenType::EndOfFile:
- {
- // if we reach the end of the file,
- // then we have an error, and need to
- // bail out
- _addEndOfStreamToken(preprocessor, outTokens);
- return SLANG_FAIL;
- }
- case TokenType::RParent:
+ case TokenType::EndOfFile:
+ // End of input means end of the argument.
+ // It is up to the caller to diagnose the
+ // lack of a closing `)`.
+ return arg;
+
+ case TokenType::RParent:
+ // If we see a right paren when we aren't nested
+ // then we are at the end of an argument.
+ //
+ if(nestingDepth == 0)
{
- // If we see a right paren when we aren't nested
- // then we are at the end of an argument
- if (nesting == 0)
- {
- _addEndOfStreamToken(preprocessor, outTokens);
- return SLANG_OK;
- }
- // Otherwise we decrease our nesting depth, add
- // the token, and keep going
- nesting--;
- break;
- }
- case TokenType::Comma:
- {
- // If we see a comma when we aren't nested
- // then we are at the end of an argument
- if (nesting == 0)
- {
- _addEndOfStreamToken(preprocessor, outTokens);
- return SLANG_OK;
- }
- // Otherwise we add it as a normal token
- break;
+ _addEndOfStreamToken(preprocessor, arg);
+ return arg;
}
- case TokenType::LParent:
+ // Otherwise we decrease our nesting depth, add
+ // the token, and keep going
+ nestingDepth--;
+ break;
+
+ case TokenType::Comma:
+ // If we see a comma when we aren't nested
+ // then we are at the end of an argument
+ if (nestingDepth == 0)
{
- // If we see a left paren then we need to
- // increase our tracking of nesting
- nesting++;
- break;
+ _addEndOfStreamToken(preprocessor, arg);
+ return arg;
}
- default: break;
+ // Otherwise we add it as a normal token
+ break;
+
+ case TokenType::LParent:
+ // If we see a left paren then we need to
+ // increase our tracking of nesting
+ nestingDepth++;
+ break;
+
+ default:
+ break;
}
// Add the token and continue parsing.
- outTokens.add(AdvanceRawToken(preprocessor));
+ arg->tokens.add(AdvanceRawToken(preprocessor));
}
}
+ /// Parse the arguments to a function-like macro invocation.
+ ///
+ /// This function assumes the opening `(` has already been parsed,
+ /// and it leaves the closing `)`, if any, for the caller to consume.
+ ///
+ /// Returns the number of arguments parsed.
+ ///
+static Index _parseMacroArgs(
+ Preprocessor* preprocessor,
+ PreprocessorMacro* macro,
+ MacroExpansion* expansion)
+{
+ // If there are no arguments present, then we
+ // will bail out immediately.
+ //
+ switch (PeekRawTokenType(preprocessor))
+ {
+ case TokenType::RParent:
+ case TokenType::EndOfFile:
+ return 0;
+ }
+
+ // Otherwise, we have one or more arguments.
+ Index paramCount = Index(macro->params.getCount());
+ Index argCount = 0;
+ for(;;)
+ {
+ // Parse an argument.
+ PreprocessorMacro* arg = _parseMacroArg(preprocessor);
+ SLANG_ASSERT(arg);
+
+ Index argIndex = argCount++;
+ if(argIndex < paramCount)
+ {
+ // The argument matches up with one of the declared
+ // parameters of the macro, so we will associate
+ // it with the parameter name.
+ //
+ NameLoc paramNameAndLoc = macro->params[argIndex];
+ Name* paramName = paramNameAndLoc.name;
+ arg->nameAndLoc = paramNameAndLoc;
+ expansion->expansionEnvironment.macros[paramName] = arg;
+ }
+ else
+ {
+ // TODO: If we supported variadic macros, we would
+ // want to check if `arg` should be appended to the
+ // tokens for the last/variadic parameter.
+ //
+ // For now, we assume that any "extra" arguments
+ // need to be disposed of, so that we don't
+ // leak.
+ //
+ delete arg;
+ }
+
+ // After consuming one macro argument, we look at
+ // the next token to decide what to do.
+ //
+ switch( PeekRawTokenType(preprocessor))
+ {
+ case TokenType::RParent:
+ case TokenType::EndOfFile:
+ // if we see a closing `)` or the end of
+ // input, we know we are done with arguments.
+ //
+ return argCount;
+
+ case TokenType::Comma:
+ // If we see a comma, then we will
+ // continue scanning for more macro
+ // arguments.
+ //
+ AdvanceRawToken(preprocessor);
+ break;
+
+ default:
+ // Another other token represents a syntax error.
+ //
+ // TODO: We could try to be clever here in deciding
+ // whether to break out of parsing macro arguments,
+ // or whether to "recover" and continue to scan
+ // ahead for a closing `)`. For now it is simplest
+ // to just bail.
+ //
+ GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::errorParsingToMacroInvocationArgument, paramCount, macro->getName());
+ return argCount;
+ }
+ }
+}
+
+
// Check whether the current token on the given input stream should be
// treated as a macro invocation, and if so set up state for expanding
// that macro.
@@ -710,7 +893,7 @@ static void MaybeBeginMacroExpansion(
// If the macro is busy (already being expanded),
// don't try to trigger recursive expansion
- if (_isMacroBusy(macro))
+ if (_isMacroBusy(macro, GetCurrentEnvironment(preprocessor)))
return;
// We might already have looked at this token,
@@ -746,120 +929,40 @@ static void MaybeBeginMacroExpansion(
return;
}
+ MacroExpansion* expansion = new MacroExpansion();
+ InitializeMacroExpansion(preprocessor, expansion, macro);
+
// Consume the opening `(`
Token leftParen = AdvanceRawToken(preprocessor);
-
- FunctionLikeMacroExpansion* expansion = new FunctionLikeMacroExpansion();
- InitializeMacroExpansion(preprocessor, expansion, macro);
- expansion->argumentEnvironment.parent = &preprocessor->globalEnv;
- expansion->environment = &expansion->argumentEnvironment;
+ // Parse the arguments to the macro invocation
+ Index argCount = _parseMacroArgs(preprocessor, macro, expansion);
- // Try to read any arguments present.
- const Index paramCount = Index(macro->params.getCount());
-
- // Set to invalid value initially
- Index argCount = -1;
- for (Index argIndex = 0; true; argIndex++)
+ // Expect a closing ')'
+ if(PeekRawTokenType(preprocessor) == TokenType::RParent)
{
- // Read an argument, as tokens
- List<Token> argTokens;
- if (SLANG_FAILED(_parseArgTokens(preprocessor, argTokens)))
- {
- GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::errorParsingToMacroInvocationArgument, paramCount, macro->getName());
- return;
- }
-
- // We always have eof in argTokens, so if 1 or less there are no values. If we are at ')' means it had no parameters
- // at all
- if (argTokens.getCount() <= 1 && PeekRawTokenType(preprocessor) == TokenType::RParent)
- {
- argCount = 0;
- break;
- }
-
- // Only bother adding the argument if the index is valid.
- // Would need to do something different here if we supported var args
- // Doing this way means we can report the amount of args passed correctly.
- if (argIndex < paramCount)
- {
- // Create the argument, represented as a special flavor of macro
- PreprocessorMacro* arg = CreateMacro(preprocessor);
- arg->flavor = PreprocessorMacroFlavor::FunctionArg;
- arg->environment = GetCurrentEnvironment(preprocessor);
-
- // Now we need to expand these tokens by applying macros (and in doing so we do
- // allow the expansion of args that invoke this macro)
-
- {
- SimpleTokenInputStream argExpandStream;
- argExpandStream.primaryStream = nullptr;
- argExpandStream.parent = nullptr;
-
- argExpandStream.lexedTokens.m_tokens.swapWith(argTokens);
- argExpandStream.tokenReader = TokenReader(argExpandStream.lexedTokens);
-
- // TODO(JS): What environment should be used for the expansion? For now we'll just use the environment set on the the macro
- argExpandStream.environment = macro->environment; // &preprocessor->globalEnv;
-
- auto prevInputStream = preprocessor->inputStream;
-
- preprocessor->inputStream = &argExpandStream;
-
- // Lets read some tokens until we hit the end of the file
-
- while (true)
- {
- Token expandToken = AdvanceToken(preprocessor);
- // Add the token to the expanded arg
- arg->tokens.add(expandToken);
- // If we hit the end then handle
- if (expandToken.type == TokenType::EndOfFile)
- {
- break;
- }
- }
-
- // Restore the previous input stream
- preprocessor->inputStream = prevInputStream;
- }
-
- // Associate the new macro with its parameter name
- NameLoc paramNameAndLoc = macro->params[argIndex];
- Name* paramName = paramNameAndLoc.name;
- arg->nameAndLoc = paramNameAndLoc;
- expansion->argumentEnvironment.macros[paramName] = arg;
- }
-
- // Do the peek to see what's next
- auto tokenType = PeekRawTokenType(preprocessor);
- if (tokenType == TokenType::RParent)
- {
- // Expect closing right paren
- AdvanceRawToken(preprocessor);
- argCount = argIndex + 1;
- break;
- }
- else if (tokenType == TokenType::Comma)
- {
- // Consume the comma
- AdvanceRawToken(preprocessor);
- }
- else
- {
- // Anything else is an error
- GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::errorParsingToMacroInvocationArgument, paramCount, macro->getName());
- return;
- }
+ AdvanceRawToken(preprocessor);
+ }
+ else
+ {
+ GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::expectedTokenInMacroArguments, TokenType::RParent, PeekRawTokenType(preprocessor));
}
+ // 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;
}
- // We are ready to expand.
+ // Now that the arguments have been parsed and validated,
+ // we are ready to proceed with expansion of the macro body.
+ //
PushMacroExpansion(preprocessor, expansion);
}
else
@@ -868,7 +971,7 @@ static void MaybeBeginMacroExpansion(
AdvanceRawToken(preprocessor);
// Object-like macros are the easy case.
- ObjectLikeMacroExpansion* expansion = new ObjectLikeMacroExpansion();
+ MacroExpansion* expansion = new MacroExpansion();
InitializeMacroExpansion(preprocessor, expansion, macro);
PushMacroExpansion(preprocessor, expansion);
}