Add scanner that can find '#version' across an array of non-null terminated, length...
authorJohn Kessenich <cepheus@frii.com>
Tue, 25 Jun 2013 18:10:05 +0000 (18:10 +0000)
committerJohn Kessenich <cepheus@frii.com>
Tue, 25 Jun 2013 18:10:05 +0000 (18:10 +0000)
git-svn-id: https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/tools/glslang@22165 e7fa87d3-cd2b-0410-9028-fcbf551c1848

StandAlone/StandAlone.cpp
glslang.vcxproj
glslang.vcxproj.filters
glslang/MachineIndependent/Makefile
glslang/MachineIndependent/Scan.cpp [new file with mode: 0644]
glslang/MachineIndependent/Scan.h [new file with mode: 0644]
glslang/MachineIndependent/ShaderLang.cpp
glslang/Public/ShaderLang.h

index cf58ed6..e0915af 100644 (file)
@@ -79,8 +79,9 @@ void usage();
 void FreeFileData(char **data);
 char** ReadFileData(const char *fileName);
 void InfoLogMsg(const char* msg, const char* name, const int num);
-//Added to accomodate the multiple strings.
-int OutputMultipleStrings = 1;
+
+// Use to test breaking a single shader file into multiple strings.
+int NumShaderStrings = 1;
 
 //
 // Set up the per compile resources
