summaryrefslogtreecommitdiffstats
path: root/source/slang
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2017-06-19 10:23:16 -0700
committerGitHub <noreply@github.com>2017-06-19 10:23:16 -0700
commitbb9ca29160f5d95f3860504262693ea650d96be5 (patch)
tree04f502283ed818f661e368b3d8d1ba5ce875d1d2 /source/slang
parentcafed774d99f95bce6f182599913f3417dc68a3a (diff)
parent634522da69b14b38c15b14d6b717b1289812e9bb (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.h22
-rw-r--r--source/slang/options.cpp566
-rw-r--r--source/slang/parser.cpp14
-rw-r--r--source/slang/preprocessor.cpp88
-rw-r--r--source/slang/preprocessor.h24
-rw-r--r--source/slang/slang.cpp172
-rw-r--r--source/slang/slang.vcxproj1
-rw-r--r--source/slang/slang.vcxproj.filters1
-rw-r--r--source/slang/token-defs.h2
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 "'")