Updated command-line options, adding -o for saving binaries, -G for OpenGL SPIR-V validation, -v etc.

Old uses should still work as they did before.
Also encapsulated use of these flags during parsing, for the parse context.
Added SPIR-V version to -v.
diff --git a/StandAlone/StandAlone.cpp b/StandAlone/StandAlone.cpp
index fbaf46d..f61389c 100644
--- a/StandAlone/StandAlone.cpp
+++ b/StandAlone/StandAlone.cpp
@@ -70,12 +70,13 @@
     EOptionDumpVersions       = 0x0400,
     EOptionSpv                = 0x0800,
     EOptionHumanReadableSpv   = 0x1000,
-    EOptionDefaultDesktop     = 0x2000,
-    EOptionOutputPreprocessed = 0x4000,
+    EOptionVulkanRules        = 0x2000,
+    EOptionDefaultDesktop     = 0x4000,
+    EOptionOutputPreprocessed = 0x8000,
 };
 
 //
-// Return codes from main.
+// Return codes from main/exit().
 //
 enum TFailCode {
     ESuccess = 0,
@@ -88,18 +89,8 @@
 };
 
 //
-// Just placeholders for testing purposes.  The stand-alone environment
-// can't actually do a full link without something specifying real
-// attribute bindings.
+// Forward declarations.
 //
-ShBinding FixedAttributeBindings[] = { 
-    { "gl_Vertex", 15 },
-    { "gl_Color", 10 },
-    { "gl_Normal", 7 },
-};
-
-ShBindingTable FixedAttributeTable = { 3, FixedAttributeBindings };
-
 EShLanguage FindLanguage(const std::string& name);
 void CompileFile(const char* fileName, ShHandle);
 void usage();
@@ -112,6 +103,7 @@
 bool LinkFailed = false;
 
 // Use to test breaking up a single shader file into multiple strings.
+// Set in ReadFileData().
 int NumShaderStrings;
 
 TBuiltInResource Resources;
@@ -452,7 +444,30 @@
 int NumWorkItems = 0;
 
 int Options = 0;
-const char* ExecutableName;
+const char* ExecutableName = nullptr;
+const char* binaryFileName = nullptr;
+
+//
+// Create the default name for saving a binary if -o is not provided.
+//
+const char* GetBinaryName(EShLanguage stage)
+{
+    const char* name;
+    if (binaryFileName == nullptr) {
+        switch (stage) {
+        case EShLangVertex:          name = "vert.spv";    break;
+        case EShLangTessControl:     name = "tesc.spv";    break;
+        case EShLangTessEvaluation:  name = "tese.spv";    break;
+        case EShLangGeometry:        name = "geom.spv";    break;
+        case EShLangFragment:        name = "frag.spv";    break;
+        case EShLangCompute:         name = "comp.spv";    break;
+        default:                     name = "unknown";     break;
+        }
+    } else
+        name = binaryFileName;
+
+    return name;
+}
 
 //
 // *.conf => this is a config file that can set limits/resources
@@ -470,17 +485,32 @@
     return false;
 }
 
-bool ProcessArguments(int argc, char* argv[])
+//
+// Give error and exit with failure code.
+//
+void Error(const char* message)
+{
+    printf("%s: Error %s (use -h for usage)\n", ExecutableName, message);
+    exit(EFailUsage);
+}
+
+//
+// Do all command-line argument parsing.  This includes building up the work-items
+// to be processed later, and saving all the command-line options.
+//
+// Does not return (it exits) if command-line is fatally flawed.
+//
+void ProcessArguments(int argc, char* argv[])
 {
     ExecutableName = argv[0];
     NumWorkItems = argc;  // will include some empties where the '-' options were, but it doesn't matter, they'll be 0
     Work = new glslang::TWorkItem*[NumWorkItems];
-    Work[0] = 0;
+    for (int w = 0; w < NumWorkItems; ++w)
+        Work[w] = 0;
 
     argc--;
     argv++;    
     for (; argc >= 1; argc--, argv++) {
-        Work[argc] = 0;
         if (argv[0][0] == '-') {
             switch (argv[0][1]) {
             case 'H':
@@ -488,6 +518,11 @@
                 // fall through to -V
             case 'V':
                 Options |= EOptionSpv;
+                Options |= EOptionVulkanRules;
+                Options |= EOptionLinkProgram;
+                break;
+            case 'G':
+                Options |= EOptionSpv;
                 Options |= EOptionLinkProgram;
                 break;
             case 'E':
@@ -499,6 +534,9 @@
             case 'd':
                 Options |= EOptionDefaultDesktop;
                 break;
+            case 'h':
+                usage();
+                break;
             case 'i':
                 Options |= EOptionIntermediate;
                 break;
@@ -508,6 +546,14 @@
             case 'm':
                 Options |= EOptionMemoryLeakMode;
                 break;
+            case 'o':
+                binaryFileName = argv[1];
+                if (argc > 0) {
+                    argc--;
+                    argv++;
+                } else
+                    Error("no <file> provided for -o");
+                break;
             case 'q':
                 Options |= EOptionDumpReflection;
                 break;
@@ -529,7 +575,8 @@
                 Options |= EOptionSuppressWarnings;
                 break;
             default:
-                return false;
+                usage();
+                break;
             }
         } else {
             std::string name(argv[0]);
@@ -540,16 +587,18 @@
         }
     }
 
-    // Make sure that -E is not specified alongside -V -H or -l.
-    if (Options & EOptionOutputPreprocessed &&
-        ((Options &
-          (EOptionSpv | EOptionHumanReadableSpv | EOptionLinkProgram)))) {
-      return false;
-    }
+    // Make sure that -E is not specified alongside linking (which includes SPV generation)
+    if ((Options & EOptionOutputPreprocessed) && (Options & EOptionLinkProgram))
+        Error("can't use -E when linking is selected");
 
-    return true;
+    // -o makes no sense if there is no target binary
+    if (binaryFileName && (Options & EOptionSpv) == 0)
+        Error("no binary generation requested (e.g., -V)");
 }
 
