Convert ES2 conformance tests to usable SkSL.

This generates usable code, but emits it all to stdout. The next step
should be automation of testing.

The output from the script:
http://go/diid/1kg7HB-DxUQ5qsKkq5LsI4nnSkmDcLd7G/view

Change-Id: I638f33dcccaf9b36b79b77ae04cb65950042dc01
Bug: skia:12484
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/453947
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: Ethan Nicholas <ethannicholas@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
diff --git a/resources/sksl/es2_conformance/import_conformance_tests.py b/resources/sksl/es2_conformance/import_conformance_tests.py
index e71c3f1..a5babf0 100755
--- a/resources/sksl/es2_conformance/import_conformance_tests.py
+++ b/resources/sksl/es2_conformance/import_conformance_tests.py
@@ -18,6 +18,8 @@
 # GLSL code appears in ""double-double quotes"" and is indicated as vert/frag-specific or "both".
 # Some tests denote that they are expected to pass/fail. (Presumably, "pass" is the default.)
 # We ignore descriptions and version fields.
+wordWithUnderscores = pp.Word(pp.alphanums + '_')
+
 pipeList = pp.delimited_list(pp.SkipTo(pp.Literal("|") | pp.Literal("]")), delim="|")
 bracketedPipeList = pp.Group(pp.Literal("[").suppress() +
                              pipeList +
@@ -31,38 +33,36 @@
              pp.Literal(";").suppress())
 value = pp.Group((pp.Keyword("input") | pp.Keyword("output") | pp.Keyword("uniform")) +
                  valueList)
-values = pp.Group(pp.Keyword("values") +
-                  pp.Literal("{").suppress() +
-                  pp.ZeroOrMore(value) +
-                  pp.Literal("}").suppress())
+values = (pp.Keyword("values") +
+          pp.Literal("{").suppress() +
+          pp.ZeroOrMore(value) +
+          pp.Literal("}").suppress())
 
-expectation = pp.Group(pp.Keyword("expect").suppress() +
-                       (pp.Keyword("compile_fail") | pp.Keyword("pass")))
+expectation = (pp.Keyword("expect").suppress() + (pp.Keyword("compile_fail") |
+                                                  pp.Keyword("pass")))
 
-code = ((pp.Group(pp.Keyword("both") + pp.QuotedString('""', multiline=True))) |
-        (pp.Group(pp.Keyword("vertex") + pp.QuotedString('""', multiline=True)) +
-                  pp.Keyword("fragment") + pp.QuotedString('""', multiline=True)))
+code = ((pp.Keyword("both") + pp.QuotedString('""', multiline=True)) |
+        (pp.Keyword("vertex") + pp.QuotedString('""', multiline=True) +
+         pp.Keyword("fragment") + pp.QuotedString('""', multiline=True)))
+
+reqGlsl100 = pp.Keyword("require").suppress() + pp.Keyword("full_glsl_es_100_support")
 
 desc = pp.Keyword("desc") + pp.QuotedString('"')
-
 version100es = pp.Keyword("version") + pp.Keyword("100") + pp.Keyword("es")
+ignoredCaseItem = (desc | version100es).suppress()
 
-reqGlsl100 = pp.Keyword("require") + pp.Keyword("full_glsl_es_100_support")
-
-ignoredCaseItem = (desc | version100es | reqGlsl100).suppress()
-
-caseItem = values | expectation | code | ignoredCaseItem
+caseItem = pp.Group(values | expectation | code | reqGlsl100) | ignoredCaseItem
 
 caseBody = pp.ZeroOrMore(caseItem)
 
 blockEnd = pp.Keyword("end").suppress();
 
-caseHeader = pp.Keyword("case") + pp.Word(pp.alphanums + '_')
+caseHeader = pp.Keyword("case") + wordWithUnderscores
 case = pp.Group(caseHeader + caseBody + blockEnd)
 
 # Groups can be nested to any depth (or can be absent), and may contain any number of cases.
 # The names in the group header are ignored.
-groupHeader = pp.Keyword("group") + pp.Word(pp.alphanums + '_') + pp.QuotedString('"')
+groupHeader = (pp.Keyword("group") + wordWithUnderscores + pp.QuotedString('"')).suppress()
 
 group = pp.Forward()
 group <<= pp.OneOrMore(case | (groupHeader + group + blockEnd))
@@ -71,6 +71,142 @@
 grammar = group
 group.ignore('#' + pp.restOfLine)
 
-out = grammar.parse_string(sys.stdin.read(), parse_all=True)
+testCases = grammar.parse_string(sys.stdin.read(), parse_all=True)
 
