glslang preprocessing: Add -E option to print out preprocessed GLSL, and do the work...
authorJohn Kessenich <cepheus@frii.com>
Wed, 17 Jun 2015 02:38:44 +0000 (02:38 +0000)
committerJohn Kessenich <cepheus@frii.com>
Wed, 17 Jun 2015 02:38:44 +0000 (02:38 +0000)
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@31508 e7fa87d3-cd2b-0410-9028-fcbf551c1848

23 files changed:
StandAlone/StandAlone.cpp
Test/baseResults/preprocessor.edge_cases.vert.out [new file with mode: 0644]
Test/baseResults/preprocessor.errors.vert.out [new file with mode: 0644]
Test/baseResults/preprocessor.extensions.vert.out [new file with mode: 0644]
Test/baseResults/preprocessor.function_macro.vert.out [new file with mode: 0644]
Test/baseResults/preprocessor.line.vert.out [new file with mode: 0644]
Test/baseResults/preprocessor.pragma.vert.out [new file with mode: 0644]
Test/baseResults/preprocessor.simple.vert.out [new file with mode: 0644]
Test/preprocessor.edge_cases.vert [new file with mode: 0644]
Test/preprocessor.errors.vert [new file with mode: 0644]
Test/preprocessor.extensions.vert [new file with mode: 0644]
Test/preprocessor.function_macro.vert [new file with mode: 0644]
Test/preprocessor.line.vert [new file with mode: 0644]
Test/preprocessor.pragma.vert [new file with mode: 0644]
Test/preprocessor.simple.vert [new file with mode: 0644]
Test/runtests
Test/test-preprocessor-list [new file with mode: 0644]
Test/test-spirv-list
glslang/MachineIndependent/ParseHelper.cpp
glslang/MachineIndependent/ParseHelper.h
glslang/MachineIndependent/ShaderLang.cpp
glslang/MachineIndependent/preprocessor/Pp.cpp
glslang/Public/ShaderLang.h

index 0c97af5..906dcbd 100644 (file)
@@ -71,6 +71,7 @@ enum TOptions {
     EOptionSpv                = 0x0800,
     EOptionHumanReadableSpv   = 0x1000,
     EOptionDefaultDesktop     = 0x2000,
+    EOptionOutputPreprocessed = 0x4000,
 };
 
 //
@@ -489,6 +490,9 @@ bool ProcessArguments(int argc, char* argv[])
                 Options |= EOptionSpv;
                 Options |= EOptionLinkProgram;
                 break;
+            case 'E':
+                Options |= EOptionOutputPreprocessed;
+                break;
             case 'c':
                 Options |= EOptionDumpConfig;
                 break;
@@ -536,6 +540,13 @@ bool ProcessArguments(int argc, char* argv[])
         }
     }
 
+    // Make sure that -E is not specified alongside -V -H or -l.
+    if (Options & EOptionOutputPreprocessed &&
+        ((Options &
+          (EOptionSpv | EOptionHumanReadableSpv | EOptionLinkProgram)))) {
+      return false;
+    }
+
     return true;
 }
 
@@ -608,12 +619,19 @@ void CompileAndLinkShaders()
             usage();
             return;
         }
+        const int defaultVersion = Options & EOptionDefaultDesktop? 110: 100;
 
         shader->setStrings(shaderStrings, 1);
-
-        if (! shader->parse(&Resources, (Options & EOptionDefaultDesktop) ? 110 : 100, false, messages))
+        if (Options & EOptionOutputPreprocessed) {
+            std::string str;
+            shader->preprocess(&Resources, defaultVersion, ENoProfile, false, false, messages, &str);
+            puts(str.c_str());
+            FreeFileData(shaderStrings);
+            continue;
+        }
+        if (! shader->parse(&Resources, defaultVersion, false, messages))
             CompileFailed = true;