+//
+// Translate the meaningful subset of command-line options to parser-behavior options.
+//
 void SetMessageOptions(EShMessages& messages)
 {
     if (Options & EOptionRelaxedErrors)
@@ -558,8 +607,13 @@
         messages = (EShMessages)(messages | EShMsgAST);
     if (Options & EOptionSuppressWarnings)
         messages = (EShMessages)(messages | EShMsgSuppressWarnings);
+    if (Options & EOptionSpv)
+        messages = (EShMessages)(messages | EShMsgSpvRules);
+    if (Options & EOptionVulkanRules)
+        messages = (EShMessages)(messages | EShMsgVulkanRules);
 }
 
+//
 // Thread entry point, for non-linking asynchronous mode.
 //
 // Return 0 for failure, 1 for success.
@@ -658,7 +712,7 @@
     // Program-level processing...
     //
 
-    if (!(Options & EOptionOutputPreprocessed) && ! program.link(messages))
+    if (! (Options & EOptionOutputPreprocessed) && ! program.link(messages))
         LinkFailed = true;
 
     if (! (Options & EOptionSuppressInfolog)) {
@@ -673,23 +727,13 @@
 
     if (Options & EOptionSpv) {
         if (CompileFailed || LinkFailed)
-            printf("SPIRV is not generated for failed compile or link\n");
+            printf("SPIR-V is not generated for failed compile or link\n");
         else {
             for (int stage = 0; stage < EShLangCount; ++stage) {
                 if (program.getIntermediate((EShLanguage)stage)) {
                     std::vector<unsigned int> spirv;
                     glslang::GlslangToSpv(*program.getIntermediate((EShLanguage)stage), spirv);
-                    const char* name;
-                    switch (stage) {
-                    case EShLangVertex:          name = "vert";    break;
-                    case EShLangTessControl:     name = "tesc";    break;
-                    case EShLangTessEvaluation:  name = "tese";    break;
-                    case EShLangGeometry:        name = "geom";    break;
-                    case EShLangFragment:        name = "frag";    break;
-                    case EShLangCompute:         name = "comp";    break;
-                    default:                     name = "unknown"; break;
-                    }
-                    glslang::OutputSpv(spirv, name);
+                    glslang::OutputSpv(spirv, GetBinaryName((EShLanguage)stage));
                     if (Options & EOptionHumanReadableSpv) {
                         spv::Parameterize();
                         GLSL_STD_450::GetDebugNames(GlslStd450DebugNames);
@@ -713,10 +757,7 @@
 
 int C_DECL main(int argc, char* argv[])
 {
-    if (! ProcessArguments(argc, argv)) {
-        usage();
-        return EFailUsage;
-    }
+    ProcessArguments(argc, argv);
 
     if (Options & EOptionDumpConfig) {
         printf("%s", DefaultConfig);
@@ -727,13 +768,15 @@
     if (Options & EOptionDumpVersions) {        
         printf("ESSL Version: %s\n", glslang::GetEsslVersionString());
         printf("GLSL Version: %s\n", glslang::GetGlslVersionString());
+        std::string spirvVersion;
+        glslang::GetSpirvVersion(spirvVersion);
+        printf("SPIR-V Version %s\n", spirvVersion.c_str());  // TODO: move to consume source-generated data
         if (Worklist.empty())
             return ESuccess;
     }
 
     if (Worklist.empty()) {
         usage();
-        return EFailUsage;
     }
 
     ProcessConfigFile();
@@ -835,8 +878,6 @@
     char** shaderStrings = ReadFileData(fileName);
     if (! shaderStrings) {
         usage();
-        CompileFailed = true;
-        return;
     }
 
     int* lengths = new int[NumShaderStrings];
@@ -883,34 +924,44 @@
     printf("Usage: glslangValidator [option]... [file]...\n"
            "\n"
            "Where: each 'file' ends in .<stage>, where <stage> is one of\n"
-           "    .conf to provide an optional config file that replaces the default configuration\n"
-           "          (see -c option below for generating a template)\n"
-           "    .vert for a vertex shader\n"
-           "    .tesc for a tessellation control shader\n"
-           "    .tese for a tessellation evaluation shader\n"
-           "    .geom for a geometry shader\n"
-           "    .frag for a fragment shader\n"
-           "    .comp for a compute shader\n"
+           "    .conf   to provide an optional config file that replaces the default configuration\n"
+           "            (see -c option below for generating a template)\n"
+           "    .vert   for a vertex shader\n"
+           "    .tesc   for a tessellation control shader\n"
+           "    .tese   for a tessellation evaluation shader\n"
+           "    .geom   for a geometry shader\n"
+           "    .frag   for a fragment shader\n"
+           "    .comp   for a compute shader\n"
            "\n"
            "Compilation warnings and errors will be printed to stdout.\n"
            "\n"
            "To get other information, use one of the following options:\n"
-           "(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"
-           "  -l  link validation of all input files\n"
-           "  -m  memory leak mode\n"
-           "  -q  dump reflection query database\n"
-           "  -r  relaxed semantic error-checking mode\n"
-           "  -s  silent mode\n"
-           "  -t  multi-threaded mode\n"
-           "  -v  print version strings\n"
-           "  -w  suppress warnings (except as required by #extension : warn)\n"
+           "Each option must be specified separately.\n"
+           "  -V          create SPIR-V binary, under Vulkan semantics; turns on -l;\n"
+           "              default file name is <stage>.spv (-o overrides this)\n"
+           "              (unless -o is specified, which overrides the default file name)\n"
+           "  -G          create SPIR-V binary, under OpenGL semantics; turns on -l;\n"
+           "              default file name is <stage>.spv (-o overrides this)\n"
+           "  -H          print human readable form of SPIR-V; turns on -V\n"
+           "  -E          print pre-processed GLSL; cannot be used with -l.\n"
+           "  -c          configuration dump;\n"
+           "              creates the default configuration file (redirect to a .conf file)\n"
+           "  -d          default to desktop (#version 110) when there is no shader #version\n"
+           "              (default is ES version 100)\n"
+           "  -h          print this usage message\n"
+           "  -i          intermediate tree (glslang AST) is printed out\n"
+           "  -l          link all input files together to form a single module\n"
+           "  -m          memory leak mode\n"
+           "  -o  <file>  save binary into <file>, requires a binary option (e.g., -V)\n"
+           "  -q          dump reflection query database\n"
+           "  -r          relaxed semantic error-checking mode\n"
+           "  -s          silent mode\n"
+           "  -t          multi-threaded mode\n"
+           "  -v          print version strings\n"
+           "  -w          suppress warnings (except as required by #extension : warn)\n"
            );
+
+    exit(EFailUsage);
 }
 
 #if !defined _MSC_VER && !defined MINGW_HAS_SECURE_API
@@ -954,10 +1005,8 @@
     const int maxSourceStrings = 5;  // for testing splitting shader/tokens across multiple strings
     char** return_data = (char**)malloc(sizeof(char *) * (maxSourceStrings+1)); // freed in FreeFileData()
 
-    if (errorCode || in == nullptr) {
-        printf("Error: unable to open input file: %s\n", fileName);
-        return nullptr;
-    }
+    if (errorCode || in == nullptr)
+        Error("unable to open input file");
     
     while (fgetc(in) != EOF)
         count++;
@@ -965,15 +1014,14 @@
     fseek(in, 0, SEEK_SET);
 
     char *fdata = (char*)malloc(count+2); // freed before return of this function
-    if (! fdata) {
-        printf("Error allocating memory\n");
-        return nullptr;
-    }
+    if (! fdata)
+        Error("can't allocate memory");
+
     if ((int)fread(fdata, 1, count, in) != count) {
-        printf("Error reading input file: %s\n", fileName);
         free(fdata);
-        return nullptr;
+        Error("can't read input file");
     }
+
     fdata[count] = '\0';
     fclose(in);