diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2017-06-19 10:23:16 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-06-19 10:23:16 -0700 |
| commit | bb9ca29160f5d95f3860504262693ea650d96be5 (patch) | |
| tree | 04f502283ed818f661e368b3d8d1ba5ce875d1d2 /source/slang | |
| parent | cafed774d99f95bce6f182599913f3417dc68a3a (diff) | |
| parent | 634522da69b14b38c15b14d6b717b1289812e9bb (diff) | |
Merge pull request #29 from tfoleyNV/auto-import
Allow for automatic importing of Slang code
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 "'") |