-out.pprint()
+for c in testCases:
+    # Parse the case header
+    assert c[0] == 'case'
+    c.pop(0)
+
+    testName = c[0]
+    assert isinstance(testName, str)
+    c.pop(0)
+
+    # Parse the case body
+    skipTest = ''
+    expectPass = True
+    testCode = ''
+    inputs = []
+    outputs = []
+
+    for b in c:
+        caseItem = b[0]
+        b.pop(0)
+
+        if caseItem == 'compile_fail':
+            expectPass = False
+
+        elif caseItem == 'pass':
+            expectPass = True
+
+        elif caseItem == 'vertex' or caseItem == 'fragment':
+            skipTest = 'Uses vertex'
+
+        elif caseItem == 'both':
+            testCode = b[0]
+            assert isinstance(testCode, str)
+
+        elif caseItem == 'values':
+            for v in b:
+                valueType = v[0]
+                v.pop(0)
+
+                if valueType == 'uniform':
+                    skipTest = 'Uses uniform'
+                elif valueType == 'input':
+                    inputs.append(v.asList())
+                elif valueType == 'output':
+                    outputs.append(v.asList())
+
+        elif caseItem == 'full_glsl_es_100_support':
+            skipTest = 'Uses while loop'
+
+        else:
+            assert 0
+
+    if skipTest == '':
+        if "void main" not in testCode:
+            skipTest = 'Missing main'
+
+    if skipTest != '':
+        print("/////////// skipped %s (%s) ///////////" % (testName, skipTest))
+        print("")
+        continue
+
+    # Apply fixups to the test code.
+    # SkSL doesn't support the `precision` keyword, so comment it out if it appears.
+    testCode = testCode.replace("precision highp ",   "// precision highp ");
+    testCode = testCode.replace("precision mediump ", "// precision mediump ");
+    testCode = testCode.replace("precision lowp ",    "// precision lowp ");
+
+    # SkSL doesn't support the `#version` declaration.
+    testCode = testCode.replace("#version",    "// #version");
+
+    # Rename the `main` function to `execute_test`.
+    testCode = testCode.replace("void main",          "bool execute_test");
+
+    # Replace ${POSITION_FRAG_COLOR} with a scratch variable.
+    if "${POSITION_FRAG_COLOR}" in testCode:
+        testCode = testCode.replace("${POSITION_FRAG_COLOR}", "PositionFragColor");
+        if "${DECLARATIONS}" in testCode:
+            testCode = testCode.replace("${DECLARATIONS}",
+                                        "vec4 PositionFragColor;\n${DECLARATIONS}");
+        else:
+            testCode = "vec4 PositionFragColor;\n" + testCode
+
+    # Create a runnable SkSL test by returning green or red based on the test result.
+    testSuffix = ''
+    if expectPass:
+        testCode += "\n"
+        testCode += "half4 main(float2 coords) {\n"
+        testCode += "    return execute_test() ? half4(0,1,0,1) : half4(1,0,0,1);\n"
+        testCode += "}\n"
+
+    else:
+        testSuffix = '_ERROR'
+
+    # Find the total number of input/output fields.
+    numVariables = 0
+    for v in inputs + outputs:
+        numVariables = max(numVariables, len(v[2]))
+
+    if numVariables > 0:
+        assert "${DECLARATIONS}" in testCode
+        assert "${OUTPUT}" in testCode
+        for varIndex in range(0, numVariables):
+            print("/////////// %s_%d%s.sksl ///////////" % (testName, varIndex, testSuffix))
+            testSpecialization = testCode
+
+            # Assemble input variable declarations for ${DECLARATIONS}.
+            declarations = ""
+            for v in inputs:
+                if len(v[2]) > varIndex:
+                    declarations += "%s %s = %s;\n" % (v[0], v[1], v[2][varIndex]);
+
+            # Assemble output variable declarations for ${DECLARATIONS}.
+            for v in outputs:
+                declarations += "%s %s;\n" % (v[0], v[1]);
+
+            # Verify output values inside ${OUTPUT}.
+            outputChecks = "return true"
+            for v in outputs:
+                if len(v[2]) > varIndex:
+                    outputChecks += " && (%s == %s)" % (v[1], v[2][varIndex])
+
+            outputChecks += ";\n"
+
+            # Apply fixups to the test code.
+            testSpecialization = testSpecialization.replace("${DECLARATIONS}", declarations)
+            testSpecialization = testSpecialization.replace("${SETUP}",        '')
+            testSpecialization = testSpecialization.replace("${OUTPUT}",       outputChecks)
+            print(testSpecialization)
+
+    else: # not (numVariables > 0)
+        print ("/////////// %s%s.sksl ///////////" % (testName, testSuffix))
+
+        testCode = testCode.replace("${DECLARATIONS}", '')
+        testCode = testCode.replace("${SETUP}",        '')
+        testCode = testCode.replace("${OUTPUT}",       'return true;')
+
+        # Generate an SkSL test file.
+        print (testCode)