-        
+
         program.addShader(shader);
 
         if (! (Options & EOptionSuppressInfolog)) {
@@ -629,7 +647,7 @@ void CompileAndLinkShaders()
     // Program-level processing...
     //
 
-    if (! program.link(messages))
+    if (!(Options & EOptionOutputPreprocessed) && ! program.link(messages))
         LinkFailed = true;
 
     if (! (Options & EOptionSuppressInfolog)) {
@@ -714,7 +732,8 @@ int C_DECL main(int argc, char* argv[])
     // 1) linking all arguments together, single-threaded, new C++ interface
     // 2) independent arguments, can be tackled by multiple asynchronous threads, for testing thread safety, using the old handle interface
     //
-    if (Options & EOptionLinkProgram) {
+    if (Options & EOptionLinkProgram ||
+        Options & EOptionOutputPreprocessed) {
         glslang::InitializeProcess();
         CompileAndLinkShaders();
         glslang::FinalizeProcess();
@@ -868,6 +887,7 @@ void usage()
            "(Each option must be specified separately, but can go anywhere in the command line.)\n"
            "  -V  create SPIR-V in file <stage>.spv\n"
            "  -H  print human readable form of SPIR-V; turns on -V\n"
+           "  -E  print pre-processed GLSL; cannot be used with -V, -H, or -l.\n"
            "  -c  configuration dump; use to create default configuration file (redirect to a .conf file)\n"
            "  -d  default to desktop (#version 110) when there is no version in the shader (default is ES version 100)\n"
            "  -i  intermediate tree (glslang AST) is printed out\n"
diff --git a/Test/baseResults/preprocessor.edge_cases.vert.out b/Test/baseResults/preprocessor.edge_cases.vert.out
new file mode 100644 (file)
index 0000000..d667ca7
--- /dev/null
@@ -0,0 +1,18 @@
+#version 310 es\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+void main(){\r
+  gl_Position = vec4(3 + 2 + 2 * 4 + 2 + 3 * 2);\r
+}\r
+\r
+\r
+\r
diff --git a/Test/baseResults/preprocessor.errors.vert.out b/Test/baseResults/preprocessor.errors.vert.out
new file mode 100644 (file)
index 0000000..409c16a
--- /dev/null
@@ -0,0 +1,18 @@
+#version 310 es\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+#error This should show up in pp output . \r
+\r
+\r
+\r
+\r
+int main(){\r
+}\r
+\r
+\r
+\r
diff --git a/Test/baseResults/preprocessor.extensions.vert.out b/Test/baseResults/preprocessor.extensions.vert.out
new file mode 100644 (file)
index 0000000..49d1804
--- /dev/null
@@ -0,0 +1,14 @@
+#version 310 es\r
+\r
+#extension GL_OES_texture_3D : enable\r
+#extension GL_EXT_frag_depth : disable\r
+#extension GL_EXT_gpu_shader5 : require\r
+#extension GL_EXT_shader_texture_image_samples : warn\r
+\r
+#extension unknown_extension : require\r
+\r
+int main(){\r
+}\r
+\r
+\r
+\r
diff --git a/Test/baseResults/preprocessor.function_macro.vert.out b/Test/baseResults/preprocessor.function_macro.vert.out
new file mode 100644 (file)
index 0000000..340d71b
--- /dev/null
@@ -0,0 +1,23 @@
+#version 310 es\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+int main(){\r
+  gl_Position = vec4(3 + 1, 3 + 4, 3 + 1);\r
+  gl_Position = vec4(1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12);\r
+  gl_Position = vec4(4 + 3 + 3);\r
+}\r
+\r
+\r
+\r
diff --git a/Test/baseResults/preprocessor.line.vert.out b/Test/baseResults/preprocessor.line.vert.out
new file mode 100644 (file)
index 0000000..a7742fc
--- /dev/null
@@ -0,0 +1,26 @@
+#line 300\r
+\r
+#line 2\r
+\r
+#line 10\r
+\r
+#line 2\r
+\r
+#line 0\r
+\r
+#line 4\r
+\r
+#line 8\r
+\r
+void main(){\r
+  gl_Position = vec4(10);\r
+}\r
+#line 8 4\r
+\r
+#line 12 3\r
+\r
+#line 1\r
+\r
+\r
+\r
+\r
diff --git a/Test/baseResults/preprocessor.pragma.vert.out b/Test/baseResults/preprocessor.pragma.vert.out
new file mode 100644 (file)
index 0000000..22de1ce
--- /dev/null
@@ -0,0 +1,14 @@
+#version 310 es\r
+\r
+#pragma optimize(on)\r
+#pragma optimize(off)\r
+#pragma debug(on)\r
+#pragma debug(off)\r
+\r
+#pragma undefined_pragma(x,4)\r
+\r
+int main(){\r
+}\r
+\r
+\r
+\r
diff --git a/Test/baseResults/preprocessor.simple.vert.out b/Test/baseResults/preprocessor.simple.vert.out
new file mode 100644 (file)
index 0000000..8364576
--- /dev/null
@@ -0,0 +1,25 @@
+#version 310 es\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+             float fn(float x){ return x + 4.0;}\r
+\r
+int main(){\r
+  gl_Position = vec4(1);\r
+  gl_Position = clamp(1, 2, 3);\r
+  gl_Position = vec4(1);\r
+  gl_Position = vec4(1, 2);\r
+  gl_Position = vec4(fn(3));\r
+}\r
+\r
+\r
+\r
diff --git a/Test/preprocessor.edge_cases.vert b/Test/preprocessor.edge_cases.vert
new file mode 100644 (file)
index 0000000..95bfbb3
--- /dev/null
@@ -0,0 +1,15 @@
+#version 310 es\r
+#define X(Y) /*\r
+                */ Y + 2\r
+\r
+#define Y(Z) 2 * Z// asdf\r
+\r
+#define Z(Y) /*\r
+                */ \\r
+  2 /*\r
+       */ + 3 \\r
+    * Y\r
+\r
+void main() {\r
+  gl_Position = vec4(X(3) + Y(4) + Z(2));\r
+}\r
diff --git a/Test/preprocessor.errors.vert b/Test/preprocessor.errors.vert
new file mode 100644 (file)
index 0000000..7de5040
--- /dev/null
@@ -0,0 +1,15 @@
+#version 310 es\r
+\r
+#define X\r
+\r
+#if X\r
+  #if Y\r
+    #error This should not show up in pp output.\r
+  #endif\r
+    #error This should show up in pp output.\r
+#else\r
+  #error This should not show up in pp output.\r
+#endif\r
+\r
+int main() {\r
+}\r
diff --git a/Test/preprocessor.extensions.vert b/Test/preprocessor.extensions.vert
new file mode 100644 (file)
index 0000000..6722820
--- /dev/null
@@ -0,0 +1,12 @@
+#version 310 es\r
+\r
+#extension GL_OES_texture_3D: enable\r
+#extension GL_EXT_frag_depth: disable\r
+#extension GL_EXT_gpu_shader5: require\r
+#extension GL_EXT_shader_texture_image_samples: warn\r
+\r
+#extension unknown_extension: require\r
+\r
+int main() {\r
+}\r
+\r
diff --git a/Test/preprocessor.function_macro.vert b/Test/preprocessor.function_macro.vert
new file mode 100644 (file)
index 0000000..577ea7e
--- /dev/null
@@ -0,0 +1,20 @@
+#version 310 es\r
+\r
+\r
+#define X(n) n + 1\r
+#define Y(n, z) n + z\r
+#define Z(f) X(f)\r
+\r
+#define REALLY_LONG_MACRO_NAME_WITH_MANY_PARAMETERS(X1, X2, X3, X4, X5, X6, X7,\\r
+    X8, X9, X10, X11, X12) X1+X2+X3+X4+X5+X6+X7+X8+X9+X10+X11+X12\r
+\r
+#define A(\\r
+  Y\\r
+  )\\r
+4 + 3 + Y\r
+\r
+int main() {\r
+  gl_Position = vec4(X(3), Y(3, 4), Z(3));\r
+  gl_Position = vec4(REALLY_LONG_MACRO_NAME_WITH_MANY_PARAMETERS(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));\r
+  gl_Position = vec4(A(3));\r
+}\r
diff --git a/Test/preprocessor.line.vert b/Test/preprocessor.line.vert
new file mode 100644 (file)
index 0000000..fed6bba
--- /dev/null
@@ -0,0 +1,39 @@
+#line 300\r
+\r
+#line 2\r
+\r
+\r
+\r
+\r
+\r
+#line __LINE__ + 3\r
+\r
+\r
+#line __FILE__ + 2\r
+\r
+#line __FILE__ * __LINE__\r
+\r
+\r
+#define X 4\r
+\r
+#line X\r
+\r
+#undef X\r
+\r
+#define X(y) y + 3 + 2\r
+\r
+#line X(3)\r
+\r
+void main() {\r
+  gl_Position = vec4(__LINE__);\r
+}\r
+\r
+#line X(3) 4\r
+\r
+#define Z(y, q) \\r
+  y*q*2 q\r
+\r
+#line Z(2, 3)\r
+\r
+#line 1\r
+\r
diff --git a/Test/preprocessor.pragma.vert b/Test/preprocessor.pragma.vert
new file mode 100644 (file)
index 0000000..79f5600
--- /dev/null
@@ -0,0 +1,11 @@
+#version 310 es\r
+\r
+#pragma optimize(on)\r
+#pragma optimize(off)\r
+#pragma debug(on)\r
+#pragma debug(off)\r
+\r
+#pragma undefined_pragma(x, 4)\r
+\r
+int main() {\r
+}\r
diff --git a/Test/preprocessor.simple.vert b/Test/preprocessor.simple.vert
new file mode 100644 (file)
index 0000000..f4749f2
--- /dev/null
@@ -0,0 +1,22 @@
+#version 310 es\r
+#define X 1\r
+#define Y clamp\r
+#define Z X\r
+\r
+#define F 1, 2\r
+\r
+#define make_function \\r
+  float fn ( float x ) \\r
+  {\\r
+    return x + 4.0; \\r
+  }\r
+\r
+make_function\r
+\r
+int main() {\r
+  gl_Position = vec4(X);\r
+  gl_Position = Y(1, 2, 3);\r
+  gl_Position = vec4(Z);\r
+  gl_Position = vec4(F);\r
+  gl_Position = vec4(fn(3));\r
+}\r
index be14ee0..09368f1 100755 (executable)
@@ -55,6 +55,16 @@ while read t; do
 done < test-spirv-list
 
 #
