diff options
| author | Tim Foley <tfoley@nvidia.com> | 2017-06-09 11:34:21 -0700 |
|---|---|---|
| committer | Tim Foley <tfoley@nvidia.com> | 2017-06-09 13:44:59 -0700 |
| commit | fcf83dbf9effab3bd98bad2b83b2468b7eb05cfd (patch) | |
| tree | 41047c94883b86ec085a81597391ce3ef557cd43 /source/slang/preprocessor.cpp | |
| parent | 52e8d4b9a27ab0060f874c3a63ab531847be35c0 (diff) | |
Initial import of code.
Diffstat (limited to 'source/slang/preprocessor.cpp')
| -rw-r--r-- | source/slang/preprocessor.cpp | 2032 |
1 files changed, 2032 insertions, 0 deletions
diff --git a/source/slang/preprocessor.cpp b/source/slang/preprocessor.cpp new file mode 100644 index 000000000..cdde2591d --- /dev/null +++ b/source/slang/preprocessor.cpp @@ -0,0 +1,2032 @@ +// Preprocessor.cpp +#include "Preprocessor.h" + +#include "Diagnostics.h" +#include "Lexer.h" + +// Needed so that we can construct modifier syntax +// to represent GLSL directives +#include "Syntax.h" + +#include <assert.h> + +using namespace CoreLib; + +// This file provides an implementation of a simple C-style preprocessor. +// It does not aim for 100% compatibility with any particular preprocessor +// specification, but the goal is to have it accept the most common +// idioms for using the preprocessor, found in shader code in the wild. + + +namespace Slang{ namespace Compiler { + +// State of a preprocessor conditional, which can change when +// we encounter directives like `#elif` or `#endif` +enum class PreprocessorConditionalState +{ + Before, // We have not yet seen a branch with a `true` condition. + During, // We are inside the branch with a `true` condition. + After, // We have already seen the branch with a `true` condition. +}; + +// Represents a preprocessor conditional that we are currently +// nested inside. +struct PreprocessorConditional +{ + // The next outer conditional in the current file/stream, or NULL. + PreprocessorConditional* parent; + + // The directive token that started the conditional (an `#if` or `#ifdef`) + Token ifToken; + + // The `#else` directive token, if one has been seen (otherwise `TokenType::Unknown`) + Token elseToken; + + // The state of the conditional + PreprocessorConditionalState state; +}; + +struct PreprocessorMacro; + +struct PreprocessorEnvironment +{ + // The "outer" environment, to be used if lookup in this env fails + PreprocessorEnvironment* parent = NULL; + + // Macros defined in this environment + Dictionary<String, PreprocessorMacro*> macros; + + ~PreprocessorEnvironment(); +}; + +// Input tokens can either come from source text, or from macro expansion. +// In general, input streams can be nested, so we have to keep a conceptual +// stack of input. + +// A stream of input tokens to be consumed +struct PreprocessorInputStream +{ + // The next input stream up the stack, if any. + PreprocessorInputStream* parent; + + // The deepest preprocessor conditional active for this stream. + PreprocessorConditional* conditional; + + // Environment to use when looking up macros + PreprocessorEnvironment* environment; + + // Reader for pre-tokenized input + TokenReader tokenReader; + + // If we are clobbering source locations with `#line`, then + // the state is tracked here: + + // Are we overriding source locations? + bool isOverridingSourceLoc; + + // What is the file name we are overriding to? + String overrideFileName; + + // What is the relative offset to apply to any line numbers? + int overrideLineOffset; + + // Destructor is virtual so that we can clean up + // after concrete subtypes. + virtual ~PreprocessorInputStream() = default; +}; + +struct SourceTextInputStream : PreprocessorInputStream +{ + // The pre-tokenized input + TokenList lexedTokens; +}; + +struct MacroExpansion : PreprocessorInputStream +{ + // The macro we will expand + PreprocessorMacro* macro; +}; + +struct ObjectLikeMacroExpansion : MacroExpansion +{ +}; + +struct FunctionLikeMacroExpansion : MacroExpansion +{ + // Environment for macro arguments + PreprocessorEnvironment argumentEnvironment; +}; + +// An enumeration for the diferent types of macros +enum class PreprocessorMacroFlavor +{ + ObjectLike, + FunctionArg, + FunctionLike, +}; + +// 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 name under which the macro was `#define`d + Token nameToken; + + // Parameters of the macro, in case of a function-like macro + List<Token> params; + + // 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 + // the environment of the macro invocation. + PreprocessorEnvironment* environment; +}; + +// State of the preprocessor +struct Preprocessor +{ + // diagnostics sink to use when writing messages + DiagnosticSink* sink; + + // An external callback interface to use when looking + // for files in a `#include` directive + IncludeHandler* includeHandler; + + // Current input stream (top of the stack of input) + PreprocessorInputStream* inputStream; + + // Currently-defined macros + PreprocessorEnvironment globalEnv; + + // A pre-allocated token that can be returned to + // represent end-of-input situations. + Token endOfFileToken; + + // Syntax for the program we are trying to parse + ProgramSyntaxNode* syntax; +}; + +// Convenience routine to access the diagnostic sink +static DiagnosticSink* GetSink(Preprocessor* preprocessor) +{ + return preprocessor->sink; +} + +// +// Forward declarations +// + +static void DestroyConditional(PreprocessorConditional* conditional); +static void DestroyMacro(Preprocessor* preprocessor, PreprocessorMacro* macro); + +// +// Basic Input Handling +// + +// Create a fresh input stream +static void InitializeInputStream(Preprocessor* preprocessor, PreprocessorInputStream* inputStream) +{ + inputStream->parent = NULL; + inputStream->conditional = NULL; + inputStream->environment = &preprocessor->globalEnv; +} + +// Destroy an input stream +static void DestroyInputStream(Preprocessor* /*preprocessor*/, PreprocessorInputStream* inputStream) +{ + delete inputStream; +} + +// Create an input stream to represent a pre-tokenized input file. +// TODO(tfoley): pre-tokenizing files isn't going to work in the long run. +static PreprocessorInputStream* CreateInputStreamForSource(Preprocessor* preprocessor, CoreLib::String const& source, CoreLib::String const& fileName) +{ + SourceTextInputStream* inputStream = new SourceTextInputStream(); + InitializeInputStream(preprocessor, inputStream); + + // Use existing `Lexer` to generate a token stream. + Lexer lexer(fileName, source, GetSink(preprocessor)); + inputStream->lexedTokens = lexer.lexAllTokens(); + inputStream->tokenReader = TokenReader(inputStream->lexedTokens); + + return inputStream; +} + + + +static void PushInputStream(Preprocessor* preprocessor, PreprocessorInputStream* inputStream) +{ + inputStream->parent = preprocessor->inputStream; + preprocessor->inputStream = inputStream; +} + +// Called when we reach the end of an input stream. +// Performs some validation and then destroys the input stream if required. +static void EndInputStream(Preprocessor* preprocessor, PreprocessorInputStream* inputStream) +{ + // If there are any conditionals that weren't completed, then it is an error + if (inputStream->conditional) + { + PreprocessorConditional* conditional = inputStream->conditional; + + GetSink(preprocessor)->diagnose(conditional->ifToken.Position, Diagnostics::endOfFileInPreprocessorConditional); + + while (conditional) + { + PreprocessorConditional* parent = conditional->parent; + DestroyConditional(conditional); + conditional = parent; + } + } + + DestroyInputStream(preprocessor, inputStream); +} + +// Potentially clobber source location information based on `#line` +static Token PossiblyOverrideSourceLoc(PreprocessorInputStream* inputStream, Token const& token) +{ + Token result = token; + if( inputStream->isOverridingSourceLoc ) + { + result.Position.FileName = inputStream->overrideFileName; + result.Position.Line += inputStream->overrideLineOffset; + } + return result; +} + +// Consume one token from an input stream +static Token AdvanceRawToken(PreprocessorInputStream* inputStream) +{ + return PossiblyOverrideSourceLoc(inputStream, inputStream->tokenReader.AdvanceToken()); +} + +// Peek one token from an input stream +static Token PeekRawToken(PreprocessorInputStream* inputStream) +{ + return PossiblyOverrideSourceLoc(inputStream, inputStream->tokenReader.PeekToken()); +} + +// Peek one token type from an input stream +static TokenType PeekRawTokenType(PreprocessorInputStream* inputStream) +{ + return inputStream->tokenReader.PeekTokenType(); +} + + +// Read one token in "raw" mode (meaning don't expand macros) +static Token AdvanceRawToken(Preprocessor* preprocessor) +{ + for (;;) + { + // Look at the input stream on top of the stack + PreprocessorInputStream* inputStream = preprocessor->inputStream; + + // If there isn't one, then there is no more input left to read. + if (!inputStream) + { + return preprocessor->endOfFileToken; + } + + // The top-most input stream may be at its end + if (PeekRawTokenType(inputStream) == TokenType::EndOfFile) + { + // If there is another stream remaining, switch to it + if (inputStream->parent) + { + preprocessor->inputStream = inputStream->parent; + EndInputStream(preprocessor, inputStream); + continue; + } + else + { + // HACK(tfoley): A place to fall into debugger... + int f = 0; + } + } + + // Everything worked, so read a token from the top-most stream + return AdvanceRawToken(inputStream); + } +} + +// Return the next token in "raw" mode, but don't advance the +// current token state. +static Token PeekRawToken(Preprocessor* preprocessor) +{ + // We need to find the strema that `advanceRawToken` would read from. + PreprocessorInputStream* inputStream = preprocessor->inputStream; + for (;;) + { + if (!inputStream) + { + // No more input streams left to read + return preprocessor->endOfFileToken; + } + + // The top-most input stream may be at its end, so + // look one entry up the stack (don't actually pop + // here, since we are just peeking) + if (PeekRawTokenType(inputStream) == TokenType::EndOfFile) + { + if (inputStream->parent) + { + inputStream = inputStream->parent; + continue; + } + else + { + // HACK(tfoley): A place to fall into debugger... + int f = 0; + } + } + + // Everything worked, so the token we just peeked is fine. + return PeekRawToken(inputStream); + } +} + +// Without advancing preprocessor state, look *two* raw tokens ahead +// (This is only needed in order to determine when we are possibly +// expanding a function-style macro) +TokenType PeekSecondRawTokenType(Preprocessor* preprocessor) +{ + // We need to find the strema that `advanceRawToken` would read from. + PreprocessorInputStream* inputStream = preprocessor->inputStream; + int count = 1; + for (;;) + { + if (!inputStream) + { + // No more input streams left to read + return TokenType::EndOfFile; + } + + // The top-most input stream may be at its end, so + // look one entry up the stack (don't actually pop + // here, since we are just peeking) + + TokenReader reader = inputStream->tokenReader; + if (reader.PeekTokenType() == TokenType::EndOfFile) + { + inputStream = inputStream->parent; + continue; + } + + if (count) + { + count--; + + // Note: we are advancing our temporary + // copy of the token reader + reader.AdvanceToken(); + if (reader.PeekTokenType() == TokenType::EndOfFile) + { + inputStream = inputStream->parent; + continue; + } + } + + // Everything worked, so peek a token from the top-most stream + return reader.PeekTokenType(); + } +} + + +// Get the location of the current (raw) token +static CodePosition PeekLoc(Preprocessor* preprocessor) +{ + return PeekRawToken(preprocessor).Position; +} + +// Get the `TokenType` of the current (raw) token +static TokenType PeekRawTokenType(Preprocessor* preprocessor) +{ + return PeekRawToken(preprocessor).Type; +} + +// +// Macros +// + +// Create a macro +static PreprocessorMacro* CreateMacro(Preprocessor* preprocessor) +{ + // TODO(tfoley): Allocate these more intelligently. + // For example, consider pooling them on the preprocessor. + + PreprocessorMacro* macro = new PreprocessorMacro(); + macro->flavor = PreprocessorMacroFlavor::ObjectLike; + macro->environment = &preprocessor->globalEnv; + return macro; +} + +// Destroy a macro +static void DestroyMacro(Preprocessor* /*preprocessor*/, PreprocessorMacro* macro) +{ + delete macro; +} + + +// Find the currently-defined macro of the given name, or return NULL +static PreprocessorMacro* LookupMacro(PreprocessorEnvironment* environment, String const& name) +{ + for(PreprocessorEnvironment* e = environment; e; e = e->parent) + { + PreprocessorMacro* macro = NULL; + if (e->macros.TryGetValue(name, macro)) + return macro; + } + + return NULL; +} + +static PreprocessorEnvironment* GetCurrentEnvironment(Preprocessor* preprocessor) +{ + PreprocessorInputStream* inputStream = preprocessor->inputStream; + return inputStream ? inputStream->environment : &preprocessor->globalEnv; +} + +static PreprocessorMacro* LookupMacro(Preprocessor* preprocessor, String const& 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*/) +{ + // TODO: need to implement this correctly + // + // The challenge here is that we are implementing expansion + // for argumenst to function-like macros in a "lazy" fashion. + // + // The letter of the spec is that we should macro expand + // each argument *before* substitution, and then go and + // macro-expand the substituted body. This means that we + // can invoke a macro as part of an argument to an + // invocation of the same macro: + // + // FOO( 1, FOO(22), 333 ); + // + // In our implementation, the "inner" invocation of `FOO` + // gets expanded at the point where it gets referenced + // in the body of the "outer" invocation of `FOO`. + // 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 false; +} + +// +// Reading Tokens With Expansion +// + +static void InitializeMacroExpansion( + Preprocessor* preprocessor, + MacroExpansion* expansion, + PreprocessorMacro* macro) +{ + InitializeInputStream(preprocessor, expansion); + expansion->environment = macro->environment; + expansion->macro = macro; + expansion->tokenReader = TokenReader(macro->tokens); +} + +static void PushMacroExpansion( + Preprocessor* preprocessor, + MacroExpansion* expansion) +{ + PushInputStream(preprocessor, expansion); +} + +static void AddEndOfStreamToken( + Preprocessor* preprocessor, + PreprocessorMacro* macro) +{ + Token token = PeekRawToken(preprocessor); + token.Type = TokenType::EndOfFile; + macro->tokens.mTokens.Add(token); +} + +// 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. +static void MaybeBeginMacroExpansion( + Preprocessor* preprocessor ) +{ + // We iterate because the first token in the expansion of one + // macro may be another macro invocation. + for (;;) + { + // Look at the next token ahead of us + Token const& token = PeekRawToken(preprocessor); + + // Not an identifier? Can't be a macro. + if (token.Type != TokenType::Identifier) + return; + + // Look for a macro with the given name. + String name = token.Content; + PreprocessorMacro* macro = LookupMacro(preprocessor, name); + + // 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 + if (IsMacroBusy(macro)) + return; + + // 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) + { + if(PeekSecondRawTokenType(preprocessor) != TokenType::LParent) + return; + + // Consume the token that triggered macro expansion + AdvanceRawToken(preprocessor); + + // Consume the opening `(` + Token leftParen = AdvanceRawToken(preprocessor); + + FunctionLikeMacroExpansion* expansion = new FunctionLikeMacroExpansion(); + InitializeMacroExpansion(preprocessor, expansion, macro); + expansion->argumentEnvironment.parent = &preprocessor->globalEnv; + expansion->environment = &expansion->argumentEnvironment; + + // Try to read any arguments present. + int paramCount = macro->params.Count(); + int argIndex = 0; + + switch (PeekRawTokenType(preprocessor)) + { + case TokenType::EndOfFile: + case TokenType::RParent: + // No arguments. + break; + + default: + // At least one argument + while(argIndex < paramCount) + { + // Read an argument + + // Create the argument, represented as a special flavor of macro + PreprocessorMacro* arg = CreateMacro(preprocessor); + arg->flavor = PreprocessorMacroFlavor::FunctionArg; + arg->environment = GetCurrentEnvironment(preprocessor); + + // Associate the new macro with its parameter name + Token paramToken = macro->params[argIndex]; + String const& paramName = paramToken.Content; + arg->nameToken = paramToken; + expansion->argumentEnvironment.macros[paramName] = arg; + argIndex++; + + // Read tokens for the argument + + // We track the nesting depth, since we don't break + // arguments on a `,` nested in balanced parentheses + // + int nesting = 0; + for (;;) + { + 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, arg); + goto doneWithAllArguments; + + case TokenType::RParent: + // 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, arg); + goto doneWithAllArguments; + } + // 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, arg); + AdvanceRawToken(preprocessor); + goto doneWithArgument; + } + // 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 + nesting++; + break; + + default: + break; + } + + // Add the token and continue parsing. + arg->tokens.mTokens.Add(AdvanceRawToken(preprocessor)); + } + doneWithArgument: {} + // We've parsed an argument and should move onto + // the next one. + } + break; + } + doneWithAllArguments: + // TODO: handle possible varargs + + // Expect closing right paren + if (PeekRawTokenType(preprocessor) == TokenType::RParent) + { + AdvanceRawToken(preprocessor); + } + else + { + GetSink(preprocessor)->diagnose(PeekLoc(preprocessor), Diagnostics::expectedTokenInMacroArguments, TokenType::RParent, PeekRawTokenType(preprocessor)); + } + + int argCount = argIndex; + if (argCount != paramCount) + { + // TODO: diagnose + throw 99; + } + + // We are ready to expand. + PushMacroExpansion(preprocessor, expansion); + } + else + { + // Consume the token that triggered macro expansion + AdvanceRawToken(preprocessor); + + // Object-like macros are the easy case. + ObjectLikeMacroExpansion* expansion = new ObjectLikeMacroExpansion(); + InitializeMacroExpansion(preprocessor, expansion, macro); + PushMacroExpansion(preprocessor, expansion); + } + } +} + +// Read one token with macro-expansion enabled. +static Token AdvanceToken(Preprocessor* preprocessor) +{ +top: + // Check whether we need to macro expand at the cursor. + MaybeBeginMacroExpansion(preprocessor); + + // Read a raw token (now that expansion has been triggered) + Token token = AdvanceRawToken(preprocessor); + + // Check if we need to perform token pasting + if (PeekRawTokenType(preprocessor) != TokenType::PoundPound) + { + // If we aren't token pasting, then we are done + return token; + } + else + { + // We are pasting tokens, which could get messy + + StringBuilder sb; + sb << token.Content; + + while (PeekRawTokenType(preprocessor) == TokenType::PoundPound) + { + // Consume the `##` + AdvanceRawToken(preprocessor); + + // Possibly macro-expand the next token + MaybeBeginMacroExpansion(preprocessor); + + // Read the next raw token (now that expansion has been triggered) + Token nextToken = AdvanceRawToken(preprocessor); + + sb << nextToken.Content; + } + + // Now re-lex the input + PreprocessorInputStream* inputStream = CreateInputStreamForSource(preprocessor, sb.ProduceString(), "token paste"); + if (inputStream->tokenReader.GetCount() != 1) + { + // We expect a token paste to produce a single token + // TODO(tfoley): emit a diagnostic here + } + + PushInputStream(preprocessor, inputStream); + goto top; + } +} + +// Read one token with macro-expansion enabled. +// +// Note that because triggering macro expansion may +// involve changing the input-stream state, this +// operation *can* have side effects. +static Token PeekToken(Preprocessor* preprocessor) +{ + // Check whether we need to macro expand at the cursor. + MaybeBeginMacroExpansion(preprocessor); + + // Peek a raw token (now that expansion has been triggered) + return PeekRawToken(preprocessor); + + // TODO: need a plan for how to handle token pasting + // here without it being onerous. Would be nice if we + // didn't have to re-do pasting on a "peek"... +} + +// Peek the type of the next token, including macro expansion. +static TokenType PeekTokenType(Preprocessor* preprocessor) +{ + return PeekToken(preprocessor).Type; +} + +// +// Preprocessor Directives +// + +// When reading a preprocessor directive, we use a context +// to wrap the direct preprocessor routines defines so far. +// +// One of the most important things the directive context +// does is give us a convenient way to read tokens with +// a guarantee that we won't read past the end of a line. +struct PreprocessorDirectiveContext +{ + // The preprocessor that is parsing the directive. + Preprocessor* preprocessor; + + // The directive token (e.g., the `if` in `#if`). + // Useful for reference in diagnostic messages. + Token directiveToken; + + // Has any kind of parse error been encountered in + // the directive so far? + bool parseError; + + // Have we done the necessary checks at the end + // of the directive already? + bool haveDoneEndOfDirectiveChecks; +}; + +// Get the token for the preprocessor directive being parsed. +inline Token const& GetDirective(PreprocessorDirectiveContext* context) +{ + return context->directiveToken; +} + +// Get the name of the directive being parsed. +inline String const& GetDirectiveName(PreprocessorDirectiveContext* context) +{ + return context->directiveToken.Content; +} + +// Get the location of the directive being parsed. +inline CodePosition const& GetDirectiveLoc(PreprocessorDirectiveContext* context) +{ + return context->directiveToken.Position; +} + +// Wrapper to get the diagnostic sink in the context of a directive. +static inline DiagnosticSink* GetSink(PreprocessorDirectiveContext* context) +{ + return GetSink(context->preprocessor); +} + +// Wrapper to get a "current" location when parsing a directive +static CodePosition PeekLoc(PreprocessorDirectiveContext* context) +{ + return PeekLoc(context->preprocessor); +} + +// Wrapper to look up a macro in the context of a directive. +static PreprocessorMacro* LookupMacro(PreprocessorDirectiveContext* context, String const& name) +{ + return LookupMacro(context->preprocessor, name); +} + +// Determine if we have read everthing on the directive's line. +static bool IsEndOfLine(PreprocessorDirectiveContext* context) +{ + return PeekRawToken(context->preprocessor).Type == TokenType::EndOfDirective; +} + +// Peek one raw token in a directive, without going past the end of the line. +static Token PeekRawToken(PreprocessorDirectiveContext* context) +{ + return PeekRawToken(context->preprocessor); +} + +// Read one raw token in a directive, without going past the end of the line. +static Token AdvanceRawToken(PreprocessorDirectiveContext* context) +{ + if (IsEndOfLine(context)) + return PeekRawToken(context); + return AdvanceRawToken(context->preprocessor); +} + +// Peek next raw token type, without going past the end of the line. +static TokenType PeekRawTokenType(PreprocessorDirectiveContext* context) +{ + return PeekRawTokenType(context->preprocessor); +} + +// Read one token, with macro-expansion, without going past the end of the line. +static Token AdvanceToken(PreprocessorDirectiveContext* context) +{ + if (IsEndOfLine(context)) + return PeekRawToken(context); + return AdvanceToken(context->preprocessor); +} + +// Peek one token, with macro-expansion, without going past the end of the line. +static Token PeekToken(PreprocessorDirectiveContext* context) +{ + if (IsEndOfLine(context)) + context->preprocessor->endOfFileToken; + return PeekToken(context->preprocessor); +} + +// Peek next token type, with macro-expansion, without going past the end of the line. +static TokenType PeekTokenType(PreprocessorDirectiveContext* context) +{ + if (IsEndOfLine(context)) + return TokenType::EndOfDirective; + return PeekTokenType(context->preprocessor); +} + +// Skip to the end of the line (useful for recovering from errors in a directive) +static void SkipToEndOfLine(PreprocessorDirectiveContext* context) +{ + while(!IsEndOfLine(context)) + { + AdvanceRawToken(context); + } +} + +static bool ExpectRaw(PreprocessorDirectiveContext* context, TokenType tokenType, DiagnosticInfo const& diagnostic, Token* outToken = NULL) +{ + if (PeekRawTokenType(context) != tokenType) + { + // Only report the first parse error within a directive + if (!context->parseError) + { + GetSink(context)->diagnose(PeekLoc(context), diagnostic, tokenType, GetDirectiveName(context)); + } + context->parseError = true; + return false; + } + Token const& token = AdvanceRawToken(context); + if (outToken) + *outToken = token; + return true; +} + +static bool Expect(PreprocessorDirectiveContext* context, TokenType tokenType, DiagnosticInfo const& diagnostic, Token* outToken = NULL) +{ + if (PeekTokenType(context) != tokenType) + { + // Only report the first parse error within a directive + if (!context->parseError) + { + GetSink(context)->diagnose(PeekLoc(context), diagnostic, tokenType, GetDirectiveName(context)); + context->parseError = true; + } + return false; + } + Token const& token = AdvanceToken(context); + if (outToken) + *outToken = token; + return true; +} + + + +// +// Preprocessor Conditionals +// + +// Determine whether the current preprocessor state means we +// should be skipping tokens. +static bool IsSkipping(Preprocessor* preprocessor) +{ + PreprocessorInputStream* inputStream = preprocessor->inputStream; + if (!inputStream) return false; + + // If we are not inside a preprocessor conditional, then don't skip + PreprocessorConditional* conditional = inputStream->conditional; + if (!conditional) return false; + + // skip tokens unless the conditional is inside its `true` case + return conditional->state != PreprocessorConditionalState::During; +} + +// Wrapper for use inside directives +static inline bool IsSkipping(PreprocessorDirectiveContext* context) +{ + return IsSkipping(context->preprocessor); +} + +// Create a preprocessor conditional +static PreprocessorConditional* CreateConditional(Preprocessor* /*preprocessor*/) +{ + // TODO(tfoley): allocate these more intelligently (for example, + // pool them on the `Preprocessor`. + return new PreprocessorConditional(); +} + +// Destroy a preprocessor conditional. +static void DestroyConditional(PreprocessorConditional* conditional) +{ + delete conditional; +} + +// Start a preprocessor conditional, with an initial enable/disable state. +static void BeginConditional(PreprocessorDirectiveContext* context, bool enable) +{ + Preprocessor* preprocessor = context->preprocessor; + PreprocessorInputStream* inputStream = preprocessor->inputStream; + assert(inputStream); + + PreprocessorConditional* conditional = CreateConditional(preprocessor); + + conditional->ifToken = context->directiveToken; + + // Set state of this condition appropriately. + // + // Default to the "haven't yet seen a `true` branch" state. + PreprocessorConditionalState state = PreprocessorConditionalState::Before; + // + // If we are nested inside a `false` branch of another condition, then + // we never want to enable, so we act as if we already *saw* the `true` branch. + // + if (IsSkipping(preprocessor)) state = PreprocessorConditionalState::After; + // + // Similarly, if we ran into any parse errors when dealing with the + // opening directive, then things are probably screwy and we should just + // skip all the branches. + if (IsSkipping(preprocessor)) state = PreprocessorConditionalState::After; + // + // Otherwise, if our condition was true, then set us to be inside the `true` branch + else if (enable) state = PreprocessorConditionalState::During; + + conditional->state = state; + + // Push conditional onto the stack + conditional->parent = inputStream->conditional; + inputStream->conditional = conditional; +} + +// +// Preprocessor Conditional Expressions +// + +// Conditional expressions are always of type `int` +typedef int PreprocessorExpressionValue; + +// Forward-declaretion +static PreprocessorExpressionValue ParseAndEvaluateExpression(PreprocessorDirectiveContext* context); + +// Parse a unary (prefix) expression inside of a preprocessor directive. +static PreprocessorExpressionValue ParseAndEvaluateUnaryExpression(PreprocessorDirectiveContext* context) +{ + switch (PeekTokenType(context)) + { + // handle prefix unary ops + case TokenType::OpSub: + AdvanceToken(context); + return -ParseAndEvaluateUnaryExpression(context); + case TokenType::OpNot: + AdvanceToken(context); + return !ParseAndEvaluateUnaryExpression(context); + case TokenType::OpBitNot: + AdvanceToken(context); + return ~ParseAndEvaluateUnaryExpression(context); + + // handle parenthized sub-expression + case TokenType::LParent: + { + Token leftParen = AdvanceToken(context); + PreprocessorExpressionValue value = ParseAndEvaluateExpression(context); + if (!Expect(context, TokenType::RParent, Diagnostics::expectedTokenInPreprocessorExpression)) + { + GetSink(context)->diagnose(leftParen.Position, Diagnostics::seeOpeningToken, leftParen); + } + return value; + } + + case TokenType::IntLiterial: + return StringToInt(AdvanceToken(context).Content); + + case TokenType::Identifier: + { + Token token = AdvanceToken(context); + if (token.Content == "defined") + { + // handle `defined(someName)` + + // Possibly parse a `(` + Token leftParen; + if (PeekRawTokenType(context) == TokenType::LParent) + { + leftParen = AdvanceRawToken(context); + } + + // Expect an identifier + Token nameToken; + if (!ExpectRaw(context, TokenType::Identifier, Diagnostics::expectedTokenInDefinedExpression, &nameToken)) + { + return 0; + } + String name = nameToken.Content; + + // If we saw an opening `(`, then expect one to close + if (leftParen.Type != TokenType::Unknown) + { + if(!ExpectRaw(context, TokenType::RParent, Diagnostics::expectedTokenInDefinedExpression)) + { + GetSink(context)->diagnose(leftParen.Position, Diagnostics::seeOpeningToken, leftParen); + return 0; + } + } + + return LookupMacro(context, name) != NULL; + } + + // An identifier here means it was not defined as a macro (or + // it is defined, but as a function-like macro. These should + // just evaluate to zero (possibly with a warning) + return 0; + } + + default: + GetSink(context)->diagnose(PeekLoc(context), Diagnostics::syntaxErrorInPreprocessorExpression); + return 0; + } +} + +// Determine the precedence level of an infix operator +// for use in parsing preprocessor conditionals. +static int GetInfixOpPrecedence(Token const& opToken) +{ + // If token is on another line, it is not part of the + // expression + if (opToken.flags & TokenFlag::AtStartOfLine) + return -1; + + // otherwise we look at the token type to figure + // out what precednece it should be parse with + switch (opToken.Type) + { + default: + // tokens that aren't infix operators should + // cause us to stop parsing an expression + return -1; + + case TokenType::OpMul: return 10; + case TokenType::OpDiv: return 10; + case TokenType::OpMod: return 10; + + case TokenType::OpAdd: return 9; + case TokenType::OpSub: return 9; + + case TokenType::OpLsh: return 8; + case TokenType::OpRsh: return 8; + + case TokenType::OpLess: return 7; + case TokenType::OpGreater: return 7; + case TokenType::OpLeq: return 7; + case TokenType::OpGeq: return 7; + + case TokenType::OpEql: return 6; + case TokenType::OpNeq: return 6; + + case TokenType::OpBitAnd: return 5; + case TokenType::OpBitOr: return 4; + case TokenType::OpBitXor: return 3; + case TokenType::OpAnd: return 2; + case TokenType::OpOr: return 1; + } +}; + +// Evaluate one infix operation in a preprocessor +// conditional expression +static PreprocessorExpressionValue EvaluateInfixOp( + PreprocessorDirectiveContext* context, + Token const& opToken, + PreprocessorExpressionValue left, + PreprocessorExpressionValue right) +{ + switch (opToken.Type) + { + default: +// SLANG_INTERNAL_ERROR(getSink(preprocessor), opToken); + return 0; + break; + + case TokenType::OpMul: return left * right; + case TokenType::OpDiv: + { + if (right == 0) + { + if (!context->parseError) + { + GetSink(context)->diagnose(opToken.Position, Diagnostics::divideByZeroInPreprocessorExpression); + } + return 0; + } + return left / right; + } + case TokenType::OpMod: + { + if (right == 0) + { + if (!context->parseError) + { + GetSink(context)->diagnose(opToken.Position, Diagnostics::divideByZeroInPreprocessorExpression); + } + return 0; + } + return left % right; + } + case TokenType::OpAdd: return left + right; + case TokenType::OpSub: return left - right; + case TokenType::OpLsh: return left << right; + case TokenType::OpRsh: return left >> right; + case TokenType::OpLess: return left < right ? 1 : 0; + case TokenType::OpGreater: return left > right ? 1 : 0; + case TokenType::OpLeq: return left <= right ? 1 : 0; + case TokenType::OpGeq: return left <= right ? 1 : 0; + case TokenType::OpEql: return left == right ? 1 : 0; + case TokenType::OpNeq: return left != right ? 1 : 0; + case TokenType::OpBitAnd: return left & right; + case TokenType::OpBitOr: return left | right; + case TokenType::OpBitXor: return left ^ right; + case TokenType::OpAnd: return left && right; + case TokenType::OpOr: return left || right; + } +} + +// Parse the rest of an infix preprocessor expression with +// precedence greater than or equal to the given `precedence` argument. +// The value of the left-hand-side expression is provided as +// an argument. +// This is used to form a simple recursive-descent expression parser. +static PreprocessorExpressionValue ParseAndEvaluateInfixExpressionWithPrecedence( + PreprocessorDirectiveContext* context, + PreprocessorExpressionValue left, + int precedence) +{ + for (;;) + { + // Look at the next token, and see if it is an operator of + // high enough precedence to be included in our expression + Token opToken = PeekToken(context); + int opPrecedence = GetInfixOpPrecedence(opToken); + + // If it isn't an operator of high enough precendece, we are done. + if(opPrecedence < precedence) + break; + + // Otherwise we need to consume the operator token. + AdvanceToken(context); + + // Next we parse a right-hand-side expression by starting with + // a unary expression and absorbing and many infix operators + // as possible with strictly higher precedence than the operator + // we found above. + PreprocessorExpressionValue right = ParseAndEvaluateUnaryExpression(context); + for (;;) + { + // Look for an operator token + Token rightOpToken = PeekToken(context); + int rightOpPrecedence = GetInfixOpPrecedence(rightOpToken); + + // If no operator was found, or the operator wasn't high + // enough precedence to fold into the right-hand-side, + // exit this loop. + if (rightOpPrecedence <= opPrecedence) + break; + + // Now invoke the parser recursively, passing in our + // existing right-hand side to form an even larger one. + right = ParseAndEvaluateInfixExpressionWithPrecedence( + context, + right, + rightOpPrecedence); + } + + // Now combine the left- and right-hand sides using + // the operator we found above. + left = EvaluateInfixOp(context, opToken, left, right); + } + return left; +} + +// Parse a complete (infix) preprocessor expression, and return its value +static PreprocessorExpressionValue ParseAndEvaluateExpression(PreprocessorDirectiveContext* context) +{ + // First read in the left-hand side (or the whole expression in the unary case) + PreprocessorExpressionValue value = ParseAndEvaluateUnaryExpression(context); + + // Try to read in trailing infix operators with correct precedence + return ParseAndEvaluateInfixExpressionWithPrecedence(context, value, 0); +} + +// Handle a `#if` directive +static void HandleIfDirective(PreprocessorDirectiveContext* context) +{ + // Parse a preprocessor expression. + PreprocessorExpressionValue value = ParseAndEvaluateExpression(context); + + // Begin a preprocessor block, enabled based on the expression. + BeginConditional(context, value != 0); +} + +// Handle a `#ifdef` directive +static void HandleIfDefDirective(PreprocessorDirectiveContext* context) +{ + // Expect a raw identifier, so we can check if it is defined + Token nameToken; + if(!ExpectRaw(context, TokenType::Identifier, Diagnostics::expectedTokenInPreprocessorDirective, &nameToken)) + return; + String name = nameToken.Content; + + // Check if the name is defined. + BeginConditional(context, LookupMacro(context, name) != NULL); +} + +// Handle a `#ifndef` directive +static void HandleIfNDefDirective(PreprocessorDirectiveContext* context) +{ + // Expect a raw identifier, so we can check if it is defined + Token nameToken; + if(!ExpectRaw(context, TokenType::Identifier, Diagnostics::expectedTokenInPreprocessorDirective, &nameToken)) + return; + String name = nameToken.Content; + + // Check if the name is defined. + BeginConditional(context, LookupMacro(context, name) == NULL); +} + +// Handle a `#else` directive +static void HandleElseDirective(PreprocessorDirectiveContext* context) +{ + PreprocessorInputStream* inputStream = context->preprocessor->inputStream; + assert(inputStream); + + // if we aren't inside a conditional, then error + PreprocessorConditional* conditional = inputStream->conditional; + if (!conditional) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::directiveWithoutIf, GetDirectiveName(context)); + return; + } + + // if we've already seen a `#else`, then it is an error + if (conditional->elseToken.Type != TokenType::Unknown) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::directiveAfterElse, GetDirectiveName(context)); + GetSink(context)->diagnose(conditional->elseToken.Position, Diagnostics::seeDirective); + return; + } + conditional->elseToken = context->directiveToken; + + switch (conditional->state) + { + case PreprocessorConditionalState::Before: + conditional->state = PreprocessorConditionalState::During; + break; + + case PreprocessorConditionalState::During: + conditional->state = PreprocessorConditionalState::After; + break; + + default: + break; + } +} + +// Handle a `#elif` directive +static void HandleElifDirective(PreprocessorDirectiveContext* context) +{ + // HACK(tfoley): handle an empty `elif` like an `else` directive + // + // This is the behavior expected by at least one input program. + // We will eventually want to be pedantic about this. + // even if t + if (PeekRawTokenType(context) == TokenType::EndOfDirective) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::directiveExpectsExpression, GetDirectiveName(context)); + HandleElseDirective(context); + return; + } + + PreprocessorExpressionValue value = ParseAndEvaluateExpression(context); + + PreprocessorInputStream* inputStream = context->preprocessor->inputStream; + assert(inputStream); + + // if we aren't inside a conditional, then error + PreprocessorConditional* conditional = inputStream->conditional; + if (!conditional) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::directiveWithoutIf, GetDirectiveName(context)); + return; + } + + // if we've already seen a `#else`, then it is an error + if (conditional->elseToken.Type != TokenType::Unknown) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::directiveAfterElse, GetDirectiveName(context)); + GetSink(context)->diagnose(conditional->elseToken.Position, Diagnostics::seeDirective); + return; + } + + switch (conditional->state) + { + case PreprocessorConditionalState::Before: + if(value) + conditional->state = PreprocessorConditionalState::During; + break; + + case PreprocessorConditionalState::During: + conditional->state = PreprocessorConditionalState::After; + break; + + default: + break; + } +} + +// Handle a `#endif` directive +static void HandleEndIfDirective(PreprocessorDirectiveContext* context) +{ + PreprocessorInputStream* inputStream = context->preprocessor->inputStream; + assert(inputStream); + + // if we aren't inside a conditional, then error + PreprocessorConditional* conditional = inputStream->conditional; + if (!conditional) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::directiveWithoutIf, GetDirectiveName(context)); + return; + } + + inputStream->conditional = conditional->parent; + DestroyConditional(conditional); +} + +// Helper routine to check that we find the end of a directive where +// we expect it. +// +// Most directives do not need to call this directly, since we have +// a catch-all case in the main `HandleDirective()` funciton. +// The `#include` case will call it directly to avoid complications +// when it switches the input stream. +static void expectEndOfDirective(PreprocessorDirectiveContext* context) +{ + if(context->haveDoneEndOfDirectiveChecks) + return; + + context->haveDoneEndOfDirectiveChecks = true; + + if (!IsEndOfLine(context)) + { + // If we already saw a previous parse error, then don't + // emit another one for the same directive. + if (!context->parseError) + { + GetSink(context)->diagnose(PeekLoc(context), Diagnostics::unexpectedTokensAfterDirective, GetDirectiveName(context)); + } + SkipToEndOfLine(context); + } + + // Clear out the end-of-directive token + AdvanceRawToken(context->preprocessor); +} + + +// Handle a `#include` directive +static void HandleIncludeDirective(PreprocessorDirectiveContext* context) +{ + Token pathToken; + if(!Expect(context, TokenType::StringLiterial, Diagnostics::expectedTokenInPreprocessorDirective, &pathToken)) + return; + + String path = getFileNameTokenValue(pathToken); + + // TODO(tfoley): make this robust in presence of `#line` + String pathIncludedFrom = GetDirectiveLoc(context).FileName; + String foundPath; + String foundSource; + + + IncludeHandler* includeHandler = context->preprocessor->includeHandler; + if (!includeHandler) + { + GetSink(context)->diagnose(pathToken.Position, Diagnostics::includeFailed, path); + GetSink(context)->diagnose(pathToken.Position, Diagnostics::noIncludeHandlerSpecified); + return; + } + if (!includeHandler->TryToFindIncludeFile(path, pathIncludedFrom, &foundPath, &foundSource)) + { + GetSink(context)->diagnose(pathToken.Position, Diagnostics::includeFailed, path); + return; + } + + // Do all checking related to the end of this directive before we push a new stream, + // just to avoid complications where that check would need to deal with + // a switch of input stream + expectEndOfDirective(context); + + // Push the new file onto our stack of input streams + // TODO(tfoley): check if we have made our include stack too deep + PreprocessorInputStream* inputStream = CreateInputStreamForSource(context->preprocessor, foundSource, foundPath); + inputStream->parent = context->preprocessor->inputStream; + context->preprocessor->inputStream = inputStream; +} + +// Handle a `#define` directive +static void HandleDefineDirective(PreprocessorDirectiveContext* context) +{ + Token nameToken; + if (!Expect(context, TokenType::Identifier, Diagnostics::expectedTokenInPreprocessorDirective, &nameToken)) + return; + String name = nameToken.Content; + + PreprocessorMacro* macro = CreateMacro(context->preprocessor); + macro->nameToken = nameToken; + + PreprocessorMacro* oldMacro = LookupMacro(&context->preprocessor->globalEnv, name); + if (oldMacro) + { + GetSink(context)->diagnose(nameToken.Position, Diagnostics::macroRedefinition, name); + GetSink(context)->diagnose(oldMacro->nameToken.Position, Diagnostics::seePreviousDefinitionOf, name); + + DestroyMacro(context->preprocessor, oldMacro); + } + context->preprocessor->globalEnv.macros[name] = macro; + + // If macro name is immediately followed (with no space) by `(`, + // then we have a function-like macro + if (PeekRawTokenType(context) == TokenType::LParent) + { + if (!(PeekRawToken(context).flags & TokenFlag::AfterWhitespace)) + { + // This is a function-like macro, so we need to remember that + // and start capturing parameters + macro->flavor = PreprocessorMacroFlavor::FunctionLike; + + AdvanceRawToken(context); + + // If there are any parameters, parse them + if (PeekRawTokenType(context) != TokenType::RParent) + { + for (;;) + { + // TODO: handle elipsis (`...`) for varags + + // A macro parameter name should be a raw identifier + Token paramToken; + if (!ExpectRaw(context, TokenType::Identifier, Diagnostics::expectedTokenInMacroParameters, ¶mToken)) + break; + + // TODO(tfoley): some validation on parameter name. + // Certain names (e.g., `defined` and `__VA_ARGS__` + // are not allowed to be used as macros or parameters). + + // Add the parameter to the macro being deifned + macro->params.Add(paramToken); + + // If we see `)` then we are done with arguments + if (PeekRawTokenType(context) == TokenType::RParent) + break; + + ExpectRaw(context, TokenType::Comma, Diagnostics::expectedTokenInMacroParameters); + } + } + + ExpectRaw(context, TokenType::RParent, Diagnostics::expectedTokenInMacroParameters); + } + } + + // consume tokens until end-of-line + for(;;) + { + Token token = AdvanceRawToken(context); + if( token.Type == TokenType::EndOfDirective ) + { + // Last token on line will be turned into a conceptual end-of-file + // token for the sub-stream that the macro expands into. + token.Type = TokenType::EndOfFile; + macro->tokens.mTokens.Add(token); + break; + } + + // In the ordinary case, we just add the token to the definition + macro->tokens.mTokens.Add(token); + } +} + +// Handle a `#undef` directive +static void HandleUndefDirective(PreprocessorDirectiveContext* context) +{ + Token nameToken; + if (!Expect(context, TokenType::Identifier, Diagnostics::expectedTokenInPreprocessorDirective, &nameToken)) + return; + String name = nameToken.Content; + + PreprocessorEnvironment* env = &context->preprocessor->globalEnv; + PreprocessorMacro* macro = LookupMacro(env, name); + if (macro != NULL) + { + // name was defined, so remove it + env->macros.Remove(name); + + DestroyMacro(context->preprocessor, macro); + } + else + { + // name wasn't defined + GetSink(context)->diagnose(nameToken.Position, Diagnostics::macroNotDefined, name); + } +} + +// Handle a `#warning` directive +static void HandleWarningDirective(PreprocessorDirectiveContext* context) +{ + // TODO: read rest of line without actual tokenization + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::userDefinedWarning, "user-defined warning"); + SkipToEndOfLine(context); +} + +// Handle a `#error` directive +static void HandleErrorDirective(PreprocessorDirectiveContext* context) +{ + // TODO: read rest of line without actual tokenization + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::userDefinedError, "user-defined warning"); + SkipToEndOfLine(context); +} + +// Handle a `#line` directive +static void HandleLineDirective(PreprocessorDirectiveContext* context) +{ + int line = 0; + if (PeekTokenType(context) == TokenType::IntLiterial) + { + line = StringToInt(AdvanceToken(context).Content); + } + else if (PeekTokenType(context) == TokenType::Identifier + && PeekToken(context).Content == "default") + { + AdvanceToken(context); + + // Stop overiding soure locations. + context->preprocessor->inputStream->isOverridingSourceLoc = false; + return; + } + else + { + GetSink(context)->diagnose(PeekLoc(context), Diagnostics::expected2TokensInPreprocessorDirective, + TokenType::IntLiterial, + "default", + GetDirectiveName(context)); + context->parseError = true; + return; + } + + CodePosition directiveLoc = GetDirectiveLoc(context); + + String file; + if (PeekTokenType(context) == TokenType::EndOfDirective) + { + file = directiveLoc.FileName; + } + else if (PeekTokenType(context) == TokenType::StringLiterial) + { + file = AdvanceToken(context).Content; + } + else if (PeekTokenType(context) == TokenType::IntLiterial) + { + // Note(tfoley): GLSL allows the "source string" to be indicated by an integer + // TODO(tfoley): Figure out a better way to handle this, if it matters + file = AdvanceToken(context).Content; + } + else + { + Expect(context, TokenType::StringLiterial, Diagnostics::expectedTokenInPreprocessorDirective); + return; + } + + PreprocessorInputStream* inputStream = context->preprocessor->inputStream; + + inputStream->isOverridingSourceLoc = true; + inputStream->overrideFileName = file; + inputStream->overrideLineOffset = line - (directiveLoc.Line + 1); +} + +// Handle a `#pragma` directive +static void HandlePragmaDirective(PreprocessorDirectiveContext* context) +{ + // TODO(tfoley): figure out which pragmas to parse, + // and which to pass along + SkipToEndOfLine(context); +} + +// Handle a `#version` directive +static void handleGLSLVersionDirective(PreprocessorDirectiveContext* context) +{ + Token versionNumberToken; + if(!ExpectRaw( + context, + TokenType::IntLiterial, + Diagnostics::expectedTokenInPreprocessorDirective, + &versionNumberToken)) + { + return; + } + + Token glslProfileToken; + if(PeekTokenType(context) == TokenType::Identifier) + { + glslProfileToken = AdvanceToken(context); + } + + // Need to construct a representation taht we can hook into our compilation result + + auto modifier = new GLSLVersionDirective(); + modifier->versionNumberToken = versionNumberToken; + modifier->glslProfileToken = glslProfileToken; + + // Attach the modifier to the program we are parsing! + + addModifier( + context->preprocessor->syntax, + modifier); +} + +// Handle a `#extension` directive, e.g., +// +// #extension some_extension_name : enable +// +static void handleGLSLExtensionDirective(PreprocessorDirectiveContext* context) +{ + Token extensionNameToken; + if(!ExpectRaw( + context, + TokenType::Identifier, + Diagnostics::expectedTokenInPreprocessorDirective, + &extensionNameToken)) + { + return; + } + + if( !ExpectRaw(context, TokenType::Colon, Diagnostics::expectedTokenInPreprocessorDirective) ) + { + return; + } + + Token dispositionToken; + if(!ExpectRaw( + context, + TokenType::Identifier, + Diagnostics::expectedTokenInPreprocessorDirective, + &dispositionToken)) + { + return; + } + + // Need to construct a representation taht we can hook into our compilation result + + auto modifier = new GLSLExtensionDirective(); + modifier->extensionNameToken = extensionNameToken; + modifier->dispositionToken = dispositionToken; + + // Attach the modifier to the program we are parsing! + + addModifier( + context->preprocessor->syntax, + modifier); +} + +// Handle an invalid directive +static void HandleInvalidDirective(PreprocessorDirectiveContext* context) +{ + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::unknownPreprocessorDirective, GetDirectiveName(context)); + SkipToEndOfLine(context); +} + +// Callback interface used by preprocessor directives +typedef void (*PreprocessorDirectiveCallback)(PreprocessorDirectiveContext* context); + +enum PreprocessorDirectiveFlag : unsigned int +{ + // Should this directive be handled even when skipping disbaled code? + ProcessWhenSkipping = 1 << 0, +}; + +// Information about a specific directive +struct PreprocessorDirective +{ + // Name of the directive + char const* name; + + // Callback to handle the directive + PreprocessorDirectiveCallback callback; + + unsigned int flags; +}; + +// A simple array of all the directives we know how to handle. +// TODO(tfoley): considering making this into a real hash map, +// and then make it easy-ish for users of the codebase to add +// their own directives as desired. +static const PreprocessorDirective kDirectives[] = +{ + { "if", &HandleIfDirective, ProcessWhenSkipping }, + { "ifdef", &HandleIfDefDirective, ProcessWhenSkipping }, + { "ifndef", &HandleIfNDefDirective, ProcessWhenSkipping }, + { "else", &HandleElseDirective, ProcessWhenSkipping }, + { "elif", &HandleElifDirective, ProcessWhenSkipping }, + { "endif", &HandleEndIfDirective, ProcessWhenSkipping }, + + { "include", &HandleIncludeDirective, 0 }, + { "define", &HandleDefineDirective, 0 }, + { "undef", &HandleUndefDirective, 0 }, + { "warning", &HandleWarningDirective, 0 }, + { "error", &HandleErrorDirective, 0 }, + { "line", &HandleLineDirective, 0 }, + { "pragma", &HandlePragmaDirective, 0 }, + + // TODO(tfoley): These are specific to GLSL, and probably + // shouldn't be enabled for HLSL or Slang + { "version", &handleGLSLVersionDirective, 0 }, + { "extension", &handleGLSLExtensionDirective, 0 }, + + { NULL, NULL }, +}; + +static const PreprocessorDirective kInvalidDirective = { + NULL, &HandleInvalidDirective, 0, +}; + +// Look up the directive with the given name. +static PreprocessorDirective const* FindDirective(String const& name) +{ + char const* nameStr = name.Buffer(); + for (int ii = 0; kDirectives[ii].name; ++ii) + { + if (strcmp(kDirectives[ii].name, nameStr) != 0) + continue; + + return &kDirectives[ii]; + } + + return &kInvalidDirective; +} + +// Process a directive, where the preprocessor has already consumed the +// `#` token that started the directive line. +static void HandleDirective(PreprocessorDirectiveContext* context) +{ + // Try to read the directive name. + context->directiveToken = PeekRawToken(context); + + TokenType directiveTokenType = GetDirective(context).Type; + + // An empty directive is allowed, and ignored. + if (directiveTokenType == TokenType::EndOfDirective) + { + return; + } + // Otherwise the directive name had better be an identifier + else if (directiveTokenType != TokenType::Identifier) + { + GetSink(context)->diagnose(GetDirectiveLoc(context), Diagnostics::expectedPreprocessorDirectiveName); + SkipToEndOfLine(context); + return; + } + + // Consume the directive name token. + AdvanceRawToken(context); + + // Look up the handler for the directive. + PreprocessorDirective const* directive = FindDirective(GetDirectiveName(context)); + + // If we are skipping disabled code, and the directive is not one + // of the small number that need to run even in that case, skip it. + if (IsSkipping(context) && !(directive->flags & PreprocessorDirectiveFlag::ProcessWhenSkipping)) + { + SkipToEndOfLine(context); + return; + } + + // Apply the directive-specific callback + (directive->callback)(context); + + // We expect the directive callback to consume the entire line, so if + // it hasn't that is a parse error. + expectEndOfDirective(context); +} + +// Read one token using the full preprocessor, with all its behaviors. +static Token ReadToken(Preprocessor* preprocessor) +{ + for (;;) + { + // Look at the next raw token in the input. + Token const& token = PeekRawToken(preprocessor); + + // If we have a directive (`#` at start of line) then handle it + if ((token.Type == TokenType::Pound) && (token.flags & TokenFlag::AtStartOfLine)) + { + // Skip the `#` + AdvanceRawToken(preprocessor); + + // Create a context for parsing the directive + PreprocessorDirectiveContext directiveContext; + directiveContext.preprocessor = preprocessor; + directiveContext.parseError = false; + directiveContext.haveDoneEndOfDirectiveChecks = false; + + // Parse and handle the directive + HandleDirective(&directiveContext); + continue; + } + + // otherwise, if we are currently in a skipping mode, then skip tokens + if (IsSkipping(preprocessor)) + { + AdvanceRawToken(preprocessor); + continue; + } + + // otherwise read a token, which may involve macro expansion + return AdvanceToken(preprocessor); + } +} + +// intialize a preprocessor context, using the given sink for errros +static void InitializePreprocessor( + Preprocessor* preprocessor, + DiagnosticSink* sink) +{ + preprocessor->sink = sink; + preprocessor->includeHandler = NULL; + preprocessor->endOfFileToken.Type = TokenType::EndOfFile; + preprocessor->endOfFileToken.flags = TokenFlag::AtStartOfLine; +} + +// clean up after an environment +PreprocessorEnvironment::~PreprocessorEnvironment() +{ + for (auto pair : this->macros) + { + DestroyMacro(NULL, pair.Value); + } +} + +// finalize a preprocessor and free any memory still in use +static void FinalizePreprocessor( + Preprocessor* preprocessor) +{ + // Clear out any waiting input streams + PreprocessorInputStream* input = preprocessor->inputStream; + while (input) + { + PreprocessorInputStream* parent = input->parent; + DestroyInputStream(preprocessor, input); + input = parent; + } + +#if 0 + // clean up any macros that were allocated + for (auto pair : preprocessor->globalEnv.macros) + { + DestroyMacro(preprocessor, pair.Value); + } +#endif +} + +// Add a simple macro definition from a string (e.g., for a +// `-D` option passed on the command line +static void DefineMacro( + Preprocessor* preprocessor, + String const& key, + String const& value) +{ + String fileName = "command line"; + PreprocessorMacro* macro = CreateMacro(preprocessor); + + // Use existing `Lexer` to generate a token stream. + Lexer lexer(fileName, value, GetSink(preprocessor)); + macro->tokens = lexer.lexAllTokens(); + macro->nameToken = Token(TokenType::Identifier, key, 0, 0, 0, fileName); + + PreprocessorMacro* oldMacro = NULL; + if (preprocessor->globalEnv.macros.TryGetValue(key, oldMacro)) + { + DestroyMacro(preprocessor, oldMacro); + } + + preprocessor->globalEnv.macros[key] = macro; +} + +// read the entire input into tokens +static TokenList ReadAllTokens( + Preprocessor* preprocessor) +{ + TokenList tokens; + for (;;) + { + Token token = ReadToken(preprocessor); + + tokens.mTokens.Add(token); + + // Note: we include the EOF token in the list, + // since that is expected by the `TokenList` type. + if (token.Type == TokenType::EndOfFile) + break; + } + return tokens; +} + +TokenList preprocessSource( + CoreLib::String const& source, + CoreLib::String const& fileName, + DiagnosticSink* sink, + IncludeHandler* includeHandler, + CoreLib::Dictionary<CoreLib::String, CoreLib::String> defines, + ProgramSyntaxNode* syntax) +{ + Preprocessor preprocessor; + InitializePreprocessor(&preprocessor, sink); + preprocessor.syntax = syntax; + + preprocessor.includeHandler = includeHandler; + for (auto p : defines) + { + DefineMacro(&preprocessor, p.Key, p.Value); + } + + // create an initial input stream based on the provided buffer + preprocessor.inputStream = CreateInputStreamForSource(&preprocessor, source, fileName); + + TokenList tokens = ReadAllTokens(&preprocessor); + + FinalizePreprocessor(&preprocessor); + + // debugging: build the pre-processed source back together +#if 0 + StringBuilder sb; + for (auto t : tokens) + { + if (t.flags & TokenFlag::AtStartOfLine) + { + sb << "\n"; + } + else if (t.flags & TokenFlag::AfterWhitespace) + { + sb << " "; + } + + sb << t.Content; + } + + String s = sb.ProduceString(); +#endif + + return tokens; +} + +}} |
