Improve preprocessor by using GLSL scanner, allowing read-only strings to be compiled...
authorJohn Kessenich <cepheus@frii.com>
Mon, 28 Oct 2013 18:12:06 +0000 (18:12 +0000)
committerJohn Kessenich <cepheus@frii.com>
Mon, 28 Oct 2013 18:12:06 +0000 (18:12 +0000)
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@23721 e7fa87d3-cd2b-0410-9028-fcbf551c1848

20 files changed:
Install/Windows/glslangValidator.exe
StandAlone/StandAlone.cpp
Test/baseResults/empty.frag.out [new file with mode: 0644]
Test/baseResults/versionsClean.frag.out
Test/empty.frag [new file with mode: 0644]
Test/empty2.frag [new file with mode: 0644]
Test/empty3.frag [new file with mode: 0644]
Test/runtests
glslang/MachineIndependent/ParseHelper.cpp
glslang/MachineIndependent/ParseHelper.h
glslang/MachineIndependent/Scan.cpp
glslang/MachineIndependent/Scan.h
glslang/MachineIndependent/ShaderLang.cpp
glslang/MachineIndependent/Versions.cpp
glslang/MachineIndependent/preprocessor/Pp.cpp
glslang/MachineIndependent/preprocessor/PpContext.cpp
glslang/MachineIndependent/preprocessor/PpContext.h
glslang/MachineIndependent/preprocessor/PpScanner.cpp
glslang/MachineIndependent/preprocessor/PpTokens.cpp
glslang/Public/ShaderLang.h

index 9cb8d50..41819bc 100644 (file)
Binary files a/Install/Windows/glslangValidator.exe and b/Install/Windows/glslangValidator.exe differ
index 5a4ebb0..441f88b 100644 (file)
@@ -92,12 +92,12 @@ ShBindingTable FixedAttributeTable = { 3, FixedAttributeBindings };
 EShLanguage FindLanguage(const std::string& name);
 bool CompileFile(const char *fileName, ShHandle, int options);
 void usage();
-void FreeFileData(char **data);
-char** ReadFileData(const char *fileName);
+void FreeFileData(char** data);
+char** ReadFileData(const charfileName);
 void InfoLogMsg(const char* msg, const char* name, const int num);
 
 // Use to test breaking up a single shader file into multiple strings.
-int NumShaderStrings = 1;
+int NumShaderStrings;
 
 TBuiltInResource Resources;
 std::string ConfigFile;