+# Preprocessor tests
+#
+while read t; do
+    echo Running Preprocessor $t...
+    b=`basename $t`
+    $EXE -E $t > $TARGETDIR/$b.out
+    diff -b $BASEDIR/$b.out $TARGETDIR/$b.out || HASERROR=1
+done < test-preprocessor-list
+
+#
 # grouped shaders for bulk (faster) tests
 #
 function runBulkTest {
diff --git a/Test/test-preprocessor-list b/Test/test-preprocessor-list
new file mode 100644 (file)
index 0000000..5d66566
--- /dev/null
@@ -0,0 +1,7 @@
+preprocessor.edge_cases.vert\r
+preprocessor.errors.vert\r
+preprocessor.extensions.vert\r
+preprocessor.function_macro.vert\r
+preprocessor.line.vert\r
+preprocessor.pragma.vert\r
+preprocessor.simple.vert\r
index 62d0f44..fed5688 100644 (file)
@@ -14,7 +14,6 @@ spv.double.comp
 spv.100ops.frag
 spv.130.frag
 spv.140.frag
-spv.140.vert
 spv.150.geom
 spv.150.vert
 spv.300BuiltIns.vert
index abb7133..48b82e6 100644 (file)
@@ -5226,20 +5226,25 @@ TIntermNode* TParseContext::addSwitch(TSourceLoc loc, TIntermTyped* expression,
     return switchNode;
 }
 
-void TParseContext::setCurrentLine(int line)
+void TParseContext::notifyVersion(int line, int version, const char* type_string)
 {
-    currentScanner->setLine(line);
-    if (lineCallback) {
-        lineCallback(line);
+    if (versionCallback) {
+        versionCallback(line, version, type_string);
     }
 }
 
-void TParseContext::notifyVersion(int line, int version, const char* type_string)
+void TParseContext::notifyErrorDirective(int line, const char* error_message)
 {
-    if (versionCallback) {
-        versionCallback(line, version, type_string);
+    if (errorCallback) {
+        errorCallback(line, error_message);
     }
 }
 
+void TParseContext::notifyLineDirective(int line, bool has_source, int source)
+{
+    if (lineCallback) {
+        lineCallback(line, has_source, source);
+    }
+}
 
 } // end namespace glslang
index 5aaaf28..ee9d892 100644 (file)
@@ -41,6 +41,7 @@
 #include "SymbolTable.h"
 #include "localintermediate.h"
 #include "Scan.h"
+#include <functional>
 
 #include <functional>
 
@@ -200,10 +201,13 @@ public:
     void addError() { ++numErrors; }
     int getNumErrors() const { return numErrors; }
     const TSourceLoc& getCurrentLoc() const { return currentScanner->getSourceLoc(); }
-    void setCurrentLine(int line);
+    void setCurrentLine(int line) { currentScanner->setLine(line); }
     void setCurrentString(int string) { currentScanner->setString(string); }
+    void setScanner(TInputScanner* scanner) { currentScanner  = scanner; }
 
     void notifyVersion(int line, int version, const char* type_string);
+    void notifyErrorDirective(int line, const char* error_message);
+    void notifyLineDirective(int line, bool has_source, int source);
 
     // The following are implemented in Versions.cpp to localize version/profile/stage/extensions control
     void initializeExtensionBehavior();
@@ -223,8 +227,9 @@ public:
 
     void setVersionCallback(const std::function<void(int, int, const char*)>& func) { versionCallback = func; }
     void setPragmaCallback(const std::function<void(int, const TVector<TString>&)>& func) { pragmaCallback = func; }
-    void setLineCallback(const std::function<void(int)>& func) { lineCallback = func; }
+    void setLineCallback(const std::function<void(int, bool, int)>& func) { lineCallback = func; }
     void setExtensionCallback(const std::function<void(int, const char*, const char*)>& func) { extensionCallback = func; }
+    void setErrorCallback(const std::function<void(int, const char*)>& func) { errorCallback = func; }
 
 protected:
     void nonInitConstCheck(TSourceLoc, TString& identifier, TType& type);
@@ -333,10 +338,11 @@ protected:
 
     // These, if set, will be called when a line, pragma ... is preprocessed.
     // They will be called with any parameters to the original directive.
-    std::function<void(int)> lineCallback;
+    std::function<void(int, bool, int)> lineCallback;
     std::function<void(int, const TVector<TString>&)> pragmaCallback;
     std::function<void(int, int, const char*)> versionCallback;
     std::function<void(int, const char*, const char*)> extensionCallback;
+    std::function<void(int, const char*)> errorCallback;
 };
 
 } // end namespace glslang
