Major rearchitecture of SkSL parsing, focused on improving performance.

Bug: skia:
Change-Id: I83e3a094d26085fc4d586e5d2581e0d61c55634e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/145080
Commit-Queue: Ethan Nicholas <ethannicholas@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
index c07fd73..b7fd93b 100644
--- a/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
+++ b/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp
@@ -46,8 +46,8 @@
                 "half4 color = %s;\nhalf4 mask_color = texture(%s, %s).%s;\nif (mask_color.w < "
                 "0.5) {\n    if (color.w > %s) {\n        half scale = %s / color.w;\n        "
                 "color.xyz *= scale;\n        color.w = %s;\n    }\n} else if (color.w < %s) {\n   "
-                " half scale = %s / max(0.001, color.w);\n    color.xyz *= scale;\n    color.w = "
-                "%s;\n}\n%s = color;\n",
+                " half scale = %s / max(0.0010000000474974513, color.w);\n    color.xyz *= "
+                "scale;\n    color.w = %s;\n}\n%s = color;\n",
                 args.fInputColor,
                 fragBuilder->getProgramBuilder()->samplerVariable(args.fTexSamplers[0]).c_str(),
                 sk_TransformedCoords2D_0.c_str(),
diff --git a/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp
index c2b3ef3..f286f0e 100644
--- a/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp
+++ b/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp
@@ -28,7 +28,7 @@
         (void)mode;
         fragBuilder->codeAppendf(
                 "half factor = 1.0 - %s.w;\n@switch (%d) {\n    case 0:\n        factor = "
-                "exp((-factor * factor) * 4.0) - 0.017999999999999999;\n        break;\n    case "
+                "exp((-factor * factor) * 4.0) - 0.017999999225139618;\n        break;\n    case "
                 "1:\n        factor = smoothstep(1.0, 0.0, factor);\n        break;\n}\n%s = "
                 "half4(factor);\n",
                 args.fInputColor, (int)_outer.mode, args.fOutputColor);
diff --git a/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp b/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp
index d0f6b00..8afed91 100644
--- a/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp
+++ b/src/gpu/effects/generated/GrColorMatrixFragmentProcessor.cpp
@@ -38,7 +38,7 @@
         vVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, kHalf4_GrSLType, "v");
         fragBuilder->codeAppendf(
                 "half4 inputColor = %s;\n@if (%s) {\n    half nonZeroAlpha = max(inputColor.w, "
-                "0.0001);\n    inputColor = half4(inputColor.xyz / nonZeroAlpha, "
+                "9.9999997473787516e-05);\n    inputColor = half4(inputColor.xyz / nonZeroAlpha, "
                 "nonZeroAlpha);\n}\n%s = %s * inputColor + %s;\n@if (%s) {\n    %s = clamp(%s, "
                 "0.0, 1.0);\n} else {\n    %s.w = clamp(%s.w, 0.0, 1.0);\n}\n@if (%s) {\n    "
                 "%s.xyz *= %s.w;\n}\n",
diff --git a/src/gpu/effects/generated/GrEllipseEffect.cpp b/src/gpu/effects/generated/GrEllipseEffect.cpp
index 269e95f..b28f961 100644
--- a/src/gpu/effects/generated/GrEllipseEffect.cpp
+++ b/src/gpu/effects/generated/GrEllipseEffect.cpp
@@ -42,20 +42,21 @@
                 "%s;\nfloat2 d = sk_FragCoord.xy - %s.xy;\n@if (medPrecision) {\n    d *= "
                 "%s.y;\n}\nfloat2 Z = d * %s.zw;\nfloat implicit = dot(Z, d) - 1.0;\nfloat "
                 "grad_dot = 4.0 * dot(Z, Z);\n@if (medPrecision) {\n    grad_dot = max(grad_dot, "
-                "6.1036000000000003e-05);\n} else {\n    grad_dot = max(grad_dot, "
-                "1.1755e-38);\n}\nfloat approx_dist = implicit * inversesqrt(grad_dot);\n@if "
-                "(medPrecision) {\n    approx_dist *= %s.x;\n}\nhalf alpha;\n@switch ",
+                "6.1036000261083245e-05);\n} else {\n    grad_dot = max(grad_dot, "
+                "1.1754999560161448e-38);\n}\nfloat approx_dist = implicit * "
+                "inversesqrt(grad_dot);\n@if (medPrecision) {\n    approx_dist *= %s.x;\n}\nhalf "
+                "alph",
                 prevRadii.fX, prevRadii.fY, (medPrecision ? "true" : "false"),
                 args.fUniformHandler->getUniformCStr(ellipseVar),
                 scaleVar.isValid() ? args.fUniformHandler->getUniformCStr(scaleVar) : "float2(0)",
                 args.fUniformHandler->getUniformCStr(ellipseVar),
                 scaleVar.isValid() ? args.fUniformHandler->getUniformCStr(scaleVar) : "float2(0)");
         fragBuilder->codeAppendf(
-                "(%d) {\n    case 0:\n        alpha = approx_dist > 0.0 ? 0.0 : 1.0;\n        "
-                "break;\n    case 1:\n        alpha = clamp(0.5 - half(approx_dist), 0.0, 1.0);\n  "
-                "      break;\n    case 2:\n        alpha = approx_dist > 0.0 ? 1.0 : 0.0;\n       "
-                " break;\n    case 3:\n        alpha = clamp(0.5 + half(approx_dist), 0.0, 1.0);\n "
-                "       break;\n    default:\n        discard;\n}\n%s = %s * alpha;\n",
+                "a;\n@switch (%d) {\n    case 0:\n        alpha = approx_dist > 0.0 ? 0.0 : 1.0;\n "
+                "       break;\n    case 1:\n        alpha = clamp(0.5 - half(approx_dist), 0.0, "
+                "1.0);\n        break;\n    case 2:\n        alpha = approx_dist > 0.0 ? 1.0 : "
+                "0.0;\n        break;\n    case 3:\n        alpha = clamp(0.5 + half(approx_dist), "
+                "0.0, 1.0);\n        break;\n    default:\n        discard;\n}\n%s = %s * alpha;\n",
                 (int)_outer.edgeType, args.fOutputColor, args.fInputColor);
     }
 
diff --git a/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp b/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp
index 337173c..3c9f24d 100644
--- a/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp
+++ b/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp
@@ -24,8 +24,8 @@
         const GrLumaColorFilterEffect& _outer = args.fFp.cast<GrLumaColorFilterEffect>();
         (void)_outer;
         fragBuilder->codeAppendf(
-                "\nhalf luma = clamp(dot(half3(0.21260000000000001, 0.71519999999999995, 0.0722), "
-                "%s.xyz), 0.0, 1.0);\n%s = half4(0.0, 0.0, 0.0, luma);\n",
+                "\nhalf luma = clamp(dot(half3(0.2125999927520752, 0.71520000696182251, "
+                "0.072200000286102295), %s.xyz), 0.0, 1.0);\n%s = half4(0.0, 0.0, 0.0, luma);\n",
                 args.fInputColor, args.fOutputColor);
     }
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index b1b61e8..6d67623 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -3380,7 +3380,6 @@
         return false;
     }
 
-    const char* version = shaderCaps->versionDeclString();
     GrShaderVar aVertex("a_vertex", kHalf2_GrSLType, GrShaderVar::kIn_TypeModifier);
     GrShaderVar uTexCoordXform("u_texCoordXform", kHalf4_GrSLType,
                                GrShaderVar::kUniform_TypeModifier);
@@ -3395,7 +3394,7 @@
     };
     GrShaderVar oFragColor("o_FragColor", kHalf4_GrSLType,GrShaderVar::kOut_TypeModifier);
 
-    SkString vshaderTxt(version);
+    SkString vshaderTxt;
     if (shaderCaps->noperspectiveInterpolationSupport()) {
         if (const char* extension = shaderCaps->noperspectiveInterpolationExtensionString()) {
             vshaderTxt.appendf("#extension %s : require\n", extension);
@@ -3448,7 +3447,7 @@
 
     vshaderTxt.append("}");
 
-    SkString fshaderTxt(version);
+    SkString fshaderTxt;
     if (shaderCaps->noperspectiveInterpolationSupport()) {
         if (const char* extension = shaderCaps->noperspectiveInterpolationExtensionString()) {
             fshaderTxt.appendf("#extension %s : require\n", extension);
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.cpp b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
index a8d7812..bd30080 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.cpp
@@ -239,7 +239,6 @@
 
 void GrGLSLShaderBuilder::finalize(uint32_t visibility) {
     SkASSERT(!fFinalized);
-    this->versionDecl() = fProgramBuilder->shaderCaps()->versionDeclString();
     this->compileAndAppendLayoutQualifiers();
     SkASSERT(visibility);
     fProgramBuilder->appendUniformDecls((GrShaderFlags) visibility, &this->uniforms());
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index a203d3a..0086e85 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -198,7 +198,6 @@
         fCodeIndex++;
     }
 
-    SkString& versionDecl() { return fShaderStrings[kVersionDecl]; }
     SkString& extensions() { return fShaderStrings[kExtensions]; }
     SkString& definitions() { return fShaderStrings[kDefinitions]; }
     SkString& precisionQualifier() { return fShaderStrings[kPrecisionQualifier]; }
@@ -213,7 +212,6 @@
     virtual void onFinalize() = 0;
 
     enum {
-        kVersionDecl,
         kExtensions,
         kDefinitions,
         kPrecisionQualifier,
diff --git a/src/gpu/gradients/generated/GrLinearGradientLayout.cpp b/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
index da6b293..28e7da8 100644
--- a/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
+++ b/src/gpu/gradients/generated/GrLinearGradientLayout.cpp
@@ -27,7 +27,7 @@
         (void)gradientMatrix;
         SkString sk_TransformedCoords2D_0 = fragBuilder->ensureCoords2D(args.fTransformedCoords[0]);
         fragBuilder->codeAppendf(
-                "half t = half(%s.x) + 1.0000000000000001e-05;\n%s = half4(t, 1.0, 0.0, 0.0);\n",
+                "half t = half(%s.x) + 9.9999997473787516e-06;\n%s = half4(t, 1.0, 0.0, 0.0);\n",
                 sk_TransformedCoords2D_0.c_str(), args.fOutputColor);
     }
 
diff --git a/src/gpu/gradients/generated/GrSweepGradientLayout.cpp b/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
index 9d1157b..18b4739 100644
--- a/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
+++ b/src/gpu/gradients/generated/GrSweepGradientLayout.cpp
@@ -36,7 +36,7 @@
         fragBuilder->codeAppendf(
                 "half angle;\nif (sk_Caps.atan2ImplementedAsAtanYOverX) {\n    angle = half(2.0 * "
                 "atan(-%s.y, length(%s) - %s.x));\n} else {\n    angle = half(atan(-%s.y, "
-                "-%s.x));\n}\nhalf t = ((angle * 0.15915494309180001 + 0.5) + %s) * %s;\n%s = "
+                "-%s.x));\n}\nhalf t = ((angle * 0.15915493667125702 + 0.5) + %s) * %s;\n%s = "
                 "half4(t, 1.0, 0.0, 0.0);\n",
                 sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
                 sk_TransformedCoords2D_0.c_str(), sk_TransformedCoords2D_0.c_str(),
diff --git a/src/sksl/SkSLASTFile.h b/src/sksl/SkSLASTFile.h
new file mode 100644
index 0000000..71fb8eb
--- /dev/null
+++ b/src/sksl/SkSLASTFile.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_ASTFILE
+#define SKSL_ASTFILE
+
+#include "src/sksl/SkSLASTNode.h"
+
+namespace SkSL {
+
+struct ASTFile {
+    ASTFile()
+        : fRoot(ASTNode::ID::Invalid()) {}
+
+    ASTNode& root() {
+        return fNodes[fRoot.fValue];
+    }
+
+private:
+    std::vector<ASTNode> fNodes;
+
+    ASTNode::ID fRoot;
+
+    friend class IRGenerator;
+    friend class Parser;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLASTNode.cpp b/src/sksl/SkSLASTNode.cpp
new file mode 100644
index 0000000..34e59eb
--- /dev/null
+++ b/src/sksl/SkSLASTNode.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/sksl/SkSLASTNode.h"
+#include "src/sksl/SkSLCompiler.h"
+#include "src/sksl/SkSLString.h"
+
+namespace SkSL {
+
+String ASTNode::description() const {
+    switch (fKind) {
+        case Kind::kNull: return "";
+        case Kind::kBinary:
+            return "(" + this->begin()->description() + " " +
+                               Compiler::OperatorName(getToken().fKind) + " " +
+                               (this->begin() + 1)->description() + ")";
+        case Kind::kBlock: {
+            String result = "{\n";
+            for (const auto& c : *this) {
+                result += c.description();
+                result += "\n";
+            }
+            result += "}";
+            return result;
+        }
+        case Kind::kBool:
+            return getBool() ? "true" : "false";
+        case Kind::kBreak:
+            return "break";
+        case Kind::kCall: {
+            auto iter = this->begin();
+            String result = iter->description();
+            result += "(";
+            const char* separator = "";
+            while (iter != this->end()) {
+                result += separator;
+                result += (iter++)->description();
+                separator = ",";
+            }
+            result += ")";
+            return result;
+        }
+        case Kind::kContinue:
+            return "continue";
+        case Kind::kDiscard:
+            return "discard";
+        case Kind::kDo:
+            return "do " + this->begin()->description() + " while (" +
+                   (this->begin() + 1)->description() + ")";
+        case Kind::kEnum: {
+            String result = "enum ";
+            result += getString();
+            result += " {\n";
+            for (const auto& c : *this) {
+                result += c.description();
+                result += "\n";
+            }
+            result += "};";
+            return result;
+        }
+        case Kind::kEnumCase:
+            if (this->begin() != this->end()) {
+                return String(getString()) + " = " + this->begin()->description();
+            }
+            return getString();
+        case Kind::kExtension:
+            return "#extension " + getString();
+        case Kind::kField:
+            return this->begin()->description() + "." + getString();
+        case Kind::kFile: {
+            String result;
+            for (const auto& c : *this) {
+                result += c.description();
+                result += "\n";
+            }
+            return result;
+        }
+        case Kind::kFloat:
+            return to_string(getFloat());
+        case Kind::kFor:
+            return "for (" + this->begin()->description() + "; " +
+                   (this->begin() + 1)->description() + "; " + (this->begin() + 2)->description() +
+                   ") " + (this->begin() + 3)->description();
+        case Kind::kFunction: {
+            FunctionData fd = getFunctionData();
+            String result = fd.fModifiers.description();
+            if (result.size()) {
+                result += " ";
+            }
+            auto iter = this->begin();
+            result += (iter++)->description() + " " + fd.fName + "(";
+            const char* separator = "";
+            for (size_t i = 0; i < fd.fParameterCount; ++i) {
+                result += separator;
+                result += (iter++)->description();
+                separator = ", ";
+            }
+            result += ")";
+            if (iter != this->end()) {
+                result += " " + (iter++)->description();
+                SkASSERT(iter == this->end());
+            }
+            else {
+                result += ";";
+            }
+            return result;
+        }
+        case Kind::kIdentifier:
+            return getString();
+        case Kind::kIndex:
+            return this->begin()->description() + "[" + (this->begin() + 1)->description() + "]";
+        case Kind::kIf: {
+            String result;
+            if (getBool()) {
+                result = "@";
+            }
+            auto iter = this->begin();
+            result += "if (" + (iter++)->description() + ") ";
+            result += (iter++)->description();
+            if (iter != this->end()) {
+                result += " else " + (iter++)->description();
+                SkASSERT(iter == this->end());
+            }
+            return result;
+        }
+        case Kind::kInt:
+            return to_string(getInt());
+        case Kind::kInterfaceBlock: {
+            InterfaceBlockData id = getInterfaceBlockData();
+            String result = id.fModifiers.description() + " " + id.fTypeName + " {\n";
+            auto iter = this->begin();
+            for (size_t i = 0; i < id.fDeclarationCount; ++i) {
+                result += (iter++)->description() + "\n";
+            }
+            result += "} ";
+            result += id.fInstanceName;
+            for (size_t i = 0; i < id.fSizeCount; ++i) {
+                result += "[" + (iter++)->description() + "]";
+            }
+            SkASSERT(iter == this->end());
+            result += ";";
+            return result;
+        }
+        case Kind::kModifiers:
+            return getModifiers().description();
+        case Kind::kParameter: {
+            ParameterData pd = getParameterData();
+            auto iter = this->begin();
+            String result = (iter++)->description() + " " + pd.fName;
+            for (size_t i = 0; i < pd.fSizeCount; ++i) {
+                result += "[" + (iter++)->description() + "]";
+            }
+            if (iter != this->end()) {
+                result += " = " + (iter++)->description();
+                SkASSERT(iter == this->end());
+            }
+            return result;
+        }
+        case Kind::kPostfix:
+            return this->begin()->description() + Compiler::OperatorName(getToken().fKind);
+        case Kind::kPrefix:
+            return Compiler::OperatorName(getToken().fKind) + this->begin()->description();
+        case Kind::kReturn:
+            if (this->begin() != this->end()) {
+                return "return " + this->begin()->description() + ";";
+            }
+            return "return;";
+        case Kind::kSection:
+            return "@section { ... }";
+        case Kind::kSwitchCase: {
+            auto iter = this->begin();
+            String result;
+            if (*iter) {
+                result.appendf("case %s:\n", iter->description().c_str());
+            } else {
+                result = "default:\n";
+            }
+            for (++iter; iter != this->end(); ++iter) {
+                result += "\n" + iter->description();
+            }
+            return result;
+        }
+        case Kind::kSwitch: {
+            auto iter = this->begin();
+            String result;
+            if (getBool()) {
+                result = "@";
+            }
+            result += "switch (" + (iter++)->description() + ") {";
+            for (; iter != this->end(); ++iter) {
+                result += iter->description() + "\n";
+            }
+            result += "}";
+            return result;
+        }
+        case Kind::kTernary:
+            return "(" + this->begin()->description() + " ? " + (this->begin() + 1)->description() +
+                   " : " + (this->begin() + 2)->description() + ")";
+        case Kind::kType:
+            return String(getTypeData().fName);
+        case Kind::kVarDeclaration: {
+            VarData vd = getVarData();
+            String result = vd.fName;
+            auto iter = this->begin();
+            for (size_t i = 0; i < vd.fSizeCount; ++i) {
+                result += "[" + (iter++)->description() + "]";
+            }
+            if (iter != this->end()) {
+                result += " = " + (iter++)->description();
+                SkASSERT(iter == this->end());
+            }
+            return result;
+        }
+        case Kind::kVarDeclarations: {
+            auto iter = this->begin();
+            String result = (iter++)->description();
+            if (result.size()) {
+                result += " ";
+            }
+            result += (iter++)->description();
+            const char* separator = " ";
+            for (; iter != this->end(); ++iter) {
+                result += separator + iter->description();
+                separator = ", ";
+            }
+            return result;
+        }
+        default:
+            SkASSERT(false);
+            return "<error>";
+    }
+}
+
+} // namespace
diff --git a/src/sksl/SkSLASTNode.h b/src/sksl/SkSLASTNode.h
new file mode 100644
index 0000000..59005a1
--- /dev/null
+++ b/src/sksl/SkSLASTNode.h
@@ -0,0 +1,635 @@
+/*
+ * Copyright 2019 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SKSL_ASTNODE
+#define SKSL_ASTNODE
+
+#include "src/sksl/SkSLLexer.h"
+#include "src/sksl/SkSLString.h"
+#include "src/sksl/ir/SkSLModifiers.h"
+
+#include <vector>
+
+namespace SkSL {
+
+// std::max isn't constexpr in some compilers
+static constexpr size_t Max(size_t a, size_t b) {
+    return a > b ? a : b;
+}
+
+/**
+ * Represents a node in the abstract syntax tree (AST). The AST is based directly on the parse tree;
+ * it is a parsed-but-not-yet-analyzed version of the program.
+ */
+struct ASTNode {
+    class ID {
+    public:
+        static ID Invalid() {
+            return ID();
+        }
+
+        bool operator==(const ID& other) {
+            return fValue == other.fValue;
+        }
+
+        bool operator!=(const ID& other) {
+            return fValue != other.fValue;
+        }
+
+        operator bool() const { return fValue >= 0; }
+
+    private:
+        ID()
+            : fValue(-1) {}
+
+        ID(int value)
+            : fValue(value) {}
+
+        int fValue;
+
+        friend struct ASTFile;
+        friend struct ASTNode;
+        friend class Parser;
+    };
+
+    enum class Kind {
+        // data: operator(Token), children: left, right
+        kBinary,
+        // children: statements
+        kBlock,
+        // data: value(bool)
+        kBool,
+        kBreak,
+        // children: target, arg1, arg2...
+        kCall,
+        kContinue,
+        kDiscard,
+        // children: statement, test
+        kDo,
+        // data: name(StringFragment), children: enumCases
+        kEnum,
+        // data: name(StringFragment), children: value?
+        kEnumCase,
+        // data: name(StringFragment)
+        kExtension,
+        // data: field(StringFragment), children: base
+        kField,
+        // children: declarations
+        kFile,
+        // data: value(float)
+        kFloat,
+        // children: init, test, next, statement
+        kFor,
+        // data: FunctionData, children: returnType, parameters, statement?
+        kFunction,
+        // data: name(StringFragment)
+        kIdentifier,
+        // children: base, index?
+        kIndex,
+        // data: isStatic(bool), children: test, ifTrue, ifFalse?
+        kIf,
+        // value(data): int
+        kInt,
+        // data: InterfaceBlockData, children: declaration1, declaration2, ..., size1, size2, ...
+        kInterfaceBlock,
+        // data: Modifiers
+        kModifiers,
+        kNull,
+        // data: ParameterData, children: type, arraySize1, arraySize2, ..., value?
+        kParameter,
+        // data: operator(Token), children: operand
+        kPostfix,
+        // data: operator(Token), children: operand
+        kPrefix,
+        // children: value
+        kReturn,
+        // ...
+        kSection,
+        // children: value, statement 1, statement 2...
+        kSwitchCase,
+        // children: value, case 1, case 2...
+        kSwitch,
+        // children: test, ifTrue, ifFalse
+        kTernary,
+        // data: TypeData, children: sizes
+        kType,
+        // data: VarData, children: arraySize1, arraySize2, ..., value?
+        kVarDeclaration,
+        // children: modifiers, type, varDeclaration1, varDeclaration2, ...
+        kVarDeclarations,
+        // children: test, statement
+        kWhile,
+    };
+
+    class iterator {
+    public:
+        iterator operator++() {
+            SkASSERT(fID);
+            fID = (**this).fNext;
+            return *this;
+        }
+
+        iterator operator++(int) {
+            SkASSERT(fID);
+            iterator old = *this;
+            fID = (**this).fNext;
+            return old;
+        }
+
+        iterator operator+=(int count) {
+            SkASSERT(count >= 0);
+            for (; count > 0; --count) {
+                ++(*this);
+            }
+            return *this;
+        }
+
+        iterator operator+(int count) {
+            iterator result(*this);
+            return result += count;
+        }
+
+        bool operator==(const iterator& other) const {
+            return fID == other.fID;
+        }
+
+        bool operator!=(const iterator& other) const {
+            return fID != other.fID;
+        }
+
+        ASTNode& operator*() {
+            SkASSERT(fID);
+            return (*fNodes)[fID.fValue];
+        }
+
+        ASTNode* operator->() {
+            SkASSERT(fID);
+            return &(*fNodes)[fID.fValue];
+        }
+
+    private:
+        iterator(std::vector<ASTNode>* nodes, ID id)
+            : fNodes(nodes)
+            , fID(id) {}
+
+        std::vector<ASTNode>* fNodes;
+
+        ID fID;
+
+        friend struct ASTNode;
+    };
+
+    struct TypeData {
+        TypeData() {}
+
+        TypeData(StringFragment name, bool isStructDeclaration, bool isNullable)
+            : fName(name)
+            , fIsStructDeclaration(isStructDeclaration)
+            , fIsNullable(isNullable) {}
+
+        StringFragment fName;
+        bool fIsStructDeclaration;
+        bool fIsNullable;
+    };
+
+    struct ParameterData {
+        ParameterData() {}
+
+        ParameterData(Modifiers modifiers, StringFragment name, size_t sizeCount)
+            : fModifiers(modifiers)
+            , fName(name)
+            , fSizeCount(sizeCount) {}
+
+        Modifiers fModifiers;
+        StringFragment fName;
+        size_t fSizeCount;
+    };
+
+    struct VarData {
+        VarData() {}
+
+        VarData(StringFragment name, size_t sizeCount)
+            : fName(name)
+            , fSizeCount(sizeCount) {}
+
+        StringFragment fName;
+        size_t fSizeCount;
+    };
+
+    struct FunctionData {
+        FunctionData() {}
+
+        FunctionData(Modifiers modifiers, StringFragment name, size_t parameterCount)
+            : fModifiers(modifiers)
+            , fName(name)
+            , fParameterCount(parameterCount) {}
+
+        Modifiers fModifiers;
+        StringFragment fName;
+        size_t fParameterCount;
+    };
+
+    struct InterfaceBlockData {
+        InterfaceBlockData() {}
+
+        InterfaceBlockData(Modifiers modifiers, StringFragment typeName, size_t declarationCount,
+                           StringFragment instanceName, size_t sizeCount)
+            : fModifiers(modifiers)
+            , fTypeName(typeName)
+            , fDeclarationCount(declarationCount)
+            , fInstanceName(instanceName)
+            , fSizeCount(sizeCount) {}
+
+        Modifiers fModifiers;
+        StringFragment fTypeName;
+        size_t fDeclarationCount;
+        StringFragment fInstanceName;
+        size_t fSizeCount;
+    };
+
+    struct SectionData {
+        SectionData() {}
+
+        SectionData(StringFragment name, StringFragment argument, StringFragment text)
+            : fName(name)
+            , fArgument(argument)
+            , fText(text) {}
+
+        StringFragment fName;
+        StringFragment fArgument;
+        StringFragment fText;
+    };
+
+    struct NodeData {
+        char fBytes[Max(sizeof(Token),
+                    Max(sizeof(StringFragment),
+                    Max(sizeof(bool),
+                    Max(sizeof(SKSL_INT),
+                    Max(sizeof(SKSL_FLOAT),
+                    Max(sizeof(Modifiers),
+                    Max(sizeof(TypeData),
+                    Max(sizeof(FunctionData),
+                    Max(sizeof(ParameterData),
+                    Max(sizeof(VarData),
+                    Max(sizeof(InterfaceBlockData),
+                        sizeof(SectionData))))))))))))];
+
+        enum class Kind {
+            kToken,
+            kStringFragment,
+            kBool,
+            kInt,
+            kFloat,
+            kModifiers,
+            kTypeData,
+            kFunctionData,
+            kParameterData,
+            kVarData,
+            kInterfaceBlockData,
+            kSectionData
+        } fKind;
+
+        NodeData() = default;
+
+        NodeData(Token data)
+            : fKind(Kind::kToken) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(StringFragment data)
+            : fKind(Kind::kStringFragment) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(bool data)
+            : fKind(Kind::kBool) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(SKSL_INT data)
+            : fKind(Kind::kInt) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(SKSL_FLOAT data)
+            : fKind(Kind::kFloat) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(Modifiers data)
+            : fKind(Kind::kModifiers) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(TypeData data)
+            : fKind(Kind::kTypeData) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(FunctionData data)
+            : fKind(Kind::kFunctionData) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(VarData data)
+            : fKind(Kind::kVarData) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(ParameterData data)
+            : fKind(Kind::kParameterData) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(InterfaceBlockData data)
+            : fKind(Kind::kInterfaceBlockData) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+
+        NodeData(SectionData data)
+            : fKind(Kind::kSectionData) {
+            memcpy(fBytes, &data, sizeof(data));
+        }
+    };
+
+    ASTNode()
+        : fOffset(-1)
+        , fKind(Kind::kNull) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind)
+        : fNodes(nodes)
+        , fOffset(offset)
+            , fKind(kind) {
+        switch (kind) {
+            case Kind::kBinary:
+            case Kind::kPostfix:
+            case Kind::kPrefix:
+                fData.fKind = NodeData::Kind::kToken;
+                break;
+
+            case Kind::kBool:
+            case Kind::kIf:
+            case Kind::kSwitch:
+                fData.fKind = NodeData::Kind::kBool;
+                break;
+
+            case Kind::kEnum:
+            case Kind::kEnumCase:
+            case Kind::kExtension:
+            case Kind::kField:
+            case Kind::kIdentifier:
+                fData.fKind = NodeData::Kind::kStringFragment;
+                break;
+
+            case Kind::kFloat:
+                fData.fKind = NodeData::Kind::kFloat;
+                break;
+
+            case Kind::kFunction:
+                fData.fKind = NodeData::Kind::kFunctionData;
+                break;
+
+            case Kind::kInt:
+                fData.fKind = NodeData::Kind::kInt;
+                break;
+
+            case Kind::kInterfaceBlock:
+                fData.fKind = NodeData::Kind::kInterfaceBlockData;
+                break;
+
+            case Kind::kModifiers:
+                fData.fKind = NodeData::Kind::kModifiers;
+                break;
+
+            case Kind::kParameter:
+                fData.fKind = NodeData::Kind::kParameterData;
+                break;
+
+            case Kind::kVarDeclaration:
+                fData.fKind = NodeData::Kind::kVarData;
+                break;
+
+            case Kind::kType:
+                fData.fKind = NodeData::Kind::kTypeData;
+                break;
+
+            default:
+                break;
+        }
+    }
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, Token t)
+        : fNodes(nodes)
+        , fData(t)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, StringFragment s)
+        : fNodes(nodes)
+        , fData(s)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, const char* s)
+        : fNodes(nodes)
+        , fData(StringFragment(s))
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, bool b)
+        : fNodes(nodes)
+        , fData(b)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, SKSL_INT i)
+        : fNodes(nodes)
+        , fData(i)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, SKSL_FLOAT f)
+        : fNodes(nodes)
+        , fData(f)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, Modifiers m)
+        : fNodes(nodes)
+        , fData(m)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, TypeData td)
+        : fNodes(nodes)
+        , fData(td)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, SectionData s)
+        : fNodes(nodes)
+        , fData(s)
+        , fOffset(offset)
+        , fKind(kind) {}
+
+    operator bool() const {
+        return fKind != Kind::kNull;
+    }
+
+    Token getToken() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kToken);
+        Token result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    bool getBool() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kBool);
+        bool result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    SKSL_INT getInt() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kInt);
+        SKSL_INT result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    SKSL_FLOAT getFloat() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kFloat);
+        SKSL_FLOAT result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    StringFragment getString() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kStringFragment);
+        StringFragment result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    Modifiers getModifiers() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kModifiers);
+        Modifiers result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void setModifiers(const Modifiers& m) {
+        memcpy(fData.fBytes, &m, sizeof(m));
+    }
+
+    TypeData getTypeData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kTypeData);
+        TypeData result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void setTypeData(const ASTNode::TypeData& td) {
+        SkASSERT(fData.fKind == NodeData::Kind::kTypeData);
+        memcpy(fData.fBytes, &td, sizeof(td));
+    }
+
+    ParameterData getParameterData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kParameterData);
+        ParameterData result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void setParameterData(const ASTNode::ParameterData& pd) {
+        SkASSERT(fData.fKind == NodeData::Kind::kParameterData);
+        memcpy(fData.fBytes, &pd, sizeof(pd));
+    }
+
+    VarData getVarData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kVarData);
+        VarData result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void setVarData(const ASTNode::VarData& vd) {
+        SkASSERT(fData.fKind == NodeData::Kind::kVarData);
+        memcpy(fData.fBytes, &vd, sizeof(vd));
+    }
+
+    FunctionData getFunctionData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kFunctionData);
+        FunctionData result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void setFunctionData(const ASTNode::FunctionData& fd) {
+        SkASSERT(fData.fKind == NodeData::Kind::kFunctionData);
+        memcpy(fData.fBytes, &fd, sizeof(fd));
+    }
+
+    InterfaceBlockData getInterfaceBlockData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kInterfaceBlockData);
+        InterfaceBlockData result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void setInterfaceBlockData(const ASTNode::InterfaceBlockData& id) {
+        SkASSERT(fData.fKind == NodeData::Kind::kInterfaceBlockData);
+        memcpy(fData.fBytes, &id, sizeof(id));
+    }
+
+    SectionData getSectionData() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kSectionData);
+        SectionData result;
+        memcpy(&result, fData.fBytes, sizeof(result));
+        return result;
+    }
+
+    void addChild(ID id) {
+        SkASSERT(!(*fNodes)[id.fValue].fNext);
+        if (fLastChild) {
+            SkASSERT(!(*fNodes)[fLastChild.fValue].fNext);
+            (*fNodes)[fLastChild.fValue].fNext = id;
+        } else {
+            fFirstChild = id;
+        }
+        fLastChild = id;
+        SkASSERT(!(*fNodes)[fLastChild.fValue].fNext);
+    }
+
+    iterator begin() const {
+        return iterator(fNodes, fFirstChild);
+    }
+
+    iterator end() const {
+        return iterator(fNodes, ID(-1));
+    }
+
+    String description() const;
+
+    std::vector<ASTNode>* fNodes;
+
+    NodeData fData;
+
+    int fOffset;
+
+    Kind fKind;
+
+    ID fFirstChild;
+
+    ID fLastChild;
+
+    ID fNext;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 350fcaf..e07769d 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -315,7 +315,7 @@
                 String var = String::printf("args.fUniformHandler->getUniformCStr(%sVar)",
                                             HCodeGenerator::FieldName(name.c_str()).c_str());
                 String code;