@@ -255,21 +256,31 @@ static EShLanguage FindLanguage(char *name)
 bool CompileFile(const char *fileName, ShHandle compiler, int debugOptions, const TBuiltInResource *resources)
 {
     int ret;
-    char **data = ReadFileData(fileName);
+    char** shaderStrings = ReadFileData(fileName);
+    int* lengths = new int[NumShaderStrings];
+
+    // move to length-based strings, rather than null-terminated strings
+    for (int s = 0; s < NumShaderStrings; ++s)
+        lengths[s] = strlen(shaderStrings[s]);
 
 #ifdef _WIN32
     PROCESS_MEMORY_COUNTERS counters;  // just for memory leak testing
 #endif
 
-    if (!data)
+    if (! shaderStrings)
         return false;
 
     EShMessages messages = EShMsgDefault;
     if (debugOptions & EDebugOpRelaxedErrors)
         messages = (EShMessages)(messages | EShMsgRelaxedErrors);
     for (int i = 0; i < ((debugOptions & EDebugOpMemoryLeakMode) ? 100 : 1); ++i) {
-        for (int j = 0; j < ((debugOptions & EDebugOpMemoryLeakMode) ? 100 : 1); ++j)
-            ret = ShCompile(compiler, data, OutputMultipleStrings, EShOptNone, resources, debugOptions, 100, false, messages);
+        for (int j = 0; j < ((debugOptions & EDebugOpMemoryLeakMode) ? 100 : 1); ++j) {
+            //ret = ShCompile(compiler, shaderStrings, NumShaderStrings, lengths, EShOptNone, resources, debugOptions, 100, false, messages);
+            ret = ShCompile(compiler, shaderStrings, NumShaderStrings, 0, EShOptNone, resources, debugOptions, 100, false, messages);
+            //const char* multi[4] = { "# ve", "rsion", " 300 e", "s" };
+            //const char* multi[7] = { "/", "/", "\\", "\n", "\n", "#", "version 300 es" };
+            //ret = ShCompile(compiler, multi, 4, 0, EShOptNone, resources, debugOptions, 100, false, messages);
+        }
 
 #ifdef _WIN32
         if (debugOptions & EDebugOpMemoryLeakMode) {
@@ -279,7 +290,8 @@ bool CompileFile(const char *fileName, ShHandle compiler, int debugOptions, cons
 #endif
     }
 
-    FreeFileData(data);
+    delete [] lengths;
+    FreeFileData(shaderStrings);
 
     return ret ? true : false;
 }
@@ -366,11 +378,11 @@ char** ReadFileData(const char *fileName)
     if(count==0){
         return_data[0]=(char*)malloc(count+2);
         return_data[0][0]='\0';
-        OutputMultipleStrings=0;
+        NumShaderStrings=0;
         return return_data;       
     }
 
-       int len = (int)(ceil)((float)count/(float)OutputMultipleStrings);
+       int len = (int)(ceil)((float)count/(float)NumShaderStrings);
     int ptr_len=0,i=0;
        while(count>0){
                return_data[i]=(char*)malloc(len+2);
@@ -380,7 +392,7 @@ char** ReadFileData(const char *fileName)
                ptr_len+=(len);
                if(count<len){
             if(count==0){
-               OutputMultipleStrings=(i+1);
+               NumShaderStrings=(i+1);
                break;
             }
            len = count;
@@ -394,7 +406,7 @@ char** ReadFileData(const char *fileName)
 
 void FreeFileData(char **data)
 {
-    for(int i=0;i<OutputMultipleStrings;i++)
+    for(int i=0;i<NumShaderStrings;i++)
         free(data[i]);
 }
 
index 0f147a2..80024de 100644 (file)
@@ -154,6 +154,7 @@ xcopy /y $(IntDir)$(TargetName)$(TargetExt) Test</Command>
     <ClCompile Include="glslang\MachineIndependent\glslang_tab.cpp" />\r
     <ClCompile Include="glslang\MachineIndependent\InfoSink.cpp" />\r
     <ClCompile Include="glslang\MachineIndependent\Initialize.cpp" />\r
+    <ClCompile Include="glslang\MachineIndependent\Scan.cpp" />\r
     <ClCompile Include="glslang\MachineIndependent\Versions.cpp" />\r
     <ClCompile Include="OGLCompilersDLL\InitializeDll.cpp" />\r
     <ClCompile Include="glslang\MachineIndependent\IntermTraverse.cpp" />\r
@@ -204,6 +205,7 @@ xcopy /y $(IntDir)$(TargetName)$(TargetExt) Test</Command>
     <ClInclude Include="glslang\Include\Common.h" />\r
     <ClInclude Include="glslang\Include\ConstantUnion.h" />\r
     <ClInclude Include="glslang\Include\InfoSink.h" />\r
+    <ClInclude Include="glslang\MachineIndependent\Scan.h" />\r
     <ClInclude Include="glslang\MachineIndependent\Versions.h" />\r
     <ClInclude Include="OGLCompilersDLL\InitializeDll.h" />\r
     <ClInclude Include="glslang\Include\InitializeGlobals.h" />\r
index 4083edc..3e5cfbd 100644 (file)
     <ClCompile Include="glslang\MachineIndependent\Constant.cpp">\r
       <Filter>Machine Independent</Filter>\r
     </ClCompile>\r
+    <ClCompile Include="glslang\MachineIndependent\Scan.cpp">\r
+      <Filter>Machine Independent</Filter>\r
+    </ClCompile>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <ClInclude Include="glslang\MachineIndependent\Initialize.h">\r
     <ClInclude Include="glslang\MachineIndependent\Versions.h">\r
       <Filter>Machine Independent</Filter>\r
     </ClInclude>\r
+    <ClInclude Include="glslang\MachineIndependent\Scan.h">\r
+      <Filter>Machine Independent</Filter>\r
+    </ClInclude>\r
   </ItemGroup>\r
   <ItemGroup>\r
     <CustomBuild Include="glslang\MachineIndependent\glslang.y">\r
index 4a84f67..508c29f 100644 (file)
@@ -11,12 +11,12 @@ LIBCODEGEN=./../GenericCodeGen/libCodeGen.a
 OBJECTS= Initialize.o IntermTraverse.o \
        Intermediate.o ParseHelper.o PoolAlloc.o QualifierAlive.o \
        RemoveTree.o ShaderLang.o intermOut.o parseConst.o SymbolTable.o \
-       InfoSink.o Versions.o Constant.o
+       InfoSink.o Versions.o Constant.o Scan.o
 
 SRCS= gen_glslang.cpp gen_glslang_tab.cpp Initialize.cpp IntermTraverse.cpp \
        Intermediate.cpp ParseHelper.cpp PoolAlloc.cp QualifierAlive.cpp \
        RemoveTree.cpp ShaderLang.cpp SymbolTable.cpp intermOut.cpp \
-       parseConst.cpp InfoSink.cpp Versions.cpp Constant.cpp
+       parseConst.cpp InfoSink.cpp Versions.cpp Constant.cpp Scan.cpp
 CPPFLAGS=$(DEFINE) $(INCLUDE) -fPIC
 SHAREDOBJECT=./lib/libglslang.so
 
@@ -161,3 +161,4 @@ parseConst.o: ../Public/ShaderLang.h
 InfoSink.o: ../Include/InfoSink.h
 Versions.o: ParseHelper.h  Versions.h ../Include/ShHandle.h SymbolTable.h localintermediate.h
 Constant.o: localintermediate.h ../Include/intermediate.h ../Public/ShaderLang.h SymbolTable.h Versions.h
+Scan.o: Scan.h Versions.h
diff --git a/glslang/MachineIndependent/Scan.cpp b/glslang/MachineIndependent/Scan.cpp
new file mode 100644 (file)
index 0000000..d7f1119
--- /dev/null
@@ -0,0 +1,216 @@
+//
+//Copyright (C) 2002-2005  3Dlabs Inc. Ltd.
+//Copyright (C) 2013 LunarG, Inc.
+//
+//All rights reserved.
+//
+//Redistribution and use in source and binary forms, with or without
+//modification, are permitted provided that the following conditions
+//are met:
+//
+//    Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+//
+//    Redistributions in binary form must reproduce the above
+//    copyright notice, this list of conditions and the following
+//    disclaimer in the documentation and/or other materials provided
+//    with the distribution.
+//
+//    Neither the name of 3Dlabs Inc. Ltd. nor the names of its
+//    contributors may be used to endorse or promote products derived
+//    from this software without specific prior written permission.
+//
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+//POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <string.h>
+
+#include "Scan.h"
+
+namespace glslang {
+    
+// read past any white space
+void ConsumeWhiteSpace(TInputScanner& input, bool& foundNonSpaceTab)
+{
+    char c = input.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();
+    }
+}
+
+// return true if a comment was actually consumed
+bool ConsumeComment(TInputScanner& input)
+{
+    if (input.peek() != '/')
+        return false;
+
+    input.get();  // consume the '/'
+    char c = input.peek();
+    if (c == '/') {
+
+        // a '//' style comment
+        input.get();  // consume the second '/'
+        c = input.get();
+        do {
+            while (c > 0 && c != '\\' && c != '\r' && c != '\n')
+                c = input.get();
+
+            if (c <= 0 || c == '\r' || c == '\n') {
+                while (c == '\r' || c == '\n')
+                    c = input.get();
+
+                // we reached the end of the comment
+                break;
+            } else {
+                // it's a '\', so we need to keep going, after skipping what's escaped
+                    
+                // read the skipped character
+                c = input.get();
+
+                // if it's a two-character newline, skip both characters
+                if (c == '\r' && input.peek() == '\n')
+                    input.get();
+                c = input.get();
+            }
+        } while (true);
+
+        // put back the last non-comment character
+        if (c > 0)
+            input.unget();
+
+        return true;
+    } else if (c == '*') {
+
+        // a '/*' style comment
+        input.get();  // consume the '*'
+        c = input.get();
+        do {
+            while (c > 0 && c != '*')
+                c = input.get();
+            if (c == '*') {
+                c = input.get();
+                if (c == '/')
+                    break;  // end of comment
+                // not end of comment
+            } else // end of input
+                break;
+        } while (true);
+
+        return true;
+    } else {
+        // it's not a comment, put the '/' back
+        input.unget();
+
+        return false;
+    }
+}
+
+// skip whitespace, then skip a comment, rinse, repeat
+void ConsumeWhitespaceComment(TInputScanner& input, bool& foundNonSpaceTab)
+{
+    do {
+        ConsumeWhiteSpace(input, foundNonSpaceTab);
+        // if not starting a comment now, then done
+        char c = input.peek();
+        if (c != '/' || c < 0)
+            return;
+
+        // skip potential comment 
+        foundNonSpaceTab = true;
+        if (! ConsumeComment(input))
+            return;
+
+    } while (true);
+}
+
+// Returns true if there was non-white space (e.g., a comment, newline) before the #version;
+// otherwise, returns true.
+//
+// N.B. does not attempt to leave input in any particular known state
+bool ScanVersion(TInputScanner& input, int& version, EProfile& profile)
+{
+    // 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.
+
+    version = 0;  // means not found
+    profile = ENoProfile;
+
+    bool foundNonSpaceTab = false;
+    ConsumeWhitespaceComment(input, foundNonSpaceTab);
+
+    // #
+    if (input.get() != '#')
+        return true;
+
+    // whitespace
+    char c;
+    do {
+        c = input.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')
+        return true;
+
+    // whitespace
+    do {
+        c = input.get();
+    } while (c == ' ' || c == '\t');
+
+    // version number
+    while (c >= '0' && c <= '9') {
+        version = 10 * version + (c - '0');
+        c = input.get();
+    }
+    if (version == 0)
+        return true;
+    
+    // whitespace
+    while (c == ' ' || c == '\t')
+        c = input.get();
+
+    // profile
+    const int maxProfileLength = 13;  // not including any 0
+    char profileString[maxProfileLength];
+    int profileLength;
+    for (profileLength = 0; profileLength < maxProfileLength; ++profileLength) {
+        if (c < 0 || c == ' ' || c == '\t' || c == '\n' || c == '\r')
+            break;
+        profileString[profileLength] = c;
+        c = input.get();
+    }
+    if (c > 0 && c != ' ' && c != '\t' && c != '\n' && c != '\r')
+        return true;
+
+    if (profileLength == 2 && strncmp(profileString, "es", profileLength) == 0)
+        profile = EEsProfile;
+    else if (profileLength == 4 && strncmp(profileString, "core", profileLength) == 0)
+        profile = ECoreProfile;
+    else if (profileLength == 13 && strncmp(profileString, "compatibility", profileLength) == 0)
+        profile = ECompatibilityProfile;
+
+    return foundNonSpaceTab;
+}
+
+}; // end glslang namespace
diff --git a/glslang/MachineIndependent/Scan.h b/glslang/MachineIndependent/Scan.h
new file mode 100644 (file)
index 0000000..1ea57ee
--- /dev/null
@@ -0,0 +1,115 @@
+//
+//Copyright (C) 2002-2005  3Dlabs Inc. Ltd.
+//Copyright (C) 2013 LunarG, Inc.
+//
+//All rights reserved.
+//
+//Redistribution and use in source and binary forms, with or without
+//modification, are permitted provided that the following conditions
+//are met:
+//
+//    Redistributions of source code must retain the above copyright
+//    notice, this list of conditions and the following disclaimer.
+//
+//    Redistributions in binary form must reproduce the above
+//    copyright notice, this list of conditions and the following
+//    disclaimer in the documentation and/or other materials provided
+//    with the distribution.
+//
+//    Neither the name of 3Dlabs Inc. Ltd. nor the names of its
+//    contributors may be used to endorse or promote products derived
+//    from this software without specific prior written permission.
+//
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+//COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+//LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+//CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+//ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+//POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include "Versions.h"
+
+namespace glslang {
+    
+//
+// A character scanner that seamlessly, on read-only strings, reads across an
+// array of strings without assuming null termination.
+//
+class TInputScanner {
+public:
+    TInputScanner(int n, const char* const i[], int L[]) : numSources(n), sources(i), lengths(L), currentSource(0), currentChar(0) { }
+
+    // return of -1 means end of strings,
+    // anything else is the next character
+
+    // retrieve the next character and advance one character
+    char get()
+    {
+        if (currentSource >= numSources)
+            return -1;
+
+        char ret = sources[currentSource][currentChar];
+        advance();
+
+        return ret;
+    }
+
+    // advance one character
+    void advance()
+    {
+        ++currentChar;
+        if (currentChar >= lengths[currentSource]) {
+            ++currentSource;
+            currentChar = 0;
+            while (currentSource < numSources && lengths[currentSource] == 0)
+                ++currentSource;
+        }
+    }
+
+    // retrieve the next character, no advance
+    char peek()
+    {
+        if (currentSource >= numSources)
+            return -1;
+
+        return sources[currentSource][currentChar];
+    }
+
+    // go back one character
+    void unget()
+    {
+        if (currentChar > 0)
+            --currentChar;
+        else {
+            do {
+                --currentSource;
+            } while (currentSource > 0 && lengths[currentSource] == 0);
+            currentChar = lengths[currentSource] - 1;
+            if (currentChar < 0)
+                currentChar = 0;
+        }
+    }
+
+protected:
+    int numSources;             // number of strings in source
+    const char* const *sources; // array of strings
+    const int *lengths;         // length of each string
+    int currentSource;
+    int currentChar;
+};
+
+// 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);
+
+}; // end glslang namespace
index f2dbfd6..0c1bd9a 100644 (file)
@@ -43,6 +43,7 @@
 #include <string.h>
 #include "SymbolTable.h"
 #include "ParseHelper.h"
+#include "Scan.h"
 
 #include "../Include/ShHandle.h"
 #include "InitializeDll.h"
@@ -206,132 +207,6 @@ void SetupBuiltinSymbolTable(int version, EProfile profile)
     SetGlobalPoolAllocatorPtr(savedGPA);
 }
 
-// returns true if something whas consumed
-bool ConsumeWhitespaceComment(const char*& s)
-{
-    const char* startPoint = s;
-
-    // first, skip white space    
-    while (*s == ' ' || *s == '\t' || *s == '\r' || *s == '\n') {
-        ++s;
-    }
-
-    // then, check for a comment
-    if (*s == '/') {
-        if (*(s+1) == '/') {
-
-            // a '//' style comment
-            s += 2;
-            do {
-                while (*s && *s != '\\' && *s != '\r' && *s != '\n')
-                    ++s;
-
-                if (*s == '\r' || *s == '\n' || *s == 0) {
-                    while (*s == '\r' || *s == '\n')
-                        ++s;
-
-                    // we reached the end of the comment
-                    break;
-                } else {
-                    // it's a '\', so we need to keep going, after skipping what's escaped
-                    ++s;
-                    if (*s == '\r' && *(s+1) == '\n')
-                        s += 2;
-                    else {
-                        // skip the escaped character
-                        if (*s)
-                            ++s;
-                    }
-                }
-            } while (true);
-
-        } else if (*(s+1) == '*') {
-
-            // a '/*' style comment
-            s += 2;
-            do {
-                while (*s && *s != '*')
-                    ++s;
-                if (*s == '*') {
-                    ++s;
-                    if (*s == '/') {
-                        ++s;
-                        break;
-                    } // else not end of comment, keep going
-                } else // end of string
-                    break;
-            } while (true);
-        } // else it's not a comment
-    } // else it's not a comment
-
-    return startPoint != s;
-}
-
-void ScanVersion(const char* const shaderStrings[], int numStrings, int& version, EProfile& profile)
-{
-    // This function doesn't have to get all the semantics correct, 
-    // just find the #version if there is a correct one present.
-    // The CPP will have the responsibility of getting all the semantics right.
-
-    version = 0;  // means not found
-    profile = ENoProfile;
-
-    const char* s = &shaderStrings[0][0];
-
-    // TODO: semantics:  ES error check: #version must be on first line
-
-    while (ConsumeWhitespaceComment(s))
-        ;
-
-    // #
-    if (*s != '#')
-        return;
-    ++s;
-
-    // whitespace
-    while (*s == ' ' || *s == '\t') {
-        ++s;
-    }
-
-    // version
-    if (strncmp(s, "version", 7) != 0)
-        return;
-
-    // whitespace
-    s += 7;
-    while (*s == ' ' || *s == '\t') {
-        ++s;
-    }
-
-    // version number
-    while (*s >= '0' && *s <= '9') {
-        version = 10 * version + (*s - '0');
-        ++s;
-    }
-    if (version == 0)
-        return;
-    
-    // whitespace
-    while (*s == ' ' || *s == '\t') {
-        ++s;
-    }
-
-    // profile
-    const char* end = s;
-    while (*end != ' ' && *end != '\t' && *end != '\n' && *end != '\r') {
-        if (*end == 0)
-            return;
-        ++end;
-    }
-    int profileLength = end - s;
-    if (profileLength == 2 && strncmp(s, "es", profileLength) == 0)
-        profile = EEsProfile;
-    else if (profileLength == 4 && strncmp(s, "core", profileLength) == 0)
-        profile = ECoreProfile;
-    else if (profileLength == 13 && strncmp(s, "compatibility", profileLength) == 0)
-        profile = ECompatibilityProfile;
-}
-
 bool DeduceProfile(TInfoSink& infoSink, int version, EProfile& profile)
 {
     const int FirstProfileVersion = 150;
@@ -382,7 +257,6 @@ bool DeduceProfile(TInfoSink& infoSink, int version, EProfile& profile)
     return true;
 }
 
-
 }; // end anonymous namespace for local functions
 
 int ShInitialize()
@@ -479,6 +353,7 @@ int ShCompile(
     const ShHandle handle,
     const char* const shaderStrings[],
     const int numStrings,
+    const int* inputLengths,
     const EShOptimizationLevel optLevel,
     const TBuiltInResource* resources,
     int debugOptions,
@@ -487,7 +362,7 @@ int ShCompile(
     EShMessages messages       // warnings/errors
     )
 {
-    if (!InitThread())
+    if (! InitThread())
         return 0;
 
     if (handle == 0)
@@ -497,17 +372,28 @@ int ShCompile(
     if (compiler == 0)
         return 0;
 
-    GlobalPoolAllocator.push();
     compiler->infoSink.info.erase();
     compiler->infoSink.debug.erase();
 
     if (numStrings == 0)
         return 1;
 
+    GlobalPoolAllocator.push();
+    
+    // move to length-based strings, rather than null-terminated strings
+    int* lengths = new int[numStrings];
+    for (int s = 0; s < numStrings; ++s) {
+        if (inputLengths == 0 || inputLengths[s] < 0)
+            lengths[s] = strlen(shaderStrings[s]);
+        else
+            lengths[s] = inputLengths[s];
+    }
+
     int version;
     EProfile profile;
     bool versionStatementMissing = false;
-    ScanVersion(shaderStrings, numStrings, version, profile);
+    glslang::TInputScanner input(numStrings, shaderStrings, lengths);
+    bool versionNotFirst = ScanVersion(input, version, profile);
     if (version == 0) {
         version = defaultVersion;
         versionStatementMissing = true;
@@ -533,6 +419,8 @@ int ShCompile(
     parseContext.initializeExtensionBehavior();
     if (versionStatementMissing)
         parseContext.warn(1, "statement missing: use #version on first line of shader", "#version", "");
+    else if (profile == EEsProfile && version >= 300 && versionNotFirst)
+        parseContext.error(1, "statement must appear first in ESSL shader; before comments or newlines", "#version", "");
 
     GlobalParseContext = &parseContext;
     
@@ -553,7 +441,7 @@ int ShCompile(
     if (parseContext.insertBuiltInArrayAtGlobalLevel())
         success = false;
 
-    int ret = PaParseStrings(const_cast<char**>(shaderStrings), 0, numStrings, parseContext, parseContext.getPreamble());
+    int ret = PaParseStrings(const_cast<char**>(shaderStrings), lengths, numStrings, parseContext, parseContext.getPreamble());
     if (ret)
         success = false;
     intermediate.addSymbolLinkageNodes(parseContext.treeRoot, parseContext.linkage, parseContext.language, symbolTable);
@@ -597,6 +485,7 @@ int ShCompile(
     // Throw away all the temporary memory used by the compilation process.
     //
     GlobalPoolAllocator.pop();
+    delete [] lengths;
 
     return success ? 1 : 0;
 }
index df9dcdf..d20b5d7 100644 (file)
@@ -164,6 +164,7 @@ SH_IMPORT_EXPORT int ShCompile(
     const ShHandle,
     const char* const shaderStrings[],
     const int numStrings,
+    const int* lengths,
     const EShOptimizationLevel,
     const TBuiltInResource *resources,
     int debugOptions,