index 59b692c..b6a4caa 100644 (file)
@@ -41,6 +41,8 @@
 // and the shading language compiler/linker.
 //
 #include <string.h>
+#include <iostream>
+#include <sstream>
 #include "SymbolTable.h"
 #include "ParseHelper.h"
 #include "Scan.h"
@@ -433,18 +435,17 @@ bool DeduceVersionProfile(TInfoSink& infoSink, EShLanguage stage, bool versionNo
     return correct;
 }
 
-//
-// Do a partial compile on the given strings for a single compilation unit
-// for a potential deferred link into a single stage (and deferred full compile of that
-// stage through machine-dependent compilation).
-//
-// All preprocessing, parsing, semantic checks, etc. for a single compilation unit
-// are done here.
-//
-// Return:  The tree and other information is filled into the intermediate argument, 
-//          and true is returned by the function for success.
-//
-bool CompileDeferred(
+// This is the common setup and cleanup code for PreprocessDeferred and
+// CompileDeferred.
+// It takes any callable with a signature of
+//  bool (TParseContext& parseContext, TPpContext& ppContext,
+//                  TInputScanner& input, bool versionWillBeError,
+//                  TSymbolTable& , TIntermediate& ,
+//                  EShOptimizationLevel , EShMessages );
+// Which returns false if a failure was detected and true otherwise.
+//
+template<typename ProcessingContext>
+bool ProcessDeferred(
     TCompiler* compiler,
     const char* const shaderStrings[],
     const int numStrings,
@@ -459,7 +460,9 @@ bool CompileDeferred(
     bool forceDefaultVersionAndProfile,
     bool forwardCompatible,     // give errors for use of deprecated features
     EShMessages messages,       // warnings/errors/AST; things to print out
-    TIntermediate& intermediate // returned tree, etc.
+    TIntermediate& intermediate, // returned tree, etc.
+    ProcessingContext& processingContext,
+    bool requireNonempty
     )
 {
     if (! InitThread())
@@ -481,7 +484,7 @@ bool CompileDeferred(
     //   string 2...numStrings+1: user's shader
     //   string numStrings+2:     "int;"
     const int numPre = 2;
-    const int numPost = 1;
+    const int numPost = requireNonempty? 1 : 0;
     size_t* lengths = new size_t[numStrings + numPre + numPost];
     const char** strings = new const char*[numStrings + numPre + numPost];
     for (int s = 0; s < numStrings; ++s) {
@@ -566,7 +569,6 @@ bool CompileDeferred(
 
     parseContext.initializeExtensionBehavior();
 
-    bool success = true;
     
     // Fill in the strings as outlined above.
     strings[0] = parseContext.getPreamble();
@@ -574,33 +576,22 @@ bool CompileDeferred(
     strings[1] = customPreamble;
     lengths[1] = strlen(strings[1]);
     assert(2 == numPre);
-    strings[numStrings + numPre] = "\n int;";
-    lengths[numStrings + numPre] = strlen(strings[numStrings + numPre]);
+    if (requireNonempty) {
+        strings[numStrings + numPre] = "\n int;";
+        lengths[numStrings + numPre] = strlen(strings[numStrings + numPre]);
+    }
     TInputScanner fullInput(numStrings + numPre + numPost, strings, lengths, numPre, numPost);
 
     // Push a new symbol allocation scope that will get used for the shader's globals.
     symbolTable.push();
 
-    // Parse the full shader.
-    if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError))
-        success = false;
-    intermediate.addSymbolLinkageNodes(parseContext.linkage, parseContext.language, symbolTable);
+    bool success = processingContext(parseContext, ppContext, fullInput,
+                                     versionWillBeError, symbolTable,
+                                     intermediate, optLevel, messages);
 
     // Clean up the symbol table. The AST is self-sufficient now.
     delete symbolTableMemory;
 
-    if (success && intermediate.getTreeRoot()) {
-        if (optLevel == EShOptNoGeneration)
-            parseContext.infoSink.info.message(EPrefixNone, "No errors.  No code generation or linking was requested.");
-        else
-            success = intermediate.postProcess(intermediate.getTreeRoot(), parseContext.language);
-    } else if (! success) {
-        parseContext.infoSink.info.prefix(EPrefixError);
-        parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors.  No code generated.\n\n";
-    }
-
-    if (messages & EShMsgAST)
-        intermediate.output(parseContext.infoSink, true);
 
     delete [] lengths;
     delete [] strings;
@@ -608,6 +599,216 @@ bool CompileDeferred(
     return success;
 }
 
+// DoPreprocessing is a valid ProcessingContext template argument,
+// which only performs the preprocessing step of compilation.
+// It places the result in the "string" argument to its constructor.
+struct DoPreprocessing {
+    explicit DoPreprocessing(std::string* string): outputString(string) {}
+    bool operator()(TParseContext& parseContext, TPpContext& ppContext,
+                    TInputScanner& input, bool versionWillBeError,
+                    TSymbolTable& , TIntermediate& ,
+                    EShOptimizationLevel , EShMessages )
+    {
+        // This is a list of tokens that do not require a space before or after.
+        static const std::string unNeededSpaceTokens = ";()[]";
+        static const std::string noSpaceBeforeTokens = ",";
+        glslang::TPpToken token;
+
+        std::stringstream outputStream;
+        int lastLine = -1; // lastLine is the line number of the last token
+        // processed. It is tracked in order for new-lines to be inserted when
+        // a token appears on a new line.
+        int lastToken = -1;
+        parseContext.setScanner(&input);
+        ppContext.setInput(input, versionWillBeError);
+
+        // Inserts newlines and incremnets lastLine until
+        // lastLine >= line.
+        auto adjustLine = [&lastLine, &outputStream](int line) {
+            int tokenLine = line - 1;
+            while(lastLine < tokenLine) {
+                if (lastLine >= 0) {
+                     outputStream << std::endl;
+                }
+                ++lastLine;
+            }
+        };
+
+        parseContext.setExtensionCallback([&adjustLine, &outputStream](
+            int line, const char* extension, const char* behavior) {
+                adjustLine(line);
+                outputStream << "#extension " << extension << " : " << behavior;
+        });
+        parseContext.setLineCallback([&lastLine, &outputStream](
+            int line, bool hasSource, int sourceNum) {
+            // SourceNum is the number of the source-string that is being parsed.
+            if (lastLine != -1) {
+                outputStream << std::endl;
+            }
+            outputStream << "#line " << line;
+            if (hasSource) {
+                outputStream << " " << sourceNum;
+            }
+            outputStream << std::endl;
+            lastLine = std::max(line - 1, 1);
+        });
+
+
+        parseContext.setVersionCallback(
+            [&adjustLine, &lastLine, &outputStream](int line, int version, const char* str) {
+                adjustLine(line);
+                outputStream << "#version " << version;
+                if (str) {
+                    outputStream << " " << str;
+                }
+                outputStream << std::endl;
+                ++lastLine;
+            });
+
+        parseContext.setPragmaCallback([&adjustLine, &outputStream](
+            int line, const glslang::TVector<glslang::TString>& ops) {
+                adjustLine(line);
+                outputStream << "#pragma ";
+                for(size_t i = 0; i < ops.size(); ++i) {
+                    outputStream << ops[i];
+                }
+        });
+
+        parseContext.setErrorCallback([&adjustLine, &outputStream](
+            int line, const char* errorMessage) {
+                adjustLine(line);
+                outputStream << "#error " << errorMessage;
+        });
+        while (const char* tok = ppContext.tokenize(&token)) {
+            int tokenLine = token.loc.line - 1;  // start at 0;
+            bool newLine = false;
+            while (lastLine < tokenLine) {
+                if (lastLine > -1) {
+                    outputStream << std::endl;
+                    newLine = true;
+                }
+                ++lastLine;
+                if (lastLine == tokenLine) {
+                    // Don't emit whitespace onto empty lines.
+                    // Copy any whitespace characters at the start of a line
+                    // from the input to the output.
+                    for(int i = 0; i < token.loc.column - 1; ++i) {
+                        outputStream << " ";
+                    }
+                }
+            }
+
+            // Output a space in between tokens, but not at the start of a line,
+            // and also not around special tokens. This helps with readability
+            // and consistency.
+            if (!newLine &&
+                lastToken != -1 &&
+                (unNeededSpaceTokens.find((char)token.token) == std::string::npos) &&
+                (unNeededSpaceTokens.find((char)lastToken) == std::string::npos) &&
+                (noSpaceBeforeTokens.find((char)token.token) == std::string::npos)) {
+                outputStream << " ";
+            }
+            lastToken = token.token;
+            outputStream << tok;
+        }
+        outputStream << std::endl;
+        *outputString = outputStream.str();
+
+        return true;
+    }
+    std::string* outputString;
+};
+
+// DoFullParse is a valid ProcessingConext template argument for fully
+// parsing the shader.  It populates the "intermediate" with the AST.
+struct DoFullParse{
+  bool operator()(TParseContext& parseContext, TPpContext& ppContext,
+                  TInputScanner& fullInput, bool versionWillBeError,
+                  TSymbolTable& symbolTable, TIntermediate& intermediate,
+                  EShOptimizationLevel optLevel, EShMessages messages) 
+    {
+        bool success = true;
+        // Parse the full shader.
+        if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError))
+            success = false;
+        intermediate.addSymbolLinkageNodes(parseContext.linkage, parseContext.language, symbolTable);
+
+        if (success && intermediate.getTreeRoot()) {
+            if (optLevel == EShOptNoGeneration)
+                parseContext.infoSink.info.message(EPrefixNone, "No errors.  No code generation or linking was requested.");
+            else
+                success = intermediate.postProcess(intermediate.getTreeRoot(), parseContext.language);
+        } else if (! success) {
+            parseContext.infoSink.info.prefix(EPrefixError);
+            parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors.  No code generated.\n\n";
+        }
+
+        if (messages & EShMsgAST)
+            intermediate.output(parseContext.infoSink, true);
+
+        return success;
+    }
+};
+
+// Take a single compilation unit, and run the preprocessor on it.
+// Return: True if there were no issues found in preprocessing,
+//         False if during preprocessing any unknown version, pragmas or
+//         extensions were found.
+bool PreprocessDeferred(
+    TCompiler* compiler,
+    const char* const shaderStrings[],
+    const int numStrings,
+    const int* inputLengths,
+    const char* preamble,
+    const EShOptimizationLevel optLevel,
+    const TBuiltInResource* resources,
+    int defaultVersion,         // use 100 for ES environment, 110 for desktop
+    EProfile defaultProfile,
+    bool forceDefaultVersionAndProfile,
+    bool forwardCompatible,     // give errors for use of deprecated features
+    EShMessages messages,       // warnings/errors/AST; things to print out
+    TIntermediate& intermediate, // returned tree, etc.
+    std::string* outputString)
+{
+    DoPreprocessing parser(outputString);
+    return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths,
+                           preamble, optLevel, resources, defaultVersion, defaultProfile, forceDefaultVersionAndProfile,
+                           forwardCompatible, messages, intermediate, parser, false);
+}
+
+
+//
+// do a partial compile on the given strings for a single compilation unit
+// for a potential deferred link into a single stage (and deferred full compile of that
+// stage through machine-dependent compilation).
+//
+// all preprocessing, parsing, semantic checks, etc. for a single compilation unit
+// are done here.
+//
+// return:  the tree and other information is filled into the intermediate argument, 
+//          and true is returned by the function for success.
+//
+bool CompileDeferred(
+    TCompiler* compiler,
+    const char* const shaderStrings[],
+    const int numStrings,
+    const int* inputLengths,
+    const char* preamble,
+    const EShOptimizationLevel optLevel,
+    const TBuiltInResource* resources,
+    int defaultVersion,         // use 100 for ES environment, 110 for desktop
+    EProfile defaultProfile,
+    bool forceDefaultVersionAndProfile,
+    bool forwardCompatible,     // give errors for use of deprecated features
+    EShMessages messages,       // warnings/errors/AST; things to print out
+    TIntermediate& intermediate) // returned tree, etc.
+{
+    DoFullParse parser;
+    return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths,
+                           preamble, optLevel, resources, defaultVersion, defaultProfile, forceDefaultVersionAndProfile,
+                           forwardCompatible, messages, intermediate, parser, true);
+}
+
 } // end anonymous namespace for local functions
 
 