-                if (ref.fVariable.fModifiers.fLayout.fWhen.size()) {
+                if (ref.fVariable.fModifiers.fLayout.fWhen.fLength) {
                     code = String::printf("%sVar.isValid() ? %s : \"%s\"",
                                           HCodeGenerator::FieldName(name.c_str()).c_str(),
                                           var.c_str(),
@@ -561,14 +561,14 @@
         ABORT("unsupported uniform type: %s %s;\n", String(var.fType.fName).c_str(),
               String(var.fName).c_str());
     }
-    if (var.fModifiers.fLayout.fWhen.size()) {
-        this->writef("        if (%s) {\n    ", var.fModifiers.fLayout.fWhen.c_str());
+    if (var.fModifiers.fLayout.fWhen.fLength) {
+        this->writef("        if (%s) {\n    ", String(var.fModifiers.fLayout.fWhen).c_str());
     }
     String name(var.fName);
     this->writef("        %sVar = args.fUniformHandler->addUniform(kFragment_GrShaderFlag, %s, "
                  "\"%s\");\n", HCodeGenerator::FieldName(name.c_str()).c_str(), type,
                  name.c_str());
-    if (var.fModifiers.fLayout.fWhen.size()) {
+    if (var.fModifiers.fLayout.fWhen.fLength) {
         this->write("        }\n");
     }
 }
@@ -1111,8 +1111,8 @@
         }
         switch (param->fModifiers.fLayout.fKey) {
             case Layout::kKey_Key:
-                if (param->fModifiers.fLayout.fWhen.size()) {
-                    this->writef("if (%s) {", param->fModifiers.fLayout.fWhen.c_str());
+                if (param->fModifiers.fLayout.fWhen.fLength) {
+                    this->writef("if (%s) {", String(param->fModifiers.fLayout.fWhen).c_str());
                 }
                 if (param->fType == *fContext.fFloat4x4_Type) {
                     ABORT("no automatic key handling for float4x4\n");
@@ -1145,7 +1145,7 @@
                     this->writef("    b->add32((int32_t) %s);\n",
                                  HCodeGenerator::FieldName(name).c_str());
                 }
-                if (param->fModifiers.fLayout.fWhen.size()) {
+                if (param->fModifiers.fLayout.fWhen.fLength) {
                     this->write("}");
                 }
                 break;
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 802e5b3..52d943e 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -12,12 +12,6 @@
 
 #include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLParser.h"
-#include "src/sksl/ast/SkSLASTBoolLiteral.h"
-#include "src/sksl/ast/SkSLASTFieldSuffix.h"
-#include "src/sksl/ast/SkSLASTFloatLiteral.h"
-#include "src/sksl/ast/SkSLASTIndexSuffix.h"
-#include "src/sksl/ast/SkSLASTIntLiteral.h"
-#include "src/sksl/ast/SkSLASTNullLiteral.h"
 #include "src/sksl/ir/SkSLAppendStage.h"
 #include "src/sksl/ir/SkSLBinaryExpression.h"
 #include "src/sksl/ir/SkSLBoolLiteral.h"
@@ -179,22 +173,42 @@
     }
 }
 
-std::unique_ptr<Extension> IRGenerator::convertExtension(const ASTExtension& extension) {
-    return std::unique_ptr<Extension>(new Extension(extension.fOffset, extension.fName));
+std::unique_ptr<Extension> IRGenerator::convertExtension(int offset, StringFragment name) {
+    return std::unique_ptr<Extension>(new Extension(offset, name));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertStatement(const ASTStatement& statement) {
+void IRGenerator::finish() {
+    this->popSymbolTable();
+    fSettings = nullptr;
+}
+
+std::unique_ptr<Statement> IRGenerator::convertStatement(const ASTNode& statement) {
     switch (statement.fKind) {
-        case ASTStatement::kBlock_Kind:
-            return this->convertBlock((ASTBlock&) statement);
-        case ASTStatement::kVarDeclaration_Kind:
-            return this->convertVarDeclarationStatement((ASTVarDeclarationStatement&) statement);
-        case ASTStatement::kExpression_Kind: {
-            std::unique_ptr<Statement> result =
-                              this->convertExpressionStatement((ASTExpressionStatement&) statement);
-            if (!result) {
-                return nullptr;
-            }
+        case ASTNode::Kind::kBlock:
+            return this->convertBlock(statement);
+        case ASTNode::Kind::kVarDeclarations:
+            return this->convertVarDeclarationStatement(statement);
+        case ASTNode::Kind::kIf:
+            return this->convertIf(statement);
+        case ASTNode::Kind::kFor:
+            return this->convertFor(statement);
+        case ASTNode::Kind::kWhile:
+            return this->convertWhile(statement);
+        case ASTNode::Kind::kDo:
+            return this->convertDo(statement);
+        case ASTNode::Kind::kSwitch:
+            return this->convertSwitch(statement);
+        case ASTNode::Kind::kReturn:
+            return this->convertReturn(statement);
+        case ASTNode::Kind::kBreak:
+            return this->convertBreak(statement);
+        case ASTNode::Kind::kContinue:
+            return this->convertContinue(statement);
+        case ASTNode::Kind::kDiscard:
+            return this->convertDiscard(statement);
+        default:
+            // it's an expression
+            std::unique_ptr<Statement> result = this->convertExpressionStatement(statement);
             if (fRTAdjust && Program::kGeometry_Kind == fKind) {
                 SkASSERT(result->fKind == Statement::kExpression_Kind);
                 Expression& expr = *((ExpressionStatement&) *result).fExpression;
@@ -211,35 +225,15 @@
                 }
             }
             return result;
-        }
-        case ASTStatement::kIf_Kind:
-            return this->convertIf((ASTIfStatement&) statement);
-        case ASTStatement::kFor_Kind:
-            return this->convertFor((ASTForStatement&) statement);
-        case ASTStatement::kWhile_Kind:
-            return this->convertWhile((ASTWhileStatement&) statement);
-        case ASTStatement::kDo_Kind:
-            return this->convertDo((ASTDoStatement&) statement);
-        case ASTStatement::kSwitch_Kind:
-            return this->convertSwitch((ASTSwitchStatement&) statement);
-        case ASTStatement::kReturn_Kind:
-            return this->convertReturn((ASTReturnStatement&) statement);
-        case ASTStatement::kBreak_Kind:
-            return this->convertBreak((ASTBreakStatement&) statement);
-        case ASTStatement::kContinue_Kind:
-            return this->convertContinue((ASTContinueStatement&) statement);
-        case ASTStatement::kDiscard_Kind:
-            return this->convertDiscard((ASTDiscardStatement&) statement);
-        default:
-            ABORT("unsupported statement type: %d\n", statement.fKind);
     }
 }
 
-std::unique_ptr<Block> IRGenerator::convertBlock(const ASTBlock& block) {
+std::unique_ptr<Block> IRGenerator::convertBlock(const ASTNode& block) {
+    SkASSERT(block.fKind == ASTNode::Kind::kBlock);
     AutoSymbolTable table(this);
     std::vector<std::unique_ptr<Statement>> statements;
-    for (size_t i = 0; i < block.fStatements.size(); i++) {
-        std::unique_ptr<Statement> statement = this->convertStatement(*block.fStatements[i]);
+    for (const auto& child : block) {
+        std::unique_ptr<Statement> statement = this->convertStatement(child);
         if (!statement) {
             return nullptr;
         }
@@ -248,51 +242,59 @@
     return std::unique_ptr<Block>(new Block(block.fOffset, std::move(statements), fSymbolTable));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertVarDeclarationStatement(
-                                                              const ASTVarDeclarationStatement& s) {
-    auto decl = this->convertVarDeclarations(*s.fDeclarations, Variable::kLocal_Storage);
+std::unique_ptr<Statement> IRGenerator::convertVarDeclarationStatement(const ASTNode& s) {
+    SkASSERT(s.fKind == ASTNode::Kind::kVarDeclarations);
+    auto decl = this->convertVarDeclarations(s, Variable::kLocal_Storage);
     if (!decl) {
         return nullptr;
     }
     return std::unique_ptr<Statement>(new VarDeclarationsStatement(std::move(decl)));
 }
 
-std::unique_ptr<VarDeclarations> IRGenerator::convertVarDeclarations(const ASTVarDeclarations& decl,
+std::unique_ptr<VarDeclarations> IRGenerator::convertVarDeclarations(const ASTNode& decls,
                                                                      Variable::Storage storage) {
+    SkASSERT(decls.fKind == ASTNode::Kind::kVarDeclarations);
+    auto iter = decls.begin();
+    const Modifiers& modifiers = iter++->getModifiers();
+    const ASTNode& rawType = *(iter++);
     std::vector<std::unique_ptr<VarDeclaration>> variables;
-    const Type* baseType = this->convertType(*decl.fType);
+    const Type* baseType = this->convertType(rawType);
     if (!baseType) {
         return nullptr;
     }
     if (fKind != Program::kFragmentProcessor_Kind &&
-        (decl.fModifiers.fFlags & Modifiers::kIn_Flag) &&
+        (modifiers.fFlags & Modifiers::kIn_Flag) &&
         baseType->kind() == Type::Kind::kMatrix_Kind) {
-        fErrors.error(decl.fOffset, "'in' variables may not have matrix type");
+        fErrors.error(decls.fOffset, "'in' variables may not have matrix type");
     }
-    if (decl.fModifiers.fLayout.fWhen.length() && fKind != Program::kFragmentProcessor_Kind &&
+    if (modifiers.fLayout.fWhen.fLength && fKind != Program::kFragmentProcessor_Kind &&
         fKind != Program::kPipelineStage_Kind) {
-        fErrors.error(decl.fOffset, "'when' is only permitted within fragment processors");
+        fErrors.error(decls.fOffset, "'when' is only permitted within fragment processors");
     }
-    if (decl.fModifiers.fLayout.fKey) {
+    if (modifiers.fLayout.fKey) {
         if (fKind != Program::kFragmentProcessor_Kind && fKind != Program::kPipelineStage_Kind) {
-            fErrors.error(decl.fOffset, "'key' is only permitted within fragment processors");
+            fErrors.error(decls.fOffset, "'key' is only permitted within fragment processors");
         }
-        if ((decl.fModifiers.fFlags & Modifiers::kUniform_Flag) != 0) {
-            fErrors.error(decl.fOffset, "'key' is not permitted on 'uniform' variables");
+        if ((modifiers.fFlags & Modifiers::kUniform_Flag) != 0) {
+            fErrors.error(decls.fOffset, "'key' is not permitted on 'uniform' variables");
         }
     }
-    for (const auto& varDecl : decl.fVars) {
-        if (decl.fModifiers.fLayout.fLocation == 0 && decl.fModifiers.fLayout.fIndex == 0 &&
-            (decl.fModifiers.fFlags & Modifiers::kOut_Flag) && fKind == Program::kFragment_Kind &&
-            varDecl.fName != "sk_FragColor") {
-            fErrors.error(decl.fOffset,
+    for (; iter != decls.end(); ++iter) {
+        const ASTNode& varDecl = *iter;
+        if (modifiers.fLayout.fLocation == 0 && modifiers.fLayout.fIndex == 0 &&
+            (modifiers.fFlags & Modifiers::kOut_Flag) && fKind == Program::kFragment_Kind &&
+            varDecl.getVarData().fName != "sk_FragColor") {
+            fErrors.error(varDecl.fOffset,
                           "out location=0, index=0 is reserved for sk_FragColor");
         }
+        const ASTNode::VarData& varData = varDecl.getVarData();
         const Type* type = baseType;
         std::vector<std::unique_ptr<Expression>> sizes;
-        for (const auto& rawSize : varDecl.fSizes) {
+        auto iter = varDecl.begin();
+        for (size_t i = 0; i < varData.fSizeCount; ++i, ++iter) {
+            const ASTNode& rawSize = *iter;
             if (rawSize) {
-                auto size = this->coerce(this->convertExpression(*rawSize), *fContext.fInt_Type);
+                auto size = this->coerce(this->convertExpression(rawSize), *fContext.fInt_Type);
                 if (!size) {
                     return nullptr;
                 }
@@ -323,16 +325,16 @@
                 sizes.push_back(nullptr);
             }
         }
-        auto var = std::unique_ptr<Variable>(new Variable(decl.fOffset, decl.fModifiers,
-                                                          varDecl.fName, *type, storage));
+        auto var = std::unique_ptr<Variable>(new Variable(varDecl.fOffset, modifiers,
+                                                          varData.fName, *type, storage));
         if (var->fName == Compiler::RTADJUST_NAME) {
             SkASSERT(!fRTAdjust);
             SkASSERT(var->fType == *fContext.fFloat4_Type);
             fRTAdjust = var.get();
         }
         std::unique_ptr<Expression> value;
-        if (varDecl.fValue) {
-            value = this->convertExpression(*varDecl.fValue);
+        if (iter != varDecl.end()) {
+            value = this->convertExpression(*iter);
             if (!value) {
                 return nullptr;
             }
@@ -343,29 +345,30 @@
             var->fWriteCount = 1;
             var->fInitialValue = value.get();
         }
-        if (storage == Variable::kGlobal_Storage && varDecl.fName == "sk_FragColor" &&
-            (*fSymbolTable)[varDecl.fName]) {
+        if (storage == Variable::kGlobal_Storage && var->fName == "sk_FragColor" &&
+            (*fSymbolTable)[var->fName]) {
             // already defined, ignore
-        } else if (storage == Variable::kGlobal_Storage && (*fSymbolTable)[varDecl.fName] &&
-                   (*fSymbolTable)[varDecl.fName]->fKind == Symbol::kVariable_Kind &&
-                   ((Variable*) (*fSymbolTable)[varDecl.fName])->fModifiers.fLayout.fBuiltin >= 0) {
+        } else if (storage == Variable::kGlobal_Storage && (*fSymbolTable)[var->fName] &&
+                   (*fSymbolTable)[var->fName]->fKind == Symbol::kVariable_Kind &&
+                   ((Variable*) (*fSymbolTable)[var->fName])->fModifiers.fLayout.fBuiltin >= 0) {
             // already defined, just update the modifiers
-            Variable* old = (Variable*) (*fSymbolTable)[varDecl.fName];
+            Variable* old = (Variable*) (*fSymbolTable)[var->fName];
             old->fModifiers = var->fModifiers;
         } else {
             variables.emplace_back(new VarDeclaration(var.get(), std::move(sizes),
                                                       std::move(value)));
-            fSymbolTable->add(varDecl.fName, std::move(var));
+            StringFragment name = var->fName;
+            fSymbolTable->add(name, std::move(var));
         }
     }
-    return std::unique_ptr<VarDeclarations>(new VarDeclarations(decl.fOffset,
+    return std::unique_ptr<VarDeclarations>(new VarDeclarations(decls.fOffset,
                                                                 baseType,
                                                                 std::move(variables)));
 }
 
-std::unique_ptr<ModifiersDeclaration> IRGenerator::convertModifiersDeclaration(
-                                                                 const ASTModifiersDeclaration& m) {
-    Modifiers modifiers = m.fModifiers;
+std::unique_ptr<ModifiersDeclaration> IRGenerator::convertModifiersDeclaration(const ASTNode& m) {
+    SkASSERT(m.fKind == ASTNode::Kind::kModifiers);
+    Modifiers modifiers = m.getModifiers();
     if (modifiers.fLayout.fInvocations != -1) {
         if (fKind != Program::kGeometry_Kind) {
             fErrors.error(m.fOffset, "'invocations' is only legal in geometry shaders");
@@ -390,19 +393,21 @@
     return std::unique_ptr<ModifiersDeclaration>(new ModifiersDeclaration(modifiers));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertIf(const ASTIfStatement& s) {
-    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*s.fTest),
+std::unique_ptr<Statement> IRGenerator::convertIf(const ASTNode& n) {
+    SkASSERT(n.fKind == ASTNode::Kind::kIf);
+    auto iter = n.begin();
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*(iter++)),
                                                     *fContext.fBool_Type);
     if (!test) {
         return nullptr;
     }
-    std::unique_ptr<Statement> ifTrue = this->convertStatement(*s.fIfTrue);
+    std::unique_ptr<Statement> ifTrue = this->convertStatement(*(iter++));
     if (!ifTrue) {
         return nullptr;
     }
     std::unique_ptr<Statement> ifFalse;
-    if (s.fIfFalse) {
-        ifFalse = this->convertStatement(*s.fIfFalse);
+    if (iter != n.end()) {
+        ifFalse = this->convertStatement(*(iter++));
         if (!ifFalse) {
             return nullptr;
         }
@@ -411,45 +416,50 @@
         // static boolean value, fold down to a single branch
         if (((BoolLiteral&) *test).fValue) {
             return ifTrue;
-        } else if (s.fIfFalse) {
+        } else if (ifFalse) {
             return ifFalse;
         } else {
             // False & no else clause. Not an error, so don't return null!
             std::vector<std::unique_ptr<Statement>> empty;
-            return std::unique_ptr<Statement>(new Block(s.fOffset, std::move(empty),
+            return std::unique_ptr<Statement>(new Block(n.fOffset, std::move(empty),
                                                         fSymbolTable));
         }
     }
-    return std::unique_ptr<Statement>(new IfStatement(s.fOffset, s.fIsStatic, std::move(test),
+    return std::unique_ptr<Statement>(new IfStatement(n.fOffset, n.getBool(), std::move(test),
                                                       std::move(ifTrue), std::move(ifFalse)));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertFor(const ASTForStatement& f) {
+std::unique_ptr<Statement> IRGenerator::convertFor(const ASTNode& f) {
+    SkASSERT(f.fKind == ASTNode::Kind::kFor);
     AutoLoopLevel level(this);
     AutoSymbolTable table(this);
     std::unique_ptr<Statement> initializer;
-    if (f.fInitializer) {
-        initializer = this->convertStatement(*f.fInitializer);
+    auto iter = f.begin();
+    if (*iter) {
+        initializer = this->convertStatement(*iter);
         if (!initializer) {
             return nullptr;
         }
     }
+    ++iter;
     std::unique_ptr<Expression> test;
-    if (f.fTest) {
-        test = this->coerce(this->convertExpression(*f.fTest), *fContext.fBool_Type);
+    if (*iter) {
+        test = this->coerce(this->convertExpression(*iter), *fContext.fBool_Type);
         if (!test) {
             return nullptr;
         }
     }
+    ++iter;
     std::unique_ptr<Expression> next;
-    if (f.fNext) {
-        next = this->convertExpression(*f.fNext);
+    if (*iter) {
+        next = this->convertExpression(*iter);
         if (!next) {
             return nullptr;
         }
         this->checkValid(*next);
     }
-    std::unique_ptr<Statement> statement = this->convertStatement(*f.fStatement);
+    ++iter;
+    std::unique_ptr<Statement> statement = this->convertStatement(*iter);
     if (!statement) {
         return nullptr;
     }
@@ -458,14 +468,16 @@
                                                        std::move(statement), fSymbolTable));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertWhile(const ASTWhileStatement& w) {
+std::unique_ptr<Statement> IRGenerator::convertWhile(const ASTNode& w) {
+    SkASSERT(w.fKind == ASTNode::Kind::kWhile);
     AutoLoopLevel level(this);
-    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*w.fTest),
+    auto iter = w.begin();
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*(iter++)),
                                                     *fContext.fBool_Type);
     if (!test) {
         return nullptr;
     }
-    std::unique_ptr<Statement> statement = this->convertStatement(*w.fStatement);
+    std::unique_ptr<Statement> statement = this->convertStatement(*(iter++));
     if (!statement) {
         return nullptr;
     }
@@ -473,24 +485,28 @@
                                                          std::move(statement)));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertDo(const ASTDoStatement& d) {
+std::unique_ptr<Statement> IRGenerator::convertDo(const ASTNode& d) {
+    SkASSERT(d.fKind == ASTNode::Kind::kDo);
     AutoLoopLevel level(this);
-    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*d.fTest),
-                                                    *fContext.fBool_Type);
-    if (!test) {
+    auto iter = d.begin();
+    std::unique_ptr<Statement> statement = this->convertStatement(*(iter++));
+    if (!statement) {
         return nullptr;
     }
-    std::unique_ptr<Statement> statement = this->convertStatement(*d.fStatement);
-    if (!statement) {
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*(iter++)),
+                                                    *fContext.fBool_Type);
+    if (!test) {
         return nullptr;
     }
     return std::unique_ptr<Statement>(new DoStatement(d.fOffset, std::move(statement),
                                                       std::move(test)));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertSwitch(const ASTSwitchStatement& s) {
+std::unique_ptr<Statement> IRGenerator::convertSwitch(const ASTNode& s) {
+    SkASSERT(s.fKind == ASTNode::Kind::kSwitch);
     AutoSwitchLevel level(this);
-    std::unique_ptr<Expression> value = this->convertExpression(*s.fValue);
+    auto iter = s.begin();
+    std::unique_ptr<Expression> value = this->convertExpression(*(iter++));
     if (!value) {
         return nullptr;
     }
@@ -503,10 +519,13 @@
     AutoSymbolTable table(this);
     std::unordered_set<int> caseValues;
     std::vector<std::unique_ptr<SwitchCase>> cases;
-    for (const auto& c : s.fCases) {
+    for (; iter != s.end(); ++iter) {
+        const ASTNode& c = *iter;
+        SkASSERT(c.fKind == ASTNode::Kind::kSwitchCase);
         std::unique_ptr<Expression> caseValue;
-        if (c->fValue) {
-            caseValue = this->convertExpression(*c->fValue);
+        auto childIter = c.begin();
+        if (*childIter) {
+            caseValue = this->convertExpression(*childIter);
             if (!caseValue) {
                 return nullptr;
             }
@@ -525,25 +544,25 @@
             }
             caseValues.insert(v);
         }
+        ++childIter;
         std::vector<std::unique_ptr<Statement>> statements;
-        for (const auto& s : c->fStatements) {
-            std::unique_ptr<Statement> converted = this->convertStatement(*s);
+        for (; childIter != c.end(); ++childIter) {
+            std::unique_ptr<Statement> converted = this->convertStatement(*childIter);
             if (!converted) {
                 return nullptr;
             }
             statements.push_back(std::move(converted));
         }
-        cases.emplace_back(new SwitchCase(c->fOffset, std::move(caseValue),
+        cases.emplace_back(new SwitchCase(c.fOffset, std::move(caseValue),
                                           std::move(statements)));
     }
-    return std::unique_ptr<Statement>(new SwitchStatement(s.fOffset, s.fIsStatic,
+    return std::unique_ptr<Statement>(new SwitchStatement(s.fOffset, s.getBool(),
                                                           std::move(value), std::move(cases),
                                                           fSymbolTable));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertExpressionStatement(
-                                                                  const ASTExpressionStatement& s) {
-    std::unique_ptr<Expression> e = this->convertExpression(*s.fExpression);
+std::unique_ptr<Statement> IRGenerator::convertExpressionStatement(const ASTNode& s) {
+    std::unique_ptr<Expression> e = this->convertExpression(s);
     if (!e) {
         return nullptr;
     }
@@ -551,14 +570,15 @@
     return std::unique_ptr<Statement>(new ExpressionStatement(std::move(e)));
 }
 
-std::unique_ptr<Statement> IRGenerator::convertReturn(const ASTReturnStatement& r) {
+std::unique_ptr<Statement> IRGenerator::convertReturn(const ASTNode& r) {
+    SkASSERT(r.fKind == ASTNode::Kind::kReturn);
     SkASSERT(fCurrentFunction);
     // early returns from a vertex main function will bypass the sk_Position normalization, so
     // SkASSERT that we aren't doing that. It is of course possible to fix this by adding a
     // normalization before each return, but it will probably never actually be necessary.
     SkASSERT(Program::kVertex_Kind != fKind || !fRTAdjust || "main" != fCurrentFunction->fName);
-    if (r.fExpression) {
-        std::unique_ptr<Expression> result = this->convertExpression(*r.fExpression);
+    if (r.begin() != r.end()) {
+        std::unique_ptr<Expression> result = this->convertExpression(*r.begin());
         if (!result) {
             return nullptr;
         }
@@ -580,7 +600,8 @@
     }
 }
 
-std::unique_ptr<Statement> IRGenerator::convertBreak(const ASTBreakStatement& b) {
+std::unique_ptr<Statement> IRGenerator::convertBreak(const ASTNode& b) {
+    SkASSERT(b.fKind == ASTNode::Kind::kBreak);
     if (fLoopLevel > 0 || fSwitchLevel > 0) {
         return std::unique_ptr<Statement>(new BreakStatement(b.fOffset));
     } else {
@@ -589,7 +610,8 @@
     }
 }
 
-std::unique_ptr<Statement> IRGenerator::convertContinue(const ASTContinueStatement& c) {
+std::unique_ptr<Statement> IRGenerator::convertContinue(const ASTNode& c) {
+    SkASSERT(c.fKind == ASTNode::Kind::kContinue);
     if (fLoopLevel > 0) {
         return std::unique_ptr<Statement>(new ContinueStatement(c.fOffset));
     } else {
@@ -598,7 +620,8 @@
     }
 }
 
-std::unique_ptr<Statement> IRGenerator::convertDiscard(const ASTDiscardStatement& d) {
+std::unique_ptr<Statement> IRGenerator::convertDiscard(const ASTNode& d) {
+    SkASSERT(d.fKind == ASTNode::Kind::kDiscard);
     return std::unique_ptr<Statement>(new DiscardStatement(d.fOffset));
 }
 
@@ -628,7 +651,7 @@
                                                             *loopIdx,
                                                             VariableReference::kReadWrite_RefKind)),
                 Token::PLUSPLUS));
-    ASTIdentifier endPrimitiveID = ASTIdentifier(-1, "EndPrimitive");
+    ASTNode endPrimitiveID(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier, "EndPrimitive");
     std::unique_ptr<Expression> endPrimitive = this->convertExpression(endPrimitiveID);
     SkASSERT(endPrimitive);
 
@@ -692,19 +715,25 @@
     return std::unique_ptr<Statement>(new ExpressionStatement(std::move(result)));
 }
 
-void IRGenerator::convertFunction(const ASTFunction& f) {
-    const Type* returnType = this->convertType(*f.fReturnType);
+void IRGenerator::convertFunction(const ASTNode& f) {
+    auto iter = f.begin();
+    const Type* returnType = this->convertType(*(iter++));
     if (!returnType) {
         return;
     }
+    const ASTNode::FunctionData& fd = f.getFunctionData();
     std::vector<const Variable*> parameters;
-    for (const auto& param : f.fParameters) {
-        const Type* type = this->convertType(*param->fType);
+    for (size_t i = 0; i < fd.fParameterCount; ++i) {
+        const ASTNode& param = *(iter++);
+        SkASSERT(param.fKind == ASTNode::Kind::kParameter);
+        ASTNode::ParameterData pd = param.getParameterData();
+        auto paramIter = param.begin();
+        const Type* type = this->convertType(*(paramIter++));
         if (!type) {
             return;
         }
-        for (int j = (int) param->fSizes.size() - 1; j >= 0; j--) {
-            int size = param->fSizes[j];
+        for (int j = (int) pd.fSizeCount; j >= 1; j--) {
+            int size = (param.begin() + j)->getInt();
             String name = type->name() + "[" + to_string(size) + "]";
             type = (Type*) fSymbolTable->takeOwnership(
                                                  std::unique_ptr<Symbol>(new Type(std::move(name),
@@ -712,17 +741,17 @@
                                                                                   *type,
                                                                                   size)));
         }
-        StringFragment name = param->fName;
+        StringFragment name = pd.fName;
         Variable* var = (Variable*) fSymbolTable->takeOwnership(
-                               std::unique_ptr<Symbol>(new Variable(param->fOffset,
-                                                                    param->fModifiers,
+                               std::unique_ptr<Symbol>(new Variable(param.fOffset,
+                                                                    pd.fModifiers,
                                                                     name,
                                                                     *type,
                                                                     Variable::kParameter_Storage)));
         parameters.push_back(var);
     }
 
-    if (f.fName == "main") {
+    if (fd.fName == "main") {
         switch (fKind) {
             case Program::kPipelineStage_Kind: {
                 bool valid;
@@ -762,7 +791,7 @@
 
     // find existing declaration
     const FunctionDeclaration* decl = nullptr;
-    auto entry = (*fSymbolTable)[f.fName];
+    auto entry = (*fSymbolTable)[fd.fName];
     if (entry) {
         std::vector<const FunctionDeclaration*> functions;
         switch (entry->fKind) {
@@ -773,11 +802,11 @@
                 functions.push_back((FunctionDeclaration*) entry);
                 break;
             default:
-                fErrors.error(f.fOffset, "symbol '" + f.fName + "' was already defined");
+                fErrors.error(f.fOffset, "symbol '" + fd.fName + "' was already defined");
                 return;
         }
         for (const auto& other : functions) {
-            SkASSERT(other->fName == f.fName);
+            SkASSERT(other->fName == fd.fName);
             if (parameters.size() == other->fParameters.size()) {
                 bool match = true;
                 for (size_t i = 0; i < parameters.size(); i++) {
@@ -788,7 +817,7 @@
                 }
                 if (match) {
                     if (*returnType != other->fReturnType) {
-                        FunctionDeclaration newDecl(f.fOffset, f.fModifiers, f.fName, parameters,
+                        FunctionDeclaration newDecl(f.fOffset, fd.fModifiers, fd.fName, parameters,
                                                     *returnType);
                         fErrors.error(f.fOffset, "functions '" + newDecl.description() +
                                                  "' and '" + other->description() +
@@ -817,20 +846,21 @@
     if (!decl) {
         // couldn't find an existing declaration
         auto newDecl = std::unique_ptr<FunctionDeclaration>(new FunctionDeclaration(f.fOffset,
-                                                                                    f.fModifiers,
-                                                                                    f.fName,
+                                                                                    fd.fModifiers,
+                                                                                    fd.fName,
                                                                                     parameters,
                                                                                     *returnType));
         decl = newDecl.get();
         fSymbolTable->add(decl->fName, std::move(newDecl));
     }
-    if (f.fBody) {
+    if (iter != f.end()) {
+        // compile body
         SkASSERT(!fCurrentFunction);
         fCurrentFunction = decl;
         decl->fDefined = true;
         std::shared_ptr<SymbolTable> old = fSymbolTable;
         AutoSymbolTable table(this);
-        if (f.fName == "main" && fKind == Program::kPipelineStage_Kind) {
+        if (fd.fName == "main" && fKind == Program::kPipelineStage_Kind) {
             if (parameters.size() == 3) {
                 parameters[0]->fModifiers.fLayout.fBuiltin = SK_MAIN_X_BUILTIN;
                 parameters[1]->fModifiers.fLayout.fBuiltin = SK_MAIN_Y_BUILTIN;
@@ -843,11 +873,11 @@
         for (size_t i = 0; i < parameters.size(); i++) {
             fSymbolTable->addWithoutOwnership(parameters[i]->fName, decl->fParameters[i]);
         }
-        bool needInvocationIDWorkaround = fInvocations != -1 && f.fName == "main" &&
+        bool needInvocationIDWorkaround = fInvocations != -1 && fd.fName == "main" &&
                                           fSettings->fCaps &&
                                           !fSettings->fCaps->gsInvocationsSupport();
         SkASSERT(!fExtraVars.size());
-        std::unique_ptr<Block> body = this->convertBlock(*f.fBody);
+        std::unique_ptr<Block> body = this->convertBlock(*iter);
         for (auto& v : fExtraVars) {
             body->fStatements.insert(body->fStatements.begin(), std::move(v));
         }
@@ -861,7 +891,7 @@
         }
         // conservatively assume all user-defined functions have side effects
         ((Modifiers&) decl->fModifiers).fFlags |= Modifiers::kHasSideEffects_Flag;
-        if (Program::kVertex_Kind == fKind && f.fName == "main" && fRTAdjust) {
+        if (Program::kVertex_Kind == fKind && fd.fName == "main" && fRTAdjust) {
             body->fStatements.insert(body->fStatements.end(), this->getNormalizeSkPositionCode());
         }
         fProgramElements->push_back(std::unique_ptr<FunctionDefinition>(
@@ -869,16 +899,19 @@
     }
 }
 
-std::unique_ptr<InterfaceBlock> IRGenerator::convertInterfaceBlock(const ASTInterfaceBlock& intf) {
+std::unique_ptr<InterfaceBlock> IRGenerator::convertInterfaceBlock(const ASTNode& intf) {
+    SkASSERT(intf.fKind == ASTNode::Kind::kInterfaceBlock);
+    ASTNode::InterfaceBlockData id = intf.getInterfaceBlockData();
     std::shared_ptr<SymbolTable> old = fSymbolTable;
     this->pushSymbolTable();
     std::shared_ptr<SymbolTable> symbols = fSymbolTable;
     std::vector<Type::Field> fields;
     bool haveRuntimeArray = false;
     bool foundRTAdjust = false;
-    for (size_t i = 0; i < intf.fDeclarations.size(); i++) {
+    auto iter = intf.begin();
+    for (size_t i = 0; i < id.fDeclarationCount; ++i) {
         std::unique_ptr<VarDeclarations> decl = this->convertVarDeclarations(
-                                                                 *intf.fDeclarations[i],
+                                                                 *(iter++),
                                                                  Variable::kInterfaceBlock_Storage);
         if (!decl) {
             return nullptr;
@@ -902,10 +935,10 @@
                               "initializers are not permitted on interface block fields");
             }
             if (vd.fVar->fModifiers.fFlags & (Modifiers::kIn_Flag |
-                                                Modifiers::kOut_Flag |
-                                                Modifiers::kUniform_Flag |
-                                                Modifiers::kBuffer_Flag |
-                                                Modifiers::kConst_Flag)) {
+                                              Modifiers::kOut_Flag |
+                                              Modifiers::kUniform_Flag |
+                                              Modifiers::kBuffer_Flag |
+                                              Modifiers::kConst_Flag)) {
                 fErrors.error(decl->fOffset,
                               "interface block fields may not have storage qualifiers");
             }
@@ -917,12 +950,13 @@
     }
     this->popSymbolTable();
     Type* type = (Type*) old->takeOwnership(std::unique_ptr<Symbol>(new Type(intf.fOffset,
-                                                                             intf.fTypeName,
+                                                                             id.fTypeName,
                                                                              fields)));
     std::vector<std::unique_ptr<Expression>> sizes;
-    for (const auto& size : intf.fSizes) {
+    for (size_t i = 0; i < id.fSizeCount; ++i) {
+        const ASTNode& size = *(iter++);
         if (size) {
-            std::unique_ptr<Expression> converted = this->convertExpression(*size);
+            std::unique_ptr<Expression> converted = this->convertExpression(size);
             if (!converted) {
                 return nullptr;
             }
@@ -955,15 +989,15 @@
     }
     Variable* var = (Variable*) old->takeOwnership(std::unique_ptr<Symbol>(
                       new Variable(intf.fOffset,
-                                   intf.fModifiers,
-                                   intf.fInstanceName.fLength ? intf.fInstanceName : intf.fTypeName,
+                                   id.fModifiers,
+                                   id.fInstanceName.fLength ? id.fInstanceName : id.fTypeName,
                                    *type,
                                    Variable::kGlobal_Storage)));
     if (foundRTAdjust) {
         fRTAdjustInterfaceBlock = var;
     }
-    if (intf.fInstanceName.fLength) {
-        old->addWithoutOwnership(intf.fInstanceName, var);
+    if (id.fInstanceName.fLength) {
+        old->addWithoutOwnership(id.fInstanceName, var);
     } else {
         for (size_t i = 0; i < fields.size(); i++) {
             old->add(fields[i].fName, std::unique_ptr<Field>(new Field(intf.fOffset, *var,
@@ -972,8 +1006,8 @@
     }
     return std::unique_ptr<InterfaceBlock>(new InterfaceBlock(intf.fOffset,
                                                               var,
-                                                              intf.fTypeName,
-                                                              intf.fInstanceName,
+                                                              id.fTypeName,
+                                                              id.fInstanceName,
                                                               std::move(sizes),
                                                               symbols));
 }
@@ -996,19 +1030,23 @@
     }
 }
 
-void IRGenerator::convertEnum(const ASTEnum& e) {
+void IRGenerator::convertEnum(const ASTNode& e) {
+    SkASSERT(e.fKind == ASTNode::Kind::kEnum);
     std::vector<Variable*> variables;
     int64_t currentValue = 0;
     Layout layout;
-    ASTType enumType(e.fOffset, e.fTypeName, ASTType::kIdentifier_Kind, {}, false);
+    ASTNode enumType(e.fNodes, e.fOffset, ASTNode::Kind::kType,
+                     ASTNode::TypeData(e.getString(), false, false));
     const Type* type = this->convertType(enumType);
     Modifiers modifiers(layout, Modifiers::kConst_Flag);
     std::shared_ptr<SymbolTable> symbols(new SymbolTable(fSymbolTable, &fErrors));
     fSymbolTable = symbols;
-    for (size_t i = 0; i < e.fNames.size(); i++) {
+    for (auto iter = e.begin(); iter != e.end(); ++iter) {
+        const ASTNode& child = *iter;
+        SkASSERT(child.fKind == ASTNode::Kind::kEnumCase);
         std::unique_ptr<Expression> value;
-        if (e.fValues[i]) {
-            value = this->convertExpression(*e.fValues[i]);
+        if (child.begin() != child.end()) {
+            value = this->convertExpression(*child.begin());
             if (!value) {
                 fSymbolTable = symbols->fParent;
                 return;
@@ -1017,25 +1055,26 @@
         }
         value = std::unique_ptr<Expression>(new IntLiteral(fContext, e.fOffset, currentValue));
         ++currentValue;
-        auto var = std::unique_ptr<Variable>(new Variable(e.fOffset, modifiers, e.fNames[i],
+        auto var = std::unique_ptr<Variable>(new Variable(e.fOffset, modifiers, child.getString(),
                                                           *type, Variable::kGlobal_Storage,
                                                           value.get()));
         variables.push_back(var.get());
-        symbols->add(e.fNames[i], std::move(var));
+        symbols->add(child.getString(), std::move(var));
         symbols->takeOwnership(std::move(value));
     }
-    fProgramElements->push_back(std::unique_ptr<ProgramElement>(new Enum(e.fOffset, e.fTypeName,
+    fProgramElements->push_back(std::unique_ptr<ProgramElement>(new Enum(e.fOffset, e.getString(),
                                                                          symbols)));
     fSymbolTable = symbols->fParent;
 }
 
-const Type* IRGenerator::convertType(const ASTType& type) {
-    const Symbol* result = (*fSymbolTable)[type.fName];
+const Type* IRGenerator::convertType(const ASTNode& type) {
+    ASTNode::TypeData td = type.getTypeData();
+    const Symbol* result = (*fSymbolTable)[td.fName];
     if (result && result->fKind == Symbol::kType_Kind) {
-        if (type.fNullable) {
+        if (td.fIsNullable) {
             if (((Type&) *result) == *fContext.fFragmentProcessor_Type) {
-                if (type.fSizes.size()) {
-                    fErrors.error(type.fOffset, "type '" + type.fName + "' may not be used in "
+                if (type.begin() != type.end()) {
+                    fErrors.error(type.fOffset, "type '" + td.fName + "' may not be used in "
                                                 "an array");
                 }
                 result = fSymbolTable->takeOwnership(std::unique_ptr<Symbol>(
@@ -1043,14 +1082,14 @@
                                                                         Type::kNullable_Kind,
                                                                         (const Type&) *result)));
             } else {
-                fErrors.error(type.fOffset, "type '" + type.fName + "' may not be nullable");
+                fErrors.error(type.fOffset, "type '" + td.fName + "' may not be nullable");
             }
         }
-        for (int size : type.fSizes) {
+        for (const auto& size : type) {
             String name(result->fName);
             name += "[";
-            if (size != -1) {
-                name += to_string(size);
+            if (size) {
+                name += to_string(size.getInt());
             }
             name += "]";
             result = (Type*) fSymbolTable->takeOwnership(std::unique_ptr<Symbol>(
@@ -1061,42 +1100,49 @@
         }
         return (const Type*) result;
     }
-    fErrors.error(type.fOffset, "unknown type '" + type.fName + "'");
+    fErrors.error(type.fOffset, "unknown type '" + td.fName + "'");
     return nullptr;
 }
 
-std::unique_ptr<Expression> IRGenerator::convertExpression(const ASTExpression& expr) {
+std::unique_ptr<Expression> IRGenerator::convertExpression(const ASTNode& expr) {
     switch (expr.fKind) {
-        case ASTExpression::kIdentifier_Kind:
-            return this->convertIdentifier((ASTIdentifier&) expr);
-        case ASTExpression::kBool_Kind:
+        case ASTNode::Kind::kBinary:
+            return this->convertBinaryExpression(expr);
+        case ASTNode::Kind::kBool:
             return std::unique_ptr<Expression>(new BoolLiteral(fContext, expr.fOffset,
-                                                               ((ASTBoolLiteral&) expr).fValue));
-        case ASTExpression::kInt_Kind:
-            return std::unique_ptr<Expression>(new IntLiteral(fContext, expr.fOffset,
-                                                              ((ASTIntLiteral&) expr).fValue));
-        case ASTExpression::kFloat_Kind:
+                                                               expr.getBool()));
+        case ASTNode::Kind::kCall:
+            return this->convertCallExpression(expr);
+        case ASTNode::Kind::kField:
+            return this->convertFieldExpression(expr);
+        case ASTNode::Kind::kFloat:
             return std::unique_ptr<Expression>(new FloatLiteral(fContext, expr.fOffset,
-                                                                ((ASTFloatLiteral&) expr).fValue));
-        case ASTExpression::kBinary_Kind:
-            return this->convertBinaryExpression((ASTBinaryExpression&) expr);
-        case ASTExpression::kNull_Kind:
+                                                                expr.getFloat()));
+        case ASTNode::Kind::kIdentifier:
+            return this->convertIdentifier(expr);
+        case ASTNode::Kind::kIndex:
+            return this->convertIndexExpression(expr);
+        case ASTNode::Kind::kInt:
+            return std::unique_ptr<Expression>(new IntLiteral(fContext, expr.fOffset,
+                                                              expr.getInt()));
+        case ASTNode::Kind::kNull:
             return std::unique_ptr<Expression>(new NullLiteral(fContext, expr.fOffset));
-        case ASTExpression::kPrefix_Kind:
-            return this->convertPrefixExpression((ASTPrefixExpression&) expr);
-        case ASTExpression::kSuffix_Kind:
-            return this->convertSuffixExpression((ASTSuffixExpression&) expr);
-        case ASTExpression::kTernary_Kind:
-            return this->convertTernaryExpression((ASTTernaryExpression&) expr);
+        case ASTNode::Kind::kPostfix:
+            return this->convertPostfixExpression(expr);
+        case ASTNode::Kind::kPrefix:
+            return this->convertPrefixExpression(expr);
+        case ASTNode::Kind::kTernary:
+            return this->convertTernaryExpression(expr);
         default:
-            ABORT("unsupported expression type: %d\n", expr.fKind);
+            ABORT("unsupported expression: %s\n", expr.description().c_str());
     }
 }
 
-std::unique_ptr<Expression> IRGenerator::convertIdentifier(const ASTIdentifier& identifier) {
-    const Symbol* result = (*fSymbolTable)[identifier.fText];
+std::unique_ptr<Expression> IRGenerator::convertIdentifier(const ASTNode& identifier) {
+    SkASSERT(identifier.fKind == ASTNode::Kind::kIdentifier);
+    const Symbol* result = (*fSymbolTable)[identifier.getString()];
     if (!result) {
-        fErrors.error(identifier.fOffset, "unknown identifier '" + identifier.fText + "'");
+        fErrors.error(identifier.fOffset, "unknown identifier '" + identifier.getString() + "'");
         return nullptr;
     }
     switch (result->fKind) {
@@ -1165,8 +1211,10 @@
     }
 }
 
-std::unique_ptr<Section> IRGenerator::convertSection(const ASTSection& s) {
-    return std::unique_ptr<Section>(new Section(s.fOffset, s.fName, s.fArgument, s.fText));
+std::unique_ptr<Section> IRGenerator::convertSection(const ASTNode& s) {
+    ASTNode::SectionData section = s.getSectionData();
+    return std::unique_ptr<Section>(new Section(s.fOffset, section.fName, section.fArgument,
+                                                section.fText));
 }
 
 
@@ -1192,11 +1240,14 @@
         args.push_back(std::move(expr));
         std::unique_ptr<Expression> ctor;
         if (type == *fContext.fFloatLiteral_Type) {
-            ctor = this->convertIdentifier(ASTIdentifier(-1, "float"));
+            ctor = this->convertIdentifier(ASTNode(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier,
+                                                   "float"));
         } else if (type == *fContext.fIntLiteral_Type) {
-            ctor = this->convertIdentifier(ASTIdentifier(-1, "int"));
+            ctor = this->convertIdentifier(ASTNode(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier,
+                                                   "int"));
         } else {
-            ctor = this->convertIdentifier(ASTIdentifier(-1, type.fName));
+            ctor = this->convertIdentifier(ASTNode(&fFile->fNodes, -1, ASTNode::Kind::kIdentifier,
+                                                   type.fName));
         }
         if (!ctor) {
             printf("error, null identifier: %s\n", String(type.fName).c_str());
@@ -1550,13 +1601,14 @@
     return nullptr;
 }
 
-std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(
-                                                            const ASTBinaryExpression& expression) {
-    std::unique_ptr<Expression> left = this->convertExpression(*expression.fLeft);
+std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(const ASTNode& expression) {
+    SkASSERT(expression.fKind == ASTNode::Kind::kBinary);
+    auto iter = expression.begin();
+    std::unique_ptr<Expression> left = this->convertExpression(*(iter++));
     if (!left) {
         return nullptr;
     }
-    std::unique_ptr<Expression> right = this->convertExpression(*expression.fRight);
+    std::unique_ptr<Expression> right = this->convertExpression(*(iter++));
     if (!right) {
         return nullptr;
     }
@@ -1575,49 +1627,48 @@
     } else {
         rawRightType = &right->fType;
     }
-    if (!determine_binary_type(fContext, expression.fOperator, *rawLeftType, *rawRightType,
-                               &leftType, &rightType, &resultType,
-                               !Compiler::IsAssignment(expression.fOperator))) {
+    Token::Kind op = expression.getToken().fKind;
+    if (!determine_binary_type(fContext, op, *rawLeftType, *rawRightType, &leftType, &rightType,
+                               &resultType, !Compiler::IsAssignment(op))) {
         fErrors.error(expression.fOffset, String("type mismatch: '") +
-                                          Compiler::OperatorName(expression.fOperator) +
+                                          Compiler::OperatorName(expression.getToken().fKind) +
                                           "' cannot operate on '" + left->fType.description() +
                                           "', '" + right->fType.description() + "'");
         return nullptr;
     }
-    if (Compiler::IsAssignment(expression.fOperator)) {
-        this->setRefKind(*left, expression.fOperator != Token::EQ ?
-                                                             VariableReference::kReadWrite_RefKind :
-                                                             VariableReference::kWrite_RefKind);
+    if (Compiler::IsAssignment(op)) {
+        this->setRefKind(*left, op != Token::EQ ? VariableReference::kReadWrite_RefKind :
+                                                  VariableReference::kWrite_RefKind);
     }
     left = this->coerce(std::move(left), *leftType);
     right = this->coerce(std::move(right), *rightType);
     if (!left || !right) {
         return nullptr;
     }
-    std::unique_ptr<Expression> result = this->constantFold(*left.get(), expression.fOperator,
-                                                            *right.get());
+    std::unique_ptr<Expression> result = this->constantFold(*left.get(), op, *right.get());
     if (!result) {
         result = std::unique_ptr<Expression>(new BinaryExpression(expression.fOffset,
                                                                   std::move(left),
-                                                                  expression.fOperator,
+                                                                  op,
                                                                   std::move(right),
                                                                   *resultType));
     }
     return result;
 }
 
-std::unique_ptr<Expression> IRGenerator::convertTernaryExpression(
-                                                           const ASTTernaryExpression& expression) {
-    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*expression.fTest),
+std::unique_ptr<Expression> IRGenerator::convertTernaryExpression(const ASTNode& node) {
+    SkASSERT(node.fKind == ASTNode::Kind::kTernary);
+    auto iter = node.begin();
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*(iter++)),
                                                     *fContext.fBool_Type);
     if (!test) {
         return nullptr;
     }
-    std::unique_ptr<Expression> ifTrue = this->convertExpression(*expression.fIfTrue);
+    std::unique_ptr<Expression> ifTrue = this->convertExpression(*(iter++));
     if (!ifTrue) {
         return nullptr;
     }
-    std::unique_ptr<Expression> ifFalse = this->convertExpression(*expression.fIfFalse);
+    std::unique_ptr<Expression> ifFalse = this->convertExpression(*(iter++));
     if (!ifFalse) {
         return nullptr;
     }
@@ -1626,9 +1677,9 @@
     const Type* resultType;
     if (!determine_binary_type(fContext, Token::EQEQ, ifTrue->fType, ifFalse->fType, &trueType,
                                &falseType, &resultType, true) || trueType != falseType) {
-        fErrors.error(expression.fOffset, "ternary operator result mismatch: '" +
-                                          ifTrue->fType.description() + "', '" +
-                                          ifFalse->fType.description() + "'");
+        fErrors.error(node.fOffset, "ternary operator result mismatch: '" +
+                                    ifTrue->fType.description() + "', '" +
+                                    ifFalse->fType.description() + "'");
         return nullptr;
     }
     ifTrue = this->coerce(std::move(ifTrue), *trueType);
@@ -1647,7 +1698,7 @@
             return ifFalse;
         }
     }
-    return std::unique_ptr<Expression>(new TernaryExpression(expression.fOffset,
+    return std::unique_ptr<Expression>(new TernaryExpression(node.fOffset,
                                                              std::move(test),
                                                              std::move(ifTrue),
                                                              std::move(ifFalse)));
@@ -1925,13 +1976,13 @@
     }
 }
 
-std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(
-                                                            const ASTPrefixExpression& expression) {
-    std::unique_ptr<Expression> base = this->convertExpression(*expression.fOperand);
+std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(const ASTNode& expression) {
+    SkASSERT(expression.fKind == ASTNode::Kind::kPrefix);
+    std::unique_ptr<Expression> base = this->convertExpression(*expression.begin());
     if (!base) {
         return nullptr;
     }
-    switch (expression.fOperator) {
+    switch (expression.getToken().fKind) {
         case Token::PLUS:
             if (!base->fType.isNumber() && base->fType.kind() != Type::kVector_Kind &&
                 base->fType != *fContext.fFloatLiteral_Type) {
@@ -1959,7 +2010,7 @@
         case Token::PLUSPLUS:
             if (!base->fType.isNumber()) {
                 fErrors.error(expression.fOffset,
-                              String("'") + Compiler::OperatorName(expression.fOperator) +
+                              String("'") + Compiler::OperatorName(expression.getToken().fKind) +
                               "' cannot operate on '" + base->fType.description() + "'");
                 return nullptr;
             }
@@ -1968,7 +2019,7 @@
         case Token::MINUSMINUS:
             if (!base->fType.isNumber()) {
                 fErrors.error(expression.fOffset,
-                              String("'") + Compiler::OperatorName(expression.fOperator) +
+                              String("'") + Compiler::OperatorName(expression.getToken().fKind) +
                               "' cannot operate on '" + base->fType.description() + "'");
                 return nullptr;
             }
@@ -1977,7 +2028,7 @@
         case Token::LOGICALNOT:
             if (base->fType != *fContext.fBool_Type) {
                 fErrors.error(expression.fOffset,
-                              String("'") + Compiler::OperatorName(expression.fOperator) +
+                              String("'") + Compiler::OperatorName(expression.getToken().fKind) +
                               "' cannot operate on '" + base->fType.description() + "'");
                 return nullptr;
             }
@@ -1989,7 +2040,7 @@
         case Token::BITWISENOT:
             if (base->fType != *fContext.fInt_Type) {
                 fErrors.error(expression.fOffset,
-                              String("'") + Compiler::OperatorName(expression.fOperator) +
+                              String("'") + Compiler::OperatorName(expression.getToken().fKind) +
                               "' cannot operate on '" + base->fType.description() + "'");
                 return nullptr;
             }
@@ -1997,21 +2048,19 @@
         default:
             ABORT("unsupported prefix operator\n");
     }
-    return std::unique_ptr<Expression>(new PrefixExpression(expression.fOperator,
+    return std::unique_ptr<Expression>(new PrefixExpression(expression.getToken().fKind,
                                                             std::move(base)));
 }
 
 std::unique_ptr<Expression> IRGenerator::convertIndex(std::unique_ptr<Expression> base,
-                                                      const ASTExpression& index) {
+                                                      const ASTNode& index) {
     if (base->fKind == Expression::kTypeReference_Kind) {
-        if (index.fKind == ASTExpression::kInt_Kind) {
+        if (index.fKind == ASTNode::Kind::kInt) {
             const Type& oldType = ((TypeReference&) *base).fValue;
-            int64_t size = ((const ASTIntLiteral&) index).fValue;
+            SKSL_INT size = index.getInt();
             Type* newType = (Type*) fSymbolTable->takeOwnership(std::unique_ptr<Symbol>(
                                               new Type(oldType.name() + "[" + to_string(size) + "]",
-                                                       Type::kArray_Kind,
-                                                       oldType,
-                                                       size)));
+                                                       Type::kArray_Kind, oldType, size)));
             return std::unique_ptr<Expression>(new TypeReference(fContext, base->fOffset,
                                                                  *newType));
 
@@ -2158,7 +2207,8 @@
         if (e->fKind == ProgramElement::kEnum_Kind && type.name() == ((Enum&) *e).fTypeName) {
             std::shared_ptr<SymbolTable> old = fSymbolTable;
             fSymbolTable = ((Enum&) *e).fSymbols;
-            result = convertIdentifier(ASTIdentifier(offset, field));
+            result = convertIdentifier(ASTNode(&fFile->fNodes, offset, ASTNode::Kind::kIdentifier,
+                                               field));
             fSymbolTable = old;
         }
     }
@@ -2170,13 +2220,13 @@
 }
 
 std::unique_ptr<Expression> IRGenerator::convertAppend(int offset,
-                                          const std::vector<std::unique_ptr<ASTExpression>>& args) {
+                                                       const std::vector<ASTNode>& args) {
 #ifndef SKSL_STANDALONE
     if (args.size() < 2) {
         fErrors.error(offset, "'append' requires at least two arguments");
         return nullptr;
     }
-    std::unique_ptr<Expression> pipeline = this->convertExpression(*args[0]);
+    std::unique_ptr<Expression> pipeline = this->convertExpression(args[0]);
     if (!pipeline) {
         return nullptr;
     }
@@ -2184,16 +2234,16 @@
         fErrors.error(offset, "first argument of 'append' must have type 'SkRasterPipeline'");
         return nullptr;
     }
-    if (ASTExpression::kIdentifier_Kind != args[1]->fKind) {
-        fErrors.error(offset, "'" + args[1]->description() + "' is not a valid stage");
+    if (ASTNode::Kind::kIdentifier != args[1].fKind) {
+        fErrors.error(offset, "'" + args[1].description() + "' is not a valid stage");
         return nullptr;
     }
-    StringFragment name = ((const ASTIdentifier&) *args[1]).fText;
+    StringFragment name = args[1].getString();
     SkRasterPipeline::StockStage stage = SkRasterPipeline::premul;
     std::vector<std::unique_ptr<Expression>> stageArgs;
     stageArgs.push_back(std::move(pipeline));
     for (size_t i = 2; i < args.size(); ++i) {
-        std::unique_ptr<Expression> arg = this->convertExpression(*args[i]);
+        std::unique_ptr<Expression> arg = this->convertExpression(args[i]);
         if (!arg) {
             return nullptr;
         }
@@ -2254,96 +2304,93 @@
 #endif
 }
 
-std::unique_ptr<Expression> IRGenerator::convertSuffixExpression(
-                                                            const ASTSuffixExpression& expression) {
-    std::unique_ptr<Expression> base = this->convertExpression(*expression.fBase);
+std::unique_ptr<Expression> IRGenerator::convertIndexExpression(const ASTNode& index) {
+    SkASSERT(index.fKind == ASTNode::Kind::kIndex);
+    auto iter = index.begin();
+    std::unique_ptr<Expression> base = this->convertExpression(*(iter++));
     if (!base) {
         return nullptr;
     }
-    switch (expression.fSuffix->fKind) {
-        case ASTSuffix::kIndex_Kind: {
-            const ASTExpression* expr = ((ASTIndexSuffix&) *expression.fSuffix).fExpression.get();
-            if (expr) {
-                return this->convertIndex(std::move(base), *expr);
-            } else if (base->fKind == Expression::kTypeReference_Kind) {
-                const Type& oldType = ((TypeReference&) *base).fValue;
-                Type* newType = (Type*) fSymbolTable->takeOwnership(std::unique_ptr<Symbol>(
+    if (iter != index.end()) {
+        return this->convertIndex(std::move(base), *(iter++));
+    } else if (base->fKind == Expression::kTypeReference_Kind) {
+        const Type& oldType = ((TypeReference&) *base).fValue;
+        Type* newType = (Type*) fSymbolTable->takeOwnership(std::unique_ptr<Symbol>(
                                                                      new Type(oldType.name() + "[]",
                                                                               Type::kArray_Kind,
                                                                               oldType,
                                                                               -1)));
-                return std::unique_ptr<Expression>(new TypeReference(fContext, base->fOffset,
-                                                                     *newType));
-            } else {
-                fErrors.error(expression.fOffset, "'[]' must follow a type name");
-                return nullptr;
-            }
-        }
-        case ASTSuffix::kCall_Kind: {
-            auto rawArguments = &((ASTCallSuffix&) *expression.fSuffix).fArguments;
-            if (Expression::kFunctionReference_Kind == base->fKind &&
-                "append" == ((const FunctionReference&) *base).fFunctions[0]->fName) {
-                return convertAppend(expression.fOffset, *rawArguments);
-            }
-            std::vector<std::unique_ptr<Expression>> arguments;
-            for (size_t i = 0; i < rawArguments->size(); i++) {
-                std::unique_ptr<Expression> converted =
-                        this->convertExpression(*(*rawArguments)[i]);
-                if (!converted) {
-                    return nullptr;
-                }
-                arguments.push_back(std::move(converted));
-            }
-            return this->call(expression.fOffset, std::move(base), std::move(arguments));
-        }
-        case ASTSuffix::kField_Kind: {
-            StringFragment field = ((ASTFieldSuffix&) *expression.fSuffix).fField;
-            if (base->fType == *fContext.fSkCaps_Type) {
-                return this->getCap(expression.fOffset, field);
-            }
-            if (base->fType == *fContext.fSkArgs_Type) {
-                return this->getArg(expression.fOffset, field);
-            }
-            if (base->fKind == Expression::kTypeReference_Kind) {
-                return this->convertTypeField(base->fOffset, ((TypeReference&) *base).fValue,
-                                              field);
-            }
-            if (base->fKind == Expression::kExternalValue_Kind) {
-                return this->convertField(std::move(base), field);
-            }
-            switch (base->fType.kind()) {
-                case Type::kVector_Kind:
-                    return this->convertSwizzle(std::move(base), field);
-                case Type::kOther_Kind:
-                case Type::kStruct_Kind:
-                    return this->convertField(std::move(base), field);
-                default:
-                    fErrors.error(base->fOffset, "cannot swizzle value of type '" +
-                                                 base->fType.description() + "'");
-                    return nullptr;
-            }
-        }
-        case ASTSuffix::kPostIncrement_Kind:
-            if (!base->fType.isNumber()) {
-                fErrors.error(expression.fOffset,
-                              "'++' cannot operate on '" + base->fType.description() + "'");
-                return nullptr;
-            }
-            this->setRefKind(*base, VariableReference::kReadWrite_RefKind);
-            return std::unique_ptr<Expression>(new PostfixExpression(std::move(base),
-                                                                     Token::PLUSPLUS));
-        case ASTSuffix::kPostDecrement_Kind:
-            if (!base->fType.isNumber()) {
-                fErrors.error(expression.fOffset,
-                              "'--' cannot operate on '" + base->fType.description() + "'");
-                return nullptr;
-            }
-            this->setRefKind(*base, VariableReference::kReadWrite_RefKind);
-            return std::unique_ptr<Expression>(new PostfixExpression(std::move(base),
-                                                                     Token::MINUSMINUS));
-        default:
-            ABORT("unsupported suffix operator");
+        return std::unique_ptr<Expression>(new TypeReference(fContext, base->fOffset,
+                                                             *newType));
     }
+    fErrors.error(index.fOffset, "'[]' must follow a type name");
+    return nullptr;
+}
+
+std::unique_ptr<Expression> IRGenerator::convertCallExpression(const ASTNode& callNode) {
+    SkASSERT(callNode.fKind == ASTNode::Kind::kCall);
+    auto iter = callNode.begin();
+    std::unique_ptr<Expression> base = this->convertExpression(*(iter++));
+    if (!base) {
+        return nullptr;
+    }
+    std::vector<std::unique_ptr<Expression>> arguments;
+    for (; iter != callNode.end(); ++iter) {
+        std::unique_ptr<Expression> converted = this->convertExpression(*iter);
+        if (!converted) {
+            return nullptr;
+        }
+        arguments.push_back(std::move(converted));
+    }
+    return this->call(callNode.fOffset, std::move(base), std::move(arguments));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertFieldExpression(const ASTNode& fieldNode) {
+    std::unique_ptr<Expression> base = this->convertExpression(*fieldNode.begin());
+    if (!base) {
+        return nullptr;
+    }
+    StringFragment field = fieldNode.getString();
+    if (base->fType == *fContext.fSkCaps_Type) {
+        return this->getCap(fieldNode.fOffset, field);
+    }
+    if (base->fType == *fContext.fSkArgs_Type) {
+        return this->getArg(fieldNode.fOffset, field);
+    }
+    if (base->fKind == Expression::kTypeReference_Kind) {
+        return this->convertTypeField(base->fOffset, ((TypeReference&) *base).fValue,
+                                      field);
+    }
+    if (base->fKind == Expression::kExternalValue_Kind) {
+        return this->convertField(std::move(base), field);
+    }
+    switch (base->fType.kind()) {
+        case Type::kVector_Kind:
+            return this->convertSwizzle(std::move(base), field);
+        case Type::kOther_Kind:
+        case Type::kStruct_Kind:
+            return this->convertField(std::move(base), field);
+        default:
+            fErrors.error(base->fOffset, "cannot swizzle value of type '" +
+                                         base->fType.description() + "'");
+            return nullptr;
+    }
+}
+
+std::unique_ptr<Expression> IRGenerator::convertPostfixExpression(const ASTNode& expression) {
+    std::unique_ptr<Expression> base = this->convertExpression(*expression.begin());
+    if (!base) {
+        return nullptr;
+    }
+    if (!base->fType.isNumber()) {
+        fErrors.error(expression.fOffset,
+                      "'" + String(Compiler::OperatorName(expression.getToken().fKind)) +
+                      "' cannot operate on '" + base->fType.description() + "'");
+        return nullptr;
+    }
+    this->setRefKind(*base, VariableReference::kReadWrite_RefKind);
+    return std::unique_ptr<Expression>(new PostfixExpression(std::move(base),
+                                                             expression.getToken().fKind));
 }
 
 void IRGenerator::checkValid(const Expression& expr) {
@@ -2431,55 +2478,54 @@
     fKind = kind;
     fProgramElements = out;
     Parser parser(text, length, types, fErrors);
-    std::vector<std::unique_ptr<ASTDeclaration>> parsed = parser.file();
+    fFile = parser.file();
     if (fErrors.errorCount()) {
         return;
     }
-    for (size_t i = 0; i < parsed.size(); i++) {
-        ASTDeclaration& decl = *parsed[i];
+    SkASSERT(fFile);
+    for (const auto& decl : fFile->root()) {
         switch (decl.fKind) {
-            case ASTDeclaration::kVar_Kind: {
+            case ASTNode::Kind::kVarDeclarations: {
                 std::unique_ptr<VarDeclarations> s = this->convertVarDeclarations(
-                                                                         (ASTVarDeclarations&) decl,
+                                                                         decl,
                                                                          Variable::kGlobal_Storage);
                 if (s) {
                     fProgramElements->push_back(std::move(s));
                 }
                 break;
             }
-            case ASTDeclaration::kEnum_Kind: {
-                this->convertEnum((ASTEnum&) decl);
+            case ASTNode::Kind::kEnum: {
+                this->convertEnum(decl);
                 break;
             }
-            case ASTDeclaration::kFunction_Kind: {
-                this->convertFunction((ASTFunction&) decl);
+            case ASTNode::Kind::kFunction: {
+                this->convertFunction(decl);
                 break;
             }
-            case ASTDeclaration::kModifiers_Kind: {
-                std::unique_ptr<ModifiersDeclaration> f = this->convertModifiersDeclaration(
-                                                                   (ASTModifiersDeclaration&) decl);
+            case ASTNode::Kind::kModifiers: {
+                std::unique_ptr<ModifiersDeclaration> f = this->convertModifiersDeclaration(decl);
                 if (f) {
                     fProgramElements->push_back(std::move(f));
                 }
                 break;
             }
-            case ASTDeclaration::kInterfaceBlock_Kind: {
-                std::unique_ptr<InterfaceBlock> i = this->convertInterfaceBlock(
-                                                                         (ASTInterfaceBlock&) decl);
+            case ASTNode::Kind::kInterfaceBlock: {
+                std::unique_ptr<InterfaceBlock> i = this->convertInterfaceBlock(decl);
                 if (i) {
                     fProgramElements->push_back(std::move(i));
                 }
                 break;
             }
-            case ASTDeclaration::kExtension_Kind: {
-                std::unique_ptr<Extension> e = this->convertExtension((ASTExtension&) decl);
+            case ASTNode::Kind::kExtension: {
+                std::unique_ptr<Extension> e = this->convertExtension(decl.fOffset,
+                                                                      decl.getString());
                 if (e) {
                     fProgramElements->push_back(std::move(e));
                 }
                 break;
             }
-            case ASTDeclaration::kSection_Kind: {
-                std::unique_ptr<Section> s = this->convertSection((ASTSection&) decl);
+            case ASTNode::Kind::kSection: {
+                std::unique_ptr<Section> s = this->convertSection(decl);
                 if (s) {
                     fProgramElements->push_back(std::move(s));
                 }
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index 00934e7..4aafaeb 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -9,33 +9,8 @@
 #define SKSL_IRGENERATOR
 
 #include "src/sksl/SkSLErrorReporter.h"
-#include "src/sksl/ast/SkSLASTBinaryExpression.h"
-#include "src/sksl/ast/SkSLASTBlock.h"
-#include "src/sksl/ast/SkSLASTBreakStatement.h"
-#include "src/sksl/ast/SkSLASTCallSuffix.h"
-#include "src/sksl/ast/SkSLASTContinueStatement.h"
-#include "src/sksl/ast/SkSLASTDiscardStatement.h"
-#include "src/sksl/ast/SkSLASTDoStatement.h"
-#include "src/sksl/ast/SkSLASTEnum.h"
-#include "src/sksl/ast/SkSLASTExpression.h"
-#include "src/sksl/ast/SkSLASTExpressionStatement.h"
-#include "src/sksl/ast/SkSLASTExtension.h"
-#include "src/sksl/ast/SkSLASTForStatement.h"
-#include "src/sksl/ast/SkSLASTFunction.h"
-#include "src/sksl/ast/SkSLASTIdentifier.h"
-#include "src/sksl/ast/SkSLASTIfStatement.h"
-#include "src/sksl/ast/SkSLASTInterfaceBlock.h"
-#include "src/sksl/ast/SkSLASTModifiersDeclaration.h"
-#include "src/sksl/ast/SkSLASTPrefixExpression.h"
-#include "src/sksl/ast/SkSLASTReturnStatement.h"
-#include "src/sksl/ast/SkSLASTSection.h"
-#include "src/sksl/ast/SkSLASTStatement.h"
-#include "src/sksl/ast/SkSLASTSuffixExpression.h"
-#include "src/sksl/ast/SkSLASTSwitchStatement.h"
-#include "src/sksl/ast/SkSLASTTernaryExpression.h"
-#include "src/sksl/ast/SkSLASTVarDeclaration.h"
-#include "src/sksl/ast/SkSLASTVarDeclarationStatement.h"
-#include "src/sksl/ast/SkSLASTWhileStatement.h"
+#include "src/sksl/SkSLASTFile.h"
+#include "src/sksl/SkSLASTNode.h"
 #include "src/sksl/ir/SkSLBlock.h"
 #include "src/sksl/ir/SkSLExpression.h"
 #include "src/sksl/ir/SkSLExtension.h"
@@ -103,15 +78,14 @@
     void pushSymbolTable();
     void popSymbolTable();
 
-    std::unique_ptr<VarDeclarations> convertVarDeclarations(const ASTVarDeclarations& decl,
+    std::unique_ptr<VarDeclarations> convertVarDeclarations(const ASTNode& decl,
                                                             Variable::Storage storage);
-    void convertFunction(const ASTFunction& f);
-    std::unique_ptr<Statement> convertStatement(const ASTStatement& statement);
-    std::unique_ptr<Expression> convertExpression(const ASTExpression& expression);
-    std::unique_ptr<ModifiersDeclaration> convertModifiersDeclaration(
-                                                                  const ASTModifiersDeclaration& m);
+    void convertFunction(const ASTNode& f);
+    std::unique_ptr<Statement> convertStatement(const ASTNode& statement);
+    std::unique_ptr<Expression> convertExpression(const ASTNode& expression);
+    std::unique_ptr<ModifiersDeclaration> convertModifiersDeclaration(const ASTNode& m);
 
-    const Type* convertType(const ASTType& type);
+    const Type* convertType(const ASTNode& type);
     std::unique_ptr<Expression> call(int offset,
                                      const FunctionDeclaration& function,
                                      std::vector<std::unique_ptr<Expression>> arguments);
@@ -121,10 +95,9 @@
                                      std::vector<std::unique_ptr<Expression>> arguments);
     int coercionCost(const Expression& expr, const Type& type);
     std::unique_ptr<Expression> coerce(std::unique_ptr<Expression> expr, const Type& type);
-    std::unique_ptr<Expression> convertAppend(int offset,
-                                           const std::vector<std::unique_ptr<ASTExpression>>& args);
-    std::unique_ptr<Block> convertBlock(const ASTBlock& block);
-    std::unique_ptr<Statement> convertBreak(const ASTBreakStatement& b);
+    std::unique_ptr<Expression> convertAppend(int offset, const std::vector<ASTNode>& args);
+    std::unique_ptr<Block> convertBlock(const ASTNode& block);
+    std::unique_ptr<Statement> convertBreak(const ASTNode& b);
     std::unique_ptr<Expression> convertNumberConstructor(
                                                    int offset,
                                                    const Type& type,
@@ -136,35 +109,38 @@
     std::unique_ptr<Expression> convertConstructor(int offset,
                                                    const Type& type,
                                                    std::vector<std::unique_ptr<Expression>> params);
-    std::unique_ptr<Statement> convertContinue(const ASTContinueStatement& c);
-    std::unique_ptr<Statement> convertDiscard(const ASTDiscardStatement& d);
-    std::unique_ptr<Statement> convertDo(const ASTDoStatement& d);
-    std::unique_ptr<Statement> convertSwitch(const ASTSwitchStatement& s);
-    std::unique_ptr<Expression> convertBinaryExpression(const ASTBinaryExpression& expression);
-    std::unique_ptr<Extension> convertExtension(const ASTExtension& e);
-    std::unique_ptr<Statement> convertExpressionStatement(const ASTExpressionStatement& s);
-    std::unique_ptr<Statement> convertFor(const ASTForStatement& f);
-    std::unique_ptr<Expression> convertIdentifier(const ASTIdentifier& identifier);
-    std::unique_ptr<Statement> convertIf(const ASTIfStatement& s);
+    std::unique_ptr<Statement> convertContinue(const ASTNode& c);
+    std::unique_ptr<Statement> convertDiscard(const ASTNode& d);
+    std::unique_ptr<Statement> convertDo(const ASTNode& d);
+    std::unique_ptr<Statement> convertSwitch(const ASTNode& s);
+    std::unique_ptr<Expression> convertBinaryExpression(const ASTNode& expression);
+    std::unique_ptr<Extension> convertExtension(int offset, StringFragment name);
+    std::unique_ptr<Statement> convertExpressionStatement(const ASTNode& s);
+    std::unique_ptr<Statement> convertFor(const ASTNode& f);
+    std::unique_ptr<Expression> convertIdentifier(const ASTNode& identifier);
+    std::unique_ptr<Statement> convertIf(const ASTNode& s);
     std::unique_ptr<Expression> convertIndex(std::unique_ptr<Expression> base,
-                                             const ASTExpression& index);
-    std::unique_ptr<InterfaceBlock> convertInterfaceBlock(const ASTInterfaceBlock& s);
+                                             const ASTNode& index);
+    std::unique_ptr<InterfaceBlock> convertInterfaceBlock(const ASTNode& s);
     Modifiers convertModifiers(const Modifiers& m);
-    std::unique_ptr<Expression> convertPrefixExpression(const ASTPrefixExpression& expression);
-    std::unique_ptr<Statement> convertReturn(const ASTReturnStatement& r);
-    std::unique_ptr<Section> convertSection(const ASTSection& e);
+    std::unique_ptr<Expression> convertPrefixExpression(const ASTNode& expression);
+    std::unique_ptr<Statement> convertReturn(const ASTNode& r);
+    std::unique_ptr<Section> convertSection(const ASTNode& e);
     std::unique_ptr<Expression> getCap(int offset, String name);
-    std::unique_ptr<Expression> convertSuffixExpression(const ASTSuffixExpression& expression);
+    std::unique_ptr<Expression> convertCallExpression(const ASTNode& expression);
+    std::unique_ptr<Expression> convertFieldExpression(const ASTNode& expression);
+    std::unique_ptr<Expression> convertIndexExpression(const ASTNode& expression);
+    std::unique_ptr<Expression> convertPostfixExpression(const ASTNode& expression);
     std::unique_ptr<Expression> convertTypeField(int offset, const Type& type,
                                                  StringFragment field);
     std::unique_ptr<Expression> convertField(std::unique_ptr<Expression> base,
                                              StringFragment field);
     std::unique_ptr<Expression> convertSwizzle(std::unique_ptr<Expression> base,
                                                StringFragment fields);
-    std::unique_ptr<Expression> convertTernaryExpression(const ASTTernaryExpression& expression);
-    std::unique_ptr<Statement> convertVarDeclarationStatement(const ASTVarDeclarationStatement& s);
-    std::unique_ptr<Statement> convertWhile(const ASTWhileStatement& w);
-    void convertEnum(const ASTEnum& e);
+    std::unique_ptr<Expression> convertTernaryExpression(const ASTNode& expression);
+    std::unique_ptr<Statement> convertVarDeclarationStatement(const ASTNode& s);
+    std::unique_ptr<Statement> convertWhile(const ASTNode& w);
+    void convertEnum(const ASTNode& e);
     std::unique_ptr<Block> applyInvocationIDWorkaround(std::unique_ptr<Block> main);
     // returns a statement which converts sk_Position from device to normalized coordinates
     std::unique_ptr<Statement> getNormalizeSkPositionCode();
@@ -174,6 +150,7 @@
     void getConstantInt(const Expression& value, int64_t* out);
     bool checkSwizzleWrite(const Swizzle& swizzle);
 
+    std::unique_ptr<ASTFile> fFile;
     const FunctionDeclaration* fCurrentFunction;
     std::unordered_map<String, Program::Settings::Value> fCapsMap;
     std::shared_ptr<SymbolTable> fRootSymbolTable;
diff --git a/src/sksl/SkSLLexer.cpp b/src/sksl/SkSLLexer.cpp
index 0ec3286..2815408 100644
--- a/src/sksl/SkSLLexer.cpp
+++ b/src/sksl/SkSLLexer.cpp
@@ -1034,4 +1034,4 @@
     return Token(kind, startOffset, fOffset - startOffset);
 }
 
-}  // namespace SkSL
+}  // namespace
diff --git a/src/sksl/SkSLLexer.h b/src/sksl/SkSLLexer.h
index 9a16696..233ccf9 100644
--- a/src/sksl/SkSLLexer.h
+++ b/src/sksl/SkSLLexer.h
@@ -241,5 +241,5 @@
     int32_t fOffset;
 };
 
-}  // namespace SkSL
+}  // namespace
 #endif
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index f9299fc..d260f0a 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -7,42 +7,7 @@
 
 #include "stdio.h"
 #include "src/sksl/SkSLParser.h"
-#include "src/sksl/ast/SkSLASTBinaryExpression.h"
-#include "src/sksl/ast/SkSLASTBlock.h"
-#include "src/sksl/ast/SkSLASTBoolLiteral.h"
-#include "src/sksl/ast/SkSLASTBreakStatement.h"
-#include "src/sksl/ast/SkSLASTCallSuffix.h"
-#include "src/sksl/ast/SkSLASTContinueStatement.h"
-#include "src/sksl/ast/SkSLASTDiscardStatement.h"
-#include "src/sksl/ast/SkSLASTDoStatement.h"
-#include "src/sksl/ast/SkSLASTEnum.h"
-#include "src/sksl/ast/SkSLASTExpression.h"
-#include "src/sksl/ast/SkSLASTExpressionStatement.h"
-#include "src/sksl/ast/SkSLASTExtension.h"
-#include "src/sksl/ast/SkSLASTFieldSuffix.h"
-#include "src/sksl/ast/SkSLASTFloatLiteral.h"
-#include "src/sksl/ast/SkSLASTForStatement.h"
-#include "src/sksl/ast/SkSLASTFunction.h"
-#include "src/sksl/ast/SkSLASTIdentifier.h"
-#include "src/sksl/ast/SkSLASTIfStatement.h"
-#include "src/sksl/ast/SkSLASTIndexSuffix.h"
-#include "src/sksl/ast/SkSLASTIntLiteral.h"
-#include "src/sksl/ast/SkSLASTInterfaceBlock.h"
-#include "src/sksl/ast/SkSLASTModifiersDeclaration.h"
-#include "src/sksl/ast/SkSLASTNullLiteral.h"
-#include "src/sksl/ast/SkSLASTParameter.h"
-#include "src/sksl/ast/SkSLASTPrefixExpression.h"
-#include "src/sksl/ast/SkSLASTReturnStatement.h"
-#include "src/sksl/ast/SkSLASTSection.h"
-#include "src/sksl/ast/SkSLASTStatement.h"
-#include "src/sksl/ast/SkSLASTSuffixExpression.h"
-#include "src/sksl/ast/SkSLASTSwitchCase.h"
-#include "src/sksl/ast/SkSLASTSwitchStatement.h"
-#include "src/sksl/ast/SkSLASTTernaryExpression.h"
-#include "src/sksl/ast/SkSLASTType.h"
-#include "src/sksl/ast/SkSLASTVarDeclaration.h"
-#include "src/sksl/ast/SkSLASTVarDeclarationStatement.h"
-#include "src/sksl/ast/SkSLASTWhileStatement.h"
+#include "src/sksl/SkSLASTNode.h"
 #include "src/sksl/ir/SkSLModifiers.h"
 #include "src/sksl/ir/SkSLSymbolTable.h"
 #include "src/sksl/ir/SkSLType.h"
@@ -144,36 +109,68 @@
     (void) layoutMapInitialized;
 }
 
+#define CREATE_NODE(result, ...)              \
+    ASTNode::ID result(fFile->fNodes.size()); \
+    fFile->fNodes.emplace_back(&fFile->fNodes, __VA_ARGS__)
+
+#define RETURN_NODE(...)                  \
+    do {                                  \
+        CREATE_NODE(result, __VA_ARGS__); \
+        return result;                    \
+    } while (false)
+
+#define CREATE_CHILD(child, target, ...)   \
+    CREATE_NODE(child, __VA_ARGS__);       \
+    fFile->fNodes[target.fValue].addChild(child)
+
+#define CREATE_EMPTY_CHILD(target)                    \
+    do {                                              \
+        ASTNode::ID child(fFile->fNodes.size());      \
+        fFile->fNodes.emplace_back();                 \
+        fFile->fNodes[target.fValue].addChild(child); \
+    } while (false)
+
 /* (directive | section | declaration)* END_OF_FILE */
-std::vector<std::unique_ptr<ASTDeclaration>> Parser::file() {
-    std::vector<std::unique_ptr<ASTDeclaration>> result;
+std::unique_ptr<ASTFile> Parser::file() {
+    fFile.reset(new ASTFile());
+    CREATE_NODE(result, 0, ASTNode::Kind::kFile);
+    fFile->fRoot = result;
     for (;;) {
         switch (this->peek().fKind) {
             case Token::END_OF_FILE:
-                return result;
+                return std::move(fFile);
             case Token::DIRECTIVE: {
-                std::unique_ptr<ASTDeclaration> decl = this->directive();
-                if (decl) {
-                    result.push_back(std::move(decl));
+                ASTNode::ID dir = this->directive();
+                if (fErrors.errorCount()) {
+                    return nullptr;
+                }
+                if (dir) {
+                    getNode(result).addChild(dir);
                 }
                 break;
             }
             case Token::SECTION: {
-                std::unique_ptr<ASTDeclaration> section = this->section();
+                ASTNode::ID section = this->section();
+                if (fErrors.errorCount()) {
+                    return nullptr;
+                }
                 if (section) {
-                    result.push_back(std::move(section));
+                    getNode(result).addChild(section);
                 }
                 break;
             }
             default: {
-                std::unique_ptr<ASTDeclaration> decl = this->declaration();
-                if (!decl) {
-                    continue;
+                ASTNode::ID decl = this->declaration();
+                if (fErrors.errorCount()) {
+                    return nullptr;
                 }
-                result.push_back(std::move(decl));
+                if (decl) {
+                    getNode(result).addChild(decl);
+                }
             }
         }
     }
+    return std::move(fFile);
 }
 
 Token Parser::nextRawToken() {
@@ -254,65 +251,58 @@
 
 /* DIRECTIVE(#version) INT_LITERAL ("es" | "compatibility")? |
    DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER */
-std::unique_ptr<ASTDeclaration> Parser::directive() {
+ASTNode::ID Parser::directive() {
     Token start;
     if (!this->expect(Token::DIRECTIVE, "a directive", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     StringFragment text = this->text(start);
-    if (text == "#version") {
-        this->expect(Token::INT_LITERAL, "a version number");
-        Token next = this->peek();
-        StringFragment nextText = this->text(next);
-        if (nextText == "es" || nextText == "compatibility") {
-            this->nextToken();
-        }
-        // version is ignored for now; it will eventually become an error when we stop pretending
-        // to be GLSL
-        return nullptr;
-    } else if (text == "#extension") {
+    if (text == "#extension") {
         Token name;
         if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
         if (!this->expect(Token::COLON, "':'")) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
         // FIXME: need to start paying attention to this token
         if (!this->expect(Token::IDENTIFIER, "an identifier")) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        return std::unique_ptr<ASTDeclaration>(new ASTExtension(start.fOffset,
-                                                                String(this->text(name))));
+        RETURN_NODE(start.fOffset, ASTNode::Kind::kExtension, this->text(name));
     } else {
         this->error(start, "unsupported directive '" + this->text(start) + "'");
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
 }
 
 /* SECTION LBRACE (LPAREN IDENTIFIER RPAREN)? <any sequence of tokens with balanced braces>
    RBRACE */
-std::unique_ptr<ASTDeclaration> Parser::section() {
+ASTNode::ID Parser::section() {
     Token start;
     if (!this->expect(Token::SECTION, "a section token", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    String argument;
+    StringFragment argument;
     if (this->peek().fKind == Token::LPAREN) {
         this->nextToken();
         Token argToken;
         if (!this->expect(Token::IDENTIFIER, "an identifier", &argToken)) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
         argument = this->text(argToken);
         if (!this->expect(Token::RPAREN, "')'")) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
     }
     if (!this->expect(Token::LBRACE, "'{'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    String text;
+    StringFragment text;
+    Token codeStart = this->nextRawToken();
+    size_t startOffset = codeStart.fOffset;
+    this->pushback(codeStart);
+    text.fChars = fText + startOffset;
     int level = 1;
     for (;;) {
         Token next = this->nextRawToken();
@@ -325,87 +315,83 @@
                 break;
             case Token::END_OF_FILE:
                 this->error(start, "reached end of file while parsing section");
-                return nullptr;
+                return ASTNode::ID::Invalid();
             default:
                 break;
         }
         if (!level) {
+            text.fLength = next.fOffset - startOffset;
             break;
         }
-        text += this->text(next);
     }
     StringFragment name = this->text(start);
     ++name.fChars;
     --name.fLength;
-    return std::unique_ptr<ASTDeclaration>(new ASTSection(start.fOffset,
-                                                          String(name),
-                                                          argument,
-                                                          text));
+    RETURN_NODE(start.fOffset, ASTNode::Kind::kSection,
+                ASTNode::SectionData(name, argument, text));
 }
 
 /* ENUM CLASS IDENTIFIER LBRACE (IDENTIFIER (EQ expression)? (COMMA IDENTIFIER (EQ expression))*)?
    RBRACE */
-std::unique_ptr<ASTDeclaration> Parser::enumDeclaration() {
+ASTNode::ID Parser::enumDeclaration() {
     Token start;
     if (!this->expect(Token::ENUM, "'enum'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::CLASS, "'class'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token name;
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LBRACE, "'{'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     fTypes.add(this->text(name), std::unique_ptr<Symbol>(new Type(this->text(name),
                                                                   Type::kEnum_Kind)));
-    std::vector<StringFragment> names;
-    std::vector<std::unique_ptr<ASTExpression>> values;
+    CREATE_NODE(result, name.fOffset, ASTNode::Kind::kEnum, this->text(name));
     if (!this->checkNext(Token::RBRACE)) {
         Token id;
         if (!this->expect(Token::IDENTIFIER, "an identifier", &id)) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        names.push_back(this->text(id));
         if (this->checkNext(Token::EQ)) {
-            std::unique_ptr<ASTExpression> value = this->assignmentExpression();
+            ASTNode::ID value = this->assignmentExpression();
             if (!value) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
-            values.push_back(std::move(value));
+            CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id));
+            getNode(child).addChild(value);
         } else {
-            values.push_back(nullptr);
+            CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id));
         }
         while (!this->checkNext(Token::RBRACE)) {
             if (!this->expect(Token::COMMA, "','")) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
             if (!this->expect(Token::IDENTIFIER, "an identifier", &id)) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
-            names.push_back(this->text(id));
             if (this->checkNext(Token::EQ)) {
-                std::unique_ptr<ASTExpression> value = this->assignmentExpression();
+                ASTNode::ID value = this->assignmentExpression();
                 if (!value) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                values.push_back(std::move(value));
+                CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id));
+                getNode(child).addChild(value);
             } else {
-                values.push_back(nullptr);
+                CREATE_CHILD(child, result, id.fOffset, ASTNode::Kind::kEnumCase, this->text(id));
             }
         }
     }
     this->expect(Token::SEMICOLON, "';'");
-    return std::unique_ptr<ASTDeclaration>(new ASTEnum(name.fOffset, this->text(name), names,
-                                                       std::move(values)));
+    return result;
 }
 
 /* enumDeclaration | modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter
    (COMMA parameter)* RPAREN (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */
-std::unique_ptr<ASTDeclaration> Parser::declaration() {
+ASTNode::ID Parser::declaration() {
     Token lookahead = this->peek();
     if (lookahead.fKind == Token::ENUM) {
         return this->enumDeclaration();
@@ -421,92 +407,98 @@
     }
     if (lookahead.fKind == Token::SEMICOLON) {
         this->nextToken();
-        return std::unique_ptr<ASTDeclaration>(new ASTModifiersDeclaration(modifiers));
+        RETURN_NODE(lookahead.fOffset, ASTNode::Kind::kModifiers, modifiers);
     }
-    std::unique_ptr<ASTType> type(this->type());
+    ASTNode::ID type = this->type();
     if (!type) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    if (type->fKind == ASTType::kStruct_Kind && this->checkNext(Token::SEMICOLON)) {
-        return nullptr;
+    if (getNode(type).getTypeData().fIsStructDeclaration && this->checkNext(Token::SEMICOLON)) {
+        return ASTNode::ID::Invalid();
     }
     Token name;
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (this->checkNext(Token::LPAREN)) {
-        std::vector<std::unique_ptr<ASTParameter>> parameters;
-        while (this->peek().fKind != Token::RPAREN) {
-            if (parameters.size() > 0) {
-                if (!this->expect(Token::COMMA, "','")) {
-                    return nullptr;
+        CREATE_NODE(result, name.fOffset, ASTNode::Kind::kFunction);
+        ASTNode::FunctionData fd(modifiers, this->text(name), 0);
+        getNode(result).addChild(type);
+        if (this->peek().fKind != Token::RPAREN) {
+            for (;;) {
+                ASTNode::ID parameter = this->parameter();
+                if (!parameter) {
+                    return ASTNode::ID::Invalid();
+                }
+                ++fd.fParameterCount;
+                getNode(result).addChild(parameter);
+                if (!this->checkNext(Token::COMMA)) {
+                    break;
                 }
             }
-            std::unique_ptr<ASTParameter> parameter = this->parameter();
-            if (!parameter) {
-                return nullptr;
-            }
-            parameters.push_back(std::move(parameter));
         }
-        this->nextToken();
-        std::unique_ptr<ASTBlock> body;
+        getNode(result).setFunctionData(fd);
+        if (!this->expect(Token::RPAREN, "')'")) {
+            return ASTNode::ID::Invalid();
+        }
+        ASTNode::ID body;
         if (!this->checkNext(Token::SEMICOLON)) {
             body = this->block();
             if (!body) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
+            getNode(result).addChild(body);
         }
-        return std::unique_ptr<ASTDeclaration>(new ASTFunction(name.fOffset,
-                                                               modifiers,
-                                                               std::move(type),
-                                                               this->text(name),
-                                                               std::move(parameters),
-                                                               std::move(body)));
+        return result;
     } else {
-        return this->varDeclarationEnd(modifiers, std::move(type), this->text(name));
+        return this->varDeclarationEnd(modifiers, type, this->text(name));
     }
 }
 
 /* modifiers type IDENTIFIER varDeclarationEnd */
-std::unique_ptr<ASTVarDeclarations> Parser::varDeclarations() {
+ASTNode::ID Parser::varDeclarations() {
     Modifiers modifiers = this->modifiers();
-    std::unique_ptr<ASTType> type(this->type());
+    ASTNode::ID type = this->type();
     if (!type) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token name;
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return this->varDeclarationEnd(modifiers, std::move(type), this->text(name));
+    return this->varDeclarationEnd(modifiers, type, this->text(name));
 }
 
 /* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */
-std::unique_ptr<ASTType> Parser::structDeclaration() {
+ASTNode::ID Parser::structDeclaration() {
     if (!this->expect(Token::STRUCT, "'struct'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token name;
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LBRACE, "'{'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     std::vector<Type::Field> fields;
     while (this->peek().fKind != Token::RBRACE) {
-        std::unique_ptr<ASTVarDeclarations> decl = this->varDeclarations();
-        if (!decl) {
-            return nullptr;
+        ASTNode::ID decls = this->varDeclarations();
+        if (!decls) {
+            return ASTNode::ID::Invalid();
         }
-        for (const auto& var : decl->fVars) {
-            auto type = (const Type*) fTypes[decl->fType->fName];
-            for (int i = (int) var.fSizes.size() - 1; i >= 0; i--) {
-                if (!var.fSizes[i] || var.fSizes[i]->fKind != ASTExpression::kInt_Kind) {
-                    this->error(decl->fOffset, "array size in struct field must be a constant");
-                    return nullptr;
+        ASTNode& declsNode = getNode(decls);
+        auto type = (const Type*) fTypes[(declsNode.begin() + 1)->getTypeData().fName];
+        for (auto iter = declsNode.begin() + 2; iter != declsNode.end(); ++iter) {
+            ASTNode& var = *iter;
+            ASTNode::VarData vd = var.getVarData();
+            for (int j = vd.fSizeCount - 1; j >= 0; j--) {
+                const ASTNode& size = *(var.begin() + j);
+                if (!size || size.fKind != ASTNode::Kind::kInt) {
+                    this->error(declsNode.fOffset, "array size in struct field must be a constant");
+                    return ASTNode::ID::Invalid();
                 }
-                uint64_t columns = ((ASTIntLiteral&) *var.fSizes[i]).fValue;
+                uint64_t columns = size.getInt();
                 String name = type->name() + "[" + to_string(columns) + "]";
                 type = (Type*) fTypes.takeOwnership(std::unique_ptr<Symbol>(
                                                                          new Type(name,
@@ -514,136 +506,134 @@
                                                                                   *type,
                                                                                   (int) columns)));
             }
-            fields.push_back(Type::Field(decl->fModifiers, var.fName, type));
-            if (var.fValue) {
-                this->error(decl->fOffset, "initializers are not permitted on struct fields");
+            fields.push_back(Type::Field(declsNode.begin()->getModifiers(), vd.fName, type));
+            if (vd.fSizeCount ? (var.begin() + (vd.fSizeCount - 1))->fNext : var.fFirstChild) {
+                this->error(declsNode.fOffset, "initializers are not permitted on struct fields");
             }
         }
     }
     if (!this->expect(Token::RBRACE, "'}'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     fTypes.add(this->text(name), std::unique_ptr<Type>(new Type(name.fOffset, this->text(name),
                                                                 fields)));
-    return std::unique_ptr<ASTType>(new ASTType(name.fOffset, this->text(name),
-                                                ASTType::kStruct_Kind, std::vector<int>(), false));
+    RETURN_NODE(name.fOffset, ASTNode::Kind::kType,
+                ASTNode::TypeData(this->text(name), true, false));
 }
 
 /* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */
-std::unique_ptr<ASTVarDeclarations> Parser::structVarDeclaration(Modifiers modifiers) {
-    std::unique_ptr<ASTType> type = this->structDeclaration();
+ASTNode::ID Parser::structVarDeclaration(Modifiers modifiers) {
+    ASTNode::ID type = this->structDeclaration();
     if (!type) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token name;
     if (this->checkNext(Token::IDENTIFIER, &name)) {
-        std::unique_ptr<ASTVarDeclarations> result = this->varDeclarationEnd(modifiers,
-                                                                             std::move(type),
-                                                                             this->text(name));
-        if (result) {
-            for (const auto& var : result->fVars) {
-                if (var.fValue) {
-                    this->error(var.fValue->fOffset,
-                                "struct variables cannot be initialized");
-                }
-            }
-        }
-        return result;
+        return this->varDeclarationEnd(modifiers, std::move(type), this->text(name));
     }
     this->expect(Token::SEMICOLON, "';'");
-    return nullptr;
+    return ASTNode::ID::Invalid();
 }
 
 /* (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)? (COMMA IDENTIFER
    (LBRACKET expression? RBRACKET)* (EQ assignmentExpression)?)* SEMICOLON */
-std::unique_ptr<ASTVarDeclarations> Parser::varDeclarationEnd(Modifiers mods,
-                                                              std::unique_ptr<ASTType> type,
-                                                              StringFragment name) {
-    std::vector<ASTVarDeclaration> vars;
-    std::vector<std::unique_ptr<ASTExpression>> currentVarSizes;
+ASTNode::ID Parser::varDeclarationEnd(Modifiers mods, ASTNode::ID type, StringFragment name) {
+    CREATE_NODE(result, -1, ASTNode::Kind::kVarDeclarations);
+    CREATE_CHILD(modifiers, result, -1, ASTNode::Kind::kModifiers, mods);
+    getNode(result).addChild(type);
+    CREATE_NODE(currentVar, -1, ASTNode::Kind::kVarDeclaration);
+    ASTNode::VarData vd(name, 0);
+    getNode(result).addChild(currentVar);
     while (this->checkNext(Token::LBRACKET)) {
         if (this->checkNext(Token::RBRACKET)) {
-            currentVarSizes.push_back(nullptr);
+            CREATE_EMPTY_CHILD(currentVar);
         } else {
-            std::unique_ptr<ASTExpression> size(this->expression());
+            ASTNode::ID size = this->expression();
             if (!size) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
-            currentVarSizes.push_back(std::move(size));
+            getNode(currentVar).addChild(size);
             if (!this->expect(Token::RBRACKET, "']'")) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
         }
+        ++vd.fSizeCount;
     }
-    std::unique_ptr<ASTExpression> value;
+    getNode(currentVar).setVarData(vd);
     if (this->checkNext(Token::EQ)) {
-        value = this->assignmentExpression();
+        ASTNode::ID value = this->assignmentExpression();
         if (!value) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
+        getNode(currentVar).addChild(value);
     }
-    vars.emplace_back(name, std::move(currentVarSizes), std::move(value));
     while (this->checkNext(Token::COMMA)) {
         Token name;
         if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        currentVarSizes.clear();
-        value.reset();
+        currentVar = ASTNode::ID(fFile->fNodes.size());
+        vd = ASTNode::VarData(this->text(name), 0);
+        fFile->fNodes.emplace_back(&fFile->fNodes, -1, ASTNode::Kind::kVarDeclaration);
+        getNode(result).addChild(currentVar);
         while (this->checkNext(Token::LBRACKET)) {
             if (this->checkNext(Token::RBRACKET)) {
-                currentVarSizes.push_back(nullptr);
+                CREATE_EMPTY_CHILD(currentVar);
             } else {
-                std::unique_ptr<ASTExpression> size(this->expression());
+                ASTNode::ID size = this->expression();
                 if (!size) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                currentVarSizes.push_back(std::move(size));
+                getNode(currentVar).addChild(size);
                 if (!this->expect(Token::RBRACKET, "']'")) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
             }
+            ++vd.fSizeCount;
         }
+        getNode(currentVar).setVarData(vd);
         if (this->checkNext(Token::EQ)) {
-            value = this->assignmentExpression();
+            ASTNode::ID value = this->assignmentExpression();
             if (!value) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
+            getNode(currentVar).addChild(value);
         }
-        vars.emplace_back(this->text(name), std::move(currentVarSizes), std::move(value));
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTVarDeclarations>(new ASTVarDeclarations(std::move(mods),
-                                                                      std::move(type),
-                                                                      std::move(vars)));
+    return result;
 }
 
 /* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */
-std::unique_ptr<ASTParameter> Parser::parameter() {
+ASTNode::ID Parser::parameter() {
     Modifiers modifiers = this->modifiersWithDefaults(0);
-    std::unique_ptr<ASTType> type = this->type();
+    ASTNode::ID type = this->type();
     if (!type) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token name;
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::vector<int> sizes;
+    CREATE_NODE(result, name.fOffset, ASTNode::Kind::kParameter);
+    ASTNode::ParameterData pd(modifiers, this->text(name), 0);
+    getNode(result).addChild(type);
     while (this->checkNext(Token::LBRACKET)) {
         Token sizeToken;
         if (!this->expect(Token::INT_LITERAL, "a positive integer", &sizeToken)) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        sizes.push_back(SkSL::stoi(this->text(sizeToken)));
+        CREATE_CHILD(child, result, sizeToken.fOffset, ASTNode::Kind::kInt,
+                     SkSL::stoi(this->text(sizeToken)));
         if (!this->expect(Token::RBRACKET, "']'")) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
+        ++pd.fSizeCount;
     }
-    return std::unique_ptr<ASTParameter>(new ASTParameter(name.fOffset, modifiers, std::move(type),
-                                                          this->text(name), std::move(sizes)));
+    getNode(result).setParameterData(pd);
+    return result;
 }
 
 /** EQ INT_LITERAL */
@@ -672,13 +662,14 @@
 
 
 /** EQ <any sequence of tokens with balanced parentheses and no top-level comma> */
-String Parser::layoutCode() {
+StringFragment Parser::layoutCode() {
     if (!this->expect(Token::EQ, "'='")) {
         return "";
     }
     Token start = this->nextRawToken();
     this->pushback(start);
-    String code;
+    StringFragment code;
+    code.fChars = fText + start.fOffset;
     int level = 1;
     bool done = false;
     while (!done) {
@@ -705,11 +696,9 @@
             done = true;
         }
         if (done) {
+            code.fLength = next.fOffset - start.fOffset;
             this->pushback(std::move(next));
         }
-        else {
-            code += this->text(next);
-        }
     }
     return code;
 }
@@ -778,7 +767,7 @@
     Layout::Primitive primitive = Layout::kUnspecified_Primitive;
     int maxVertices = -1;
     int invocations = -1;
-    String when;
+    StringFragment when;
     Layout::Key key = Layout::kNo_Key;
     Layout::CType ctype = Layout::CType::kDefault;
     if (this->checkNext(Token::LAYOUT)) {
@@ -1024,7 +1013,7 @@
 }
 
 /* ifStatement | forStatement | doStatement | whileStatement | block | expression */
-std::unique_ptr<ASTStatement> Parser::statement() {
+ASTNode::ID Parser::statement() {
     Token start = this->peek();
     switch (start.fKind) {
         case Token::IF: // fall through
@@ -1051,23 +1040,12 @@
             return this->block();
         case Token::SEMICOLON:
             this->nextToken();
-            return std::unique_ptr<ASTStatement>(new ASTBlock(start.fOffset,
-                                                     std::vector<std::unique_ptr<ASTStatement>>()));
-        case Token::CONST: {
-            auto decl = this->varDeclarations();
-            if (!decl) {
-                return nullptr;
-            }
-            return std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(std::move(decl)));
-        }
+            RETURN_NODE(start.fOffset, ASTNode::Kind::kBlock);
+        case Token::CONST:
+            return this->varDeclarations();
         case Token::IDENTIFIER:
             if (this->isType(this->text(start))) {
-                auto decl = this->varDeclarations();
-                if (!decl) {
-                    return nullptr;
-                }
-                return std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(
-                                                                                  std::move(decl)));
+                return this->varDeclarations();
             }
             // fall through
         default:
@@ -1076,227 +1054,232 @@
 }
 
 /* IDENTIFIER(type) (LBRACKET intLiteral? RBRACKET)* QUESTION? */
-std::unique_ptr<ASTType> Parser::type() {
+ASTNode::ID Parser::type() {
     Token type;
     if (!this->expect(Token::IDENTIFIER, "a type", &type)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->isType(this->text(type))) {
         this->error(type, ("no type named '" + this->text(type) + "'").c_str());
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::vector<int> sizes;
+    CREATE_NODE(result, type.fOffset, ASTNode::Kind::kType);
+    ASTNode::TypeData td(this->text(type), false, false);
     while (this->checkNext(Token::LBRACKET)) {
         if (this->peek().fKind != Token::RBRACKET) {
-            int64_t i;
+            SKSL_INT i;
             if (this->intLiteral(&i)) {
-                sizes.push_back(i);
+                CREATE_CHILD(child, result, -1, ASTNode::Kind::kInt, i);
             } else {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
         } else {
-            sizes.push_back(-1);
+            CREATE_EMPTY_CHILD(result);
         }
         this->expect(Token::RBRACKET, "']'");
     }
-    bool nullable = this->checkNext(Token::QUESTION);
-    return std::unique_ptr<ASTType>(new ASTType(type.fOffset, this->text(type),
-                                                ASTType::kIdentifier_Kind, sizes, nullable));
+    td.fIsNullable = this->checkNext(Token::QUESTION);
+    getNode(result).setTypeData(td);
+    return result;
 }
 
 /* IDENTIFIER LBRACE varDeclaration* RBRACE (IDENTIFIER (LBRACKET expression? RBRACKET)*)? */
-std::unique_ptr<ASTDeclaration> Parser::interfaceBlock(Modifiers mods) {
+ASTNode::ID Parser::interfaceBlock(Modifiers mods) {
     Token name;
     if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (peek().fKind != Token::LBRACE) {
         // we only get into interfaceBlock if we found a top-level identifier which was not a type.
         // 99% of the time, the user was not actually intending to create an interface block, so
         // it's better to report it as an unknown type
         this->error(name, "no type named '" + this->text(name) + "'");
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
+    CREATE_NODE(result, name.fOffset, ASTNode::Kind::kInterfaceBlock);
+    ASTNode::InterfaceBlockData id(mods, this->text(name), 0, "", 0);
     this->nextToken();
-    std::vector<std::unique_ptr<ASTVarDeclarations>> decls;
     while (this->peek().fKind != Token::RBRACE) {
-        std::unique_ptr<ASTVarDeclarations> decl = this->varDeclarations();
+        ASTNode::ID decl = this->varDeclarations();
         if (!decl) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        decls.push_back(std::move(decl));
+        getNode(result).addChild(decl);
+        ++id.fDeclarationCount;
     }
     this->nextToken();
-    std::vector<std::unique_ptr<ASTExpression>> sizes;
+    std::vector<ASTNode> sizes;
     StringFragment instanceName;
     Token instanceNameToken;
     if (this->checkNext(Token::IDENTIFIER, &instanceNameToken)) {
+        id.fInstanceName = this->text(instanceNameToken);
         while (this->checkNext(Token::LBRACKET)) {
             if (this->peek().fKind != Token::RBRACKET) {
-                std::unique_ptr<ASTExpression> size = this->expression();
+                ASTNode::ID size = this->expression();
                 if (!size) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                sizes.push_back(std::move(size));
+                getNode(result).addChild(size);
             } else {
-                sizes.push_back(nullptr);
+                CREATE_EMPTY_CHILD(result);
             }
+            ++id.fSizeCount;
             this->expect(Token::RBRACKET, "']'");
         }
         instanceName = this->text(instanceNameToken);
     }
+    getNode(result).setInterfaceBlockData(id);
     this->expect(Token::SEMICOLON, "';'");
-    return std::unique_ptr<ASTDeclaration>(new ASTInterfaceBlock(name.fOffset, mods,
-                                                                 this->text(name),
-                                                                 std::move(decls),
-                                                                 instanceName,
-                                                                 std::move(sizes)));
+    return result;
 }
 
 /* IF LPAREN expression RPAREN statement (ELSE statement)? */
-std::unique_ptr<ASTIfStatement> Parser::ifStatement() {
+ASTNode::ID Parser::ifStatement() {
     Token start;
     bool isStatic = this->checkNext(Token::STATIC_IF, &start);
     if (!isStatic && !this->expect(Token::IF, "'if'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kIf, isStatic);
     if (!this->expect(Token::LPAREN, "'('")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> test(this->expression());
+    ASTNode::ID test = this->expression();
     if (!test) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
+    getNode(result).addChild(test);
     if (!this->expect(Token::RPAREN, "')'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTStatement> ifTrue(this->statement());
+    ASTNode::ID ifTrue = this->statement();
     if (!ifTrue) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTStatement> ifFalse;
+    getNode(result).addChild(ifTrue);
+    ASTNode::ID ifFalse;
     if (this->checkNext(Token::ELSE)) {
         ifFalse = this->statement();
         if (!ifFalse) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
+        getNode(result).addChild(ifFalse);
     }
-    return std::unique_ptr<ASTIfStatement>(new ASTIfStatement(start.fOffset,
-                                                              isStatic,
-                                                              std::move(test),
-                                                              std::move(ifTrue),
-                                                              std::move(ifFalse)));
+    return result;
 }
 
 /* DO statement WHILE LPAREN expression RPAREN SEMICOLON */
-std::unique_ptr<ASTDoStatement> Parser::doStatement() {
+ASTNode::ID Parser::doStatement() {
     Token start;
     if (!this->expect(Token::DO, "'do'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTStatement> statement(this->statement());
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kDo);
+    ASTNode::ID statement = this->statement();
     if (!statement) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
+    getNode(result).addChild(statement);
     if (!this->expect(Token::WHILE, "'while'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LPAREN, "'('")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> test(this->expression());
+    ASTNode::ID test = this->expression();
     if (!test) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
+    getNode(result).addChild(test);
     if (!this->expect(Token::RPAREN, "')'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTDoStatement>(new ASTDoStatement(start.fOffset,
-                                                              std::move(statement),
-                                                              std::move(test)));
+    return result;
 }
 
 /* WHILE LPAREN expression RPAREN STATEMENT */
-std::unique_ptr<ASTWhileStatement> Parser::whileStatement() {
+ASTNode::ID Parser::whileStatement() {
     Token start;
     if (!this->expect(Token::WHILE, "'while'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LPAREN, "'('")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> test(this->expression());
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kWhile);
+    ASTNode::ID test = this->expression();
     if (!test) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
+    getNode(result).addChild(test);
     if (!this->expect(Token::RPAREN, "')'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTStatement> statement(this->statement());
+    ASTNode::ID statement = this->statement();
     if (!statement) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTWhileStatement>(new ASTWhileStatement(start.fOffset,
-                                                                    std::move(test),
-                                                                    std::move(statement)));
+    getNode(result).addChild(statement);
+    return result;
 }
 
 /* CASE expression COLON statement* */
-std::unique_ptr<ASTSwitchCase> Parser::switchCase() {
+ASTNode::ID Parser::switchCase() {
     Token start;
     if (!this->expect(Token::CASE, "'case'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> value = this->expression();
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kSwitchCase);
+    ASTNode::ID value = this->expression();
     if (!value) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::COLON, "':'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::vector<std::unique_ptr<ASTStatement>> statements;
+    getNode(result).addChild(value);
     while (this->peek().fKind != Token::RBRACE && this->peek().fKind != Token::CASE &&
            this->peek().fKind != Token::DEFAULT) {
-        std::unique_ptr<ASTStatement> s = this->statement();
+        ASTNode::ID s = this->statement();
         if (!s) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        statements.push_back(std::move(s));
+        getNode(result).addChild(s);
     }
-    return std::unique_ptr<ASTSwitchCase>(new ASTSwitchCase(start.fOffset, std::move(value),
-                                                            std::move(statements)));
+    return result;
 }
 
 /* SWITCH LPAREN expression RPAREN LBRACE switchCase* (DEFAULT COLON statement*)? RBRACE */
-std::unique_ptr<ASTStatement> Parser::switchStatement() {
+ASTNode::ID Parser::switchStatement() {
     Token start;
     bool isStatic = this->checkNext(Token::STATIC_SWITCH, &start);
     if (!isStatic && !this->expect(Token::SWITCH, "'switch'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LPAREN, "'('")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> value(this->expression());
+    ASTNode::ID value = this->expression();
     if (!value) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::RPAREN, "')'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LBRACE, "'{'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::vector<std::unique_ptr<ASTSwitchCase>> cases;
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kSwitch, isStatic);
+    getNode(result).addChild(value);
     while (this->peek().fKind == Token::CASE) {
-        std::unique_ptr<ASTSwitchCase> c = this->switchCase();
+        ASTNode::ID c = this->switchCase();
         if (!c) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        cases.push_back(std::move(c));
+        getNode(result).addChild(c);
     }
     // Requiring default: to be last (in defiance of C and GLSL) was a deliberate decision. Other
     // parts of the compiler may rely upon this assumption.
@@ -1304,218 +1287,215 @@
         Token defaultStart;
         SkAssertResult(this->expect(Token::DEFAULT, "'default'", &defaultStart));
         if (!this->expect(Token::COLON, "':'")) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        std::vector<std::unique_ptr<ASTStatement>> statements;
+        CREATE_CHILD(defaultCase, result, defaultStart.fOffset, ASTNode::Kind::kSwitchCase);
+        CREATE_EMPTY_CHILD(defaultCase); // empty test to signify default case
         while (this->peek().fKind != Token::RBRACE) {
-            std::unique_ptr<ASTStatement> s = this->statement();
+            ASTNode::ID s = this->statement();
             if (!s) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
-            statements.push_back(std::move(s));
+            getNode(defaultCase).addChild(s);
         }
-        cases.emplace_back(new ASTSwitchCase(defaultStart.fOffset, nullptr,
-                                             std::move(statements)));
     }
     if (!this->expect(Token::RBRACE, "'}'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTStatement>(new ASTSwitchStatement(start.fOffset,
-                                                                isStatic,
-                                                                std::move(value),
-                                                                std::move(cases)));
+    return result;
 }
 
 /* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN
    STATEMENT */
-std::unique_ptr<ASTForStatement> Parser::forStatement() {
+ASTNode::ID Parser::forStatement() {
     Token start;
     if (!this->expect(Token::FOR, "'for'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::LPAREN, "'('")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTStatement> initializer;
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kFor);
+    ASTNode::ID initializer;
     Token nextToken = this->peek();
     switch (nextToken.fKind) {
         case Token::SEMICOLON:
             this->nextToken();
+            CREATE_EMPTY_CHILD(result);
             break;
         case Token::CONST: {
-            std::unique_ptr<ASTVarDeclarations> vd = this->varDeclarations();
-            if (!vd) {
-                return nullptr;
+            initializer = this->varDeclarations();
+            if (!initializer) {
+                return ASTNode::ID::Invalid();
             }
-            initializer = std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(
-                                                                                    std::move(vd)));
+            getNode(result).addChild(initializer);
             break;
         }
         case Token::IDENTIFIER: {
             if (this->isType(this->text(nextToken))) {
-                std::unique_ptr<ASTVarDeclarations> vd = this->varDeclarations();
-                if (!vd) {
-                    return nullptr;
+                initializer = this->varDeclarations();
+                if (!initializer) {
+                    return ASTNode::ID::Invalid();
                 }
-                initializer = std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(
-                                                                                    std::move(vd)));
+                getNode(result).addChild(initializer);
                 break;
             }
         } // fall through
         default:
             initializer = this->expressionStatement();
+            if (!initializer) {
+                return ASTNode::ID::Invalid();
+            }
+            getNode(result).addChild(initializer);
     }
-    std::unique_ptr<ASTExpression> test;
+    ASTNode::ID test;
     if (this->peek().fKind != Token::SEMICOLON) {
         test = this->expression();
         if (!test) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
+        getNode(result).addChild(test);
+    } else {
+        CREATE_EMPTY_CHILD(result);
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> next;
+    ASTNode::ID next;
     if (this->peek().fKind != Token::RPAREN) {
         next = this->expression();
         if (!next) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
+        getNode(result).addChild(next);
+    } else {
+        CREATE_EMPTY_CHILD(result);
     }
     if (!this->expect(Token::RPAREN, "')'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTStatement> statement(this->statement());
+    ASTNode::ID statement = this->statement();
     if (!statement) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTForStatement>(new ASTForStatement(start.fOffset,
-                                                                std::move(initializer),
-                                                                std::move(test), std::move(next),
-                                                                std::move(statement)));
+    getNode(result).addChild(statement);
+    return result;
 }
 
 /* RETURN expression? SEMICOLON */
-std::unique_ptr<ASTReturnStatement> Parser::returnStatement() {
+ASTNode::ID Parser::returnStatement() {
     Token start;
     if (!this->expect(Token::RETURN, "'return'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::unique_ptr<ASTExpression> expression;
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kReturn);
     if (this->peek().fKind != Token::SEMICOLON) {
-        expression = this->expression();
+        ASTNode::ID expression = this->expression();
         if (!expression) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
+        getNode(result).addChild(expression);
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTReturnStatement>(new ASTReturnStatement(start.fOffset,
-                                                                      std::move(expression)));
+    return result;
 }
 
 /* BREAK SEMICOLON */
-std::unique_ptr<ASTBreakStatement> Parser::breakStatement() {
+ASTNode::ID Parser::breakStatement() {
     Token start;
     if (!this->expect(Token::BREAK, "'break'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTBreakStatement>(new ASTBreakStatement(start.fOffset));
+    RETURN_NODE(start.fOffset, ASTNode::Kind::kBreak);
 }
 
 /* CONTINUE SEMICOLON */
-std::unique_ptr<ASTContinueStatement> Parser::continueStatement() {
+ASTNode::ID Parser::continueStatement() {
     Token start;
     if (!this->expect(Token::CONTINUE, "'continue'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTContinueStatement>(new ASTContinueStatement(start.fOffset));
+    RETURN_NODE(start.fOffset, ASTNode::Kind::kContinue);
 }
 
 /* DISCARD SEMICOLON */
-std::unique_ptr<ASTDiscardStatement> Parser::discardStatement() {
+ASTNode::ID Parser::discardStatement() {
     Token start;
     if (!this->expect(Token::DISCARD, "'continue'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     if (!this->expect(Token::SEMICOLON, "';'")) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return std::unique_ptr<ASTDiscardStatement>(new ASTDiscardStatement(start.fOffset));
+    RETURN_NODE(start.fOffset, ASTNode::Kind::kDiscard);
 }
 
 /* LBRACE statement* RBRACE */
-std::unique_ptr<ASTBlock> Parser::block() {
+ASTNode::ID Parser::block() {
     AutoDepth depth(this);
     if (!depth.checkValid()) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token start;
     if (!this->expect(Token::LBRACE, "'{'", &start)) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    std::vector<std::unique_ptr<ASTStatement>> statements;
+    CREATE_NODE(result, start.fOffset, ASTNode::Kind::kBlock);
     for (;;) {
         switch (this->peek().fKind) {
             case Token::RBRACE:
                 this->nextToken();
-                return std::unique_ptr<ASTBlock>(new ASTBlock(start.fOffset,
-                                                              std::move(statements)));
+                return result;
             case Token::END_OF_FILE:
                 this->error(this->peek(), "expected '}', but found end of file");
-                return nullptr;
+                return ASTNode::ID::Invalid();
             default: {
-                std::unique_ptr<ASTStatement> statement = this->statement();
+                ASTNode::ID statement = this->statement();
                 if (!statement) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                statements.push_back(std::move(statement));
+                getNode(result).addChild(statement);
             }
         }
     }
+    return result;
 }
 
 /* expression SEMICOLON */
-std::unique_ptr<ASTExpressionStatement> Parser::expressionStatement() {
-    std::unique_ptr<ASTExpression> expr = this->expression();
+ASTNode::ID Parser::expressionStatement() {
+    ASTNode::ID expr = this->expression();
     if (expr) {
         if (this->expect(Token::SEMICOLON, "';'")) {
-            ASTExpressionStatement* result = new ASTExpressionStatement(std::move(expr));
-            return std::unique_ptr<ASTExpressionStatement>(result);
+            return expr;
         }
     }
-    return nullptr;
-}
-
-/* commaExpression */
-std::unique_ptr<ASTExpression> Parser::expression() {
-    AutoDepth depth(this);
-    if (!depth.checkValid()) {
-        return nullptr;
-    }
-    return this->commaExpression();
+    return ASTNode::ID::Invalid();
 }
 
 /* assignmentExpression (COMMA assignmentExpression)* */
-std::unique_ptr<ASTExpression> Parser::commaExpression() {
-    std::unique_ptr<ASTExpression> result = this->assignmentExpression();
+ASTNode::ID Parser::expression() {
+    ASTNode::ID result = this->assignmentExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::COMMA, &t)) {
-        std::unique_ptr<ASTExpression> right = this->commaExpression();
+        ASTNode::ID right = this->assignmentExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, t.fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
@@ -1524,10 +1504,10 @@
    BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ)
    assignmentExpression)*
  */
-std::unique_ptr<ASTExpression> Parser::assignmentExpression() {
-    std::unique_ptr<ASTExpression> result = this->ternaryExpression();
+ASTNode::ID Parser::assignmentExpression() {
+    ASTNode::ID result = this->ternaryExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
@@ -1546,14 +1526,16 @@
             case Token::LOGICALXOREQ: // fall through
             case Token::LOGICALOREQ: {
                 Token t = this->nextToken();
-                std::unique_ptr<ASTExpression> right = this->assignmentExpression();
+                ASTNode::ID right = this->assignmentExpression();
                 if (!right) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                result = std::unique_ptr<ASTExpression>(new ASTBinaryExpression(std::move(result),
-                                                                                std::move(t),
-                                                                                std::move(right)));
-                return result;
+                CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary,
+                            std::move(t));
+                getNode(newResult).addChild(result);
+                getNode(newResult).addChild(right);
+                result = newResult;
+                break;
             }
             default:
                 return result;
@@ -1562,145 +1544,172 @@
 }
 
 /* logicalOrExpression ('?' expression ':' assignmentExpression)? */
-std::unique_ptr<ASTExpression> Parser::ternaryExpression() {
-    std::unique_ptr<ASTExpression> result = this->logicalOrExpression();
-    if (!result) {
-        return nullptr;
+ASTNode::ID Parser::ternaryExpression() {
+    ASTNode::ID base = this->logicalOrExpression();
+    if (!base) {
+        return ASTNode::ID::Invalid();
     }
     if (this->checkNext(Token::QUESTION)) {
-        std::unique_ptr<ASTExpression> trueExpr = this->expression();
+        ASTNode::ID trueExpr = this->expression();
         if (!trueExpr) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
         if (this->expect(Token::COLON, "':'")) {
-            std::unique_ptr<ASTExpression> falseExpr = this->assignmentExpression();
-            return std::unique_ptr<ASTExpression>(new ASTTernaryExpression(std::move(result),
-                                                                           std::move(trueExpr),
-                                                                           std::move(falseExpr)));
+            ASTNode::ID falseExpr = this->assignmentExpression();
+            if (!falseExpr) {
+                return ASTNode::ID::Invalid();
+            }
+            CREATE_NODE(ternary, getNode(base).fOffset, ASTNode::Kind::kTernary);
+            getNode(ternary).addChild(base);
+            getNode(ternary).addChild(trueExpr);
+            getNode(ternary).addChild(falseExpr);
+            return ternary;
         }
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
-    return result;
+    return base;
 }
 
 /* logicalXorExpression (LOGICALOR logicalXorExpression)* */
-std::unique_ptr<ASTExpression> Parser::logicalOrExpression() {
-    std::unique_ptr<ASTExpression> result = this->logicalXorExpression();
+ASTNode::ID Parser::logicalOrExpression() {
+    ASTNode::ID result = this->logicalXorExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::LOGICALOR, &t)) {
-        std::unique_ptr<ASTExpression> right = this->logicalXorExpression();
+        ASTNode::ID right = this->logicalXorExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
 
 /* logicalAndExpression (LOGICALXOR logicalAndExpression)* */
-std::unique_ptr<ASTExpression> Parser::logicalXorExpression() {
-    std::unique_ptr<ASTExpression> result = this->logicalAndExpression();
+ASTNode::ID Parser::logicalXorExpression() {
+    ASTNode::ID result = this->logicalAndExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::LOGICALXOR, &t)) {
-        std::unique_ptr<ASTExpression> right = this->logicalAndExpression();
+        ASTNode::ID right = this->logicalAndExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
 
 /* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */
-std::unique_ptr<ASTExpression> Parser::logicalAndExpression() {
-    std::unique_ptr<ASTExpression> result = this->bitwiseOrExpression();
+ASTNode::ID Parser::logicalAndExpression() {
+    ASTNode::ID result = this->bitwiseOrExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::LOGICALAND, &t)) {
-        std::unique_ptr<ASTExpression> right = this->bitwiseOrExpression();
+        ASTNode::ID right = this->bitwiseOrExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
 
 /* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */
-std::unique_ptr<ASTExpression> Parser::bitwiseOrExpression() {
-    std::unique_ptr<ASTExpression> result = this->bitwiseXorExpression();
+ASTNode::ID Parser::bitwiseOrExpression() {
+    ASTNode::ID result = this->bitwiseXorExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::BITWISEOR, &t)) {
-        std::unique_ptr<ASTExpression> right = this->bitwiseXorExpression();
+        ASTNode::ID right = this->bitwiseXorExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
 
 /* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */
-std::unique_ptr<ASTExpression> Parser::bitwiseXorExpression() {
-    std::unique_ptr<ASTExpression> result = this->bitwiseAndExpression();
+ASTNode::ID Parser::bitwiseXorExpression() {
+    ASTNode::ID result = this->bitwiseAndExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::BITWISEXOR, &t)) {
-        std::unique_ptr<ASTExpression> right = this->bitwiseAndExpression();
+        ASTNode::ID right = this->bitwiseAndExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
 
 /* equalityExpression (BITWISEAND equalityExpression)* */
-std::unique_ptr<ASTExpression> Parser::bitwiseAndExpression() {
-    std::unique_ptr<ASTExpression> result = this->equalityExpression();
+ASTNode::ID Parser::bitwiseAndExpression() {
+    ASTNode::ID result = this->equalityExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     Token t;
     while (this->checkNext(Token::BITWISEAND, &t)) {
-        std::unique_ptr<ASTExpression> right = this->equalityExpression();
+        ASTNode::ID right = this->equalityExpression();
         if (!right) {
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
-        result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+        CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        getNode(newResult).addChild(result);
+        getNode(newResult).addChild(right);
+        result = newResult;
     }
     return result;
 }
 
 /* relationalExpression ((EQEQ | NEQ) relationalExpression)* */
-std::unique_ptr<ASTExpression> Parser::equalityExpression() {
-    std::unique_ptr<ASTExpression> result = this->relationalExpression();
+ASTNode::ID Parser::equalityExpression() {
+    ASTNode::ID result = this->relationalExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
             case Token::EQEQ:   // fall through
             case Token::NEQ: {
                 Token t = this->nextToken();
-                std::unique_ptr<ASTExpression> right = this->relationalExpression();
+                ASTNode::ID right = this->relationalExpression();
                 if (!right) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                result.reset(new ASTBinaryExpression(std::move(result), std::move(t), std::move(right)));
+                CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary,
+                            std::move(t));
+                getNode(newResult).addChild(result);
+                getNode(newResult).addChild(right);
+                result = newResult;
                 break;
             }
             default:
@@ -1710,10 +1719,10 @@
 }
 
 /* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */
-std::unique_ptr<ASTExpression> Parser::relationalExpression() {
-    std::unique_ptr<ASTExpression> result = this->shiftExpression();
+ASTNode::ID Parser::relationalExpression() {
+    ASTNode::ID result = this->shiftExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
@@ -1722,12 +1731,15 @@
             case Token::LTEQ: // fall through
             case Token::GTEQ: {
                 Token t = this->nextToken();
-                std::unique_ptr<ASTExpression> right = this->shiftExpression();
+                ASTNode::ID right = this->shiftExpression();
                 if (!right) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                result.reset(new ASTBinaryExpression(std::move(result), std::move(t),
-                                                     std::move(right)));
+                CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary,
+                            std::move(t));
+                getNode(newResult).addChild(result);
+                getNode(newResult).addChild(right);
+                result = newResult;
                 break;
             }
             default:
@@ -1737,22 +1749,25 @@
 }
 
 /* additiveExpression ((SHL | SHR) additiveExpression)* */
-std::unique_ptr<ASTExpression> Parser::shiftExpression() {
-    std::unique_ptr<ASTExpression> result = this->additiveExpression();
+ASTNode::ID Parser::shiftExpression() {
+    ASTNode::ID result = this->additiveExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
             case Token::SHL: // fall through
             case Token::SHR: {
                 Token t = this->nextToken();
-                std::unique_ptr<ASTExpression> right = this->additiveExpression();
+                ASTNode::ID right = this->additiveExpression();
                 if (!right) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                result.reset(new ASTBinaryExpression(std::move(result), std::move(t),
-                                                     std::move(right)));
+                CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary,
+                            std::move(t));
+                getNode(newResult).addChild(result);
+                getNode(newResult).addChild(right);
+                result = newResult;
                 break;
             }
             default:
@@ -1762,22 +1777,25 @@
 }
 
 /* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */
-std::unique_ptr<ASTExpression> Parser::additiveExpression() {
-    std::unique_ptr<ASTExpression> result = this->multiplicativeExpression();
+ASTNode::ID Parser::additiveExpression() {
+    ASTNode::ID result = this->multiplicativeExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
             case Token::PLUS: // fall through
             case Token::MINUS: {
                 Token t = this->nextToken();
-                std::unique_ptr<ASTExpression> right = this->multiplicativeExpression();
+                ASTNode::ID right = this->multiplicativeExpression();
                 if (!right) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                result.reset(new ASTBinaryExpression(std::move(result), std::move(t),
-                                                     std::move(right)));
+                CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary,
+                            std::move(t));
+                getNode(newResult).addChild(result);
+                getNode(newResult).addChild(right);
+                result = newResult;
                 break;
             }
             default:
@@ -1787,10 +1805,10 @@
 }
 
 /* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */
-std::unique_ptr<ASTExpression> Parser::multiplicativeExpression() {
-    std::unique_ptr<ASTExpression> result = this->unaryExpression();
+ASTNode::ID Parser::multiplicativeExpression() {
+    ASTNode::ID result = this->unaryExpression();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
@@ -1798,12 +1816,15 @@
             case Token::SLASH: // fall through
             case Token::PERCENT: {
                 Token t = this->nextToken();
-                std::unique_ptr<ASTExpression> right = this->unaryExpression();
+                ASTNode::ID right = this->unaryExpression();
                 if (!right) {
-                    return nullptr;
+                    return ASTNode::ID::Invalid();
                 }
-                result.reset(new ASTBinaryExpression(std::move(result), std::move(t),
-                                                     std::move(right)));
+                CREATE_NODE(newResult, getNode(result).fOffset, ASTNode::Kind::kBinary,
+                            std::move(t));
+                getNode(newResult).addChild(result);
+                getNode(newResult).addChild(right);
+                result = newResult;
                 break;
             }
             default:
@@ -1813,7 +1834,7 @@
 }
 
 /* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */
-std::unique_ptr<ASTExpression> Parser::unaryExpression() {
+ASTNode::ID Parser::unaryExpression() {
     switch (this->peek().fKind) {
         case Token::PLUS:       // fall through
         case Token::MINUS:      // fall through
@@ -1823,15 +1844,16 @@
         case Token::MINUSMINUS: {
             AutoDepth depth(this);
             if (!depth.checkValid()) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
             Token t = this->nextToken();
-            std::unique_ptr<ASTExpression> expr = this->unaryExpression();
+            ASTNode::ID expr = this->unaryExpression();
             if (!expr) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
-            return std::unique_ptr<ASTExpression>(new ASTPrefixExpression(std::move(t),
-                                                                          std::move(expr)));
+            CREATE_NODE(result, t.fOffset, ASTNode::Kind::kPrefix, std::move(t));
+            getNode(result).addChild(expr);
+            return result;
         }
         default:
             return this->postfixExpression();
@@ -1839,10 +1861,10 @@
 }
 
 /* term suffix* */
-std::unique_ptr<ASTExpression> Parser::postfixExpression() {
-    std::unique_ptr<ASTExpression> result = this->term();
+ASTNode::ID Parser::postfixExpression() {
+    ASTNode::ID result = this->term();
     if (!result) {
-        return nullptr;
+        return ASTNode::ID::Invalid();
     }
     for (;;) {
         switch (this->peek().fKind) {
@@ -1851,14 +1873,9 @@
             case Token::LPAREN:     // fall through
             case Token::PLUSPLUS:   // fall through
             case Token::MINUSMINUS: // fall through
-            case Token::COLONCOLON: {
-                std::unique_ptr<ASTSuffix> s = this->suffix();
-                if (!s) {
-                    return nullptr;
-                }
-                result.reset(new ASTSuffixExpression(std::move(result), std::move(s)));
+            case Token::COLONCOLON:
+                result = this->suffix(result);
                 break;
-            }
             default:
                 return result;
         }
@@ -1867,84 +1884,89 @@
 
 /* LBRACKET expression? RBRACKET | DOT IDENTIFIER | LPAREN parameters RPAREN |
    PLUSPLUS | MINUSMINUS | COLONCOLON IDENTIFIER */
-std::unique_ptr<ASTSuffix> Parser::suffix() {
+ASTNode::ID Parser::suffix(ASTNode::ID base) {
     Token next = this->nextToken();
     switch (next.fKind) {
         case Token::LBRACKET: {
             if (this->checkNext(Token::RBRACKET)) {
-                return std::unique_ptr<ASTSuffix>(new ASTIndexSuffix(next.fOffset));
+                CREATE_NODE(result, next.fOffset, ASTNode::Kind::kIndex);
+                getNode(result).addChild(base);
+                return result;
             }
-            std::unique_ptr<ASTExpression> e = this->expression();
+            ASTNode::ID e = this->expression();
             if (!e) {
-                return nullptr;
+                return ASTNode::ID::Invalid();
             }
             this->expect(Token::RBRACKET, "']' to complete array access expression");
-            return std::unique_ptr<ASTSuffix>(new ASTIndexSuffix(std::move(e)));
+            CREATE_NODE(result, next.fOffset, ASTNode::Kind::kIndex);
+            getNode(result).addChild(base);
+            getNode(result).addChild(e);
+            return result;
         }
         case Token::DOT: // fall through
         case Token::COLONCOLON: {
             int offset = this->peek().fOffset;
             StringFragment text;
             if (this->identifier(&text)) {
-                return std::unique_ptr<ASTSuffix>(new ASTFieldSuffix(offset, std::move(text)));
+                CREATE_NODE(result, offset, ASTNode::Kind::kField, std::move(text));
+                getNode(result).addChild(base);
+                return result;
             }
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
         case Token::LPAREN: {
-            std::vector<std::unique_ptr<ASTExpression>> parameters;
+            CREATE_NODE(result, next.fOffset, ASTNode::Kind::kCall);
+            getNode(result).addChild(base);
             if (this->peek().fKind != Token::RPAREN) {
                 for (;;) {
-                    std::unique_ptr<ASTExpression> expr = this->assignmentExpression();
+                    ASTNode::ID expr = this->assignmentExpression();
                     if (!expr) {
-                        return nullptr;
+                        return ASTNode::ID::Invalid();
                     }
-                    parameters.push_back(std::move(expr));
+                    getNode(result).addChild(expr);
                     if (!this->checkNext(Token::COMMA)) {
                         break;
                     }
                 }
             }
             this->expect(Token::RPAREN, "')' to complete function parameters");
-            return std::unique_ptr<ASTSuffix>(new ASTCallSuffix(next.fOffset,
-                                                                std::move(parameters)));
+            return result;
         }
-        case Token::PLUSPLUS:
-            return std::unique_ptr<ASTSuffix>(new ASTSuffix(next.fOffset,
-                                                            ASTSuffix::kPostIncrement_Kind));
-        case Token::MINUSMINUS:
-            return std::unique_ptr<ASTSuffix>(new ASTSuffix(next.fOffset,
-                                                            ASTSuffix::kPostDecrement_Kind));
+        case Token::PLUSPLUS: // fall through
+        case Token::MINUSMINUS: {
+            CREATE_NODE(result, next.fOffset, ASTNode::Kind::kPostfix, next);
+            getNode(result).addChild(base);
+            return result;
+        }
         default: {
             this->error(next,  "expected expression suffix, but found '" + this->text(next) +
                                          "'\n");
-            return nullptr;
+            return ASTNode::ID::Invalid();
         }
     }
 }
 
 /* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | NULL_LITERAL | '(' expression ')' */
-std::unique_ptr<ASTExpression> Parser::term() {
-    std::unique_ptr<ASTExpression> result;
+ASTNode::ID Parser::term() {
     Token t = this->peek();
     switch (t.fKind) {
         case Token::IDENTIFIER: {
             StringFragment text;
             if (this->identifier(&text)) {
-                result.reset(new ASTIdentifier(t.fOffset, std::move(text)));
+                RETURN_NODE(t.fOffset, ASTNode::Kind::kIdentifier, std::move(text));
             }
-            break;
         }
         case Token::INT_LITERAL: {
-            int64_t i;
+            SKSL_INT i;
             if (this->intLiteral(&i)) {
-                result.reset(new ASTIntLiteral(t.fOffset, i));
+                RETURN_NODE(t.fOffset, ASTNode::Kind::kInt, i);
             }
             break;
         }
         case Token::FLOAT_LITERAL: {
-            double f;
+            SKSL_FLOAT f;
             if (this->floatLiteral(&f)) {
-                result.reset(new ASTFloatLiteral(t.fOffset, f));
+                RETURN_NODE(t.fOffset, ASTNode::Kind::kFloat, f);
             }
             break;
         }
@@ -1952,32 +1974,31 @@
         case Token::FALSE_LITERAL: {
             bool b;
             if (this->boolLiteral(&b)) {
-                result.reset(new ASTBoolLiteral(t.fOffset, b));
+                RETURN_NODE(t.fOffset, ASTNode::Kind::kBool, b);
             }
             break;
         }
         case Token::NULL_LITERAL:
             this->nextToken();
-            result.reset(new ASTNullLiteral(t.fOffset));
-            break;
+            RETURN_NODE(t.fOffset, ASTNode::Kind::kNull);
         case Token::LPAREN: {
             this->nextToken();
-            result = this->expression();
+            ASTNode::ID result = this->expression();
             if (result) {
                 this->expect(Token::RPAREN, "')' to complete expression");
+                return result;
             }
             break;
         }
         default:
             this->nextToken();
             this->error(t.fOffset,  "expected expression, but found '" + this->text(t) + "'\n");
-            result = nullptr;
     }
-    return result;
+    return ASTNode::ID::Invalid();
 }
 
 /* INT_LITERAL */
-bool Parser::intLiteral(int64_t* dest) {
+bool Parser::intLiteral(SKSL_INT* dest) {
     Token t;
     if (this->expect(Token::INT_LITERAL, "integer literal", &t)) {
         *dest = SkSL::stol(this->text(t));
@@ -1987,7 +2008,7 @@
 }
 
 /* FLOAT_LITERAL */
-bool Parser::floatLiteral(double* dest) {
+bool Parser::floatLiteral(SKSL_FLOAT* dest) {
     Token t;
     if (this->expect(Token::FLOAT_LITERAL, "float literal", &t)) {
         *dest = SkSL::stod(this->text(t));
diff --git a/src/sksl/SkSLParser.h b/src/sksl/SkSLParser.h
index 9f69ac5..a9eaa49 100644
--- a/src/sksl/SkSLParser.h
+++ b/src/sksl/SkSLParser.h
@@ -12,8 +12,10 @@
 #include <memory>
 #include <unordered_map>
 #include <unordered_set>
+#include "src/sksl/SkSLASTFile.h"
 #include "src/sksl/SkSLErrorReporter.h"
 #include "src/sksl/SkSLLexer.h"
+#include "src/sksl/SkSLASTNode.h"
 #include "src/sksl/ir/SkSLLayout.h"
 
 struct yy_buffer_state;
@@ -22,27 +24,6 @@
 
 namespace SkSL {
 
-struct ASTBlock;
-struct ASTBreakStatement;
-struct ASTContinueStatement;
-struct ASTDeclaration;
-struct ASTDiscardStatement;
-struct ASTDoStatement;
-struct ASTExpression;
-struct ASTExpressionStatement;
-struct ASTForStatement;
-struct ASTIfStatement;
-struct ASTInterfaceBlock;
-struct ASTParameter;
-struct ASTPrecision;
-struct ASTReturnStatement;
-struct ASTStatement;
-struct ASTSuffix;
-struct ASTSwitchCase;
-struct ASTSwitchStatement;
-struct ASTType;
-struct ASTWhileStatement;
-struct ASTVarDeclarations;
 struct Modifiers;
 class SymbolTable;
 
@@ -105,11 +86,10 @@
     Parser(const char* text, size_t length, SymbolTable& types, ErrorReporter& errors);
 
     /**
-     * Consumes a complete .sksl file and produces a list of declarations. Errors are reported via
-     * the ErrorReporter; the return value may contain some declarations even when errors have
-     * occurred.
+     * Consumes a complete .sksl file and returns the parse tree. Errors are reported via the
+     * ErrorReporter; the return value may contain some declarations even when errors have occurred.
      */
-    std::vector<std::unique_ptr<ASTDeclaration>> file();
+    std::unique_ptr<ASTFile> file();
 
     StringFragment text(Token token);
 
@@ -166,37 +146,40 @@
      */
     bool isType(StringFragment name);
 
+    // The pointer to the node may be invalidated by modifying the fNodes vector
+    ASTNode& getNode(ASTNode::ID id) {
+        return fFile->fNodes[id.fValue];
+    }
+
     // these functions parse individual grammar rules from the current parse position; you probably
     // don't need to call any of these outside of the parser. The function declarations in the .cpp
     // file have comments describing the grammar rules.
 
-    std::unique_ptr<ASTDeclaration> precision();
+    ASTNode::ID precision();
 
-    std::unique_ptr<ASTDeclaration> directive();
+    ASTNode::ID directive();
 
-    std::unique_ptr<ASTDeclaration> section();
+    ASTNode::ID section();
 
-    std::unique_ptr<ASTDeclaration> enumDeclaration();
+    ASTNode::ID enumDeclaration();
 
-    std::unique_ptr<ASTDeclaration> declaration();
+    ASTNode::ID declaration();
 
-    std::unique_ptr<ASTVarDeclarations> varDeclarations();
+    ASTNode::ID varDeclarations();
 
-    std::unique_ptr<ASTType> structDeclaration();
+    ASTNode::ID structDeclaration();
 
-    std::unique_ptr<ASTVarDeclarations> structVarDeclaration(Modifiers modifiers);
+    ASTNode::ID structVarDeclaration(Modifiers modifiers);
 
-    std::unique_ptr<ASTVarDeclarations> varDeclarationEnd(Modifiers modifiers,
-                                                          std::unique_ptr<ASTType> type,
-                                                          StringFragment name);
+    ASTNode::ID varDeclarationEnd(Modifiers modifiers, ASTNode::ID type, StringFragment name);
 
-    std::unique_ptr<ASTParameter> parameter();
+    ASTNode::ID parameter();
 
     int layoutInt();
 
     StringFragment layoutIdentifier();
 
-    String layoutCode();
+    StringFragment layoutCode();
 
     Layout::Key layoutKey();
 
@@ -208,77 +191,75 @@
 
     Modifiers modifiersWithDefaults(int defaultFlags);
 
-    std::unique_ptr<ASTStatement> statement();
+    ASTNode::ID statement();
 
-    std::unique_ptr<ASTType> type();
+    ASTNode::ID type();
 
-    std::unique_ptr<ASTDeclaration> interfaceBlock(Modifiers mods);
+    ASTNode::ID interfaceBlock(Modifiers mods);
 
-    std::unique_ptr<ASTIfStatement> ifStatement();
+    ASTNode::ID ifStatement();
 
-    std::unique_ptr<ASTDoStatement> doStatement();
+    ASTNode::ID doStatement();
 
-    std::unique_ptr<ASTWhileStatement> whileStatement();
+    ASTNode::ID whileStatement();
 
-    std::unique_ptr<ASTForStatement> forStatement();
+    ASTNode::ID forStatement();
 
-    std::unique_ptr<ASTSwitchCase> switchCase();
+    ASTNode::ID switchCase();
 
-    std::unique_ptr<ASTStatement> switchStatement();
+    ASTNode::ID switchStatement();
 
-    std::unique_ptr<ASTReturnStatement> returnStatement();
+    ASTNode::ID returnStatement();
 
-    std::unique_ptr<ASTBreakStatement> breakStatement();
+    ASTNode::ID breakStatement();
 
-    std::unique_ptr<ASTContinueStatement> continueStatement();
+    ASTNode::ID continueStatement();
 
-    std::unique_ptr<ASTDiscardStatement> discardStatement();
+    ASTNode::ID discardStatement();
 
-    std::unique_ptr<ASTBlock> block();
+    ASTNode::ID block();
 
-    std::unique_ptr<ASTExpressionStatement> expressionStatement();
+    ASTNode::ID expressionStatement();
 
-    std::unique_ptr<ASTExpression> expression();
+    ASTNode::ID expression();
 
-    std::unique_ptr<ASTExpression> commaExpression();
+    ASTNode::ID assignmentExpression();
 
-    std::unique_ptr<ASTExpression> assignmentExpression();
+    ASTNode::ID ternaryExpression();
 
-    std::unique_ptr<ASTExpression> ternaryExpression();
+    ASTNode::ID logicalOrExpression();
 
-    std::unique_ptr<ASTExpression> logicalOrExpression();
+    ASTNode::ID logicalXorExpression();
 
-    std::unique_ptr<ASTExpression> logicalXorExpression();
+    ASTNode::ID logicalAndExpression();
 
-    std::unique_ptr<ASTExpression> logicalAndExpression();
+    ASTNode::ID bitwiseOrExpression();
 
-    std::unique_ptr<ASTExpression> bitwiseOrExpression();
+    ASTNode::ID bitwiseXorExpression();
 
-    std::unique_ptr<ASTExpression> bitwiseXorExpression();
+    ASTNode::ID bitwiseAndExpression();
 
-    std::unique_ptr<ASTExpression> bitwiseAndExpression();
+    ASTNode::ID equalityExpression();
 
-    std::unique_ptr<ASTExpression> equalityExpression();
+    ASTNode::ID relationalExpression();
 
-    std::unique_ptr<ASTExpression> relationalExpression();
+    ASTNode::ID shiftExpression();
 
-    std::unique_ptr<ASTExpression> shiftExpression();
+    ASTNode::ID additiveExpression();
 
-    std::unique_ptr<ASTExpression> additiveExpression();
+    ASTNode::ID multiplicativeExpression();
 
-    std::unique_ptr<ASTExpression> multiplicativeExpression();
+    ASTNode::ID unaryExpression();
 
-    std::unique_ptr<ASTExpression> unaryExpression();
+    ASTNode::ID postfixExpression();
 
-    std::unique_ptr<ASTExpression> postfixExpression();
+    ASTNode::ID suffix(ASTNode::ID base);
 
-    std::unique_ptr<ASTSuffix> suffix();
+    ASTNode::ID term();
 
-    std::unique_ptr<ASTExpression> term();
+    bool intLiteral(SKSL_INT* dest);
 
-    bool intLiteral(int64_t* dest);
-
-    bool floatLiteral(double* dest);
+    bool floatLiteral(SKSL_FLOAT* dest);
 
     bool boolLiteral(bool* dest);
 
@@ -296,6 +277,8 @@
     SymbolTable& fTypes;
     ErrorReporter& fErrors;
 
+    std::unique_ptr<ASTFile> fFile;
+
     friend class AutoDepth;
     friend class HCodeGenerator;
 };
diff --git a/src/sksl/SkSLPosition.h b/src/sksl/SkSLPosition.h
index d1569d4..96a2f4b 100644
--- a/src/sksl/SkSLPosition.h
+++ b/src/sksl/SkSLPosition.h
@@ -8,6 +8,7 @@
 #ifndef SKSL_POSITION
 #define SKSL_POSITION
 
+#include "src/sksl/SkSLString.h"
 #include "src/sksl/SkSLUtil.h"
 
 namespace SkSL {
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.h b/src/sksl/SkSLSPIRVCodeGenerator.h
index 97266db..5fd7e38 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.h
+++ b/src/sksl/SkSLSPIRVCodeGenerator.h
@@ -40,6 +40,7 @@
 #include "src/sksl/ir/SkSLVarDeclarationsStatement.h"
 #include "src/sksl/ir/SkSLVariableReference.h"
 #include "src/sksl/ir/SkSLWhileStatement.h"
+#include "src/sksl/SkSLStringStream.h"
 #include "src/sksl/spirv.h"
 
 union ConstantValue {
diff --git a/src/sksl/SkSLString.cpp b/src/sksl/SkSLString.cpp
index b0efd77..cc58764 100644
--- a/src/sksl/SkSLString.cpp
+++ b/src/sksl/SkSLString.cpp
@@ -263,16 +263,16 @@
     return String(buffer.str().c_str());
 }
 
-int stoi(const String& s) {
+SKSL_INT stoi(const String& s) {
     char* p;
     SkDEBUGCODE(errno = 0;)
     long result = strtoul(s.c_str(), &p, 0);
     SkASSERT(*p == 0);
     SkASSERT(!errno);
-    return (int) result;
+    return result;
 }
 
-double stod(const String& s) {
+SKSL_FLOAT stod(const String& s) {
     double result;
     std::string str(s.c_str(), s.size());
     std::stringstream buffer(str);
diff --git a/src/sksl/SkSLString.h b/src/sksl/SkSLString.h
index 81c2fef..091634d 100644
--- a/src/sksl/SkSLString.h
+++ b/src/sksl/SkSLString.h
@@ -22,6 +22,8 @@
     #include "include/core/SkString.h"
 #endif
 
+#include "SkSLUtil.h"
+
 namespace SkSL {
 
 // Represents a (not necessarily null-terminated) slice of a string.
@@ -127,9 +129,9 @@
 
 String to_string(uint64_t value);
 
-int stoi(const String& s);
+SKSL_INT stoi(const String& s);
 
-double stod(const String& s);
+SKSL_FLOAT stod(const String& s);
 
 long stol(const String& s);
 
diff --git a/src/sksl/SkSLStringStream.h b/src/sksl/SkSLStringStream.h
index 8de1d40..d34d08d 100644
--- a/src/sksl/SkSLStringStream.h
+++ b/src/sksl/SkSLStringStream.h
@@ -9,6 +9,7 @@
 #define SKSL_STRINGSTREAM
 
 #include "src/sksl/SkSLOutputStream.h"
+#include "src/sksl/SkSLString.h"
 
 #ifdef SKSL_STANDALONE
 
diff --git a/src/sksl/SkSLUtil.cpp b/src/sksl/SkSLUtil.cpp
index 19f5f5a..e8b2d1b 100644
--- a/src/sksl/SkSLUtil.cpp
+++ b/src/sksl/SkSLUtil.cpp
@@ -7,6 +7,8 @@
 
 #include "src/sksl/SkSLUtil.h"
 
+#include "SkSLStringStream.h"
+
 #ifndef __STDC_FORMAT_MACROS
 #define __STDC_FORMAT_MACROS
 #endif
diff --git a/src/sksl/SkSLUtil.h b/src/sksl/SkSLUtil.h
index 7655cb6..e92e38d 100644
--- a/src/sksl/SkSLUtil.h
+++ b/src/sksl/SkSLUtil.h
@@ -14,8 +14,6 @@
 #include "string.h"
 #include "src/sksl/SkSLDefines.h"
 #include "src/sksl/SkSLLexer.h"
-#include "src/sksl/SkSLString.h"
-#include "src/sksl/SkSLStringStream.h"
 
 #ifndef SKSL_STANDALONE
 #include "include/core/SkTypes.h"
@@ -25,6 +23,9 @@
 #endif // SK_SUPPORT_GPU
 #endif // SKSL_STANDALONE
 
+using SKSL_INT = uint32_t;
+using SKSL_FLOAT = float;
+
 class GrShaderCaps;
 
 namespace SkSL {
diff --git a/src/sksl/ast/SkSLASTBinaryExpression.h b/src/sksl/ast/SkSLASTBinaryExpression.h
deleted file mode 100644
index 6a573b5..0000000
--- a/src/sksl/ast/SkSLASTBinaryExpression.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTBINARYEXPRESSION
-#define SKSL_ASTBINARYEXPRESSION
-
-#include "src/sksl/SkSLCompiler.h"
-#include "src/sksl/SkSLLexer.h"
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * Represents a binary operation, with the operator represented by the token's type.
- */
-struct ASTBinaryExpression : public ASTExpression {
-    ASTBinaryExpression(std::unique_ptr<ASTExpression> left, Token op,
-                        std::unique_ptr<ASTExpression> right)
-    : INHERITED(op.fOffset, kBinary_Kind)
-    , fLeft(std::move(left))
-    , fOperator(op.fKind)
-    , fRight(std::move(right)) {}
-
-    String description() const override {
-        return "(" + fLeft->description() + " " + Compiler::OperatorName(fOperator) + " " +
-               fRight->description() + ")";
-    }
-
-    const std::unique_ptr<ASTExpression> fLeft;
-    const Token::Kind fOperator;
-    const std::unique_ptr<ASTExpression> fRight;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTBlock.h b/src/sksl/ast/SkSLASTBlock.h
deleted file mode 100644
index 69995cb..0000000
--- a/src/sksl/ast/SkSLASTBlock.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTBLOCK
-#define SKSL_ASTBLOCK
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * Represents a curly-braced block of statements.
- */
-struct ASTBlock : public ASTStatement {
-    ASTBlock(int offset, std::vector<std::unique_ptr<ASTStatement>> statements)
-    : INHERITED(offset, kBlock_Kind)
-    , fStatements(std::move(statements)) {}
-
-    String description() const override {
-        String result("{");
-        for (size_t i = 0; i < fStatements.size(); i++) {
-            result += "\n";
-            result += fStatements[i]->description();
-        }
-        result += "\n}\n";
-        return result;
-    }
-
-    const std::vector<std::unique_ptr<ASTStatement>> fStatements;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTBoolLiteral.h b/src/sksl/ast/SkSLASTBoolLiteral.h
deleted file mode 100644
index 2fff338..0000000
--- a/src/sksl/ast/SkSLASTBoolLiteral.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTBOOLLITERAL
-#define SKSL_ASTBOOLLITERAL
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * Represents "true" or "false".
- */
-struct ASTBoolLiteral : public ASTExpression {
-    ASTBoolLiteral(int offset, bool value)
-    : INHERITED(offset, kBool_Kind)
-    , fValue(value) {}
-
-    String description() const override {
-        return String(fValue ? "true" : "false");
-    }
-
-    const bool fValue;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTBreakStatement.h b/src/sksl/ast/SkSLASTBreakStatement.h
deleted file mode 100644
index 1335ede..0000000
--- a/src/sksl/ast/SkSLASTBreakStatement.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTBREAKSTATEMENT
-#define SKSL_ASTBREAKSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'break' statement.
- */
-struct ASTBreakStatement : public ASTStatement {
-    ASTBreakStatement(int offset)
-    : INHERITED(offset, kBreak_Kind) {}
-
-    String description() const override {
-        return String("break;");
-    }
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTCallSuffix.h b/src/sksl/ast/SkSLASTCallSuffix.h
deleted file mode 100644
index b249f27..0000000
--- a/src/sksl/ast/SkSLASTCallSuffix.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTCALLSUFFIX
-#define SKSL_ASTCALLSUFFIX
-
-#include <vector>
-#include "src/sksl/ast/SkSLASTSuffix.h"
-
-namespace SkSL {
-
-/**
- * A parenthesized list of arguments following an expression, indicating a function call.
- */
-struct ASTCallSuffix : public ASTSuffix {
-    ASTCallSuffix(int offset, std::vector<std::unique_ptr<ASTExpression>> arguments)
-    : INHERITED(offset, ASTSuffix::kCall_Kind)
-    , fArguments(std::move(arguments)) {}
-
-    String description() const override {
-        String result("(");
-        String separator;
-        for (size_t i = 0; i < fArguments.size(); ++i) {
-            result += separator;
-            separator = ", ";
-            result += fArguments[i]->description();
-        }
-        result += ")";
-        return result;
-    }
-
-    std::vector<std::unique_ptr<ASTExpression>> fArguments;
-
-    typedef ASTSuffix INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTContinueStatement.h b/src/sksl/ast/SkSLASTContinueStatement.h
deleted file mode 100644
index 6866b00..0000000
--- a/src/sksl/ast/SkSLASTContinueStatement.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTCONTINUESTATEMENT
-#define SKSL_ASTCONTINUESTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'continue' statement.
- */
-struct ASTContinueStatement : public ASTStatement {
-    ASTContinueStatement(int offset)
-    : INHERITED(offset, kContinue_Kind) {}
-
-    String description() const override {
-        return String("continue;");
-    }
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTDeclaration.h b/src/sksl/ast/SkSLASTDeclaration.h
deleted file mode 100644
index 9f260f3..0000000
--- a/src/sksl/ast/SkSLASTDeclaration.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTDECLARATION
-#define SKSL_ASTDECLARATION
-
-#include "src/sksl/ast/SkSLASTPositionNode.h"
-
-namespace SkSL {
-
-/**
- * Abstract supertype of declarations such as variables and functions.
- */
-struct ASTDeclaration : public ASTPositionNode {
-    enum Kind {
-        kVar_Kind,
-        kFunction_Kind,
-        kInterfaceBlock_Kind,
-        kExtension_Kind,
-        kPrecision_Kind,
-        kModifiers_Kind,
-        kSection_Kind,
-        kEnum_Kind
-    };
-
-    ASTDeclaration(int offset, Kind kind)
-    : INHERITED(offset)
-    , fKind(kind) {}
-
-    Kind fKind;
-
-    typedef ASTPositionNode INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTDiscardStatement.h b/src/sksl/ast/SkSLASTDiscardStatement.h
deleted file mode 100644
index 3cae08a..0000000
--- a/src/sksl/ast/SkSLASTDiscardStatement.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTDISCARDSTATEMENT
-#define SKSL_ASTDISCARDSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'discard' statement.
- */
-struct ASTDiscardStatement : public ASTStatement {
-    ASTDiscardStatement(int offset)
-    : INHERITED(offset, kDiscard_Kind) {}
-
-    String description() const override {
-        return String("discard;");
-    }
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTDoStatement.h b/src/sksl/ast/SkSLASTDoStatement.h
deleted file mode 100644
index 22fcca2..0000000
--- a/src/sksl/ast/SkSLASTDoStatement.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTDOSTATEMENT
-#define SKSL_ASTDOSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'do' loop.
- */
-struct ASTDoStatement : public ASTStatement {
-    ASTDoStatement(int offset, std::unique_ptr<ASTStatement> statement,
-                   std::unique_ptr<ASTExpression> test)
-    : INHERITED(offset, kDo_Kind)
-    , fStatement(std::move(statement))
-    , fTest(std::move(test)) {}
-
-    String description() const override {
-        return "do " + fStatement->description() + " while (" + fTest->description() + ");";
-    }
-
-    const std::unique_ptr<ASTStatement> fStatement;
-    const std::unique_ptr<ASTExpression> fTest;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTEnum.h b/src/sksl/ast/SkSLASTEnum.h
deleted file mode 100644
index 16f9c62..0000000
--- a/src/sksl/ast/SkSLASTEnum.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTENUM
-#define SKSL_ASTENUM
-
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-namespace SkSL {
-
-struct ASTEnum : public ASTDeclaration {
-    ASTEnum(int offset, StringFragment typeName, std::vector<StringFragment> names,
-            std::vector<std::unique_ptr<ASTExpression>> values)
-    : INHERITED(offset, kEnum_Kind)
-    , fTypeName(typeName)
-    , fNames(std::move(names))
-    , fValues(std::move(values)) {
-        SkASSERT(fNames.size() == fValues.size());
-    }
-
-    String description() const override {
-        String result = "enum class " + fTypeName + " {\n";
-        String separator;
-        for (StringFragment name : fNames) {
-            result += separator + "    " + name;
-            separator = ",\n";
-        }
-        result += "};";
-        return result;
-    }
-
-    const StringFragment fTypeName;
-    const std::vector<StringFragment> fNames;
-    const std::vector<std::unique_ptr<ASTExpression>> fValues;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTExpression.h b/src/sksl/ast/SkSLASTExpression.h
deleted file mode 100644
index efff173..0000000
--- a/src/sksl/ast/SkSLASTExpression.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTEXPRESSION
-#define SKSL_ASTEXPRESSION
-
-#include "src/sksl/ast/SkSLASTPositionNode.h"
-
-namespace SkSL {
-
-/**
- * Abstract supertype of all expressions.
- */
-struct ASTExpression : public ASTPositionNode {
-    enum Kind {
-        kBinary_Kind,
-        kBool_Kind,
-        kFloat_Kind,
-        kIdentifier_Kind,
-        kInt_Kind,
-        kNull_Kind,
-        kPrefix_Kind,
-        kSuffix_Kind,
-        kTernary_Kind
-    };
-
-    ASTExpression(int offset, Kind kind)
-    : INHERITED(offset)
-    , fKind(kind) {}
-
-    const Kind fKind;
-
-    typedef ASTPositionNode INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTExpressionStatement.h b/src/sksl/ast/SkSLASTExpressionStatement.h
deleted file mode 100644
index 0e18d2e..0000000
--- a/src/sksl/ast/SkSLASTExpressionStatement.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTEXPRESSIONSTATEMENT
-#define SKSL_ASTEXPRESSIONSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A lone expression being used as a statement.
- */
-struct ASTExpressionStatement : public ASTStatement {
-    ASTExpressionStatement(std::unique_ptr<ASTExpression> expression)
-    : INHERITED(expression->fOffset, kExpression_Kind)
-    , fExpression(std::move(expression)) {}
-
-    String description() const override {
-        return fExpression->description() + ";";
-    }
-
-    const std::unique_ptr<ASTExpression> fExpression;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTExtension.h b/src/sksl/ast/SkSLASTExtension.h
deleted file mode 100644
index 39865bf..0000000
--- a/src/sksl/ast/SkSLASTExtension.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTEXTENSION
-#define SKSL_ASTEXTENSION
-
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-
-namespace SkSL {
-
-/**
- * An extension declaration.
- */
-struct ASTExtension : public ASTDeclaration {
-    ASTExtension(int offset, String name)
-    : INHERITED(offset, kExtension_Kind)
-    , fName(std::move(name)) {}
-
-    String description() const override {
-        return "#extension " + fName + " : enable";
-    }
-
-    const String fName;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTFieldSuffix.h b/src/sksl/ast/SkSLASTFieldSuffix.h
deleted file mode 100644
index 48653ca..0000000
--- a/src/sksl/ast/SkSLASTFieldSuffix.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTFIELDSUFFIX
-#define SKSL_ASTFIELDSUFFIX
-
-#include "src/sksl/ast/SkSLASTSuffix.h"
-
-namespace SkSL {
-
-/**
- * A dotted identifier of the form ".foo". We refer to these as "fields" at parse time even if it is
- * actually vector swizzle (which looks the same to the parser).
- */
-struct ASTFieldSuffix : public ASTSuffix {
-    ASTFieldSuffix(int offset, StringFragment field)
-    : INHERITED(offset, ASTSuffix::kField_Kind)
-    , fField(field) {}
-
-    String description() const override {
-        return "." + fField;
-    }
-
-    StringFragment fField;
-
-    typedef ASTSuffix INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTFloatLiteral.h b/src/sksl/ast/SkSLASTFloatLiteral.h
deleted file mode 100644
index e05aeb4..0000000
--- a/src/sksl/ast/SkSLASTFloatLiteral.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTFLOATLITERAL
-#define SKSL_ASTFLOATLITERAL
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * A literal floating point number.
- */
-struct ASTFloatLiteral : public ASTExpression {
-    ASTFloatLiteral(int offset, double value)
-    : INHERITED(offset, kFloat_Kind)
-    , fValue(value) {}
-
-    String description() const override {
-        return to_string(fValue);
-    }
-
-    const double fValue;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTForStatement.h b/src/sksl/ast/SkSLASTForStatement.h
deleted file mode 100644
index 2597257..0000000
--- a/src/sksl/ast/SkSLASTForStatement.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTFORSTATEMENT
-#define SKSL_ASTFORSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'for' loop.
- */
-struct ASTForStatement : public ASTStatement {
-    ASTForStatement(int offset, std::unique_ptr<ASTStatement> initializer,
-                   std::unique_ptr<ASTExpression> test, std::unique_ptr<ASTExpression> next,
-                   std::unique_ptr<ASTStatement> statement)
-    : INHERITED(offset, kFor_Kind)
-    , fInitializer(std::move(initializer))
-    , fTest(std::move(test))
-    , fNext(std::move(next))
-    , fStatement(std::move(statement)) {}
-
-    String description() const override {
-        String result("for (");
-        if (fInitializer) {
-            result.append(fInitializer->description());
-        }
-        result += " ";
-        if (fTest) {
-            result.append(fTest->description());
-        }
-        result += "; ";
-        if (fNext) {
-            result.append(fNext->description());
-        }
-        result += ") ";
-        result += fStatement->description();
-        return result;
-    }
-
-    const std::unique_ptr<ASTStatement> fInitializer;
-    const std::unique_ptr<ASTExpression> fTest;
-    const std::unique_ptr<ASTExpression> fNext;
-    const std::unique_ptr<ASTStatement> fStatement;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTFunction.h b/src/sksl/ast/SkSLASTFunction.h
deleted file mode 100644
index f03631b..0000000
--- a/src/sksl/ast/SkSLASTFunction.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTFUNCTION
-#define SKSL_ASTFUNCTION
-
-#include "src/sksl/ast/SkSLASTBlock.h"
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-#include "src/sksl/ast/SkSLASTParameter.h"
-#include "src/sksl/ast/SkSLASTType.h"
-
-namespace SkSL {
-
-/**
- * A function declaration or definition. The fBody field will be null for declarations.
- */
-struct ASTFunction : public ASTDeclaration {
-    ASTFunction(int offset, Modifiers modifiers,  std::unique_ptr<ASTType> returnType,
-                StringFragment name, std::vector<std::unique_ptr<ASTParameter>> parameters,
-                std::unique_ptr<ASTBlock> body)
-    : INHERITED(offset, kFunction_Kind)
-    , fModifiers(modifiers)
-    , fReturnType(std::move(returnType))
-    , fName(name)
-    , fParameters(std::move(parameters))
-    , fBody(std::move(body)) {}
-
-    String description() const override {
-        String result = fReturnType->description() + " " + fName + "(";
-        for (size_t i = 0; i < fParameters.size(); i++) {
-            if (i > 0) {
-                result += ", ";
-            }
-            result += fParameters[i]->description();
-        }
-        if (fBody) {
-            result += ") " + fBody->description();
-        } else {
-            result += ");";
-        }
-        return result;
-    }
-
-    const Modifiers fModifiers;
-    const std::unique_ptr<ASTType> fReturnType;
-    const StringFragment fName;
-    const std::vector<std::unique_ptr<ASTParameter>> fParameters;
-    const std::unique_ptr<ASTBlock> fBody;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTIdentifier.h b/src/sksl/ast/SkSLASTIdentifier.h
deleted file mode 100644
index 47f4296..0000000
--- a/src/sksl/ast/SkSLASTIdentifier.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTIDENTIFIER
-#define SKSL_ASTIDENTIFIER
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * An identifier in an expression context.
- */
-struct ASTIdentifier : public ASTExpression {
-    ASTIdentifier(int offset, StringFragment text)
-    : INHERITED(offset, kIdentifier_Kind)
-    , fText(text) {}
-
-    String description() const override {
-        return String(fText);
-    }
-
-    const StringFragment fText;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTIfStatement.h b/src/sksl/ast/SkSLASTIfStatement.h
deleted file mode 100644
index 64da88a..0000000
--- a/src/sksl/ast/SkSLASTIfStatement.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTIFSTATEMENT
-#define SKSL_ASTIFSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * An 'if' statement.
- */
-struct ASTIfStatement : public ASTStatement {
-    ASTIfStatement(int offset, bool isStatic, std::unique_ptr<ASTExpression> test,
-                   std::unique_ptr<ASTStatement> ifTrue, std::unique_ptr<ASTStatement> ifFalse)
-    : INHERITED(offset, kIf_Kind)
-    , fIsStatic(isStatic)
-    , fTest(std::move(test))
-    , fIfTrue(std::move(ifTrue))
-    , fIfFalse(std::move(ifFalse)) {}
-
-    String description() const override {
-        String result;
-        if (fIsStatic) {
-            result += "@";
-        }
-        result += "if (";
-        result += fTest->description();
-        result += ") ";
-        result += fIfTrue->description();
-        if (fIfFalse) {
-            result += " else ";
-            result += fIfFalse->description();
-        }
-        return result;
-    }
-
-    const bool fIsStatic;
-    const std::unique_ptr<ASTExpression> fTest;
-    const std::unique_ptr<ASTStatement> fIfTrue;
-    const std::unique_ptr<ASTStatement> fIfFalse;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTIndexSuffix.h b/src/sksl/ast/SkSLASTIndexSuffix.h
deleted file mode 100644
index d192d2c..0000000
--- a/src/sksl/ast/SkSLASTIndexSuffix.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTINDEXSUFFIX
-#define SKSL_ASTINDEXSUFFIX
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-#include "src/sksl/ast/SkSLASTSuffix.h"
-
-namespace SkSL {
-
-/**
- * A bracketed expression, as in '[0]', indicating an array access. Empty brackets (as occur in
- * 'float[](5, 6)' are represented with a null fExpression.
- */
-struct ASTIndexSuffix : public ASTSuffix {
-    ASTIndexSuffix(int offset)
-    : INHERITED(offset, ASTSuffix::kIndex_Kind)
-    , fExpression(nullptr) {}
-
-    ASTIndexSuffix(std::unique_ptr<ASTExpression> expression)
-    : INHERITED(expression ? expression->fOffset : -1, ASTSuffix::kIndex_Kind)
-    , fExpression(std::move(expression)) {}
-
-    String description() const override {
-        if (fExpression) {
-            return "[" + fExpression->description() + "]";
-        } else {
-            return String("[]");
-        }
-    }
-
-    // may be null
-    std::unique_ptr<ASTExpression> fExpression;
-
-    typedef ASTSuffix INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTIntLiteral.h b/src/sksl/ast/SkSLASTIntLiteral.h
deleted file mode 100644
index 6554d51..0000000
--- a/src/sksl/ast/SkSLASTIntLiteral.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTINTLITERAL
-#define SKSL_ASTINTLITERAL
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * A literal integer. At the AST level, integer literals are always positive; a negative number will
- * appear as a unary minus being applied to an integer literal.
- */
-struct ASTIntLiteral : public ASTExpression {
-    ASTIntLiteral(int offset, uint64_t value)
-    : INHERITED(offset, kInt_Kind)
-    , fValue(value) {}
-
-    String description() const override {
-        return to_string(fValue);
-    }
-
-    const uint64_t fValue;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTInterfaceBlock.h b/src/sksl/ast/SkSLASTInterfaceBlock.h
deleted file mode 100644
index 6fae28c..0000000
--- a/src/sksl/ast/SkSLASTInterfaceBlock.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTINTERFACEBLOCK
-#define SKSL_ASTINTERFACEBLOCK
-
-#include "src/sksl/ast/SkSLASTVarDeclaration.h"
-#include "src/sksl/ir/SkSLModifiers.h"
-
-namespace SkSL {
-
-/**
- * An interface block, as in:
- *
- * out sk_PerVertex {
- *   layout(builtin=0) float4 sk_Position;
- *   layout(builtin=1) float sk_PointSize;
- * };
- */
-struct ASTInterfaceBlock : public ASTDeclaration {
-    // valueName is empty when it was not present in the source
-    ASTInterfaceBlock(int offset,
-                      Modifiers modifiers,
-                      StringFragment typeName,
-                      std::vector<std::unique_ptr<ASTVarDeclarations>> declarations,
-                      StringFragment instanceName,
-                      std::vector<std::unique_ptr<ASTExpression>> sizes)
-    : INHERITED(offset, kInterfaceBlock_Kind)
-    , fModifiers(modifiers)
-    , fTypeName(typeName)
-    , fDeclarations(std::move(declarations))
-    , fInstanceName(instanceName)
-    , fSizes(std::move(sizes)) {}
-
-    String description() const override {
-        String result = fModifiers.description() + fTypeName + " {\n";
-        for (size_t i = 0; i < fDeclarations.size(); i++) {
-            result += fDeclarations[i]->description() + "\n";
-        }
-        result += "}";
-        if (fInstanceName.fLength) {
-            result += " " + fInstanceName;
-            for (const auto& size : fSizes) {
-                result += "[";
-                if (size) {
-                    result += size->description();
-                }
-                result += "]";
-            }
-        }
-        return result + ";";
-    }
-
-    const Modifiers fModifiers;
-    const StringFragment fTypeName;
-    const std::vector<std::unique_ptr<ASTVarDeclarations>> fDeclarations;
-    const StringFragment fInstanceName;
-    const std::vector<std::unique_ptr<ASTExpression>> fSizes;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTModifiersDeclaration.h b/src/sksl/ast/SkSLASTModifiersDeclaration.h
deleted file mode 100644
index dd1531a..0000000
--- a/src/sksl/ast/SkSLASTModifiersDeclaration.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTMODIFIERDECLARATION
-#define SKSL_ASTMODIFIERDECLARATION
-
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-#include "src/sksl/ir/SkSLModifiers.h"
-
-namespace SkSL {
-
-/**
- * A declaration that consists only of modifiers, e.g.:
- *
- * layout(blend_support_all_equations) out;
- */
-struct ASTModifiersDeclaration : public ASTDeclaration {
-    ASTModifiersDeclaration(Modifiers modifiers)
-    : INHERITED(-1, kModifiers_Kind)
-    , fModifiers(modifiers) {}
-
-    String description() const {
-        return fModifiers.description() + ";";
-    }
-
-    Modifiers fModifiers;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTNode.h b/src/sksl/ast/SkSLASTNode.h
deleted file mode 100644
index 1b5f2bd..0000000
--- a/src/sksl/ast/SkSLASTNode.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTNODE
-#define SKSL_ASTNODE
-
-#include "src/sksl/SkSLString.h"
-
-namespace SkSL {
-
-/**
- * Represents a node in the abstract syntax tree (AST). The AST is based directly on the parse tree;
- * it is a parsed-but-not-yet-analyzed version of the program.
- */
-struct ASTNode {
-    virtual ~ASTNode() {}
-
-    virtual String description() const = 0;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTNullLiteral.h b/src/sksl/ast/SkSLASTNullLiteral.h
deleted file mode 100644
index 6ea27dc..0000000
--- a/src/sksl/ast/SkSLASTNullLiteral.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTNULLLITERAL
-#define SKSL_ASTNULLLITERAL
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * Represents "null".
- */
-struct ASTNullLiteral : public ASTExpression {
-    ASTNullLiteral(int offset)
-    : INHERITED(offset, kNull_Kind) {}
-
-    String description() const override {
-        return "null";
-    }
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTParameter.h b/src/sksl/ast/SkSLASTParameter.h
deleted file mode 100644
index d5ba55a..0000000
--- a/src/sksl/ast/SkSLASTParameter.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTPARAMETER
-#define SKSL_ASTPARAMETER
-
-#include "src/sksl/ast/SkSLASTPositionNode.h"
-#include "src/sksl/ast/SkSLASTType.h"
-#include "src/sksl/ir/SkSLModifiers.h"
-
-namespace SkSL {
-
-/**
- * A declaration of a parameter, as part of a function declaration.
- */
-struct ASTParameter : public ASTPositionNode {
-    // 'sizes' is a list of the array sizes appearing on a parameter, in source order.
-    // e.g. int x[3][1] would have sizes [3, 1].
-    ASTParameter(int offset, Modifiers modifiers, std::unique_ptr<ASTType> type,
-                 StringFragment name, std::vector<int> sizes)
-    : INHERITED(offset)
-    , fModifiers(modifiers)
-    , fType(std::move(type))
-    , fName(name)
-    , fSizes(std::move(sizes)) {}
-
-    String description() const override {
-        String result = fModifiers.description() + fType->description() + " " + fName;
-        for (int size : fSizes) {
-            result += "[" + to_string(size) + "]";
-        }
-        return result;
-    }
-
-    const Modifiers fModifiers;
-    const std::unique_ptr<ASTType> fType;
-    const StringFragment fName;
-    const std::vector<int> fSizes;
-
-    typedef ASTPositionNode INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTPositionNode.h b/src/sksl/ast/SkSLASTPositionNode.h
deleted file mode 100644
index a9e3d54..0000000
--- a/src/sksl/ast/SkSLASTPositionNode.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTPOSITIONNODE
-#define SKSL_ASTPOSITIONNODE
-
-#include "src/sksl/SkSLPosition.h"
-#include "src/sksl/ast/SkSLASTNode.h"
-
-namespace SkSL {
-
-/**
- * An AST node with an associated position in the source.
- */
-struct ASTPositionNode : public ASTNode {
-    ASTPositionNode(int offset)
-    : fOffset(offset) {}
-
-    // character offset of this element within the program being compiled, for error reporting
-    // purposes
-    const int fOffset;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTPrecision.h b/src/sksl/ast/SkSLASTPrecision.h
deleted file mode 100644
index 06d083d..0000000
--- a/src/sksl/ast/SkSLASTPrecision.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTPRECISION
-#define SKSL_ASTPRECISION
-
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-#include "src/sksl/ir/SkSLModifiers.h"
-
-namespace SkSL {
-
-/**
- * Represents a precision declaration (e.g. 'precision mediump float;').
- */
-struct ASTPrecision : public ASTDeclaration {
-    // FIXME handle the type
-    ASTPrecision(int offset, Modifiers::Flag precision)
-    : INHERITED(offset, kPrecision_Kind)
-    , fPrecision(precision) {}
-
-    String description() const {
-        switch (fPrecision) {
-            case Modifiers::kLowp_Flag: return String("precision lowp float;");
-            case Modifiers::kMediump_Flag: return String("precision mediump float;");
-            case Modifiers::kHighp_Flag: return String("precision highp float;");
-            default:
-                SkASSERT(false);
-                return String("<error>");
-        }
-        SkASSERT(false);
-        return String("<error>");
-    }
-
-    const Modifiers::Flag fPrecision;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTPrefixExpression.h b/src/sksl/ast/SkSLASTPrefixExpression.h
deleted file mode 100644
index 53150f8..0000000
--- a/src/sksl/ast/SkSLASTPrefixExpression.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTPREFIXEXPRESSION
-#define SKSL_ASTPREFIXEXPRESSION
-
-#include "src/sksl/SkSLCompiler.h"
-#include "src/sksl/SkSLLexer.h"
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * An expression modified by a unary operator appearing in front of it, such as '-x' or '!inside'.
- */
-struct ASTPrefixExpression : public ASTExpression {
-    ASTPrefixExpression(Token op, std::unique_ptr<ASTExpression> operand)
-    : INHERITED(op.fOffset, kPrefix_Kind)
-    , fOperator(op.fKind)
-    , fOperand(std::move(operand)) {}
-
-    String description() const override {
-        return Compiler::OperatorName(fOperator) + fOperand->description();
-    }
-
-    const Token::Kind fOperator;
-    const std::unique_ptr<ASTExpression> fOperand;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTReturnStatement.h b/src/sksl/ast/SkSLASTReturnStatement.h
deleted file mode 100644
index 77b6970..0000000
--- a/src/sksl/ast/SkSLASTReturnStatement.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTRETURNSTATEMENT
-#define SKSL_ASTRETURNSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'return' statement.
- */
-struct ASTReturnStatement : public ASTStatement {
-    // expression may be null
-    ASTReturnStatement(int offset, std::unique_ptr<ASTExpression> expression)
-    : INHERITED(offset, kReturn_Kind)
-    , fExpression(std::move(expression)) {}
-
-    String description() const override {
-        String result("return");
-        if (fExpression) {
-            result += " " + fExpression->description();
-        }
-        return result + ";";
-    }
-
-    const std::unique_ptr<ASTExpression> fExpression;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTSection.h b/src/sksl/ast/SkSLASTSection.h
deleted file mode 100644
index bd4655c..0000000
--- a/src/sksl/ast/SkSLASTSection.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTSECTION
-#define SKSL_ASTSECTION
-
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-
-namespace SkSL {
-
-/**
- * A section declaration (e.g. @body { body code here })..
- */
-struct ASTSection : public ASTDeclaration {
-    ASTSection(int offset, String name, String arg, String text)
-    : INHERITED(offset, kSection_Kind)
-    , fName(std::move(name))
-    , fArgument(std::move(arg))
-    , fText(std::move(text)) {}
-
-    String description() const override {
-        String result = "@" + fName;
-        if (fArgument.size()) {
-            result += "(" + fArgument + ")";
-        }
-        result += " { " + fText + " }";
-        return result;
-    }
-
-    const String fName;
-    const String fArgument;
-    const String fText;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTStatement.h b/src/sksl/ast/SkSLASTStatement.h
deleted file mode 100644
index d672e22..0000000
--- a/src/sksl/ast/SkSLASTStatement.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTSTATEMENT
-#define SKSL_ASTSTATEMENT
-
-#include <vector>
-#include "src/sksl/ast/SkSLASTExpression.h"
-#include "src/sksl/ast/SkSLASTPositionNode.h"
-
-namespace SkSL {
-
-/**
- * Abstract supertype of all statements.
- */
-struct ASTStatement : public ASTPositionNode {
-    enum Kind {
-        kBlock_Kind,
-        kVarDeclaration_Kind,
-        kExpression_Kind,
-        kIf_Kind,
-        kFor_Kind,
-        kWhile_Kind,
-        kDo_Kind,
-        kSwitch_Kind,
-        kReturn_Kind,
-        kBreak_Kind,
-        kContinue_Kind,
-        kDiscard_Kind
-    };
-
-    ASTStatement(int offset, Kind kind)
-    : INHERITED(offset)
-    , fKind(kind) {}
-
-    Kind fKind;
-
-    typedef ASTPositionNode INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTSuffix.h b/src/sksl/ast/SkSLASTSuffix.h
deleted file mode 100644
index 2d44d60..0000000
--- a/src/sksl/ast/SkSLASTSuffix.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTSUFFIX
-#define SKSL_ASTSUFFIX
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-#include "src/sksl/ast/SkSLASTPositionNode.h"
-
-namespace SkSL {
-
-/**
- * This and its subclasses represents expression suffixes, such as '[0]' or '.rgb'. Suffixes are not
- * expressions in and of themselves; they are attached to expressions to modify them.
- */
-struct ASTSuffix : public ASTPositionNode {
-    enum Kind {
-        kIndex_Kind,
-        kCall_Kind,
-        kField_Kind,
-        kPostIncrement_Kind,
-        kPostDecrement_Kind
-    };
-
-    ASTSuffix(int offset, Kind kind)
-    : INHERITED(offset)
-    , fKind(kind) {}
-
-    String description() const override {
-        switch (fKind) {
-            case kPostIncrement_Kind:
-                return String("++");
-            case kPostDecrement_Kind:
-                return String("--");
-            default:
-                ABORT("unsupported suffix operator");
-        }
-    }
-
-    Kind fKind;
-
-    typedef ASTPositionNode INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTSuffixExpression.h b/src/sksl/ast/SkSLASTSuffixExpression.h
deleted file mode 100644
index cbafe4e..0000000
--- a/src/sksl/ast/SkSLASTSuffixExpression.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTSUFFIXEXPRESSION
-#define SKSL_ASTSUFFIXEXPRESSION
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-#include "src/sksl/ast/SkSLASTSuffix.h"
-
-namespace SkSL {
-
-/**
- * An expression with an associated suffix.
- */
-struct ASTSuffixExpression : public ASTExpression {
-    ASTSuffixExpression(std::unique_ptr<ASTExpression> base, std::unique_ptr<ASTSuffix> suffix)
-    : INHERITED(base->fOffset, kSuffix_Kind)
-    , fBase(std::move(base))
-    , fSuffix(std::move(suffix)) {}
-
-    String description() const override {
-        return fBase->description() + fSuffix->description();
-    }
-
-    const std::unique_ptr<ASTExpression> fBase;
-    const std::unique_ptr<ASTSuffix> fSuffix;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTSwitchCase.h b/src/sksl/ast/SkSLASTSwitchCase.h
deleted file mode 100644
index 9bbab39..0000000
--- a/src/sksl/ast/SkSLASTSwitchCase.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTSWITCHCASE
-#define SKSL_ASTSWITCHCASE
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A single case of a 'switch' statement.
- */
-struct ASTSwitchCase : public ASTStatement {
-    // a null value means "default:"
-    ASTSwitchCase(int offset, std::unique_ptr<ASTExpression> value,
-                  std::vector<std::unique_ptr<ASTStatement>> statements)
-    : INHERITED(offset, kSwitch_Kind)
-    , fValue(std::move(value))
-    , fStatements(std::move(statements)) {}
-
-    String description() const override {
-        String result;
-        if (fValue) {
-            result.appendf("case %s:\n", fValue->description().c_str());
-        } else {
-            result += "default:\n";
-        }
-        for (const auto& s : fStatements) {
-            result += s->description() + "\n";
-        }
-        return result;
-    }
-
-    // null value implies "default" case
-    const std::unique_ptr<ASTExpression> fValue;
-    const std::vector<std::unique_ptr<ASTStatement>> fStatements;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTSwitchStatement.h b/src/sksl/ast/SkSLASTSwitchStatement.h
deleted file mode 100644
index af0c05d..0000000
--- a/src/sksl/ast/SkSLASTSwitchStatement.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTSWITCHSTATEMENT
-#define SKSL_ASTSWITCHSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-#include "src/sksl/ast/SkSLASTSwitchCase.h"
-
-namespace SkSL {
-
-/**
- * A 'switch' statement.
- */
-struct ASTSwitchStatement : public ASTStatement {
-    ASTSwitchStatement(int offset, bool isStatic, std::unique_ptr<ASTExpression> value,
-                       std::vector<std::unique_ptr<ASTSwitchCase>> cases)
-    : INHERITED(offset, kSwitch_Kind)
-    , fIsStatic(isStatic)
-    , fValue(std::move(value))
-    , fCases(std::move(cases)) {}
-
-    String description() const override {
-        String result;
-        if (fIsStatic) {
-            result += "@";
-        }
-        result += String::printf("switch (%s) {\n", fValue->description().c_str());
-        for (const auto& c : fCases) {
-            result += c->description();
-        }
-        result += "}";
-        return result;
-    }
-
-    bool fIsStatic;
-    const std::unique_ptr<ASTExpression> fValue;
-    const std::vector<std::unique_ptr<ASTSwitchCase>> fCases;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTTernaryExpression.h b/src/sksl/ast/SkSLASTTernaryExpression.h
deleted file mode 100644
index b78acb1..0000000
--- a/src/sksl/ast/SkSLASTTernaryExpression.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTTERNARYEXPRESSION
-#define SKSL_ASTTERNARYEXPRESSION
-
-#include "src/sksl/ast/SkSLASTExpression.h"
-
-namespace SkSL {
-
-/**
- * A ternary expression (test ? ifTrue : ifFalse).
- */
-struct ASTTernaryExpression : public ASTExpression {
-    ASTTernaryExpression(std::unique_ptr<ASTExpression> test,
-                         std::unique_ptr<ASTExpression> ifTrue,
-                         std::unique_ptr<ASTExpression> ifFalse)
-    : INHERITED(test->fOffset, kTernary_Kind)
-    , fTest(std::move(test))
-    , fIfTrue(std::move(ifTrue))
-    , fIfFalse(std::move(ifFalse)) {}
-
-    String description() const override {
-        return "(" + fTest->description() + " ? " + fIfTrue->description() + " : " +
-               fIfFalse->description() + ")";
-    }
-
-    const std::unique_ptr<ASTExpression> fTest;
-    const std::unique_ptr<ASTExpression> fIfTrue;
-    const std::unique_ptr<ASTExpression> fIfFalse;
-
-    typedef ASTExpression INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTType.h b/src/sksl/ast/SkSLASTType.h
deleted file mode 100644
index 56660b9..0000000
--- a/src/sksl/ast/SkSLASTType.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTTYPE
-#define SKSL_ASTTYPE
-
-#include "src/sksl/ast/SkSLASTPositionNode.h"
-
-namespace SkSL {
-
-/**
- * A type, such as 'int' or 'struct foo'.
- */
-struct ASTType : public ASTPositionNode {
-    enum Kind {
-        kIdentifier_Kind,
-        kStruct_Kind
-    };
-
-    ASTType(int offset, StringFragment name, Kind kind, std::vector<int> sizes, bool nullable)
-    : INHERITED(offset)
-    , fName(name)
-    , fKind(kind)
-    , fSizes(std::move(sizes))
-    , fNullable(nullable) {}
-
-    String description() const override {
-        return fName;
-    }
-
-    const StringFragment fName;
-
-    const Kind fKind;
-
-    // array sizes, -1 meaning unspecified
-    const std::vector<int> fSizes;
-
-    bool fNullable;
-
-    typedef ASTPositionNode INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTVarDeclaration.h b/src/sksl/ast/SkSLASTVarDeclaration.h
deleted file mode 100644
index 880ea2d..0000000
--- a/src/sksl/ast/SkSLASTVarDeclaration.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTVARDECLARATIONS
-#define SKSL_ASTVARDECLARATIONS
-
-#include "src/sksl/SkSLUtil.h"
-#include "src/sksl/ast/SkSLASTDeclaration.h"
-#include "src/sksl/ast/SkSLASTStatement.h"
-#include "src/sksl/ast/SkSLASTType.h"
-#include "src/sksl/ir/SkSLModifiers.h"
-
-namespace SkSL {
-
-/**
- * A single variable declaration within a var declaration statement. For instance, the statement
- * 'int x = 2, y[3];' is an ASTVarDeclarations statement containing two individual ASTVarDeclaration
- * instances.
- */
-struct ASTVarDeclaration {
-    ASTVarDeclaration(StringFragment name,
-                      std::vector<std::unique_ptr<ASTExpression>> sizes,
-                      std::unique_ptr<ASTExpression> value)
-    : fName(name)
-    , fSizes(std::move(sizes))
-    , fValue(std::move(value)) {}
-
-    String description() const {
-        String result(fName);
-        for (const auto& size : fSizes) {
-            if (size) {
-                result += "[" + size->description() + "]";
-            } else {
-                result += "[]";
-            }
-        }
-        if (fValue) {
-            result += " = " + fValue->description();
-        }
-        return result;
-    }
-
-    StringFragment fName;
-
-    // array sizes, if any. e.g. 'foo[3][]' has sizes [3, null]
-    std::vector<std::unique_ptr<ASTExpression>> fSizes;
-
-    // initial value, may be null
-    std::unique_ptr<ASTExpression> fValue;
-};
-
-/**
- * A variable declaration statement, which may consist of one or more individual variables.
- */
-struct ASTVarDeclarations : public ASTDeclaration {
-    ASTVarDeclarations(Modifiers modifiers,
-                       std::unique_ptr<ASTType> type,
-                       std::vector<ASTVarDeclaration> vars)
-    : INHERITED(type->fOffset, kVar_Kind)
-    , fModifiers(modifiers)
-    , fType(std::move(type))
-    , fVars(std::move(vars)) {}
-
-    String description() const override {
-        String result = fModifiers.description() + fType->description() + " ";
-        String separator;
-        for (const auto& var : fVars) {
-            result += separator;
-            separator = ", ";
-            result += var.description();
-        }
-        return result;
-    }
-
-    const Modifiers fModifiers;
-    const std::unique_ptr<ASTType> fType;
-    const std::vector<ASTVarDeclaration> fVars;
-
-    typedef ASTDeclaration INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTVarDeclarationStatement.h b/src/sksl/ast/SkSLASTVarDeclarationStatement.h
deleted file mode 100644
index 48edef3..0000000
--- a/src/sksl/ast/SkSLASTVarDeclarationStatement.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTVARDECLARATIONSTATEMENT
-#define SKSL_ASTVARDECLARATIONSTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-#include "src/sksl/ast/SkSLASTVarDeclaration.h"
-
-namespace SkSL {
-
-/**
- * A variable declaration appearing as a statement within a function.
- */
-struct ASTVarDeclarationStatement : public ASTStatement {
-    ASTVarDeclarationStatement(std::unique_ptr<ASTVarDeclarations> decl)
-    : INHERITED(decl->fOffset, kVarDeclaration_Kind)
-    , fDeclarations(std::move(decl)) {}
-
-    String description() const override {
-        return fDeclarations->description() + ";";
-    }
-
-    std::unique_ptr<ASTVarDeclarations> fDeclarations;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ast/SkSLASTWhileStatement.h b/src/sksl/ast/SkSLASTWhileStatement.h
deleted file mode 100644
index 450aa05..0000000
--- a/src/sksl/ast/SkSLASTWhileStatement.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SKSL_ASTWHILESTATEMENT
-#define SKSL_ASTWHILESTATEMENT
-
-#include "src/sksl/ast/SkSLASTStatement.h"
-
-namespace SkSL {
-
-/**
- * A 'while' statement.
- */
-struct ASTWhileStatement : public ASTStatement {
-    ASTWhileStatement(int offset, std::unique_ptr<ASTExpression> test,
-                      std::unique_ptr<ASTStatement> statement)
-    : INHERITED(offset, kWhile_Kind)
-    , fTest(std::move(test))
-    , fStatement(std::move(statement)) {}
-
-    String description() const override {
-        return "while (" + fTest->description() + ") " + fStatement->description();
-    }
-
-    const std::unique_ptr<ASTExpression> fTest;
-    const std::unique_ptr<ASTStatement> fStatement;
-
-    typedef ASTStatement INHERITED;
-};
-
-} // namespace
-
-#endif
diff --git a/src/sksl/ir/SkSLBinaryExpression.h b/src/sksl/ir/SkSLBinaryExpression.h
index f4c61fb..4ab25d2 100644
--- a/src/sksl/ir/SkSLBinaryExpression.h
+++ b/src/sksl/ir/SkSLBinaryExpression.h
@@ -8,6 +8,7 @@
 #ifndef SKSL_BINARYEXPRESSION
 #define SKSL_BINARYEXPRESSION
 
+#include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLIRGenerator.h"
 #include "src/sksl/SkSLLexer.h"
 #include "src/sksl/ir/SkSLExpression.h"
diff --git a/src/sksl/ir/SkSLLayout.h b/src/sksl/ir/SkSLLayout.h
index 9916f47..d4b1cb6 100644
--- a/src/sksl/ir/SkSLLayout.h
+++ b/src/sksl/ir/SkSLLayout.h
@@ -181,7 +181,7 @@
 
     Layout(int flags, int location, int offset, int binding, int index, int set, int builtin,
            int inputAttachmentIndex, Format format, Primitive primitive, int maxVertices,
-           int invocations, String when, Key key, CType ctype)
+           int invocations, StringFragment when, Key key, CType ctype)
     : fFlags(flags)
     , fLocation(location)
     , fOffset(offset)
@@ -369,7 +369,7 @@
             result += separator + "invocations = " + to_string(fInvocations);
             separator = ", ";
         }
-        if (fWhen.size()) {
+        if (fWhen.fLength) {
             result += separator + "when = " + fWhen;
             separator = ", ";
         }
@@ -417,7 +417,7 @@
     Primitive fPrimitive;
     int fMaxVertices;
     int fInvocations;
-    String fWhen;
+    StringFragment fWhen;
     Key fKey;
     CType fCType;
 };
diff --git a/src/sksl/ir/SkSLPrefixExpression.h b/src/sksl/ir/SkSLPrefixExpression.h
index 304830a..3cdd1fa 100644
--- a/src/sksl/ir/SkSLPrefixExpression.h
+++ b/src/sksl/ir/SkSLPrefixExpression.h
@@ -8,6 +8,7 @@
 #ifndef SKSL_PREFIXEXPRESSION
 #define SKSL_PREFIXEXPRESSION
 
+#include "src/sksl/SkSLCompiler.h"
 #include "src/sksl/SkSLIRGenerator.h"
 #include "src/sksl/SkSLLexer.h"
 #include "src/sksl/ir/SkSLExpression.h"