diff options
| author | Tim Foley <tfoley@nvidia.com> | 2017-06-19 08:47:23 -0700 |
|---|---|---|
| committer | Tim Foley <tfoley@nvidia.com> | 2017-06-19 09:56:42 -0700 |
| commit | 634522da69b14b38c15b14d6b717b1289812e9bb (patch) | |
| tree | 04f502283ed818f661e368b3d8d1ba5ce875d1d2 /source/slang | |
| parent | cafed774d99f95bce6f182599913f3417dc68a3a (diff) | |
Allow for automatic importing of Slang code
The basic idea of this change is that user code can just write:
#include "foo.h"
and then if `foo.h` gets found in a list of registered directories for "auto-import," then it actually gets interpreted as if the user had writte, more or less:
__import foo;
That is, the code in `foo.h` will be treated as Slang, and will be fully parsed and checked (no matter what the source language had been), and the scoping rules will be those of `__import` instead of `#include`.
This is a really big hammer, and I could imagine it smashing fingers if used poorly.
I'm not sure this feature will pan out, but we need to try things to know.
One big piece of that that I'll likely keep in either case is an overhaul of command-line options parsing for `slangc`. In particular, this logic has been moved into the core `slang` library (so that users can just pass options in via the API), and it is all done on UTF-8 strings rather than wide strings (which was always going to be Windows-specific).
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/compiler.h | 22 | ||||
| -rw-r--r-- | source/slang/options.cpp | 566 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 14 | ||||
| -rw-r--r-- | source/slang/preprocessor.cpp | 88 | ||||
| -rw-r--r-- | source/slang/preprocessor.h | 24 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 172 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 1 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 1 | ||||
| -rw-r--r-- | source/slang/token-defs.h | 2 |
9 files changed, 820 insertions, 70 deletions
diff --git a/source/slang/compiler.h b/source/slang/compiler.h index f34be794b..38640fd0f 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -91,6 +91,26 @@ namespace Slang Dictionary<String, String> preprocessorDefinitions; }; + + struct SearchDirectory + { + enum Kind + { + Default, + AutoImport, + }; + + SearchDirectory() = default; + SearchDirectory(SearchDirectory const& other) = default; + SearchDirectory(String const& path, Kind kind) + : path(path) + , kind(kind) + {} + + String path; + Kind kind; + }; + class CompileOptions { public: @@ -98,7 +118,7 @@ namespace Slang CodeGenTarget Target = CodeGenTarget::Unknown; // Directories to search for `#include` files or `import`ed modules - List<String> SearchDirectories; + List<SearchDirectory> searchDirectories; // Definitions to provide during preprocessing Dictionary<String, String> preprocessorDefinitions; diff --git a/source/slang/options.cpp b/source/slang/options.cpp new file mode 100644 index 000000000..b3f0629c6 --- /dev/null +++ b/source/slang/options.cpp @@ -0,0 +1,566 @@ +// options.cpp + +// Implementation of options parsing for `slangc` command line, +// and also for API interface that takes command-line argument strings. + +#include "../../slang.h" + +#include "profile.h" + +#include <assert.h> + +namespace Slang { + +char const* tryReadCommandLineArgumentRaw(char const* option, char const* const**ioCursor, char const* const*end) +{ + char const* const*& cursor = *ioCursor; + if (cursor == end) + { + fprintf(stderr, "expected an argument for command-line option '%s'", option); + exit(1); + } + else + { + return *cursor++; + } +} + +String tryReadCommandLineArgument(char const* option, char const* const**ioCursor, char const* const*end) +{ + return String(tryReadCommandLineArgumentRaw(option, ioCursor, end)); +} + + + + + +struct OptionsParser +{ + SlangSession* session = nullptr; + SlangCompileRequest* compileRequest = nullptr; + + struct RawTranslationUnit + { + SlangSourceLanguage sourceLanguage; + SlangProfileID implicitProfile; + int translationUnitIndex; + }; + + // Collect translation units so that we can futz with them later + List<RawTranslationUnit> rawTranslationUnits; + + struct RawEntryPoint + { + String name; + SlangProfileID profileID = SLANG_PROFILE_UNKNOWN; + int translationUnitIndex = -1; + }; + + // Collect entry point names, so that we can associate them + // with entry points later... + List<RawEntryPoint> rawEntryPoints; + + // The number of input files that have been specified + int inputPathCount = 0; + + // If we already have a translation unit for Slang code, then this will give its index. + // If not, it will be `-1`. + int slangTranslationUnit = -1; + + int translationUnitCount = 0; + int currentTranslationUnitIndex = -1; + + SlangProfileID currentProfileID = SLANG_PROFILE_UNKNOWN; + + SlangCompileFlags flags = 0; + + int addTranslationUnit( + SlangSourceLanguage language, + SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN) + { + auto translationUnitIndex = spAddTranslationUnit(compileRequest, language, nullptr); + + assert(translationUnitIndex == rawTranslationUnits.Count()); + + RawTranslationUnit rawTranslationUnit; + rawTranslationUnit.sourceLanguage = language; + rawTranslationUnit.implicitProfile = implicitProfile; + rawTranslationUnit.translationUnitIndex = translationUnitIndex; + + rawTranslationUnits.Add(rawTranslationUnit); + + return translationUnitIndex; + } + + void addInputSlangPath( + String const& path) + { + // All of the input .slang files will be grouped into a single logical translation unit, + // which we create lazily when the first .slang file is encountered. + if( slangTranslationUnit == -1 ) + { + translationUnitCount++; + slangTranslationUnit = addTranslationUnit(SLANG_SOURCE_LANGUAGE_SLANG); + } + + spAddTranslationUnitSourceFile( + compileRequest, + slangTranslationUnit, + path.begin()); + + // Set the translation unit to be used by subsequent entry points + currentTranslationUnitIndex = slangTranslationUnit; + } + + void addInputForeignShaderPath( + String const& path, + SlangSourceLanguage language, + SlangProfileID implicitProfile = SLANG_PROFILE_UNKNOWN) + { + translationUnitCount++; + currentTranslationUnitIndex = addTranslationUnit(language, implicitProfile); + + spAddTranslationUnitSourceFile( + compileRequest, + currentTranslationUnitIndex, + path.begin()); + } + + void addInputPath( + char const* inPath) + { + inputPathCount++; + + // look at the extension on the file name to determine + // how we should handle it. + String path = String(inPath); + + if( path.EndsWith(".slang") ) + { + // Plain old slang code + addInputSlangPath(path); + } +#define CASE(EXT, LANG) \ + else if(path.EndsWith(EXT)) do { addInputForeignShaderPath(path, SLANG_SOURCE_LANGUAGE_##LANG); } while(0) + + CASE(".hlsl", HLSL); + CASE(".fx", HLSL); + + CASE(".glsl", GLSL); +#undef CASE + +#define CASE(EXT, LANG, PROFILE) \ + else if(path.EndsWith(EXT)) do { addInputForeignShaderPath(path, SLANG_SOURCE_LANGUAGE_##LANG, SlangProfileID(Slang::Profile::PROFILE)); } while(0) + // TODO: need a way to pass along stage/profile and entry-point info for these cases... + CASE(".vert", GLSL, GLSL_Vertex); + CASE(".frag", GLSL, GLSL_Fragment); + CASE(".geom", GLSL, GLSL_Geometry); + CASE(".tesc", GLSL, GLSL_TessControl); + CASE(".tese", GLSL, GLSL_TessEval); + CASE(".comp", GLSL, GLSL_Compute); + +#undef CASE + + else + { + fprintf(stderr, "error: can't deduce language for input file '%s'\n", inPath); + exit(1); + } + } + + int parse( + int argc, + char const* const* argv) + { + char const* const* argCursor = &argv[0]; + char const* const* argEnd = &argv[argc]; + while (argCursor != argEnd) + { + char const* arg = *argCursor++; + if (arg[0] == '-') + { + String argStr = String(arg); + + // The argument looks like an option, so try to parse it. +// if (argStr == "-outdir") +// outputDir = tryReadCommandLineArgument(arg, &argCursor, argEnd); +// if (argStr == "-out") +// options.outputName = tryReadCommandLineArgument(arg, &argCursor, argEnd); +// else if (argStr == "-symbo") +// options.SymbolToCompile = tryReadCommandLineArgument(arg, &argCursor, argEnd); + //else + if (argStr == "-no-checking") + flags |= SLANG_COMPILE_FLAG_NO_CHECKING; + else if (argStr == "-backend" || argStr == "-target") + { + String name = tryReadCommandLineArgument(arg, &argCursor, argEnd); + SlangCompileTarget target = SLANG_TARGET_UNKNOWN; + + if (name == "glsl") + { + target = SLANG_GLSL; + } + else if (name == "glsl_vk") + { + target = SLANG_GLSL_VULKAN; + } +// else if (name == "glsl_vk_onedesc") +// { +// options.Target = CodeGenTarget::GLSL_Vulkan_OneDesc; +// } + else if (name == "hlsl") + { + target = SLANG_HLSL; + } + else if (name == "spriv") + { + target = SLANG_SPIRV; + } + else if (name == "dxbc") + { + target = SLANG_DXBC; + } + else if (name == "dxbc-assembly") + { + target = SLANG_DXBC_ASM; + } + #define CASE(NAME, TARGET) \ + else if(name == #NAME) do { target = SLANG_##TARGET; } while(0) + + CASE(spirv, SPIRV); + CASE(spirv-assembly, SPIRV_ASM); + + #undef CASE + + else if (name == "reflection-json") + { + target = SLANG_REFLECTION_JSON; + } + else + { + fprintf(stderr, "unknown code generation target '%S'\n", name.ToWString()); + exit(1); + } + + spSetCodeGenTarget(compileRequest, target); + } + // A "profile" specifies both a specific target stage and a general level + // of capability required by the program. + else if (argStr == "-profile") + { + String name = tryReadCommandLineArgument(arg, &argCursor, argEnd); + + SlangProfileID profileID = spFindProfile(session, name.begin()); + if( profileID == SLANG_PROFILE_UNKNOWN ) + { + fprintf(stderr, "unknown profile '%s'\n", name.begin()); + } + else + { + currentProfileID = profileID; + } + } + else if (argStr == "-entry") + { + String name = tryReadCommandLineArgument(arg, &argCursor, argEnd); + + RawEntryPoint entry; + entry.name = name; + entry.translationUnitIndex = currentTranslationUnitIndex; + + // TODO(tfoley): Allow user to fold a specification of a profile into the entry-point name, + // for the case where they might be compiling multiple entry points in one invocation... + // + // For now, just use the last profile set on the command-line to specify this + + entry.profileID = currentProfileID; + + rawEntryPoints.Add(entry); + } +#if 0 + else if (argStr == "-stage") + { + String name = tryReadCommandLineArgument(arg, &argCursor, argEnd); + StageTarget stage = StageTarget::Unknown; + if (name == "vertex") { stage = StageTarget::VertexShader; } + else if (name == "fragment") { stage = StageTarget::FragmentShader; } + else if (name == "hull") { stage = StageTarget::HullShader; } + else if (name == "domain") { stage = StageTarget::DomainShader; } + else if (name == "compute") { stage = StageTarget::ComputeShader; } + else + { + fprintf(stderr, "unknown stage '%S'\n", name.ToWString()); + } + options.stage = stage; + } +#endif + else if (argStr == "-pass-through") + { + String name = tryReadCommandLineArgument(arg, &argCursor, argEnd); + SlangPassThrough passThrough = SLANG_PASS_THROUGH_NONE; + if (name == "fxc") { passThrough = SLANG_PASS_THROUGH_FXC; } + else if (name == "glslang") { passThrough = SLANG_PASS_THROUGH_GLSLANG; } + else + { + fprintf(stderr, "unknown pass-through target '%S'\n", name.ToWString()); + exit(1); + } + + spSetPassThrough( + compileRequest, + passThrough); + } +// else if (argStr == "-genchoice") +// options.Mode = CompilerMode::GenerateChoice; + else if (argStr[1] == 'D') + { + // The value to be defined might be part of the same option, as in: + // -DFOO + // or it might come separately, as in: + // -D FOO + char const* defineStr = arg + 2; + if (defineStr[0] == 0) + { + // Need to read another argument from the command line + defineStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd); + } + // The string that sets up the define can have an `=` between + // the name to be defined and its value, so we search for one. + char const* eqPos = nullptr; + for(char const* dd = defineStr; *dd; ++dd) + { + if (*dd == '=') + { + eqPos = dd; + break; + } + } + + // Now set the preprocessor define + // + if (eqPos) + { + // If we found an `=`, we split the string... + + spAddPreprocessorDefine( + compileRequest, + String(defineStr, eqPos).begin(), + String(eqPos+1).begin()); + } + else + { + // If there was no `=`, then just #define it to an empty string + + spAddPreprocessorDefine( + compileRequest, + String(defineStr).begin(), + ""); + } + } + else if (argStr[1] == 'I') + { + // The value to be defined might be part of the same option, as in: + // -IFOO + // or it might come separately, as in: + // -I FOO + // (see handling of `-D` above) + char const* includeDirStr = arg + 2; + if (includeDirStr[0] == 0) + { + // Need to read another argument from the command line + includeDirStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd); + } + + spAddSearchPath( + compileRequest, + String(includeDirStr).begin()); + } + else if (argStr == "-auto-import-dir") + { + char const* importDirStr = tryReadCommandLineArgumentRaw(arg, &argCursor, argEnd); + + spAddAutoImportPath( + compileRequest, + String(importDirStr).begin()); + } + else if (argStr == "--") + { + // The `--` option causes us to stop trying to parse options, + // and treat the rest of the command line as input file names: + while (argCursor != argEnd) + { + addInputPath(*argCursor++); + } + break; + } + else + { + fprintf(stderr, "unknown command-line option '%S'\n", argStr.ToWString()); + // TODO: print a usage message + exit(1); + } + } + else + { + addInputPath(arg); + } + } + + spSetCompileFlags(compileRequest, flags); + + // TODO(tfoley): This kind of validation needs to wait until + // after all options have been specified for API usage +#if 0 + if (inputPathCount == 0) + { + fprintf(stderr, "error: no input file specified\n"); + exit(1); + } + + // No point in moving forward if there is nothing to compile + if( translationUnitCount == 0 ) + { + fprintf(stderr, "error: no compilation requested\n"); + exit(1); + } +#endif + + // If the user didn't list any explicit entry points, then we can + // try to infer one from the type of input file + if(rawEntryPoints.Count() == 0) + { + for(auto rawTranslationUnit : rawTranslationUnits) + { + // Dont' add implicit entry points when compiling from Slang files, + // since Slang doesn't require entry points to be named on the + // command line. + if(rawTranslationUnit.sourceLanguage == SLANG_SOURCE_LANGUAGE_SLANG ) + continue; + + // Use a default entry point name + char const* entryPointName = "main"; + + // Try to determine a profile + SlangProfileID entryPointProfile = SLANG_PROFILE_UNKNOWN; + + // If a profile was specified on the command line, then we use it + if(currentProfileID != SLANG_PROFILE_UNKNOWN) + { + entryPointProfile = currentProfileID; + } + // Otherwise, check if the translation unit implied a profile + // (e.g., a `*.vert` file implies the `GLSL_Vertex` profile) + else if(rawTranslationUnit.implicitProfile != SLANG_PROFILE_UNKNOWN) + { + entryPointProfile = rawTranslationUnit.implicitProfile; + } + + RawEntryPoint entry; + entry.name = entryPointName; + entry.translationUnitIndex = rawTranslationUnit.translationUnitIndex; + entry.profileID = entryPointProfile; + rawEntryPoints.Add(entry); + } + } + + // For any entry points that were given without an explicit profile, we can now apply + // the profile that was given to them. + if( rawEntryPoints.Count() != 0 ) + { + bool anyEntryPointWithoutProfile = false; + for( auto& entryPoint : rawEntryPoints ) + { + // Skip entry points that are already associated with a translation unit... + if( entryPoint.profileID != SLANG_PROFILE_UNKNOWN ) + continue; + + anyEntryPointWithoutProfile = true; + break; + } + + if( anyEntryPointWithoutProfile ) + { + fprintf(stderr, "error: no profile specified; use the '-profile <profile name>' option"); + exit(1); + } + // TODO: issue an error if we have multiple `-profile` options *and* + // there are entry points that didn't get a profile. + else + { + for( auto& e : rawEntryPoints ) + { + if( e.profileID == SLANG_PROFILE_UNKNOWN ) + { + e.profileID = currentProfileID; + } + } + } + } + + // Next, we want to make sure that entry points get attached to the appropriate translation + // unit that will provide them. + { + bool anyEntryPointWithoutTranslationUnit = false; + for( auto& entryPoint : rawEntryPoints ) + { + // Skip entry points that are already associated with a translation unit... + if( entryPoint.translationUnitIndex != -1 ) + continue; + + anyEntryPointWithoutTranslationUnit = true; + entryPoint.translationUnitIndex = 0; + } + + if( anyEntryPointWithoutTranslationUnit && translationUnitCount != 1 ) + { + fprintf(stderr, "error: when using multiple translation units, entry points must be specified after their translation unit file(s)"); + exit(1); + } + + // Now place all those entry points where they belong + for( auto& entryPoint : rawEntryPoints ) + { + spAddTranslationUnitEntryPoint( + compileRequest, + entryPoint.translationUnitIndex, + entryPoint.name.begin(), + entryPoint.profileID); + } + } + +#if 0 + // Automatically derive an output directory based on the first file specified. + // + // TODO: require manual specification if there are multiple input files, in different directories + String fileName = options.translationUnits[0].sourceFilePaths[0]; + if (outputDir.Length() == 0) + { + outputDir = Path::GetDirectoryName(fileName); + } +#endif + + return 0; + } +}; + + +int parseOptions( + SlangCompileRequest* compileRequest, + int argc, + char const* const* argv) +{ + OptionsParser parser; + parser.compileRequest = compileRequest; + return parser.parse(argc, argv); +} + + +} // namespace Slang + +SLANG_API int spProcessCommandLineArguments( + SlangCompileRequest* request, + char const* const* args, + int argCount) +{ + return Slang::parseOptions(request, argCount, args); +} diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index b8997b1d7..fb66fbbcf 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -802,6 +802,18 @@ namespace Slang return decl; } + static RefPtr<Decl> parseAutoImportDecl( + Parser* parser) + { + Token importToken = parser->ReadToken(TokenType::AutoImport); + + auto decl = new ImportDecl(); + decl->nameToken = importToken; + decl->scope = parser->currentScope; + + return decl; + } + static Token ParseDeclName( Parser* parser) { @@ -2159,6 +2171,8 @@ parser->ReadToken(TokenType::Comma); decl = parseModifierDecl(parser); else if(parser->LookAheadToken("__import")) decl = parseImportDecl(parser); + else if(parser->LookAheadToken(TokenType::AutoImport)) + decl = parseAutoImportDecl(parser); else if (AdvanceIf(parser, TokenType::Semicolon)) { decl = new EmptyDecl(); diff --git a/source/slang/preprocessor.cpp b/source/slang/preprocessor.cpp index b8a42d46d..6e0906d9e 100644 --- a/source/slang/preprocessor.cpp +++ b/source/slang/preprocessor.cpp @@ -16,7 +16,16 @@ // idioms for using the preprocessor, found in shader code in the wild. -namespace Slang{ +namespace Slang { + +// Forward declaration for code provided in `slang.cpp` +// +// TODO: Need an appropriate header for this. +String autoImportModule( + CompileRequest* request, + String const& path, + String const& source, + CodePosition const& loc); // State of a preprocessor conditional, which can change when // we encounter directives like `#elif` or `#endif` @@ -170,6 +179,9 @@ struct Preprocessor // Syntax for the program we are trying to parse ProgramSyntaxNode* syntax; + + // The over-arching compile request taht is invoking us + CompileRequest* compileRequest; }; // Convenience routine to access the diagnostic sink @@ -1475,7 +1487,6 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context) String foundPath; String foundSource; - IncludeHandler* includeHandler = context->preprocessor->includeHandler; if (!includeHandler) { @@ -1483,10 +1494,17 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context) GetSink(context)->diagnose(pathToken.Position, Diagnostics::noIncludeHandlerSpecified); return; } - if (!includeHandler->TryToFindIncludeFile(path, pathIncludedFrom, &foundPath, &foundSource)) + auto includeResult = includeHandler->TryToFindIncludeFile(path, pathIncludedFrom, &foundPath, &foundSource); + + switch (includeResult) { + case IncludeResult::NotFound: + case IncludeResult::Error: GetSink(context)->diagnose(pathToken.Position, Diagnostics::includeFailed, path); return; + + case IncludeResult::FoundIncludeFile: + break; } // Do all checking related to the end of this directive before we push a new stream, @@ -1494,12 +1512,50 @@ static void HandleIncludeDirective(PreprocessorDirectiveContext* context) // 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; -} + switch( includeResult ) + { + case IncludeResult::FoundIncludeFile: + { + // 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; + } + break; + + case IncludeResult::FoundAutoImportFile: + { + // + + String autoImportName = autoImportModule( + context->preprocessor->compileRequest, + foundPath, + foundSource, + GetDirectiveLoc(context)); + + SourceTextInputStream* inputStream = new SourceTextInputStream(); + + Token token; + token.Type = TokenType::AutoImport; + token.Position = GetDirectiveLoc(context); + token.flags = 0; + token.Content = autoImportName; + + inputStream->lexedTokens.mTokens.Add(token); + + token.Type = TokenType::EndOfFile; + token.flags = TokenFlag::AfterWhitespace | TokenFlag::AtStartOfLine; + inputStream->lexedTokens.mTokens.Add(token); + + inputStream->tokenReader = TokenReader(inputStream->lexedTokens); + + inputStream->parent = context->preprocessor->inputStream; + context->preprocessor->inputStream = inputStream; + } + break; + } + } // Handle a `#define` directive static void HandleDefineDirective(PreprocessorDirectiveContext* context) @@ -2007,16 +2063,18 @@ static TokenList ReadAllTokens( } TokenList preprocessSource( - String const& source, - String const& fileName, - DiagnosticSink* sink, - IncludeHandler* includeHandler, - Dictionary<String, String> defines, - ProgramSyntaxNode* syntax) + String const& source, + String const& fileName, + DiagnosticSink* sink, + IncludeHandler* includeHandler, + Dictionary<String, String> defines, + ProgramSyntaxNode* syntax, + CompileRequest* compileRequest) { Preprocessor preprocessor; InitializePreprocessor(&preprocessor, sink); preprocessor.syntax = syntax; + preprocessor.compileRequest = compileRequest; preprocessor.includeHandler = includeHandler; for (auto p : defines) diff --git a/source/slang/preprocessor.h b/source/slang/preprocessor.h index a3c5336f5..f16bc3929 100644 --- a/source/slang/preprocessor.h +++ b/source/slang/preprocessor.h @@ -7,14 +7,23 @@ namespace Slang { +struct CompileRequest; class DiagnosticSink; class ProgramSyntaxNode; +enum class IncludeResult +{ + Error, + NotFound, + FoundIncludeFile, + FoundAutoImportFile, +}; + // Callback interface for the preprocessor to use when looking // for files in `#include` directives. struct IncludeHandler { - virtual bool TryToFindIncludeFile( + virtual IncludeResult TryToFindIncludeFile( String const& pathToInclude, String const& pathIncludedFrom, String* outFoundPath, @@ -23,12 +32,13 @@ struct IncludeHandler // Take a string of source code and preprocess it into a list of tokens. TokenList preprocessSource( - String const& source, - String const& fileName, - DiagnosticSink* sink, - IncludeHandler* includeHandler, - Dictionary<String, String> defines, - ProgramSyntaxNode* syntax); + String const& source, + String const& fileName, + DiagnosticSink* sink, + IncludeHandler* includeHandler, + Dictionary<String, String> defines, + ProgramSyntaxNode* syntax, + CompileRequest* compileRequest); } // namespace Slang diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index ce48f9032..5ec530592 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -130,9 +130,9 @@ struct CompileRequest { CompileRequest* request; - List<String> searchDirs; + List<SearchDirectory> searchDirs; - virtual bool TryToFindIncludeFile( + virtual IncludeResult TryToFindIncludeFile( String const& pathToInclude, String const& pathIncludedFrom, String* outFoundPath, @@ -146,12 +146,26 @@ struct CompileRequest request->mDependencyFilePaths.Add(path); - return true; + // HACK(tfoley): We might have found the file in the same directory, + // but what if this is also inside an auto-import path? + for (auto & dir : searchDirs) + { + // Only consider auto-import paths + if(dir.kind != SearchDirectory::Kind::AutoImport) + continue; + + String otherPath = Path::Combine(dir.path, pathToInclude); + + if(otherPath == path) + return IncludeResult::FoundAutoImportFile; + } + + return IncludeResult::FoundIncludeFile; } for (auto & dir : searchDirs) { - path = Path::Combine(dir, pathToInclude); + path = Path::Combine(dir.path, pathToInclude); if (File::Exists(path)) { *outFoundPath = path; @@ -159,10 +173,17 @@ struct CompileRequest request->mDependencyFilePaths.Add(path); - return true; + switch( dir.kind ) + { + case SearchDirectory::Kind::Default: + return IncludeResult::FoundIncludeFile; + + case SearchDirectory::Kind::AutoImport: + return IncludeResult::FoundAutoImportFile; + } } } - return false; + return IncludeResult::NotFound; } }; @@ -205,9 +226,9 @@ struct CompileRequest { auto sourceFilePath = sourceFile->path; - auto searchDirs = options.SearchDirectories; + auto searchDirs = options.searchDirectories; searchDirs.Reverse(); - searchDirs.Add(Path::GetDirectoryName(sourceFilePath)); + searchDirs.Add(SearchDirectory(Path::GetDirectoryName(sourceFilePath), SearchDirectory::Kind::Default)); searchDirs.Reverse(); includeHandler.searchDirs = searchDirs; @@ -219,7 +240,8 @@ struct CompileRequest mResult.GetErrorWriter(), &includeHandler, preprocessorDefinitions, - translationUnitSyntax.Ptr()); + translationUnitSyntax.Ptr(), + this); parseSourceFile( translationUnitSyntax.Ptr(), @@ -448,6 +470,63 @@ struct CompileRequest Dictionary<String, RefPtr<ProgramSyntaxNode>> loadedModules; + RefPtr<ProgramSyntaxNode> loadModule( + String const& name, + String const& path, + String const& source, + CodePosition const& loc) + { + // now we need to try compiling it, etc. + + // We don't want to use the same options that the user specified + // for loading modules on-demand. In particular, we always want + // semantic checking to be enabled. + CompileOptions moduleOptions; + moduleOptions.searchDirectories = Options.searchDirectories; + moduleOptions.profile = Options.profile; + + RefPtr<SourceFile> sourceFile = new SourceFile(); + sourceFile->path = path; + sourceFile->content = source; + + TranslationUnitOptions translationUnitOptions; + translationUnitOptions.sourceFiles.Add(sourceFile); + + CompileUnit translationUnit = parseTranslationUnit(translationUnitOptions, moduleOptions); + + // TODO: handle errors + + checkTranslationUnit(translationUnit, moduleOptions); + + // Skip code generation + + // + + RefPtr<ProgramSyntaxNode> moduleDecl = translationUnit.SyntaxNode; + + loadedModules.Add(name, moduleDecl); + + return moduleDecl; + + } + + String autoImportModule( + String const& path, + String const& source, + CodePosition const& loc) + { + // TODO: may want to have some kind of canonicalization step here + String name = path; + + // Have we already loaded a module matching this name? + if (loadedModules.TryGetValue(name)) + return name; + + loadModule(name, path, source, loc); + + return name; + } + RefPtr<ProgramSyntaxNode> findOrImportModule( String const& name, CodePosition const& loc) @@ -486,47 +565,30 @@ struct CompileRequest String foundPath; String foundSource; - bool found = includeHandler.TryToFindIncludeFile(fileName, pathIncludedFrom, &foundPath, &foundSource); - if (!found) + IncludeResult includeResult = includeHandler.TryToFindIncludeFile(fileName, pathIncludedFrom, &foundPath, &foundSource); + switch( includeResult ) { - this->mSink.diagnose(loc, Diagnostics::cannotFindFile, fileName); + case IncludeResult::NotFound: + case IncludeResult::Error: + { + this->mSink.diagnose(loc, Diagnostics::cannotFindFile, fileName); + + loadedModules[name] = nullptr; + return nullptr; + } + break; - loadedModules[name] = nullptr; - return nullptr; + default: + break; } // We've found a file that we can load for the given module, so - // now we need to try compiling it, etc. - - // We don't want to use the same options that the user specified - // for loading modules on-demand. In particular, we always want - // semantic checking to be enabled. - CompileOptions moduleOptions; - moduleOptions.SearchDirectories = Options.SearchDirectories; - moduleOptions.profile = Options.profile; - - RefPtr<SourceFile> sourceFile = new SourceFile(); - sourceFile->path = foundPath; - sourceFile->content = foundSource; - - TranslationUnitOptions translationUnitOptions; - translationUnitOptions.sourceFiles.Add(sourceFile); - - CompileUnit translationUnit = parseTranslationUnit(translationUnitOptions, moduleOptions); - - // TODO: handle errors - - checkTranslationUnit(translationUnit, moduleOptions); - - // Skip code generation - - // - - moduleDecl = translationUnit.SyntaxNode; - - loadedModules.Add(name, moduleDecl); - - return moduleDecl; + // go ahead and perform the module-load action + return loadModule( + name, + foundPath, + foundSource, + loc); } }; @@ -539,6 +601,15 @@ RefPtr<ProgramSyntaxNode> findOrImportModule( return request->findOrImportModule(name, loc); } +String autoImportModule( + CompileRequest* request, + String const& path, + String const& source, + CodePosition const& loc) +{ + return request->autoImportModule(path, source, loc); +} + void Session::addBuiltinSource( RefPtr<Scope> const& scope, String const& path, @@ -687,9 +758,16 @@ SLANG_API void spSetDiagnosticCallback( SLANG_API void spAddSearchPath( SlangCompileRequest* request, - const char* searchDir) + const char* path) +{ + REQ(request)->Options.searchDirectories.Add(Slang::SearchDirectory(path, Slang::SearchDirectory::Kind::Default)); +} + +SLANG_API void spAddAutoImportPath( + SlangCompileRequest* request, + const char* path) { - REQ(request)->Options.SearchDirectories.Add(searchDir); + REQ(request)->Options.searchDirectories.Add(Slang::SearchDirectory(path, Slang::SearchDirectory::Kind::AutoImport)); } SLANG_API void spAddPreprocessorDefine( diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 7f5e0e32e..4a3313b07 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -193,6 +193,7 @@ <ClCompile Include="emit.cpp" /> <ClCompile Include="lexer.cpp" /> <ClCompile Include="lookup.cpp" /> + <ClCompile Include="options.cpp" /> <ClCompile Include="parameter-binding.cpp" /> <ClCompile Include="parser.cpp" /> <ClCompile Include="preprocessor.cpp" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index 31c332295..7eee0643d 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -43,5 +43,6 @@ <ClCompile Include="syntax.cpp" /> <ClCompile Include="token.cpp" /> <ClCompile Include="type-layout.cpp" /> + <ClCompile Include="options.cpp" /> </ItemGroup> </Project>
\ No newline at end of file diff --git a/source/slang/token-defs.h b/source/slang/token-defs.h index f29574bbb..f7800de55 100644 --- a/source/slang/token-defs.h +++ b/source/slang/token-defs.h @@ -30,6 +30,8 @@ TOKEN(NewLine, "newline") TOKEN(LineComment, "line comment") TOKEN(BlockComment, "block comment") +TOKEN(AutoImport, "automatic import directive") + #define PUNCTUATION(id, text) \ TOKEN(id, "'" text "'") |