@@ -1026,7 +1227,8 @@ TShader::~TShader()
 //
 // Returns true for success.
 //
-bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile, bool forwardCompatible, EShMessages messages)
+bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile,
+                    bool forwardCompatible, EShMessages messages)
 {
     if (! InitThread())
         return false;
@@ -1041,7 +1243,28 @@ bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion
 
 bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, bool forwardCompatible, EShMessages messages)
 {
-  return parse(builtInResources, defaultVersion, ENoProfile, false, forwardCompatible, messages);
+    return parse(builtInResources, defaultVersion, ENoProfile, false, forwardCompatible, messages);
+}
+
+// Fill in a string with the result of preprocessing ShaderStrings
+// Returns true if all extensions, pragmas and version strings were valid.
+bool TShader::preprocess(const TBuiltInResource* builtInResources,
+                         int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile,
+                         bool forwardCompatible,
+                         EShMessages message, std::string* output_string)
+{
+    if (! InitThread())
+        return false;
+
+    pool = new TPoolAllocator();
+    SetThreadPoolAllocator(*pool);
+    if (! preamble)
+        preamble = "";
+
+    return PreprocessDeferred(compiler, strings, numStrings,
+                              nullptr, preamble, EShOptNone, builtInResources,
+                              defaultVersion, defaultProfile, forceDefaultVersionAndProfile, forwardCompatible, message,
+                              *intermediate, output_string);
 }
 
 const char* TShader::getInfoLog()