@@ -205,7 +205,7 @@ void ProcessConfigFile()
     char** configStrings = 0;
     char *config = 0;
     if (ConfigFile.size() > 0) {
-        char** configStrings = ReadFileData(ConfigFile.c_str());
+        configStrings = ReadFileData(ConfigFile.c_str());
         if (configStrings)
             config = *configStrings;
         else {
@@ -731,9 +731,11 @@ bool CompileFile(const char *fileName, ShHandle compiler, int Options)
         for (int j = 0; j < ((Options & EOptionMemoryLeakMode) ? 100 : 1); ++j) {
             //ret = ShCompile(compiler, shaderStrings, NumShaderStrings, lengths, EShOptNone, &Resources, Options, 100, false, messages);
             ret = ShCompile(compiler, shaderStrings, NumShaderStrings, 0, EShOptNone, &Resources, Options, 100, false, messages);
-            //const char* multi[4] = { "# ve", "rsion", " 300 e", "s" };
+            //const char* multi[12] = { "# ve", "rsion", " 300 e", "s", "\n#err", 
+            //                         "or should be l", "ine 1", "string 5\n", "float glo", "bal", 
+            //                         ";\n#error should be line 2\n void main() {", "global = 2.3;}" };
             //const char* multi[7] = { "/", "/", "\\", "\n", "\n", "#", "version 300 es" };
-            //ret = ShCompile(compiler, multi, 4, 0, EShOptNone, &Resources, Options, 100, false, messages);
+            //ret = ShCompile(compiler, multi, 7, 0, EShOptNone, &Resources, Options, 100, false, messages);
         }
 
         if (Options & EOptionMemoryLeakMode)
@@ -836,12 +838,13 @@ char** ReadFileData(const char *fileName)
     }
     fdata[count] = '\0';
     fclose(in);
-    if(count==0){
+    if (count == 0) {
         return_data[0]=(char*)malloc(count+2);
         return_data[0][0]='\0';
-        NumShaderStrings=0;
+        NumShaderStrings = 0;
         return return_data;       
-    }
+    } else
+        NumShaderStrings = 1;
 
        int len = (int)(ceil)((float)count/(float)NumShaderStrings);
     int ptr_len=0,i=0;
diff --git a/Test/baseResults/empty.frag.out b/Test/baseResults/empty.frag.out
new file mode 100644 (file)
index 0000000..fa91cf3
--- /dev/null
@@ -0,0 +1,30 @@
+empty.frag\r
+WARNING: #version: statement missing; use #version on first line of shader\r
+\r
+0:? Sequence\r
+0:?   Linker Objects\r
+\r
+empty2.frag\r
+WARNING: #version: statement missing; use #version on first line of shader\r
+\r
+0:? Sequence\r
+0:?   Linker Objects\r
+\r
+empty3.frag\r
+Warning, version 110 is not yet complete; most features are present, but a few are missing.\r
+\r
+0:? Sequence\r
+0:?   Linker Objects\r
+\r
+\r
+Linked fragment stage:\r
+\r
+ERROR: Linking fragment stage: Cannot mix ES profile with non-ES profile shaders\r
+\r
+ERROR: Linking fragment stage: Cannot mix ES profile with non-ES profile shaders\r
+\r
+ERROR: Linking fragment stage: Missing entry point: Each stage requires one "void main()" entry point\r
+\r
+0:? Sequence\r
+0:?   Linker Objects\r
+\r
index 19082c3..783d469 100644 (file)
@@ -1,5 +1,6 @@
 ERROR: #version: statement must appear first in es-profile shader; before comments or newlines\r
-ERROR: 1 compilation errors.  No code generated.\r
+ERROR: 0:34: '#version' : must occur first in shader \r
+ERROR: 2 compilation errors.  No code generated.\r
 \r
 ERROR: node is still EOpNull!\r
 0:41  Function Definition: main( (void)\r
diff --git a/Test/empty.frag b/Test/empty.frag
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/empty2.frag b/Test/empty2.frag
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/Test/empty3.frag b/Test/empty3.frag
new file mode 100644 (file)
index 0000000..14cd83d
--- /dev/null
@@ -0,0 +1 @@
+#version 110
index 0bde19e..5f0e941 100644 (file)
@@ -41,6 +41,7 @@ runLinkTest recurse1.vert recurse1.frag recurse2.frag
 runLinkTest 300link.frag
 runLinkTest 300link2.frag
 runLinkTest 300link3.frag
+runLinkTest empty.frag empty2.frag empty3.frag
 
 #
 # multi-threaded test
index 7abc7be..77100a8 100644 (file)
@@ -35,6 +35,7 @@
 //
 
 #include "ParseHelper.h"
+#include "Scan.h"
 
 #include "osinclude.h"
 #include <stdarg.h>
@@ -51,12 +52,10 @@ TParseContext::TParseContext(TSymbolTable& symt, TIntermediate& interm, bool pb,
             intermediate(interm), symbolTable(symt), infoSink(is), language(L),
             version(v), profile(p), forwardCompatible(fc), messages(m),
             contextPragma(true, false), loopNestingLevel(0), structNestingLevel(0),
-            tokensBeforeEOF(false),
-            numErrors(0), parsingBuiltins(pb), afterEOF(false)
+            tokensBeforeEOF(false), currentScanner(0),
+            numErrors(0), parsingBuiltins(pb), afterEOF(false),
+            anyIndexLimits(false)
 {
-    currentLoc.line = 1;
-    currentLoc.string = 0;
-
     // ensure we always have a linkage node, even if empty, to simplify tree topology algorithms
     linkage = new TIntermAggregate;
 
@@ -106,43 +105,9 @@ TParseContext::TParseContext(TSymbolTable& symt, TIntermediate& interm, bool pb,
         globalOutputDefaults.layoutStream = 0;
 }
 
-//
-// Parse an array of strings using yyparse, going through the
-// preprocessor to tokenize the shader strings, then through
-// the GLSL scanner.
-//
-// Returns true for successful acceptance of the shader, false if any errors.
-//
-bool TParseContext::parseShaderStrings(TPpContext& ppContext, char* strings[], size_t lengths[], int numStrings)
+void TParseContext::setLimits(const TLimits& L)
 {
-    // empty shaders are okay
-    if (! strings || numStrings == 0 || lengths[0] == 0)
-        return true;
-
-    for (int i = 0; i < numStrings; ++i) {
-        if (! strings[i]) {
-            TSourceLoc loc;
-            loc.string = i;
-            loc.line = 1;
-            error(loc, "Null shader source string", "", "");
-
-            return false;
-        }
-    }
-
-    if (getPreamble())
-        ppContext.setPreamble(getPreamble(), strlen(getPreamble()));
-    ppContext.setShaderStrings(strings, lengths, numStrings);
-
-    // TODO: desktop PP: a shader containing nothing but white space and comments is valid, even though it has no parse tokens
-    size_t len = 0;
-    while (strings[0][len] == ' '  ||
-           strings[0][len] == '\t' ||
-           strings[0][len] == '\n' ||
-           strings[0][len] == '\r') {
-        if (++len >= lengths[0])
-            return true;
-    }
+    limits = L;
 
     anyIndexLimits = ! limits.generalAttributeMatrixVectorIndexing ||
                      ! limits.generalConstantMatrixVectorIndexing ||
@@ -150,10 +115,21 @@ bool TParseContext::parseShaderStrings(TPpContext& ppContext, char* strings[], s
                      ! limits.generalUniformIndexing ||
                      ! limits.generalVariableIndexing ||
                      ! limits.generalVaryingIndexing;
+}
 
+//
+// Parse an array of strings using yyparse, going through the
+// preprocessor to tokenize the shader strings, then through
+// the GLSL scanner.
+//
+// Returns true for successful acceptance of the shader, false if any errors.
+//
+bool TParseContext::parseShaderStrings(TPpContext& ppContext, TInputScanner& input, bool versionWillBeError)
+{
+    currentScanner = &input;
+    ppContext.setInput(input, versionWillBeError);
     yyparse((void*)this);
-
-    finalize();
+    finalErrorCheck();
 
     return numErrors == 0;
 }
@@ -163,21 +139,21 @@ void TParseContext::parserError(const char *s)
 {
     if (afterEOF) {
         if (tokensBeforeEOF == 1)
-            error(currentLoc, "", "pre-mature EOF", s, "");
+            error(getCurrentLoc(), "", "pre-mature EOF", s, "");
     } else
-        error(currentLoc, "", "", s, "");
+        error(getCurrentLoc(), "", "", s, "");
 }
 
 void TParseContext::handlePragma(const char **tokens, int numTokens)
 {
     if (!strcmp(tokens[0], "optimize")) {
         if (numTokens != 4) {
-            error(currentLoc, "optimize pragma syntax is incorrect", "#pragma", "");
+            error(getCurrentLoc(), "optimize pragma syntax is incorrect", "#pragma", "");
             return;
         }
 
         if (strcmp(tokens[1], "(")) {
-            error(currentLoc, "\"(\" expected after 'optimize' keyword", "#pragma", "");
+            error(getCurrentLoc(), "\"(\" expected after 'optimize' keyword", "#pragma", "");
             return;
         }
 
@@ -186,22 +162,22 @@ void TParseContext::handlePragma(const char **tokens, int numTokens)
         else if (!strcmp(tokens[2], "off"))
             contextPragma.optimize = false;
         else {
-            error(currentLoc, "\"on\" or \"off\" expected after '(' for 'optimize' pragma", "#pragma", "");
+            error(getCurrentLoc(), "\"on\" or \"off\" expected after '(' for 'optimize' pragma", "#pragma", "");
             return;
         }
 
         if (strcmp(tokens[3], ")")) {
-            error(currentLoc, "\")\" expected to end 'optimize' pragma", "#pragma", "");
+            error(getCurrentLoc(), "\")\" expected to end 'optimize' pragma", "#pragma", "");
             return;
         }
     } else if (!strcmp(tokens[0], "debug")) {
         if (numTokens != 4) {
-            error(currentLoc, "debug pragma syntax is incorrect", "#pragma", "");
+            error(getCurrentLoc(), "debug pragma syntax is incorrect", "#pragma", "");
             return;
         }
 
         if (strcmp(tokens[1], "(")) {
-            error(currentLoc, "\"(\" expected after 'debug' keyword", "#pragma", "");
+            error(getCurrentLoc(), "\"(\" expected after 'debug' keyword", "#pragma", "");
             return;
         }
 
@@ -210,12 +186,12 @@ void TParseContext::handlePragma(const char **tokens, int numTokens)
         else if (!strcmp(tokens[2], "off"))
             contextPragma.debug = false;
         else {
-            error(currentLoc, "\"on\" or \"off\" expected after '(' for 'debug' pragma", "#pragma", "");
+            error(getCurrentLoc(), "\"on\" or \"off\" expected after '(' for 'debug' pragma", "#pragma", "");
             return;
         }
 
         if (strcmp(tokens[3], ")")) {
-            error(currentLoc, "\")\" expected to end 'debug' pragma", "#pragma", "");
+            error(getCurrentLoc(), "\")\" expected to end 'debug' pragma", "#pragma", "");
             return;
         }
     } else {
@@ -2282,7 +2258,7 @@ void TParseContext::inductiveLoopCheck(TSourceLoc loc, TIntermNode* init, TInter
 //
 // Do any additional error checking, etc., once we know the parsing is done.
 //
-void TParseContext::finalize()
+void TParseContext::finalErrorCheck()
 {
     // Check on array indexes for ES 2.0 (version 100) limitations.
     for (size_t i = 0; i < needsIndexLimitationChecking.size(); ++i)
index eab5fb7..4f9e053 100644 (file)
@@ -40,6 +40,7 @@
 #include "../Include/ShHandle.h"
 #include "SymbolTable.h"
 #include "localintermediate.h"
+#include "Scan.h"
 
 namespace glslang {
 
@@ -64,9 +65,10 @@ public:
     TParseContext(TSymbolTable&, TIntermediate&, bool parsingBuiltins, int version, EProfile, EShLanguage, TInfoSink&,
                   bool forwardCompatible = false, EShMessages messages = EShMsgDefault);
 
-public:
-    bool parseShaderStrings(TPpContext&, char* strings[], size_t strLen[], int numStrings);
+    void setLimits(const TLimits&);
+    bool parseShaderStrings(TPpContext&, TInputScanner& input, bool versionWillBeError = false);
     void parserError(const char *s);     // for bison's yyerror
+    const char* getPreamble();
 
     void C_DECL error(TSourceLoc, const char *szReason, const char *szToken,
                       const char *szExtraInfoFormat, ...);
@@ -163,6 +165,9 @@ public:
     TPpContext* getPpContext() const { return ppContext; }
     void addError() { ++numErrors; }
     int getNumErrors() const { return numErrors; }
+    const TSourceLoc& getCurrentLoc() const { return currentScanner->getSourceLoc(); }
+    void setCurrentLine(int line) { currentScanner->setLine(line); }
+    void setCurrentString(int string) { currentScanner->setString(string); }
 
     // The following are implemented in Versions.cpp to localize version/profile/stage/extensions control
     void initializeExtensionBehavior();
@@ -177,14 +182,13 @@ public:
     void doubleCheck(TSourceLoc, const char* op);
 
 protected:
-    const char* getPreamble();
     void nonInitConstCheck(TSourceLoc, TString& identifier, TType& type);
     TVariable* declareNonArray(TSourceLoc, TString& identifier, TType&, bool& newDeclaration);
     void declareArray(TSourceLoc, TString& identifier, const TType&, TSymbol*&, bool& newDeclaration);
     TIntermNode* executeInitializer(TSourceLoc, TString& identifier, TIntermTyped* initializer, TVariable* variable);
     TIntermTyped* convertInitializerList(TSourceLoc, const TType&, TIntermTyped* initializer);
     TOperator mapTypeToConstructorOp(const TType&);
-    void finalize();
+    void finalErrorCheck();
 
 public:
     //
@@ -213,13 +217,13 @@ public:
     TQualifier currentBlockQualifier;
     TIntermAggregate *linkage;   // aggregate node of objects the linker may need, if not referenced by the rest of the AST
     TPrecisionQualifier defaultPrecision[EbtNumTypes];
-    TSourceLoc currentLoc;
     bool tokensBeforeEOF;
     TLimits limits;
 
 protected:
     TScanContext* scanContext;
     TPpContext* ppContext;
+    TInputScanner* currentScanner;
     int numErrors;               // number of compile-time errors encountered
     bool parsingBuiltins;        // true if parsing built-in symbols/functions
     TMap<TString, TExtensionBehavior> extensionBehavior;    // for each extension string, what its current behavior is set to
index 2866c7b..4f0d4af 100644 (file)
 
 #include <string.h>
 
-#include "Scan.h"
 #include "../Include/Types.h"
 #include "SymbolTable.h"
 #include "glslang_tab.cpp.h"
 #include "ParseHelper.h"
 #include "ScanContext.h"
+#include "Scan.h"
 
 // preprocessor includes
 #include "preprocessor/PpContext.h"
 namespace glslang {
     
 // read past any white space
-void ConsumeWhiteSpace(TInputScanner& input, bool& foundNonSpaceTab)
+void TInputScanner::consumeWhiteSpace(bool& foundNonSpaceTab)
 {
-    char c = input.peek();  // don't accidentally consume anything other than whitespace
+    char c = peek();  // don't accidentally consume anything other than whitespace
     while (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
         if (c == '\r' || c == '\n')
             foundNonSpaceTab = true;
-        input.get();
-        c = input.peek();
+        get();
+        c = peek();
     }
 }
 
 // return true if a comment was actually consumed
-bool ConsumeComment(TInputScanner& input)
+bool TInputScanner::consumeComment()
 {
-    if (input.peek() != '/')
+    if (peek() != '/')
         return false;
 
-    input.get();  // consume the '/'
-    char c = input.peek();
+    get();  // consume the '/'
+    char c = peek();
     if (c == '/') {
 
         // a '//' style comment
-        input.get();  // consume the second '/'
-        c = input.get();
+        get();  // consume the second '/'
+        c = get();
         do {
             while (c > 0 && c != '\\' && c != '\r' && c != '\n')
-                c = input.get();
+                c = get();
 
             if (c <= 0 || c == '\r' || c == '\n') {
                 while (c == '\r' || c == '\n')
-                    c = input.get();
+                    c = get();
 
                 // we reached the end of the comment
                 break;
@@ -92,30 +92,30 @@ bool ConsumeComment(TInputScanner& input)
                 // it's a '\', so we need to keep going, after skipping what's escaped
                     
                 // read the skipped character
-                c = input.get();
+                c = get();
 
                 // if it's a two-character newline, skip both characters
-                if (c == '\r' && input.peek() == '\n')
-                    input.get();
-                c = input.get();
+                if (c == '\r' && peek() == '\n')
+                    get();
+                c = get();
             }
         } while (true);
 
         // put back the last non-comment character
         if (c > 0)
-            input.unget();
+            unget();
 
         return true;
     } else if (c == '*') {
 
         // a '/*' style comment
-        input.get();  // consume the '*'
-        c = input.get();
+        get();  // consume the '*'
+        c = get();
         do {
             while (c > 0 && c != '*')
-                c = input.get();
+                c = get();
             if (c == '*') {
-                c = input.get();
+                c = get();
                 if (c == '/')
                     break;  // end of comment
                 // not end of comment
@@ -126,26 +126,26 @@ bool ConsumeComment(TInputScanner& input)
         return true;
     } else {
         // it's not a comment, put the '/' back
-        input.unget();
+        unget();
 
         return false;
     }
 }
 
 // skip whitespace, then skip a comment, rinse, repeat
-void ConsumeWhitespaceComment(TInputScanner& input, bool& foundNonSpaceTab)
+void TInputScanner::consumeWhitespaceComment(bool& foundNonSpaceTab)
 {
     do {
-        ConsumeWhiteSpace(input, foundNonSpaceTab);
+        consumeWhiteSpace(foundNonSpaceTab);
  
         // if not starting a comment now, then done
-        char c = input.peek();
+        char c = peek();
         if (c != '/' || c < 0)
             return;
 
         // skip potential comment 
         foundNonSpaceTab = true;
-        if (! ConsumeComment(input))
+        if (! consumeComment())
             return;
 
     } while (true);
@@ -159,9 +159,9 @@ void ConsumeWhitespaceComment(TInputScanner& input, bool& foundNonSpaceTab)
 // is that scanning will start anew, following the rules for the chosen version/profile,
 // and with a corresponding parsing context.
 //
-bool ScanVersion(TInputScanner& input, int& version, EProfile& profile)
+bool TInputScanner::scanVersion(int& version, EProfile& profile)
 {
-    // This function doesn't have to get all the semantics correct, 
+    // This function doesn't have to get all the semantics correct,
     // just find the #version if there is a correct one present.
     // The preprocessor will have the responsibility of getting all the semantics right.
 
@@ -169,43 +169,43 @@ bool ScanVersion(TInputScanner& input, int& version, EProfile& profile)
     profile = ENoProfile;
 
     bool foundNonSpaceTab = false;
-    ConsumeWhitespaceComment(input, foundNonSpaceTab);
+    consumeWhitespaceComment(foundNonSpaceTab);
 
     // #
-    if (input.get() != '#')
+    if (get() != '#')
         return true;
 
     // whitespace
     char c;
     do {
-        c = input.get();
+        c = get();
     } while (c == ' ' || c == '\t');
 
-    if (          c != 'v' ||
-        input.get() != 'e' ||
-        input.get() != 'r' ||
-        input.get() != 's' ||
-        input.get() != 'i' ||
-        input.get() != 'o' ||
-        input.get() != 'n')
+    if (    c != 'v' ||
+        get() != 'e' ||
+        get() != 'r' ||
+        get() != 's' ||
+        get() != 'i' ||
+        get() != 'o' ||
+        get() != 'n')
         return true;
 
     // whitespace
     do {
-        c = input.get();
+        c = get();
     } while (c == ' ' || c == '\t');
 
     // version number
     while (c >= '0' && c <= '9') {
         version = 10 * version + (c - '0');
-        c = input.get();
+        c = get();
     }
     if (version == 0)
         return true;
     
     // whitespace
     while (c == ' ' || c == '\t')
-        c = input.get();
+        c = get();
 
     // profile
     const int maxProfileLength = 13;  // not including any 0
@@ -215,7 +215,7 @@ bool ScanVersion(TInputScanner& input, int& version, EProfile& profile)
         if (c < 0 || c == ' ' || c == '\t' || c == '\n' || c == '\r')
             break;
         profileString[profileLength] = c;
-        c = input.get();
+        c = get();
     }
     if (c > 0 && c != ' ' && c != '\t' && c != '\n' && c != '\r')
         return true;
index 9d600fc..fb05037 100644 (file)
@@ -33,6 +33,8 @@
 //ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 //POSSIBILITY OF SUCH DAMAGE.
 //
+#ifndef _GLSLANG_SCAN_INCLUDED_
+#define _GLSLANG_SCAN_INCLUDED_
 
 #include "Versions.h"
 
@@ -44,7 +46,17 @@ namespace glslang {
 //
 class TInputScanner {
 public:
-    TInputScanner(int n, const char* const i[], size_t L[]) : numSources(n), sources(i), lengths(L), currentSource(0), currentChar(0) { }
+    TInputScanner(int n, const char* const s[], size_t L[], int b = 0) : 
+        numSources(n), sources(s), lengths(L), currentSource(0), currentChar(0), stringBias(b)
+    {
+        loc = new TSourceLoc[numSources];
+        loc[currentSource].string = -stringBias;
+        loc[currentSource].line = 1;
+    }
+    virtual ~TInputScanner()
+    {
+        delete [] loc;
+    }
 
     // return of -1 means end of strings,
     // anything else is the next character
@@ -56,23 +68,13 @@ public:
             return -1;
 
         char ret = sources[currentSource][currentChar];
+        if (ret == '\n')
+            ++loc[currentSource].line;
         advance();
 
         return ret;
     }
 
-    // advance one character
-    void advance()
-    {
-        ++currentChar;
-        if (currentChar >= static_cast<int>(lengths[currentSource])) {
-            ++currentSource;
-            currentChar = 0;
-            while (currentSource < numSources && lengths[currentSource] == 0)
-                ++currentSource;
-        }
-    }
-
     // retrieve the next character, no advance
     char peek()
     {
@@ -95,21 +97,58 @@ public:
             if (currentChar < 0)
                 currentChar = 0;
         }
+        if (peek() == '\n')
+            --loc[currentSource].line;
     }
 
+    // for #line override
+    void setLine(int newLine) { loc[currentSource].line = newLine; }
+    void setString(int newString) { loc[currentSource].string = newString; }
+
+    const TSourceLoc& getSourceLoc() const { return loc[currentSource]; }
+
+    void consumeWhiteSpace(bool& foundNonSpaceTab);
+    bool consumeComment();
+    void consumeWhitespaceComment(bool& foundNonSpaceTab);
+    bool scanVersion(int& version, EProfile& profile);
+
 protected:
+
+    // advance one character
+    void advance()
+    {
+        ++currentChar;
+        if (currentChar >= static_cast<int>(lengths[currentSource])) {
+            ++currentSource;
+            if (currentSource < numSources) {
+                loc[currentSource].string = loc[currentSource - 1].string + 1;
+                loc[currentSource].line = 1;
+            }
+            while (currentSource < numSources && lengths[currentSource] == 0) {
+                ++currentSource;
+                if (currentSource < numSources) {
+                    loc[currentSource].string = loc[currentSource - 1].string + 1;
+                    loc[currentSource].line = 1;
+                }
+            }
+            currentChar = 0;
+        }
+    }
+
     int numSources;             // number of strings in source
     const char* const *sources; // array of strings
     const size_t *lengths;      // length of each string
     int currentSource;
     int currentChar;
-};
 
-// TODO: The location of these is still pending a grand design for going to a singular
-// scanner for version finding, preprocessing, and tokenizing:
-void ConsumeWhiteSpace(TInputScanner& input, bool& foundNonSpaceTab);
-bool ConsumeComment(TInputScanner& input);
-void ConsumeWhitespaceComment(TInputScanner& input, bool& foundNonSpaceTab);
-bool ScanVersion(TInputScanner& input, int& version, EProfile& profile);
+    // This is for reporting what string/line an error occurred on, and can be overridden by #line.
+    // It remembers the last state of each source string as it is left for the next one, so unget() 
+    // can restore that state.
+    TSourceLoc* loc;  // an array
+
+    int stringBias;   // the first string that is the user's string number 0
+};
 
 } // end namespace glslang
+
+#endif // _GLSLANG_SCAN_INCLUDED_
index fe5c5a3..9f8bc17 100644 (file)
@@ -143,7 +143,8 @@ bool InitializeSymbolTable(const TString& builtIns, int version, EProfile profil
     builtInShaders[0] = builtIns.c_str();
     builtInLengths[0] = builtIns.size();
 
-    if (! parseContext.parseShaderStrings(ppContext, const_cast<char**>(builtInShaders), builtInLengths, 1) != 0) {
+    TInputScanner input(1, builtInShaders, builtInLengths);
+    if (! parseContext.parseShaderStrings(ppContext, input) != 0) {
         infoSink.info.message(EPrefixInternalError, "Unable to parse built-ins");
         printf("Unable to parse built-ins\n%s\n", infoSink.info.c_str());
 
@@ -420,27 +421,42 @@ bool CompileDeferred(
     if (! InitThread())
         return false;
 
-    if (numStrings == 0)
-        return true;
-
     // This must be undone (.pop()) by the caller, after it finishes consuming the created tree.
     GetThreadPoolAllocator().push();
+
+    if (numStrings == 0)
+        return true;
     
-    // move to length-based strings, rather than null-terminated strings
-    size_t* lengths = new size_t[numStrings];
+    // Move to length-based strings, rather than null-terminated strings.
+    // Also, add strings to include the preamble and to ensure the shader is not null,
+    // which lets the grammar accept what was a null (post preprocessing) shader.
+    //
+    // Shader will look like
+    //   string 0:                preamble
+    //   string 1...numStrings:   user's shader
+    //   string numStrings+1:     "int;"
+    //
+    size_t* lengths = new size_t[numStrings + 2];
+    const char** strings = new const char*[numStrings + 2];
     for (int s = 0; s < numStrings; ++s) {
+        strings[s + 1] = shaderStrings[s];
         if (inputLengths == 0 || inputLengths[s] < 0)
-            lengths[s] = strlen(shaderStrings[s]);
+            lengths[s + 1] = strlen(shaderStrings[s]);
         else
-            lengths[s] = inputLengths[s];
+            lengths[s + 1] = inputLengths[s];
     }
 
+    // First, without using the preprocessor or parser, find the #version, so we know what
+    // symbol tables, processing rules, etc. to set up.  This does not need the extra strings
+    // outlined above, just the user shader.
     int version;
     EProfile profile;
-    glslang::TInputScanner input(numStrings, shaderStrings, lengths);
-    bool versionNotFirst = ScanVersion(input, version, profile);
+    glslang::TInputScanner userInput(numStrings, &strings[1], &lengths[1]);  // no preamble
+    bool versionNotFirst = userInput.scanVersion(version, profile);
+    bool versionNotFound = version == 0;
     bool goodVersion = DeduceVersionProfile(compiler->infoSink, compiler->getLanguage(), versionNotFirst, defaultVersion, version, profile);
-
+    bool versionWillBeError = (versionNotFound || (profile == EEsProfile && versionNotFirst));
+    
     intermediate.setVersion(version);
     intermediate.setProfile(profile);
     SetupBuiltinSymbolTable(version, profile);
@@ -458,31 +474,36 @@ bool CompileDeferred(
     // Add built-in symbols that are potentially context dependent;
     // they get popped again further down.
     AddContextSpecificSymbols(resources, compiler->infoSink, symbolTable, version, profile, compiler->getLanguage());
+    
+    //
+    // Now we can process the full shader under proper symbols and rules.
+    //
 
     TParseContext parseContext(symbolTable, intermediate, false, version, profile, compiler->getLanguage(), compiler->infoSink, forwardCompatible, messages);
     glslang::TScanContext scanContext(parseContext);
     TPpContext ppContext(parseContext);
     parseContext.setScanContext(&scanContext);
     parseContext.setPpContext(&ppContext);
-    parseContext.limits = resources->limits;
+    parseContext.setLimits(resources->limits);
     if (! goodVersion)
         parseContext.addError();
 
     parseContext.initializeExtensionBehavior();
 
-    //
-    // Parse the application's shaders.  All the following symbol table
-    // work will be throw-away, so push a new allocation scope that can
-    // be thrown away, then push a scope for the current shader's globals.
-    //
     bool success = true;
     
+    // Fill in the strings as outlined above.
+    strings[0] = parseContext.getPreamble();
+    lengths[0] = strlen(strings[0]);
+    strings[numStrings + 1] = "\n int;";
+    lengths[numStrings + 1] = strlen(strings[numStrings + 1]);
+    TInputScanner fullInput(numStrings + 2, strings, lengths, 1);
+
+    // Push a new symbol allocation scope that can for the shader's globals.
     symbolTable.push();
-    if (! symbolTable.atGlobalLevel())
-        parseContext.infoSink.info.message(EPrefixInternalError, "Wrong symbol table level");
 
-    bool ret = parseContext.parseShaderStrings(ppContext, const_cast<char**>(shaderStrings), lengths, numStrings);
-    if (! ret)
+    // Parse the full shader.
+    if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError))
         success = false;
     intermediate.addSymbolLinkageNodes(parseContext.linkage, parseContext.language, symbolTable);
 
@@ -503,6 +524,7 @@ bool CompileDeferred(
         intermediate.outputTree(parseContext.infoSink);
 
     delete [] lengths;
+    delete [] strings;
 
     return success;
 }
index f6cf13f..8b9680f 100644 (file)
@@ -330,14 +330,14 @@ void TParseContext::updateExtensionBehavior(const char* extName, const char* beh
     else if (! strcmp("warn", behaviorString))
         behavior = EBhWarn;
     else
-        error(currentLoc, "behavior not supported", "#extension", behaviorString);
+        error(getCurrentLoc(), "behavior not supported", "#extension", behaviorString);
 
     // Update the current behavior
     TMap<TString, TExtensionBehavior>::iterator iter;
     if (! strcmp(extName, "all")) {
         // special case for the 'all' extension; apply it to every extension present
         if (behavior == EBhRequire || behavior == EBhEnable) {
-            error(currentLoc, "extension 'all' cannot have 'require' or 'enable' behavior", "#extension", "");
+            error(getCurrentLoc(), "extension 'all' cannot have 'require' or 'enable' behavior", "#extension", "");
             return;
         } else {
             for (iter = extensionBehavior.begin(); iter != extensionBehavior.end(); ++iter)
@@ -349,12 +349,12 @@ void TParseContext::updateExtensionBehavior(const char* extName, const char* beh
         if (iter == extensionBehavior.end()) {
             switch (behavior) {
             case EBhRequire:
-                error(currentLoc, "extension not supported", "#extension", extName);
+                error(getCurrentLoc(), "extension not supported", "#extension", extName);
                 break;
             case EBhEnable:
             case EBhWarn:
             case EBhDisable:
-                warn(currentLoc, "extension not supported", "#extension", extName);
+                warn(getCurrentLoc(), "extension not supported", "#extension", extName);
                 break;
             default:
                 assert(0 && "unexpected behavior");
index 5b4d83c..aa2ab67 100644 (file)
@@ -134,7 +134,7 @@ int TPpContext::FinalCPP()
     mem_FreePool(pool);
 
     if (ifdepth)
-        parseContext.error(parseContext.currentLoc, "missing #endif", "#if", "");
+        parseContext.error(parseContext.getCurrentLoc(), "missing #endif", "#if", "");
 
     return 1;
 }
@@ -552,21 +552,21 @@ int TPpContext::CPPline(TPpToken * ppToken)
         return token;
     }
     else if (token == CPP_INTCONSTANT) {
-        parseContext.currentLoc.line = atoi(ppToken->name);
+        parseContext.setCurrentLine(atoi(ppToken->name));
         token = currentInput->scan(this, currentInput, ppToken);
 
         if (token == CPP_INTCONSTANT) {
-            parseContext.currentLoc.string = atoi(ppToken->name);
+            parseContext.setCurrentString(atoi(ppToken->name));
             token = currentInput->scan(this, currentInput, ppToken);
             if (token != '\n')
-                parseContext.error(parseContext.currentLoc, "cannot be followed by more than two integral literals", "#line", "");
+                parseContext.error(parseContext.getCurrentLoc(), "cannot be followed by more than two integral literals", "#line", "");
         } else if (token == '\n')
 
             return token;
         else
-            parseContext.error(parseContext.currentLoc, "second argument can only be an integral literal", "#line", "");
+            parseContext.error(parseContext.getCurrentLoc(), "second argument can only be an integral literal", "#line", "");
     } else
-        parseContext.error(parseContext.currentLoc, "first argument can only be an integral literal", "#line", "");
+        parseContext.error(parseContext.getCurrentLoc(), "first argument can only be an integral literal", "#line", "");
 
     return token;
 }
@@ -662,8 +662,8 @@ int TPpContext::CPPversion(TPpToken * ppToken)
 {
     int token = currentInput->scan(this, currentInput, ppToken);
 
-    if (notAVersionToken)
-        parseContext.error(ppToken->loc, "must occur before any other statement in the program", "#version", "");
+    if (errorOnVersion)
+        parseContext.error(ppToken->loc, "must occur first in shader", "#version", "");
 
     if (token == '\n') {
         parseContext.error(ppToken->loc, "must be followed by version number", "#version", "");
@@ -759,7 +759,6 @@ int TPpContext::readCPPline(TPpToken * ppToken)
             } else {
                 parseContext.error(ppToken->loc, "#else after a #else", "#else", "");
                 ifdepth = 0;
-                notAVersionToken = true;
                 return 0;
             }
         } else if (ppToken->atom == elifAtom) {
@@ -805,8 +804,6 @@ int TPpContext::readCPPline(TPpToken * ppToken)
         token = currentInput->scan(this, currentInput, ppToken);
     }
 
-    notAVersionToken = ! isVersion;
-
     return token;
 } // readCPPline
 
@@ -931,7 +928,7 @@ int TPpContext::MacroExpand(int atom, TPpToken* ppToken, int expandUndef)
     int depth = 0;
 
     if (atom == __LINE__Atom) {
-        ppToken->ival = parseContext.currentLoc.line;
+        ppToken->ival = parseContext.getCurrentLoc().line;
         sprintf(ppToken->name, "%d", ppToken->ival);
         UngetToken(CPP_INTCONSTANT, ppToken);
 
@@ -939,7 +936,7 @@ int TPpContext::MacroExpand(int atom, TPpToken* ppToken, int expandUndef)
     }
 
     if (atom == __FILE__Atom) {
-        ppToken->ival = parseContext.currentLoc.string;
+        ppToken->ival = parseContext.getCurrentLoc().string;
         sprintf(ppToken->name, "%d", ppToken->ival);
         UngetToken(CPP_INTCONSTANT, ppToken);
 
@@ -964,8 +961,6 @@ int TPpContext::MacroExpand(int atom, TPpToken* ppToken, int expandUndef)
 
     in = (MacroInputSrc*)malloc(sizeof(*in));
     memset(in, 0, sizeof(*in));
-    in->base.line = currentInput->line;
-    in->base.name = currentInput->name;
 
     if ((! sym || sym->mac.undef) && expandUndef) {
         // push input
index 40cf47f..5290f0e 100644 (file)
@@ -84,7 +84,7 @@ NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace glslang {
 
 TPpContext::TPpContext(TParseContext& pc) : 
-    preamble(0), strings(0), notAVersionToken(false), parseContext(pc)
+    preamble(0), strings(0), parseContext(pc)
 {
     InitAtomTable();
     InitScanner(this);
@@ -101,28 +101,17 @@ TPpContext::~TPpContext()
     delete [] preamble;
 }
 
-void TPpContext::setPreamble(const char* p, size_t l)
+void TPpContext::setInput(TInputScanner& input, bool versionWillBeError)
 {
-    if (p && l > 0) {
-        // preAmble could be a hard-coded string; make writable copy
-        // TODO: efficiency PP: make it not need writable strings
-        preambleLength = l;
-        preamble = new char[preambleLength + 1];
-        memcpy(preamble, p, preambleLength + 1);  // TODO: PP: assuming nul-terminated strings
-        ScanFromString(preamble);
-        currentString = -1;
-    }
-}
-
-void TPpContext::setShaderStrings(char* s[], size_t l[], int n)
-{
-    strings = s;
-    lengths = l;
-    numStrings = n;
-    if (! preamble) {
-        ScanFromString(strings[0]);
-        currentString = 0;
-    }
+    StringInputSrc *in = (StringInputSrc *)malloc(sizeof(StringInputSrc));
+    memset(in, 0, sizeof(StringInputSrc));
+    in->input = &input;
+    in->base.scan = byte_scan;
+    in->base.getch = (int (*)(TPpContext*, InputSrc *, TPpToken *))str_getch;
+    in->base.ungetch = (void (*)(TPpContext*, InputSrc *, int, TPpToken *))str_ungetch;
+    in->base.prev = currentInput;
+    currentInput = &in->base;
+    errorOnVersion = versionWillBeError;
 }
 
 } // end namespace glslang
index b19d814..d130408 100644 (file)
@@ -95,6 +95,8 @@ public:
     char   name[maxTokenLength+1];
 };
 
+class TInputScanner;
+
 // This class is the result of turning a huge pile of C code communicating through globals
 // into a class.  This was done to allowing instancing to attain thread safety.
 // Don't expect too much in terms of OO design.
@@ -104,7 +106,7 @@ public:
     virtual ~TPpContext();
 
     void setPreamble(const char* preamble, size_t length);
-    void setShaderStrings(char* strings[], size_t lengths[], int numStrings);
+    void setInput(TInputScanner& input, bool versionWillBeError);
 
     const char* tokenize(TPpToken* ppToken);
 
@@ -113,8 +115,6 @@ public:
         int                    (*scan)(TPpContext*, struct InputSrc *, TPpToken *);
         int                    (*getch)(TPpContext*, struct InputSrc *, TPpToken *);
         void           (*ungetch)(TPpContext*, struct InputSrc *, int, TPpToken *);
-        int                    name;  /* atom */
-        int                    line;
     };
 
     struct TokenBlock {
@@ -177,7 +177,6 @@ protected:
     // Scanner data:
     int mostRecentToken;        // Most recent token seen by the scanner
     int previous_token;
-    bool notAVersionToken;      // used to make sure that #version is the first token seen in the file, if present
     TParseContext& parseContext;
 
     static const int maxMacroArgs = 64;
@@ -195,6 +194,7 @@ protected:
     };
 
     InputSrc *currentInput;
+    bool errorOnVersion;
 
     //
     // from Pp.cpp
@@ -289,7 +289,7 @@ protected:
     //
     struct StringInputSrc {
         InputSrc base;
-        char *p;
+        TInputScanner* input;
     };
     int InitScanner(TPpContext *cpp);
     static int str_getch(TPpContext*, StringInputSrc *in);
index 98f1608..1352deb 100644 (file)
@@ -88,23 +88,7 @@ NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "PpContext.h"
 #include "PpTokens.h"
-
-namespace {
-
-using namespace glslang;
-
-int eof_scan(TPpContext*, TPpContext::InputSrc*, TPpToken*)
-{
-    return EOF;
-}
-
-void noop(TPpContext*, TPpContext::InputSrc *in, int ch, TPpToken * ppToken)
-{
-}
-
-TPpContext::InputSrc eof_inputsrc = { 0, &eof_scan, &eof_scan, &noop };
-
-} // end anonymous namespace
+#include "Scan.h"
 
 namespace glslang {
 
@@ -115,79 +99,26 @@ int TPpContext::InitScanner(TPpContext *cpp)
         return 0;
 
     mostRecentToken = 0;
-    currentInput = &eof_inputsrc;
+    currentInput = 0;
     previous_token = '\n';
-    notAVersionToken = false;
 
     return 1;
-} // InitScanner
+}
 
-/*
-* str_getch()
-* takes care of reading from multiple strings.
-* returns the next-char from the input stream.
-* returns EOF when the complete shader is parsed.
-*/
 int TPpContext::str_getch(TPpContext* pp, StringInputSrc *in)
 {
-    for(;;) {
-        if (*in->p) {
-            if (*in->p == '\n') {
-                in->base.line++;
-                ++pp->parseContext.currentLoc.line;
-            }
-            return *in->p++;
-        }
-        if (pp->currentString < 0) {
-            // we only parsed the built-in pre-amble; start with clean slate for user code
-            pp->notAVersionToken = false;
-        }
-        if (++(pp->currentString) < pp->numStrings) {
-            free(in);
-            pp->parseContext.currentLoc.string = pp->currentString;
-            pp->parseContext.currentLoc.line = 1;
-            pp->ScanFromString(pp->strings[pp->currentString]);
-            in=(StringInputSrc*)pp->currentInput;
-            continue;             
-        } else {
-            pp->currentInput = in->base.prev;
-            pp->currentString = 0;
-            free(in);
-            return EOF;
-        }  
-    }
-} // str_getch
+    int ch = in->input->get();
 
-void TPpContext::str_ungetch(TPpContext* pp, StringInputSrc *in, int ch, TPpToken *type)
-{
-    if (in->p[-1] == ch)in->p--;
-    else {
-        *(in->p)='\0'; //this would take care of shifting to the previous string.
-        pp->currentString--;
-        pp->parseContext.currentLoc.string = pp->currentString;
-    }  
-    if (ch == '\n') {
-        in->base.line--;
-        --pp->parseContext.currentLoc.line;
-    }
-} // str_ungetch
+    if (ch == EOF)
+        free(in);
 
-int TPpContext::ScanFromString(char *s)
-{
-
-    StringInputSrc *in = (StringInputSrc *)malloc(sizeof(StringInputSrc));
-    memset(in, 0, sizeof(StringInputSrc));
-    in->p = s;
-    in->base.line = 1;
-    in->base.scan = byte_scan;
-    in->base.getch = (int (*)(TPpContext*, InputSrc *, TPpToken *))str_getch;
-    in->base.ungetch = (void (*)(TPpContext*, InputSrc *, int, TPpToken *))str_ungetch;
-    in->base.prev = currentInput;
-    currentInput = &in->base;
-
-    return 1;
+    return ch;
 }
 
+void TPpContext::str_ungetch(TPpContext* pp, StringInputSrc *in, int ch, TPpToken *type)
+{
+    in->input->unget();
+}
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 /////////////////////////////////// Floating point constants: /////////////////////////////////
@@ -332,7 +263,7 @@ int TPpContext::byte_scan(TPpContext* pp, InputSrc *in, TPpToken * ppToken)
             ch = pp->currentInput->getch(pp, pp->currentInput, ppToken);
         }
 
-        ppToken->loc = pp->parseContext.currentLoc;
+        ppToken->loc = pp->parseContext.getCurrentLoc();
         len = 0;
         switch (ch) {
         default:
@@ -696,6 +627,7 @@ int TPpContext::byte_scan(TPpContext* pp, InputSrc *in, TPpToken * ppToken)
                 return '.';
             }
         case '/':
+            // TODO: preprocessor: use the Scan.cpp comment scanner
             ch = pp->currentInput->getch(pp, pp->currentInput, ppToken);
             if (ch == '/') {
                 do {
@@ -801,8 +733,6 @@ const char* TPpContext::tokenize(TPpToken* ppToken)
         if (token == '\n')
             continue;
 
-        notAVersionToken = true;
-
         // expand macros
         if (token == CPP_IDENTIFIER && MacroExpand(ppToken->atom, ppToken, 0) == 1)
             continue;
@@ -831,7 +761,7 @@ int TPpContext::check_EOF(int token)
 {
     if (token == EOF) {
         if (ifdepth > 0)
-            parseContext.error(parseContext.currentLoc, "missing #endif", "#if", "");
+            parseContext.error(parseContext.getCurrentLoc(), "missing #endif", "#if", "");
         return 1;
     }
     return 0;
index 1b4455a..00a47a2 100644 (file)
@@ -306,7 +306,7 @@ int TPpContext::ReadToken(TokenStream *pTok, TPpToken *ppToken)
     char ch;
 
     ltoken = lReadByte(pTok);
-    ppToken->loc = parseContext.currentLoc;
+    ppToken->loc = parseContext.getCurrentLoc();
     if (ltoken >= 0) {
         if (ltoken > 127)
             ltoken += 128;
@@ -399,12 +399,9 @@ int TPpContext::scan_token(TPpContext* pp, TokenInputSrc *in, TPpToken * ppToken
 {
     int token = pp->ReadToken(in->tokens, ppToken);
     int (*final)(TPpContext *);
-    if (token == '\n') {
-        in->base.line++;
-        return token;
-    }
     if (token > 0)
         return token;
+    
     pp->currentInput = in->base.prev;
     final = in->final;
     free(in);
@@ -418,10 +415,8 @@ int TPpContext::ReadFromTokenStream(TokenStream *ts, int name, int (*final)(TPpC
 {
     TokenInputSrc *in = (TokenInputSrc *) malloc(sizeof(TokenInputSrc));
     memset(in, 0, sizeof(TokenInputSrc));
-    in->base.name = name;
     in->base.prev = currentInput;
     in->base.scan = (int (*)(TPpContext*, InputSrc*, TPpToken*))scan_token;
-    in->base.line = 1;
     in->tokens = ts;
     in->final = final;
     RewindTokenStream(ts);
@@ -449,8 +444,6 @@ void TPpContext::UngetToken(int token, TPpToken * ppToken)
     t->lval = *ppToken;
     t->base.scan = (int(*)(TPpContext*, struct InputSrc *, TPpToken *))reget_token;
     t->base.prev = currentInput;
-    t->base.name = currentInput->name;
-    t->base.line = currentInput->line;
     currentInput = &t->base;
 }
 
index 801d551..2463ea2 100644 (file)
@@ -270,7 +270,7 @@ class TShader {
 public:
     explicit TShader(EShLanguage);
     virtual ~TShader();
-    void setStrings(char** s, int n) { strings = s; numStrings = n; }
+    void setStrings(const char* const* s, int n) { strings = s; numStrings = n; }
     bool parse(const TBuiltInResource*, int defaultVersion, bool forwardCompatible, EShMessages);
     const char* getInfoLog();
     const char* getInfoDebugLog();
@@ -280,7 +280,7 @@ protected:
     TCompiler* compiler;
     TIntermediate* intermediate;
     TInfoSink* infoSink;
-    char** strings;
+    const char* const* strings;
     int numStrings;
 
     friend class TProgram;