index c309d55..741f089 100644 (file)
@@ -629,10 +629,15 @@ int TPpContext::CPPline(TPpToken* ppToken)
         return token;
     }
 
-    int lineRes = 0;
+    int lineRes = 0; // Line number after macro expansion.
+    int lineToken = 0;
+    int fileRes = 0; // Source file number after macro expansion.
+    bool hasFile = false;
     bool lineErr = false;
+    bool fileErr = false;
     token = eval(token, MIN_PRECEDENCE, false, lineRes, lineErr, ppToken);
     if (! lineErr) {
+        lineToken = lineRes;
         if (token == '\n')
             ++lineRes;
 
@@ -648,14 +653,15 @@ int TPpContext::CPPline(TPpToken* ppToken)
         parseContext.setCurrentLine(lineRes);
 
         if (token != '\n') {
-            int fileRes = 0;
-            bool fileErr = false;
             token = eval(token, MIN_PRECEDENCE, false, fileRes, fileErr, ppToken);
             if (! fileErr)
                 parseContext.setCurrentString(fileRes);
+                hasFile = true;
         }
     }
-
+    if (!fileErr && !lineErr) {
+      parseContext.notifyLineDirective(lineToken, hasFile, fileRes);
+    }
     token = extraTokenCheck(lineAtom, ppToken, token);
 
     return token;
@@ -680,6 +686,7 @@ int TPpContext::CPPerror(TPpToken* ppToken)
         message.append(" ");
         token = scanToken(ppToken);
     }
+    parseContext.notifyErrorDirective(loc.line, message.c_str());
     //store this msg into the shader's information log..set the Compile Error flag!!!!
     parseContext.error(loc, message.c_str(), "#error", "");
 
index e2e7eb2..a671221 100644 (file)
@@ -245,6 +245,7 @@ SH_IMPORT_EXPORT int ShGetUniformLocation(const ShHandle uniformMap, const char*
 //
 
 #include <list>
+#include <string>
 
 class TCompiler;
 class TInfoSink;
@@ -284,6 +285,10 @@ public:
     // Equivalent to parse() without a default profile and without forcing defaults.
     // Provided for backwards compatibility.
     bool parse(const TBuiltInResource*, int defaultVersion, bool forwardCompatible, EShMessages);
+    bool preprocess(const TBuiltInResource* builtInResources,
+                    int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile,
+                    bool forwardCompatible,
+                    EShMessages message, std::string* outputString);
 
     const char* getInfoLog();
     const char* getInfoDebugLog();