initial checkin of SkSL compiler

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1984363002
CQ_EXTRA_TRYBOTS=client.skia.compile:Build-Ubuntu-GCC-x86_64-Release-CMake-Trybot,Build-Mac-Clang-x86_64-Release-CMake-Trybot

Review-Url: https://codereview.chromium.org/1984363002
diff --git a/src/sksl/GLSL.std.450.h b/src/sksl/GLSL.std.450.h
new file mode 100644
index 0000000..54cc00e
--- /dev/null
+++ b/src/sksl/GLSL.std.450.h
@@ -0,0 +1,131 @@
+/*
+** Copyright (c) 2014-2016 The Khronos Group Inc.
+**
+** Permission is hereby granted, free of charge, to any person obtaining a copy
+** of this software and/or associated documentation files (the "Materials"),
+** to deal in the Materials without restriction, including without limitation
+** the rights to use, copy, modify, merge, publish, distribute, sublicense,
+** and/or sell copies of the Materials, and to permit persons to whom the
+** Materials are furnished to do so, subject to the following conditions:
+**
+** The above copyright notice and this permission notice shall be included in
+** all copies or substantial portions of the Materials.
+**
+** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
+** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
+** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ 
+**
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
+** IN THE MATERIALS.
+*/
+
+#ifndef GLSLstd450_H
+#define GLSLstd450_H
+
+static const int GLSLstd450Version = 100;
+static const int GLSLstd450Revision = 3;
+
+enum GLSLstd450 {
+    GLSLstd450Bad = 0,              // Don't use
+
+    GLSLstd450Round = 1,
+    GLSLstd450RoundEven = 2,
+    GLSLstd450Trunc = 3,
+    GLSLstd450FAbs = 4,
+    GLSLstd450SAbs = 5,
+    GLSLstd450FSign = 6,
+    GLSLstd450SSign = 7,
+    GLSLstd450Floor = 8,
+    GLSLstd450Ceil = 9,
+    GLSLstd450Fract = 10,
+
+    GLSLstd450Radians = 11,
+    GLSLstd450Degrees = 12,
+    GLSLstd450Sin = 13,
+    GLSLstd450Cos = 14,
+    GLSLstd450Tan = 15,
+    GLSLstd450Asin = 16,
+    GLSLstd450Acos = 17,
+    GLSLstd450Atan = 18,
+    GLSLstd450Sinh = 19,
+    GLSLstd450Cosh = 20,
+    GLSLstd450Tanh = 21,
+    GLSLstd450Asinh = 22,
+    GLSLstd450Acosh = 23,
+    GLSLstd450Atanh = 24,
+    GLSLstd450Atan2 = 25,
+
+    GLSLstd450Pow = 26,
+    GLSLstd450Exp = 27,
+    GLSLstd450Log = 28,
+    GLSLstd450Exp2 = 29,
+    GLSLstd450Log2 = 30,
+    GLSLstd450Sqrt = 31,
+    GLSLstd450InverseSqrt = 32,
+
+    GLSLstd450Determinant = 33,
+    GLSLstd450MatrixInverse = 34,
+
+    GLSLstd450Modf = 35,            // second operand needs an OpVariable to write to
+    GLSLstd450ModfStruct = 36,      // no OpVariable operand
+    GLSLstd450FMin = 37,
+    GLSLstd450UMin = 38,
+    GLSLstd450SMin = 39,
+    GLSLstd450FMax = 40,
+    GLSLstd450UMax = 41,
+    GLSLstd450SMax = 42,
+    GLSLstd450FClamp = 43,
+    GLSLstd450UClamp = 44,
+    GLSLstd450SClamp = 45,
+    GLSLstd450FMix = 46,
+    GLSLstd450IMix = 47,            // Reserved
+    GLSLstd450Step = 48,
+    GLSLstd450SmoothStep = 49,
+
+    GLSLstd450Fma = 50,
+    GLSLstd450Frexp = 51,            // second operand needs an OpVariable to write to
+    GLSLstd450FrexpStruct = 52,      // no OpVariable operand
+    GLSLstd450Ldexp = 53,
+
+    GLSLstd450PackSnorm4x8 = 54,
+    GLSLstd450PackUnorm4x8 = 55,
+    GLSLstd450PackSnorm2x16 = 56,
+    GLSLstd450PackUnorm2x16 = 57,
+    GLSLstd450PackHalf2x16 = 58,
+    GLSLstd450PackDouble2x32 = 59,
+    GLSLstd450UnpackSnorm2x16 = 60,
+    GLSLstd450UnpackUnorm2x16 = 61,
+    GLSLstd450UnpackHalf2x16 = 62,
+    GLSLstd450UnpackSnorm4x8 = 63,
+    GLSLstd450UnpackUnorm4x8 = 64,
+    GLSLstd450UnpackDouble2x32 = 65,
+
+    GLSLstd450Length = 66,
+    GLSLstd450Distance = 67,
+    GLSLstd450Cross = 68,
+    GLSLstd450Normalize = 69,
+    GLSLstd450FaceForward = 70,
+    GLSLstd450Reflect = 71,
+    GLSLstd450Refract = 72,
+
+    GLSLstd450FindILsb = 73,
+    GLSLstd450FindSMsb = 74,
+    GLSLstd450FindUMsb = 75,
+
+    GLSLstd450InterpolateAtCentroid = 76,
+    GLSLstd450InterpolateAtSample = 77,
+    GLSLstd450InterpolateAtOffset = 78,
+
+    GLSLstd450NMin = 79,
+    GLSLstd450NMax = 80,
+    GLSLstd450NClamp = 81,
+
+    GLSLstd450Count
+};
+
+#endif  // #ifndef GLSLstd450_H
diff --git a/src/sksl/SkSLCodeGenerator.h b/src/sksl/SkSLCodeGenerator.h
new file mode 100644
index 0000000..cd50cc8
--- /dev/null
+++ b/src/sksl/SkSLCodeGenerator.h
@@ -0,0 +1,30 @@
+/*
+ * 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_CODEGENERATOR
+#define SKSL_CODEGENERATOR
+
+#include "ir/SkSLProgram.h"
+#include <vector>
+#include <iostream>
+
+namespace SkSL {
+
+/**
+ * Abstract superclass of all code generators, which take a Program as input and produce code as
+ * output.
+ */
+class CodeGenerator {
+public:
+	virtual ~CodeGenerator() {}
+	
+    virtual void generateCode(Program& program, std::ostream& out) = 0;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
new file mode 100644
index 0000000..2b4adc1
--- /dev/null
+++ b/src/sksl/SkSLCompiler.cpp
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+ 
+#include "SkSLCompiler.h"
+
+#include <fstream>
+#include <streambuf>
+
+#include "SkSLIRGenerator.h"
+#include "SkSLParser.h"
+#include "SkSLSPIRVCodeGenerator.h"
+#include "ir/SkSLExpression.h"
+#include "ir/SkSLIntLiteral.h"
+#include "ir/SkSLSymbolTable.h"
+#include "ir/SkSLVarDeclaration.h"
+#include "SkMutex.h"
+
+#define STRINGIFY(x) #x
+
+// include the built-in shader symbols as static strings
+
+static std::string SKSL_INCLUDE = 
+#include "sksl.include"
+;
+
+static std::string SKSL_VERT_INCLUDE = 
+#include "sksl_vert.include"
+;
+
+static std::string SKSL_FRAG_INCLUDE = 
+#include "sksl_frag.include"
+;
+
+namespace SkSL {
+
+Compiler::Compiler() 
+: fErrorCount(0) {
+    auto types = std::shared_ptr<SymbolTable>(new SymbolTable(*this));
+    auto symbols = std::shared_ptr<SymbolTable>(new SymbolTable(types, *this));
+    fIRGenerator = new IRGenerator(symbols, *this);
+    fTypes = types;
+    #define ADD_TYPE(t) types->add(k ## t ## _Type->fName, k ## t ## _Type)
+    ADD_TYPE(Void);
+    ADD_TYPE(Float);
+    ADD_TYPE(Vec2);
+    ADD_TYPE(Vec3);
+    ADD_TYPE(Vec4);
+    ADD_TYPE(Double);
+    ADD_TYPE(DVec2);
+    ADD_TYPE(DVec3);
+    ADD_TYPE(DVec4);
+    ADD_TYPE(Int);
+    ADD_TYPE(IVec2);
+    ADD_TYPE(IVec3);
+    ADD_TYPE(IVec4);
+    ADD_TYPE(UInt);
+    ADD_TYPE(UVec2);
+    ADD_TYPE(UVec3);
+    ADD_TYPE(UVec4);
+    ADD_TYPE(Bool);
+    ADD_TYPE(BVec2);
+    ADD_TYPE(BVec3);
+    ADD_TYPE(BVec4);
+    ADD_TYPE(Mat2x2);
+    ADD_TYPE(Mat2x3);
+    ADD_TYPE(Mat2x4);
+    ADD_TYPE(Mat3x2);
+    ADD_TYPE(Mat3x3);
+    ADD_TYPE(Mat3x4);
+    ADD_TYPE(Mat4x2);
+    ADD_TYPE(Mat4x3);
+    ADD_TYPE(Mat4x4);
+    ADD_TYPE(GenType);
+    ADD_TYPE(GenDType);
+    ADD_TYPE(GenIType);
+    ADD_TYPE(GenUType);
+    ADD_TYPE(GenBType);
+    ADD_TYPE(Mat);
+    ADD_TYPE(Vec);
+    ADD_TYPE(GVec);
+    ADD_TYPE(GVec2);
+    ADD_TYPE(GVec3);
+    ADD_TYPE(GVec4);
+    ADD_TYPE(DVec);
+    ADD_TYPE(IVec);
+    ADD_TYPE(UVec);
+    ADD_TYPE(BVec);
+
+    ADD_TYPE(Sampler1D);
+    ADD_TYPE(Sampler2D);
+    ADD_TYPE(Sampler3D);
+    ADD_TYPE(SamplerCube);
+    ADD_TYPE(Sampler2DRect);
+    ADD_TYPE(Sampler1DArray);
+    ADD_TYPE(Sampler2DArray);
+    ADD_TYPE(SamplerCubeArray);
+    ADD_TYPE(SamplerBuffer);
+    ADD_TYPE(Sampler2DMS);
+    ADD_TYPE(Sampler2DMSArray);
+
+    ADD_TYPE(GSampler1D);
+    ADD_TYPE(GSampler2D);
+    ADD_TYPE(GSampler3D);
+    ADD_TYPE(GSamplerCube);
+    ADD_TYPE(GSampler2DRect);
+    ADD_TYPE(GSampler1DArray);
+    ADD_TYPE(GSampler2DArray);
+    ADD_TYPE(GSamplerCubeArray);
+    ADD_TYPE(GSamplerBuffer);
+    ADD_TYPE(GSampler2DMS);
+    ADD_TYPE(GSampler2DMSArray);
+
+    ADD_TYPE(Sampler1DShadow);
+    ADD_TYPE(Sampler2DShadow);
+    ADD_TYPE(SamplerCubeShadow);
+    ADD_TYPE(Sampler2DRectShadow);
+    ADD_TYPE(Sampler1DArrayShadow);
+    ADD_TYPE(Sampler2DArrayShadow);
+    ADD_TYPE(SamplerCubeArrayShadow);
+    ADD_TYPE(GSampler2DArrayShadow);
+    ADD_TYPE(GSamplerCubeArrayShadow);
+
+    std::vector<std::unique_ptr<ProgramElement>> ignored;
+    this->internalConvertProgram(SKSL_INCLUDE, &ignored);
+    ASSERT(!fErrorCount);
+}
+
+Compiler::~Compiler() {
+    delete fIRGenerator;
+}
+
+void Compiler::internalConvertProgram(std::string text,
+                                      std::vector<std::unique_ptr<ProgramElement>>* result) {
+    Parser parser(text, *fTypes, *this);
+    std::vector<std::unique_ptr<ASTDeclaration>> parsed = parser.file();
+    if (fErrorCount) {
+        return;
+    }
+    for (size_t i = 0; i < parsed.size(); i++) {
+        ASTDeclaration& decl = *parsed[i];
+        switch (decl.fKind) {
+            case ASTDeclaration::kVar_Kind: {
+                std::unique_ptr<VarDeclaration> s = fIRGenerator->convertVarDeclaration(
+                                                                         (ASTVarDeclaration&) decl, 
+                                                                         Variable::kGlobal_Storage);
+                if (s) {
+                    result->push_back(std::move(s));
+                }
+                break;
+            }
+            case ASTDeclaration::kFunction_Kind: {
+                std::unique_ptr<FunctionDefinition> f = fIRGenerator->convertFunction(
+                                                                               (ASTFunction&) decl);
+                if (f) {
+                    result->push_back(std::move(f));
+                }
+                break;
+            }
+            case ASTDeclaration::kInterfaceBlock_Kind: {
+                std::unique_ptr<InterfaceBlock> i = fIRGenerator->convertInterfaceBlock(
+                                                                         (ASTInterfaceBlock&) decl);
+                if (i) {
+                    result->push_back(std::move(i));
+                }
+                break;
+            }
+            case ASTDeclaration::kExtension_Kind: {
+                std::unique_ptr<Extension> e = fIRGenerator->convertExtension((ASTExtension&) decl);
+                if (e) {
+                    result->push_back(std::move(e));
+                }
+                break;
+            }
+            default:
+                ABORT("unsupported declaration: %s\n", decl.description().c_str());
+        }
+    }
+}
+
+std::unique_ptr<Program> Compiler::convertProgram(Program::Kind kind, std::string text) {
+    fErrorText = "";
+    fErrorCount = 0;
+    fIRGenerator->pushSymbolTable();
+    std::vector<std::unique_ptr<ProgramElement>> result;
+    switch (kind) {
+        case Program::kVertex_Kind:
+            this->internalConvertProgram(SKSL_VERT_INCLUDE, &result);
+            break;
+        case Program::kFragment_Kind:
+            this->internalConvertProgram(SKSL_FRAG_INCLUDE, &result);
+            break;
+    }
+    this->internalConvertProgram(text, &result);
+    fIRGenerator->popSymbolTable();
+    this->writeErrorCount();
+    return std::unique_ptr<Program>(new Program(kind, std::move(result)));;
+}
+
+void Compiler::error(Position position, std::string msg) {
+    fErrorCount++;
+    fErrorText += "error: " + position.description() + ": " + msg.c_str() + "\n";
+}
+
+std::string Compiler::errorText() {
+    std::string result = fErrorText;
+    return result;
+}
+
+void Compiler::writeErrorCount() {
+    if (fErrorCount) {
+        fErrorText += to_string(fErrorCount) + " error";
+        if (fErrorCount > 1) {
+            fErrorText += "s";
+        }
+        fErrorText += "\n";
+    }
+}
+
+#include <fstream>
+bool Compiler::toSPIRV(Program::Kind kind, std::string text, std::ostream& out) {
+    auto program = this->convertProgram(kind, text);
+    if (fErrorCount == 0) {
+        SkSL::SPIRVCodeGenerator cg;
+        cg.generateCode(*program.get(), out);
+        ASSERT(!out.rdstate());
+    }
+    return fErrorCount == 0;
+}
+
+bool Compiler::toSPIRV(Program::Kind kind, std::string text, std::string* out) {
+    std::stringstream buffer;
+    bool result = this->toSPIRV(kind, text, buffer);
+    if (result) {
+        *out = buffer.str();
+    }
+    return fErrorCount == 0;
+}
+
+} // namespace
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
new file mode 100644
index 0000000..2209427
--- /dev/null
+++ b/src/sksl/SkSLCompiler.h
@@ -0,0 +1,59 @@
+/*
+ * 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_COMPILER
+#define SKSL_COMPILER
+
+#include <vector>
+#include "ir/SkSLProgram.h"
+#include "ir/SkSLSymbolTable.h"
+#include "SkSLErrorReporter.h"
+
+namespace SkSL {
+
+class IRGenerator;
+
+/**
+ * Main compiler entry point. This is a traditional compiler design which first parses the .sksl
+ * file into an abstract syntax tree (a tree of ASTNodes), then performs semantic analysis to 
+ * produce a Program (a tree of IRNodes), then feeds the Program into a CodeGenerator to produce
+ * compiled output.
+ */
+class Compiler : public ErrorReporter {
+public:
+    Compiler();
+
+    ~Compiler();
+
+    std::unique_ptr<Program> convertProgram(Program::Kind kind, std::string text);
+
+	bool toSPIRV(Program::Kind kind, std::string text, std::ostream& out);
+	
+	bool toSPIRV(Program::Kind kind, std::string text, std::string* out);
+
+    void error(Position position, std::string msg) override;
+
+    std::string errorText();
+
+    void writeErrorCount();
+
+private:
+
+    void internalConvertProgram(std::string text,
+    							std::vector<std::unique_ptr<ProgramElement>>* result);
+
+    std::shared_ptr<SymbolTable> fTypes;
+    IRGenerator* fIRGenerator;
+    std::string fSkiaVertText; // FIXME store parsed version instead
+
+    int fErrorCount;
+    std::string fErrorText;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLErrorReporter.h b/src/sksl/SkSLErrorReporter.h
new file mode 100644
index 0000000..26b4471
--- /dev/null
+++ b/src/sksl/SkSLErrorReporter.h
@@ -0,0 +1,27 @@
+/*
+ * 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_ERRORREPORTER
+#define SKSL_ERRORREPORTER
+
+#include "SkSLPosition.h"
+
+namespace SkSL {
+
+/**
+ * Interface for the compiler to report errors.
+ */
+class ErrorReporter {
+public:
+    virtual ~ErrorReporter() {}
+
+    virtual void error(Position position, std::string msg) = 0;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
new file mode 100644
index 0000000..2cc7eac
--- /dev/null
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -0,0 +1,1217 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+ 
+#include "SkSLIRGenerator.h"
+
+#include "limits.h"
+
+#include "ast/SkSLASTBoolLiteral.h"
+#include "ast/SkSLASTFieldSuffix.h"
+#include "ast/SkSLASTFloatLiteral.h"
+#include "ast/SkSLASTIndexSuffix.h"
+#include "ast/SkSLASTIntLiteral.h"
+#include "ir/SkSLBinaryExpression.h"
+#include "ir/SkSLBoolLiteral.h"
+#include "ir/SkSLBreakStatement.h"
+#include "ir/SkSLConstructor.h"
+#include "ir/SkSLContinueStatement.h"
+#include "ir/SkSLDiscardStatement.h"
+#include "ir/SkSLDoStatement.h"
+#include "ir/SkSLExpressionStatement.h"
+#include "ir/SkSLField.h"
+#include "ir/SkSLFieldAccess.h"
+#include "ir/SkSLFloatLiteral.h"
+#include "ir/SkSLForStatement.h"
+#include "ir/SkSLFunctionCall.h"
+#include "ir/SkSLFunctionDeclaration.h"
+#include "ir/SkSLFunctionDefinition.h"
+#include "ir/SkSLFunctionReference.h"
+#include "ir/SkSLIfStatement.h"
+#include "ir/SkSLIndexExpression.h"
+#include "ir/SkSLInterfaceBlock.h"
+#include "ir/SkSLIntLiteral.h"
+#include "ir/SkSLLayout.h"
+#include "ir/SkSLPostfixExpression.h"
+#include "ir/SkSLPrefixExpression.h"
+#include "ir/SkSLReturnStatement.h"
+#include "ir/SkSLSwizzle.h"
+#include "ir/SkSLTernaryExpression.h"
+#include "ir/SkSLUnresolvedFunction.h"
+#include "ir/SkSLVariable.h"
+#include "ir/SkSLVarDeclaration.h"
+#include "ir/SkSLVarDeclarationStatement.h"
+#include "ir/SkSLVariableReference.h"
+#include "ir/SkSLWhileStatement.h"
+
+namespace SkSL {
+
+class AutoSymbolTable {
+public:
+    AutoSymbolTable(IRGenerator* ir) 
+    : fIR(ir)
+    , fPrevious(fIR->fSymbolTable) {
+        fIR->pushSymbolTable();
+    }
+
+    ~AutoSymbolTable() {
+        fIR->popSymbolTable();
+        ASSERT(fPrevious == fIR->fSymbolTable);
+    }
+
+    IRGenerator* fIR;
+    std::shared_ptr<SymbolTable> fPrevious;
+};
+
+IRGenerator::IRGenerator(std::shared_ptr<SymbolTable> symbolTable, 
+                         ErrorReporter& errorReporter)
+: fSymbolTable(std::move(symbolTable))
+, fErrors(errorReporter) {
+}
+
+void IRGenerator::pushSymbolTable() {
+    fSymbolTable.reset(new SymbolTable(std::move(fSymbolTable), fErrors));
+}
+
+void IRGenerator::popSymbolTable() {
+    fSymbolTable = fSymbolTable->fParent;
+}
+
+std::unique_ptr<Extension> IRGenerator::convertExtension(const ASTExtension& extension) {
+    return std::unique_ptr<Extension>(new Extension(extension.fPosition, extension.fName));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertStatement(const ASTStatement& 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:
+            return this->convertExpressionStatement((ASTExpressionStatement&) statement);
+        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::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) {
+    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]);
+        if (!statement) {
+            return nullptr;
+        }
+        statements.push_back(std::move(statement));
+    }
+    return std::unique_ptr<Block>(new Block(block.fPosition, std::move(statements)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertVarDeclarationStatement(
+                                                              const ASTVarDeclarationStatement& s) {
+    auto decl = this->convertVarDeclaration(*s.fDeclaration, Variable::kLocal_Storage);
+    if (!decl) {
+        return nullptr;
+    }
+    return std::unique_ptr<Statement>(new VarDeclarationStatement(std::move(decl)));
+}
+
+Modifiers IRGenerator::convertModifiers(const ASTModifiers& modifiers) {
+    return Modifiers(modifiers);
+}
+
+std::unique_ptr<VarDeclaration> IRGenerator::convertVarDeclaration(const ASTVarDeclaration& decl,
+                                                                   Variable::Storage storage) {
+    std::vector<std::shared_ptr<Variable>> variables;
+    std::vector<std::vector<std::unique_ptr<Expression>>> sizes;
+    std::vector<std::unique_ptr<Expression>> values;
+    std::shared_ptr<Type> baseType = this->convertType(*decl.fType);
+    if (!baseType) {
+        return nullptr;
+    }
+    for (size_t i = 0; i < decl.fNames.size(); i++) {
+        Modifiers modifiers = this->convertModifiers(decl.fModifiers);
+        std::shared_ptr<Type> type = baseType;
+        ASSERT(type->kind() != Type::kArray_Kind);
+        std::vector<std::unique_ptr<Expression>> currentVarSizes;
+        for (size_t j = 0; j < decl.fSizes[i].size(); j++) {
+            if (decl.fSizes[i][j]) {
+                ASTExpression& rawSize = *decl.fSizes[i][j];
+                auto size = this->coerce(this->convertExpression(rawSize), kInt_Type);
+                if (!size) {
+                    return nullptr;
+                }
+                std::string name = type->fName;
+                uint64_t count;
+                if (size->fKind == Expression::kIntLiteral_Kind) {
+                    count = ((IntLiteral&) *size).fValue;
+                    if (count <= 0) {
+                        fErrors.error(size->fPosition, "array size must be positive");
+                    }
+                    name += "[" + to_string(count) + "]";
+                } else {
+                    count = -1;
+                    name += "[]";
+                }
+                type = std::shared_ptr<Type>(new Type(name, Type::kArray_Kind, type, (int) count));
+                currentVarSizes.push_back(std::move(size));
+            } else {
+                type = std::shared_ptr<Type>(new Type(type->fName + "[]", Type::kArray_Kind, type, 
+                                                      -1));
+                currentVarSizes.push_back(nullptr);
+            }
+        }
+        sizes.push_back(std::move(currentVarSizes));
+        auto var = std::make_shared<Variable>(decl.fPosition, modifiers, decl.fNames[i], type, 
+                                              storage);
+        variables.push_back(var);
+        std::unique_ptr<Expression> value;
+        if (decl.fValues[i]) {
+            value = this->convertExpression(*decl.fValues[i]);
+            if (!value) {
+                return nullptr;
+            }
+            value = this->coerce(std::move(value), type);
+        }
+        fSymbolTable->add(var->fName, var);
+        values.push_back(std::move(value));
+    }
+    return std::unique_ptr<VarDeclaration>(new VarDeclaration(decl.fPosition, std::move(variables), 
+                                                              std::move(sizes), std::move(values)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertIf(const ASTIfStatement& s) {
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*s.fTest), kBool_Type);
+    if (!test) {
+        return nullptr;
+    }
+    std::unique_ptr<Statement> ifTrue = this->convertStatement(*s.fIfTrue);
+    if (!ifTrue) {
+        return nullptr;
+    }
+    std::unique_ptr<Statement> ifFalse;
+    if (s.fIfFalse) {
+        ifFalse = this->convertStatement(*s.fIfFalse);
+        if (!ifFalse) {
+            return nullptr;
+        }
+    }
+    return std::unique_ptr<Statement>(new IfStatement(s.fPosition, std::move(test), 
+                                                      std::move(ifTrue), std::move(ifFalse)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertFor(const ASTForStatement& f) {
+    AutoSymbolTable table(this);
+    std::unique_ptr<Statement> initializer = this->convertStatement(*f.fInitializer);
+    if (!initializer) {
+        return nullptr;
+    }
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*f.fTest), kBool_Type);
+    if (!test) {
+        return nullptr;
+    }
+    std::unique_ptr<Expression> next = this->convertExpression(*f.fNext);
+    if (!next) {
+        return nullptr;
+    }
+    this->checkValid(*next);
+    std::unique_ptr<Statement> statement = this->convertStatement(*f.fStatement);
+    if (!statement) {
+        return nullptr;
+    }
+    return std::unique_ptr<Statement>(new ForStatement(f.fPosition, std::move(initializer), 
+                                                       std::move(test), std::move(next),
+                                                       std::move(statement)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertWhile(const ASTWhileStatement& w) {
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*w.fTest), kBool_Type);
+    if (!test) {
+        return nullptr;
+    }
+    std::unique_ptr<Statement> statement = this->convertStatement(*w.fStatement);
+    if (!statement) {
+        return nullptr;
+    }
+    return std::unique_ptr<Statement>(new WhileStatement(w.fPosition, std::move(test),
+                                                         std::move(statement)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertDo(const ASTDoStatement& d) {
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*d.fTest), kBool_Type);
+    if (!test) {
+        return nullptr;
+    }
+    std::unique_ptr<Statement> statement = this->convertStatement(*d.fStatement);
+    if (!statement) {
+        return nullptr;
+    }
+    return std::unique_ptr<Statement>(new DoStatement(d.fPosition, std::move(statement), 
+                                                      std::move(test)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertExpressionStatement(
+                                                                  const ASTExpressionStatement& s) {
+    std::unique_ptr<Expression> e = this->convertExpression(*s.fExpression);
+    if (!e) {
+        return nullptr;
+    }
+    this->checkValid(*e);
+    return std::unique_ptr<Statement>(new ExpressionStatement(std::move(e)));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertReturn(const ASTReturnStatement& r) {
+    ASSERT(fCurrentFunction);
+    if (r.fExpression) {
+        std::unique_ptr<Expression> result = this->convertExpression(*r.fExpression);
+        if (!result) {
+            return nullptr;
+        }
+        if (fCurrentFunction->fReturnType == kVoid_Type) {
+            fErrors.error(result->fPosition, "may not return a value from a void function");
+        } else {
+            result = this->coerce(std::move(result), fCurrentFunction->fReturnType);
+            if (!result) {
+                return nullptr;
+            }
+        }
+        return std::unique_ptr<Statement>(new ReturnStatement(std::move(result)));
+    } else {
+        if (fCurrentFunction->fReturnType != kVoid_Type) {
+            fErrors.error(r.fPosition, "expected function to return '" +
+                                       fCurrentFunction->fReturnType->description() + "'");
+        }
+        return std::unique_ptr<Statement>(new ReturnStatement(r.fPosition));
+    }
+}
+
+std::unique_ptr<Statement> IRGenerator::convertBreak(const ASTBreakStatement& b) {
+    return std::unique_ptr<Statement>(new BreakStatement(b.fPosition));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertContinue(const ASTContinueStatement& c) {
+    return std::unique_ptr<Statement>(new ContinueStatement(c.fPosition));
+}
+
+std::unique_ptr<Statement> IRGenerator::convertDiscard(const ASTDiscardStatement& d) {
+    return std::unique_ptr<Statement>(new DiscardStatement(d.fPosition));
+}
+
+static std::shared_ptr<Type> expand_generics(std::shared_ptr<Type> type, int i) {
+    if (type->kind() == Type::kGeneric_Kind) {
+        return type->coercibleTypes()[i];
+    }
+    return type;
+}
+
+static void expand_generics(FunctionDeclaration& decl, 
+                            SymbolTable& symbolTable) {
+    for (int i = 0; i < 4; i++) {
+        std::shared_ptr<Type> returnType = expand_generics(decl.fReturnType, i);
+        std::vector<std::shared_ptr<Variable>> arguments;
+        for (const auto& p : decl.fParameters) {
+            arguments.push_back(std::shared_ptr<Variable>(new Variable(
+                                                                    p->fPosition, 
+                                                                    Modifiers(p->fModifiers), 
+                                                                    p->fName,
+                                                                    expand_generics(p->fType, i),
+                                                                    Variable::kParameter_Storage)));
+        }
+        std::shared_ptr<FunctionDeclaration> expanded(new FunctionDeclaration(
+                                                                            decl.fPosition, 
+                                                                            decl.fName, 
+                                                                            std::move(arguments), 
+                                                                            std::move(returnType)));
+        symbolTable.add(expanded->fName, expanded);
+    }
+}
+
+std::unique_ptr<FunctionDefinition> IRGenerator::convertFunction(const ASTFunction& f) {
+    std::shared_ptr<SymbolTable> old = fSymbolTable;
+    AutoSymbolTable table(this);
+    bool isGeneric;
+    std::shared_ptr<Type> returnType = this->convertType(*f.fReturnType);
+    if (!returnType) {
+        return nullptr;
+    }
+    isGeneric = returnType->kind() == Type::kGeneric_Kind;
+    std::vector<std::shared_ptr<Variable>> parameters;
+    for (const auto& param : f.fParameters) {
+        std::shared_ptr<Type> type = this->convertType(*param->fType);
+        if (!type) {
+            return nullptr;
+        }
+        for (int j = (int) param->fSizes.size() - 1; j >= 0; j--) {
+            int size = param->fSizes[j];
+            std::string name = type->name() + "[" + to_string(size) + "]";
+            type = std::shared_ptr<Type>(new Type(std::move(name), Type::kArray_Kind, 
+                                                  std::move(type), size));
+        }
+        std::string name = param->fName;
+        Modifiers modifiers = this->convertModifiers(param->fModifiers);
+        Position pos = param->fPosition;
+        std::shared_ptr<Variable> var = std::shared_ptr<Variable>(new Variable(
+                                                                     pos, 
+                                                                     modifiers, 
+                                                                     std::move(name), 
+                                                                     type,
+                                                                     Variable::kParameter_Storage));
+        parameters.push_back(std::move(var));
+        isGeneric |= type->kind() == Type::kGeneric_Kind;
+    }
+
+    // find existing declaration
+    std::shared_ptr<FunctionDeclaration> decl;
+    auto entry = (*old)[f.fName];
+    if (entry) {
+        std::vector<std::shared_ptr<FunctionDeclaration>> functions;
+        switch (entry->fKind) {
+            case Symbol::kUnresolvedFunction_Kind:
+                functions = std::static_pointer_cast<UnresolvedFunction>(entry)->fFunctions;
+                break;
+            case Symbol::kFunctionDeclaration_Kind:
+                functions.push_back(std::static_pointer_cast<FunctionDeclaration>(entry));
+                break;
+            default:
+                fErrors.error(f.fPosition, "symbol '" + f.fName + "' was already defined");
+                return nullptr;
+        }
+        for (const auto& other : functions) {
+            ASSERT(other->fName == f.fName);
+            if (parameters.size() == other->fParameters.size()) {
+                bool match = true;
+                for (size_t i = 0; i < parameters.size(); i++) {
+                    if (parameters[i]->fType != other->fParameters[i]->fType) {
+                        match = false;
+                        break;
+                    }
+                }
+                if (match) {
+                    if (returnType != other->fReturnType) {
+                        FunctionDeclaration newDecl = FunctionDeclaration(f.fPosition, 
+                                                                          f.fName, 
+                                                                          parameters, 
+                                                                          returnType);
+                        fErrors.error(f.fPosition, "functions '" + newDecl.description() +
+                                                   "' and '" + other->description() + 
+                                                   "' differ only in return type");
+                        return nullptr;
+                    }
+                    decl = other;
+                    for (size_t i = 0; i < parameters.size(); i++) {
+                        if (parameters[i]->fModifiers != other->fParameters[i]->fModifiers) {
+                            fErrors.error(f.fPosition, "modifiers on parameter " + 
+                                                       to_string(i + 1) + " differ between " +
+                                                       "declaration and definition");
+                            return nullptr;
+                        }
+                        fSymbolTable->add(parameters[i]->fName, decl->fParameters[i]);
+                    }
+                    if (other->fDefined) {
+                        fErrors.error(f.fPosition, "duplicate definition of " + 
+                                                   other->description());
+                    }
+                    break;
+                }
+            }
+        }
+    }
+    if (!decl) {
+        // couldn't find an existing declaration
+        decl.reset(new FunctionDeclaration(f.fPosition, f.fName, parameters, returnType));
+        for (auto var : parameters) {
+            fSymbolTable->add(var->fName, var);
+        }
+    }
+    if (isGeneric) {
+        ASSERT(!f.fBody);
+        expand_generics(*decl, *old);
+    } else {
+        old->add(decl->fName, decl);
+        if (f.fBody) {
+            ASSERT(!fCurrentFunction);
+            fCurrentFunction = decl;
+            decl->fDefined = true;
+            std::unique_ptr<Block> body = this->convertBlock(*f.fBody);
+            fCurrentFunction = nullptr;
+            if (!body) {
+                return nullptr;
+            }
+            return std::unique_ptr<FunctionDefinition>(new FunctionDefinition(f.fPosition, decl, 
+                                                                              std::move(body)));
+        }
+    }
+    return nullptr;
+}
+
+std::unique_ptr<InterfaceBlock> IRGenerator::convertInterfaceBlock(const ASTInterfaceBlock& intf) {
+    std::shared_ptr<SymbolTable> old = fSymbolTable;
+    AutoSymbolTable table(this);
+    Modifiers mods = this->convertModifiers(intf.fModifiers);
+    std::vector<Type::Field> fields;
+    for (size_t i = 0; i < intf.fDeclarations.size(); i++) {
+        std::unique_ptr<VarDeclaration> decl = this->convertVarDeclaration(
+                                                                         *intf.fDeclarations[i], 
+                                                                         Variable::kGlobal_Storage);
+        for (size_t j = 0; j < decl->fVars.size(); j++) {
+            fields.push_back(Type::Field(decl->fVars[j]->fModifiers, decl->fVars[j]->fName, 
+                                         decl->fVars[j]->fType));
+            if (decl->fValues[j]) {
+                fErrors.error(decl->fPosition, 
+                              "initializers are not permitted on interface block fields");
+            }
+            if (decl->fVars[j]->fModifiers.fFlags & (Modifiers::kIn_Flag |
+                                                     Modifiers::kOut_Flag |
+                                                     Modifiers::kUniform_Flag |
+                                                     Modifiers::kConst_Flag)) {
+                fErrors.error(decl->fPosition, 
+                              "interface block fields may not have storage qualifiers");
+            }
+        }        
+    }
+    std::shared_ptr<Type> type = std::shared_ptr<Type>(new Type(intf.fInterfaceName, fields));
+    std::string name = intf.fValueName.length() > 0 ? intf.fValueName : intf.fInterfaceName;
+    std::shared_ptr<Variable> var = std::shared_ptr<Variable>(new Variable(intf.fPosition, mods, 
+                                                                          name, type,
+                                                                Variable::kGlobal_Storage));
+    if (intf.fValueName.length()) {
+        old->add(intf.fValueName, var);
+
+    } else {
+        for (size_t i = 0; i < fields.size(); i++) {
+            std::shared_ptr<Field> field = std::shared_ptr<Field>(new Field(intf.fPosition, var, 
+                                                                            (int) i));
+            old->add(fields[i].fName, field);
+        }
+    }
+    return std::unique_ptr<InterfaceBlock>(new InterfaceBlock(intf.fPosition, var));
+}
+
+std::shared_ptr<Type> IRGenerator::convertType(const ASTType& type) {
+    std::shared_ptr<Symbol> result = (*fSymbolTable)[type.fName];
+    if (result && result->fKind == Symbol::kType_Kind) {
+        return std::static_pointer_cast<Type>(result);
+    }
+    fErrors.error(type.fPosition, "unknown type '" + type.fName + "'");
+    return nullptr;
+}
+
+std::unique_ptr<Expression> IRGenerator::convertExpression(const ASTExpression& expr) {
+    switch (expr.fKind) {
+        case ASTExpression::kIdentifier_Kind:
+            return this->convertIdentifier((ASTIdentifier&) expr);
+        case ASTExpression::kBool_Kind:
+            return std::unique_ptr<Expression>(new BoolLiteral(expr.fPosition,
+                                                               ((ASTBoolLiteral&) expr).fValue));
+        case ASTExpression::kInt_Kind:
+            return std::unique_ptr<Expression>(new IntLiteral(expr.fPosition,
+                                                              ((ASTIntLiteral&) expr).fValue));
+        case ASTExpression::kFloat_Kind:
+            return std::unique_ptr<Expression>(new FloatLiteral(expr.fPosition,
+                                                                ((ASTFloatLiteral&) expr).fValue));
+        case ASTExpression::kBinary_Kind:
+            return this->convertBinaryExpression((ASTBinaryExpression&) expr);
+        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);
+        default:
+            ABORT("unsupported expression type: %d\n", expr.fKind);
+    }
+}
+
+std::unique_ptr<Expression> IRGenerator::convertIdentifier(const ASTIdentifier& identifier) {
+    std::shared_ptr<Symbol> result = (*fSymbolTable)[identifier.fText];
+    if (!result) {
+        fErrors.error(identifier.fPosition, "unknown identifier '" + identifier.fText + "'");
+        return nullptr;
+    }
+    switch (result->fKind) {
+        case Symbol::kFunctionDeclaration_Kind: {
+            std::vector<std::shared_ptr<FunctionDeclaration>> f = {
+                std::static_pointer_cast<FunctionDeclaration>(result)
+            };
+            return std::unique_ptr<FunctionReference>(new FunctionReference(identifier.fPosition,
+                                                                            std::move(f)));
+        }
+        case Symbol::kUnresolvedFunction_Kind: {
+            auto f = std::static_pointer_cast<UnresolvedFunction>(result);
+            return std::unique_ptr<FunctionReference>(new FunctionReference(identifier.fPosition,
+                                                                            f->fFunctions));
+        }
+        case Symbol::kVariable_Kind: {
+            std::shared_ptr<Variable> var = std::static_pointer_cast<Variable>(result);
+            this->markReadFrom(var);
+            return std::unique_ptr<VariableReference>(new VariableReference(identifier.fPosition,
+                                                                            std::move(var)));
+        }
+        case Symbol::kField_Kind: {
+            std::shared_ptr<Field> field = std::static_pointer_cast<Field>(result);
+            VariableReference* base = new VariableReference(identifier.fPosition, field->fOwner);
+            return std::unique_ptr<Expression>(new FieldAccess(std::unique_ptr<Expression>(base),
+                                                               field->fFieldIndex));
+        }
+        case Symbol::kType_Kind: {
+            auto t = std::static_pointer_cast<Type>(result);
+            return std::unique_ptr<TypeReference>(new TypeReference(identifier.fPosition, 
+                                                                    std::move(t)));
+        }
+        default:
+            ABORT("unsupported symbol type %d\n", result->fKind);
+    }
+
+}
+
+std::unique_ptr<Expression> IRGenerator::coerce(std::unique_ptr<Expression> expr, 
+                                                std::shared_ptr<Type> type) {
+    if (!expr) {
+        return nullptr;
+    }
+    if (*expr->fType == *type) {
+        return expr;
+    }
+    this->checkValid(*expr);
+    if (*expr->fType == *kInvalid_Type) {
+        return nullptr;
+    }
+    if (!expr->fType->canCoerceTo(type)) {
+        fErrors.error(expr->fPosition, "expected '" + type->description() + "', but found '" + 
+                                        expr->fType->description() + "'");
+        return nullptr;
+    }
+    if (type->kind() == Type::kScalar_Kind) {
+        std::vector<std::unique_ptr<Expression>> args;
+        args.push_back(std::move(expr));
+        ASTIdentifier id(Position(), type->description());
+        std::unique_ptr<Expression> ctor = this->convertIdentifier(id);
+        ASSERT(ctor);
+        return this->call(Position(), std::move(ctor), std::move(args));
+    }
+    ABORT("cannot coerce %s to %s", expr->fType->description().c_str(), 
+          type->description().c_str());
+}
+
+/**
+ * Determines the operand and result types of a binary expression. Returns true if the expression is
+ * legal, false otherwise. If false, the values of the out parameters are undefined.
+ */
+static bool determine_binary_type(Token::Kind op, std::shared_ptr<Type> left, 
+                                  std::shared_ptr<Type> right, 
+                                  std::shared_ptr<Type>* outLeftType,
+                                  std::shared_ptr<Type>* outRightType,
+                                  std::shared_ptr<Type>* outResultType,
+                                  bool tryFlipped) {
+    bool isLogical;
+    switch (op) {
+        case Token::EQEQ: // fall through
+        case Token::NEQ:  // fall through
+        case Token::LT:   // fall through
+        case Token::GT:   // fall through
+        case Token::LTEQ: // fall through
+        case Token::GTEQ:
+            isLogical = true;
+            break;
+        case Token::LOGICALOR: // fall through
+        case Token::LOGICALAND: // fall through
+        case Token::LOGICALXOR: // fall through
+        case Token::LOGICALOREQ: // fall through
+        case Token::LOGICALANDEQ: // fall through
+        case Token::LOGICALXOREQ:
+            *outLeftType = kBool_Type;
+            *outRightType = kBool_Type;
+            *outResultType = kBool_Type;
+            return left->canCoerceTo(kBool_Type) && right->canCoerceTo(kBool_Type);
+        case Token::STAR: // fall through
+        case Token::STAREQ: 
+            // FIXME need to handle non-square matrices
+            if (left->kind() == Type::kMatrix_Kind && right->kind() == Type::kVector_Kind) {
+                *outLeftType = left;
+                *outRightType = right;
+                *outResultType = right;
+                return left->rows() == right->columns();
+            }  
+            if (left->kind() == Type::kVector_Kind && right->kind() == Type::kMatrix_Kind) {
+                *outLeftType = left;
+                *outRightType = right;
+                *outResultType = left;
+                return left->columns() == right->columns();
+            }
+            // fall through
+        default:
+            isLogical = false;
+    }
+    // FIXME: need to disallow illegal operations like vec3 > vec3. Also do not currently have
+    // full support for numbers other than float.
+    if (left == right) {
+        *outLeftType = left;
+        *outRightType = left;
+        if (isLogical) {
+            *outResultType = kBool_Type;
+        } else {
+            *outResultType = left;
+        }
+        return true;
+    }
+    // FIXME: incorrect for shift operations
+    if (left->canCoerceTo(right)) {
+        *outLeftType = right;
+        *outRightType = right;
+        if (isLogical) {
+            *outResultType = kBool_Type;
+        } else {
+            *outResultType = right;
+        }
+        return true;
+    }
+    if ((left->kind() == Type::kVector_Kind || left->kind() == Type::kMatrix_Kind) && 
+        (right->kind() == Type::kScalar_Kind)) {
+        if (determine_binary_type(op, left->componentType(), right, outLeftType, outRightType,
+                                  outResultType, false)) {
+            *outLeftType = (*outLeftType)->toCompound(left->columns(), left->rows());
+            if (!isLogical) {
+                *outResultType = (*outResultType)->toCompound(left->columns(), left->rows());
+            }
+            return true;
+        }
+        return false;
+    }
+    if (tryFlipped) {
+        return determine_binary_type(op, right, left, outRightType, outLeftType, outResultType, 
+                                     false);
+    }
+    return false;
+}
+
+std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(
+                                                            const ASTBinaryExpression& expression) {
+    std::unique_ptr<Expression> left = this->convertExpression(*expression.fLeft);
+    if (!left) {
+        return nullptr;
+    }
+    std::unique_ptr<Expression> right = this->convertExpression(*expression.fRight);
+    if (!right) {
+        return nullptr;
+    }
+    std::shared_ptr<Type> leftType;
+    std::shared_ptr<Type> rightType;
+    std::shared_ptr<Type> resultType;
+    if (!determine_binary_type(expression.fOperator, left->fType, right->fType, &leftType,
+                               &rightType, &resultType, true)) {
+        fErrors.error(expression.fPosition, "type mismatch: '" + 
+                                            Token::OperatorName(expression.fOperator) + 
+                                            "' cannot operate on '" + left->fType->fName + 
+                                            "', '" + right->fType->fName + "'");
+        return nullptr;
+    }
+    switch (expression.fOperator) {
+        case Token::EQ:           // fall through
+        case Token::PLUSEQ:       // fall through
+        case Token::MINUSEQ:      // fall through
+        case Token::STAREQ:       // fall through
+        case Token::SLASHEQ:      // fall through
+        case Token::PERCENTEQ:    // fall through
+        case Token::SHLEQ:        // fall through
+        case Token::SHREQ:        // fall through
+        case Token::BITWISEOREQ:  // fall through
+        case Token::BITWISEXOREQ: // fall through
+        case Token::BITWISEANDEQ: // fall through
+        case Token::LOGICALOREQ:  // fall through
+        case Token::LOGICALXOREQ: // fall through
+        case Token::LOGICALANDEQ: 
+            this->markWrittenTo(*left);
+        default:
+            break;
+    }
+    return std::unique_ptr<Expression>(new BinaryExpression(expression.fPosition, 
+                                                            this->coerce(std::move(left), leftType), 
+                                                            expression.fOperator, 
+                                                            this->coerce(std::move(right), 
+                                                                         rightType), 
+                                                            resultType));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertTernaryExpression(  
+                                                           const ASTTernaryExpression& expression) {
+    std::unique_ptr<Expression> test = this->coerce(this->convertExpression(*expression.fTest), 
+                                                    kBool_Type);
+    if (!test) {
+        return nullptr;
+    }
+    std::unique_ptr<Expression> ifTrue = this->convertExpression(*expression.fIfTrue);
+    if (!ifTrue) {
+        return nullptr;
+    }
+    std::unique_ptr<Expression> ifFalse = this->convertExpression(*expression.fIfFalse);
+    if (!ifFalse) {
+        return nullptr;
+    }
+    std::shared_ptr<Type> trueType;
+    std::shared_ptr<Type> falseType;
+    std::shared_ptr<Type> resultType;
+    if (!determine_binary_type(Token::EQEQ, ifTrue->fType, ifFalse->fType, &trueType,
+                               &falseType, &resultType, true)) {
+        fErrors.error(expression.fPosition, "ternary operator result mismatch: '" + 
+                                            ifTrue->fType->fName + "', '" + 
+                                            ifFalse->fType->fName + "'");
+        return nullptr;
+    }
+    ASSERT(trueType == falseType);
+    ifTrue = this->coerce(std::move(ifTrue), trueType);
+    ifFalse = this->coerce(std::move(ifFalse), falseType);
+    return std::unique_ptr<Expression>(new TernaryExpression(expression.fPosition, 
+                                                             std::move(test),
+                                                             std::move(ifTrue), 
+                                                             std::move(ifFalse)));
+}
+
+std::unique_ptr<Expression> IRGenerator::call(
+                                         Position position, 
+                                         std::shared_ptr<FunctionDeclaration> function, 
+                                         std::vector<std::unique_ptr<Expression>> arguments) {
+    if (function->fParameters.size() != arguments.size()) {
+        std::string msg = "call to '" + function->fName + "' expected " + 
+                                 to_string(function->fParameters.size()) + 
+                                 " argument";
+        if (function->fParameters.size() != 1) {
+            msg += "s";
+        }
+        msg += ", but found " + to_string(arguments.size());
+        fErrors.error(position, msg);
+        return nullptr;
+    }
+    for (size_t i = 0; i < arguments.size(); i++) {
+        arguments[i] = this->coerce(std::move(arguments[i]), function->fParameters[i]->fType);
+        if (arguments[i] && (function->fParameters[i]->fModifiers.fFlags & Modifiers::kOut_Flag)) {
+            this->markWrittenTo(*arguments[i]);
+        }
+    }
+    return std::unique_ptr<FunctionCall>(new FunctionCall(position, std::move(function),
+                                                          std::move(arguments)));
+}
+
+/**
+ * Determines the cost of coercing the arguments of a function to the required types. Returns true 
+ * if the cost could be computed, false if the call is not valid. Cost has no particular meaning 
+ * other than "lower costs are preferred".
+ */
+bool IRGenerator::determineCallCost(std::shared_ptr<FunctionDeclaration> function, 
+                                    const std::vector<std::unique_ptr<Expression>>& arguments,
+                                    int* outCost) {
+    if (function->fParameters.size() != arguments.size()) {
+        return false;
+    }
+    int total = 0;
+    for (size_t i = 0; i < arguments.size(); i++) {
+        int cost;
+        if (arguments[i]->fType->determineCoercionCost(function->fParameters[i]->fType, &cost)) {
+            total += cost;
+        } else {
+            return false;
+        }
+    }
+    *outCost = total;
+    return true;
+}
+
+std::unique_ptr<Expression> IRGenerator::call(Position position, 
+                                              std::unique_ptr<Expression> functionValue, 
+                                              std::vector<std::unique_ptr<Expression>> arguments) {
+    if (functionValue->fKind == Expression::kTypeReference_Kind) {
+        return this->convertConstructor(position, 
+                                        ((TypeReference&) *functionValue).fValue, 
+                                        std::move(arguments));
+    }
+    if (functionValue->fKind != Expression::kFunctionReference_Kind) {
+        fErrors.error(position, "'" + functionValue->description() + "' is not a function");
+        return nullptr;
+    }
+    FunctionReference* ref = (FunctionReference*) functionValue.get();
+    int bestCost = INT_MAX;
+    std::shared_ptr<FunctionDeclaration> best;
+    if (ref->fFunctions.size() > 1) {
+        for (const auto& f : ref->fFunctions) {
+            int cost;
+            if (this->determineCallCost(f, arguments, &cost) && cost < bestCost) {
+                bestCost = cost;
+                best = f;
+            }
+        }
+        if (best) {
+            return this->call(position, std::move(best), std::move(arguments));
+        }
+        std::string msg = "no match for " + ref->fFunctions[0]->fName + "(";
+        std::string separator = "";
+        for (size_t i = 0; i < arguments.size(); i++) {
+            msg += separator;
+            separator = ", ";
+            msg += arguments[i]->fType->description();
+        }
+        msg += ")";
+        fErrors.error(position, msg);
+        return nullptr;
+    }
+    return this->call(position, ref->fFunctions[0], std::move(arguments));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertConstructor(
+                                                    Position position, 
+                                                    std::shared_ptr<Type> type, 
+                                                    std::vector<std::unique_ptr<Expression>> args) {
+    // FIXME: add support for structs and arrays
+    Type::Kind kind = type->kind();
+    if (!type->isNumber() && kind != Type::kVector_Kind && kind != Type::kMatrix_Kind) {
+        fErrors.error(position, "cannot construct '" + type->description() + "'");
+        return nullptr;
+    }
+    if (type == kFloat_Type && args.size() == 1 && 
+        args[0]->fKind == Expression::kIntLiteral_Kind) {
+        int64_t value = ((IntLiteral&) *args[0]).fValue;
+        return std::unique_ptr<Expression>(new FloatLiteral(position, (double) value));
+    }
+    if (args.size() == 1 && args[0]->fType == type) {
+        // argument is already the right type, just return it
+        return std::move(args[0]);
+    }
+    if (type->isNumber()) {
+        if (args.size() != 1) {
+            fErrors.error(position, "invalid arguments to '" + type->description() + 
+                                    "' constructor, (expected exactly 1 argument, but found " +
+                                    to_string(args.size()) + ")");
+        }
+        if (args[0]->fType == kBool_Type) {
+            std::unique_ptr<IntLiteral> zero(new IntLiteral(position, 0));
+            std::unique_ptr<IntLiteral> one(new IntLiteral(position, 1));
+            return std::unique_ptr<Expression>(
+                                         new TernaryExpression(position, std::move(args[0]),
+                                                               this->coerce(std::move(one), type),
+                                                               this->coerce(std::move(zero), 
+                                                                            type)));
+        } else if (!args[0]->fType->isNumber()) {
+            fErrors.error(position, "invalid argument to '" + type->description() + 
+                                    "' constructor (expected a number or bool, but found '" +
+                                    args[0]->fType->description() + "')");
+        }
+    } else {
+        ASSERT(kind == Type::kVector_Kind || kind == Type::kMatrix_Kind);
+        int actual = 0;
+        for (size_t i = 0; i < args.size(); i++) {
+            if (args[i]->fType->kind() == Type::kVector_Kind || 
+                args[i]->fType->kind() == Type::kMatrix_Kind) {
+                int columns = args[i]->fType->columns();
+                int rows = args[i]->fType->rows();
+                args[i] = this->coerce(std::move(args[i]), 
+                                       type->componentType()->toCompound(columns, rows));
+                actual += args[i]->fType->rows() * args[i]->fType->columns();
+            } else if (args[i]->fType->kind() == Type::kScalar_Kind) {
+                actual += 1;
+                if (type->kind() != Type::kScalar_Kind) {
+                    args[i] = this->coerce(std::move(args[i]), type->componentType());
+                }
+            } else {
+                fErrors.error(position, "'" + args[i]->fType->description() + "' is not a valid "
+                                        "parameter to '" + type->description() + "' constructor");
+                return nullptr;
+            }
+        }
+        int min = type->rows() * type->columns();
+        int max = type->columns() > 1 ? INT_MAX : min;
+        if ((actual < min || actual > max) &&
+            !((kind == Type::kVector_Kind || kind == Type::kMatrix_Kind) && (actual == 1))) {
+            fErrors.error(position, "invalid arguments to '" + type->description() + 
+                                    "' constructor (expected " + to_string(min) + " scalar" + 
+                                    (min == 1 ? "" : "s") + ", but found " + to_string(actual) + 
+                                    ")");
+            return nullptr;
+        }
+    }
+    return std::unique_ptr<Expression>(new Constructor(position, std::move(type), std::move(args)));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(
+                                                            const ASTPrefixExpression& expression) {
+    std::unique_ptr<Expression> base = this->convertExpression(*expression.fOperand);
+    if (!base) {
+        return nullptr;
+    }
+    switch (expression.fOperator) {
+        case Token::PLUS:
+            if (!base->fType->isNumber() && base->fType->kind() != Type::kVector_Kind) {
+                fErrors.error(expression.fPosition, 
+                              "'+' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            return base;
+        case Token::MINUS:
+            if (!base->fType->isNumber() && base->fType->kind() != Type::kVector_Kind) {
+                fErrors.error(expression.fPosition, 
+                              "'-' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            if (base->fKind == Expression::kIntLiteral_Kind) {
+                return std::unique_ptr<Expression>(new IntLiteral(base->fPosition,
+                                                                  -((IntLiteral&) *base).fValue));
+            }
+            if (base->fKind == Expression::kFloatLiteral_Kind) {
+                double value = -((FloatLiteral&) *base).fValue;
+                return std::unique_ptr<Expression>(new FloatLiteral(base->fPosition, value));
+            }
+            return std::unique_ptr<Expression>(new PrefixExpression(Token::MINUS, std::move(base)));
+        case Token::PLUSPLUS:
+            if (!base->fType->isNumber()) {
+                fErrors.error(expression.fPosition, 
+                              "'" + Token::OperatorName(expression.fOperator) + 
+                              "' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            this->markWrittenTo(*base);
+            break;
+        case Token::MINUSMINUS: 
+            if (!base->fType->isNumber()) {
+                fErrors.error(expression.fPosition, 
+                              "'" + Token::OperatorName(expression.fOperator) + 
+                              "' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            this->markWrittenTo(*base);
+            break;
+        case Token::NOT:
+            if (base->fType != kBool_Type) {
+                fErrors.error(expression.fPosition, 
+                              "'" + Token::OperatorName(expression.fOperator) + 
+                              "' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            break;
+        default: 
+            ABORT("unsupported prefix operator\n");
+    }
+    return std::unique_ptr<Expression>(new PrefixExpression(expression.fOperator, 
+                                                            std::move(base)));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertIndex(std::unique_ptr<Expression> base,
+                                                      const ASTExpression& index) {
+    if (base->fType->kind() != Type::kArray_Kind && base->fType->kind() != Type::kMatrix_Kind) {
+        fErrors.error(base->fPosition, "expected array, but found '" + base->fType->description() + 
+                                       "'");
+        return nullptr;
+    }
+    std::unique_ptr<Expression> converted = this->convertExpression(index);
+    if (!converted) {
+        return nullptr;
+    }
+    converted = this->coerce(std::move(converted), kInt_Type);
+    if (!converted) {
+        return nullptr;
+    }
+    return std::unique_ptr<Expression>(new IndexExpression(std::move(base), std::move(converted)));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertField(std::unique_ptr<Expression> base,
+                                                      const std::string& field) {
+    auto fields = base->fType->fields();
+    for (size_t i = 0; i < fields.size(); i++) {
+        if (fields[i].fName == field) {
+            return std::unique_ptr<Expression>(new FieldAccess(std::move(base), (int) i));
+        }
+    }
+    fErrors.error(base->fPosition, "type '" + base->fType->description() + "' does not have a "
+                                   "field named '" + field + "");
+    return nullptr;
+}
+
+std::unique_ptr<Expression> IRGenerator::convertSwizzle(std::unique_ptr<Expression> base,
+                                                        const std::string& fields) {
+    if (base->fType->kind() != Type::kVector_Kind) {
+        fErrors.error(base->fPosition, "cannot swizzle type '" + base->fType->description() + "'");
+        return nullptr;
+    }
+    std::vector<int> swizzleComponents;
+    for (char c : fields) {
+        switch (c) {
+            case 'x': // fall through
+            case 'r': // fall through
+            case 's': 
+                swizzleComponents.push_back(0);
+                break;
+            case 'y': // fall through
+            case 'g': // fall through
+            case 't':
+                if (base->fType->columns() >= 2) {
+                    swizzleComponents.push_back(1);
+                    break;
+                }
+                // fall through
+            case 'z': // fall through
+            case 'b': // fall through
+            case 'p': 
+                if (base->fType->columns() >= 3) {
+                    swizzleComponents.push_back(2);
+                    break;
+                }
+                // fall through
+            case 'w': // fall through
+            case 'a': // fall through
+            case 'q':
+                if (base->fType->columns() >= 4) {
+                    swizzleComponents.push_back(3);
+                    break;
+                }
+                // fall through
+            default:
+                fErrors.error(base->fPosition, "invalid swizzle component '" + std::string(1, c) +
+                                               "'");
+                return nullptr;
+        }
+    }
+    ASSERT(swizzleComponents.size() > 0);
+    if (swizzleComponents.size() > 4) {
+        fErrors.error(base->fPosition, "too many components in swizzle mask '" + fields + "'");
+        return nullptr;
+    }
+    return std::unique_ptr<Expression>(new Swizzle(std::move(base), swizzleComponents));
+}
+
+std::unique_ptr<Expression> IRGenerator::convertSuffixExpression(
+                                                            const ASTSuffixExpression& expression) {
+    std::unique_ptr<Expression> base = this->convertExpression(*expression.fBase);
+    if (!base) {
+        return nullptr;
+    }
+    switch (expression.fSuffix->fKind) {
+        case ASTSuffix::kIndex_Kind:
+            return this->convertIndex(std::move(base), 
+                                      *((ASTIndexSuffix&) *expression.fSuffix).fExpression);
+        case ASTSuffix::kCall_Kind: {
+            auto rawArguments = &((ASTCallSuffix&) *expression.fSuffix).fArguments;
+            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.fPosition, std::move(base), std::move(arguments));
+        }
+        case ASTSuffix::kField_Kind: {
+            switch (base->fType->kind()) {
+                case Type::kVector_Kind:
+                    return this->convertSwizzle(std::move(base), 
+                                                ((ASTFieldSuffix&) *expression.fSuffix).fField);
+                case Type::kStruct_Kind:
+                    return this->convertField(std::move(base),
+                                              ((ASTFieldSuffix&) *expression.fSuffix).fField);
+                default:
+                    fErrors.error(base->fPosition, "cannot swizzle value of type '" + 
+                                                   base->fType->description() + "'");
+                    return nullptr;
+            }
+        }
+        case ASTSuffix::kPostIncrement_Kind:
+            if (!base->fType->isNumber()) {
+                fErrors.error(expression.fPosition, 
+                              "'++' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            this->markWrittenTo(*base);
+            return std::unique_ptr<Expression>(new PostfixExpression(std::move(base), 
+                                                                     Token::PLUSPLUS));
+        case ASTSuffix::kPostDecrement_Kind:
+            if (!base->fType->isNumber()) {
+                fErrors.error(expression.fPosition, 
+                              "'--' cannot operate on '" + base->fType->description() + "'");
+                return nullptr;
+            }
+            this->markWrittenTo(*base);
+            return std::unique_ptr<Expression>(new PostfixExpression(std::move(base), 
+                                                                     Token::MINUSMINUS));
+        default:
+            ABORT("unsupported suffix operator");
+    }
+}
+
+void IRGenerator::checkValid(const Expression& expr) {
+    switch (expr.fKind) {
+        case Expression::kFunctionReference_Kind:
+            fErrors.error(expr.fPosition, "expected '(' to begin function call");
+            break;
+        case Expression::kTypeReference_Kind:
+            fErrors.error(expr.fPosition, "expected '(' to begin constructor invocation");
+            break;
+        default:
+            ASSERT(expr.fType != kInvalid_Type);
+            break;
+    }
+}
+
+void IRGenerator::markReadFrom(std::shared_ptr<Variable> var) {
+    var->fIsReadFrom = true;
+}
+
+static bool has_duplicates(const Swizzle& swizzle) {
+    int bits = 0;
+    for (int idx : swizzle.fComponents) {
+        ASSERT(idx >= 0 && idx <= 3);
+        int bit = 1 << idx;
+        if (bits & bit) {
+            return true;
+        }
+        bits |= bit;
+    }
+    return false;
+}
+
+void IRGenerator::markWrittenTo(const Expression& expr) {
+    switch (expr.fKind) {
+        case Expression::kVariableReference_Kind: {
+            const Variable& var = *((VariableReference&) expr).fVariable;
+            if (var.fModifiers.fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag)) {
+                fErrors.error(expr.fPosition, 
+                              "cannot modify immutable variable '" + var.fName + "'");
+            }
+            var.fIsWrittenTo = true;
+            break;
+        }
+        case Expression::kFieldAccess_Kind:
+            this->markWrittenTo(*((FieldAccess&) expr).fBase);
+            break;
+        case Expression::kSwizzle_Kind:
+            if (has_duplicates((Swizzle&) expr)) {
+                fErrors.error(expr.fPosition, 
+                              "cannot write to the same swizzle field more than once");
+            }
+            this->markWrittenTo(*((Swizzle&) expr).fBase);
+            break;
+        case Expression::kIndex_Kind:
+            this->markWrittenTo(*((IndexExpression&) expr).fBase);
+            break;
+        default:
+            fErrors.error(expr.fPosition, "cannot assign to '" + expr.description() + "'");
+            break;
+    }
+}
+
+}
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
new file mode 100644
index 0000000..d23e5a1
--- /dev/null
+++ b/src/sksl/SkSLIRGenerator.h
@@ -0,0 +1,122 @@
+/*
+ * 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_IRGENERATOR
+#define SKSL_IRGENERATOR
+
+#include "SkSLErrorReporter.h"
+#include "ast/SkSLASTBinaryExpression.h"
+#include "ast/SkSLASTBlock.h"
+#include "ast/SkSLASTBreakStatement.h"
+#include "ast/SkSLASTCallSuffix.h"
+#include "ast/SkSLASTContinueStatement.h"
+#include "ast/SkSLASTDiscardStatement.h"
+#include "ast/SkSLASTDoStatement.h"
+#include "ast/SkSLASTExpression.h"
+#include "ast/SkSLASTExpressionStatement.h"
+#include "ast/SkSLASTExtension.h"
+#include "ast/SkSLASTForStatement.h"
+#include "ast/SkSLASTFunction.h"
+#include "ast/SkSLASTIdentifier.h"
+#include "ast/SkSLASTIfStatement.h"
+#include "ast/SkSLASTInterfaceBlock.h"
+#include "ast/SkSLASTModifiers.h"
+#include "ast/SkSLASTPrefixExpression.h"
+#include "ast/SkSLASTReturnStatement.h"
+#include "ast/SkSLASTStatement.h"
+#include "ast/SkSLASTSuffixExpression.h"
+#include "ast/SkSLASTTernaryExpression.h"
+#include "ast/SkSLASTVarDeclaration.h"
+#include "ast/SkSLASTVarDeclarationStatement.h"
+#include "ast/SkSLASTWhileStatement.h"
+#include "ir/SkSLBlock.h"
+#include "ir/SkSLExpression.h"
+#include "ir/SkSLExtension.h"
+#include "ir/SkSLFunctionDefinition.h"
+#include "ir/SkSLInterfaceBlock.h"
+#include "ir/SkSLModifiers.h"
+#include "ir/SkSLSymbolTable.h"
+#include "ir/SkSLStatement.h"
+#include "ir/SkSLType.h"
+#include "ir/SkSLTypeReference.h"
+#include "ir/SkSLVarDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * Performs semantic analysis on an abstract syntax tree (AST) and produces the corresponding 
+ * (unoptimized) intermediate representation (IR).
+ */
+class IRGenerator {
+public:
+    IRGenerator(std::shared_ptr<SymbolTable> root, ErrorReporter& errorReporter);
+
+    std::unique_ptr<VarDeclaration> convertVarDeclaration(const ASTVarDeclaration& decl, 
+                                                          Variable::Storage storage);
+    std::unique_ptr<FunctionDefinition> convertFunction(const ASTFunction& f);
+    std::unique_ptr<Statement> convertStatement(const ASTStatement& statement);
+    std::unique_ptr<Expression> convertExpression(const ASTExpression& expression);
+
+private:
+    void pushSymbolTable();
+    void popSymbolTable();
+
+    std::shared_ptr<Type> convertType(const ASTType& type);
+    std::unique_ptr<Expression> call(Position position, 
+                                     std::shared_ptr<FunctionDeclaration> function, 
+                                     std::vector<std::unique_ptr<Expression>> arguments);
+    bool determineCallCost(std::shared_ptr<FunctionDeclaration> function, 
+                           const std::vector<std::unique_ptr<Expression>>& arguments,
+                           int* outCost);
+    std::unique_ptr<Expression> call(Position position, std::unique_ptr<Expression> function, 
+                                     std::vector<std::unique_ptr<Expression>> arguments);
+    std::unique_ptr<Expression> coerce(std::unique_ptr<Expression> expr, 
+                                       std::shared_ptr<Type> type);
+    std::unique_ptr<Block> convertBlock(const ASTBlock& block);
+    std::unique_ptr<Statement> convertBreak(const ASTBreakStatement& b);
+    std::unique_ptr<Expression> convertConstructor(Position position, 
+                                                   std::shared_ptr<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<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<Expression> convertIndex(std::unique_ptr<Expression> base,
+                                             const ASTExpression& index);
+    std::unique_ptr<InterfaceBlock> convertInterfaceBlock(const ASTInterfaceBlock& s);
+    Modifiers convertModifiers(const ASTModifiers& m);
+    std::unique_ptr<Expression> convertPrefixExpression(const ASTPrefixExpression& expression);
+    std::unique_ptr<Statement> convertReturn(const ASTReturnStatement& r);
+    std::unique_ptr<Expression> convertSuffixExpression(const ASTSuffixExpression& expression);
+    std::unique_ptr<Expression> convertField(std::unique_ptr<Expression> base, 
+                                             const std::string& field);
+    std::unique_ptr<Expression> convertSwizzle(std::unique_ptr<Expression> base,
+                                               const std::string& 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 checkValid(const Expression& expr);
+    void markReadFrom(std::shared_ptr<Variable> var);
+    void markWrittenTo(const Expression& expr);
+
+    std::shared_ptr<FunctionDeclaration> fCurrentFunction;
+    std::shared_ptr<SymbolTable> fSymbolTable;
+    ErrorReporter& fErrors;
+
+    friend class AutoSymbolTable;
+    friend class Compiler;
+};
+
+}
+
+#endif
diff --git a/src/sksl/SkSLMain.cpp b/src/sksl/SkSLMain.cpp
new file mode 100644
index 0000000..24fbb6c
--- /dev/null
+++ b/src/sksl/SkSLMain.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "stdio.h"
+#include <fstream>
+#include "SkSLCompiler.h"
+
+/**
+ * Very simple standalone executable to facilitate testing.
+ */
+int main(int argc, const char** argv) {
+    if (argc != 3) {
+        printf("usage: skslc <input> <output>\n");
+        exit(1);
+    }
+    SkSL::Program::Kind kind;
+    size_t len = strlen(argv[1]);
+    if (len > 5 && !strcmp(argv[1] + strlen(argv[1]) - 5, ".vert")) {
+        kind = SkSL::Program::kVertex_Kind;
+    } else if (len > 5 && !strcmp(argv[1] + strlen(argv[1]) - 5, ".frag")) {
+        kind = SkSL::Program::kFragment_Kind;
+    } else {
+        printf("input filename must end in '.vert' or '.frag'\n");
+        exit(1);
+    }
+
+    std::ifstream in(argv[1]);
+    std::string text((std::istreambuf_iterator<char>(in)),
+                     std::istreambuf_iterator<char>());
+    if (in.rdstate()) {
+        printf("error reading '%s'\n", argv[1]);
+        exit(2);
+    }
+    std::ofstream out(argv[2], std::ofstream::binary);
+    SkSL::Compiler compiler;
+    if (!compiler.toSPIRV(kind, text, out)) {
+        printf("%s", compiler.errorText().c_str());
+        exit(3);
+    }
+    if (out.rdstate()) {
+        printf("error writing '%s'\n", argv[2]);
+        exit(4);
+    }
+}
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
new file mode 100644
index 0000000..3526c6e
--- /dev/null
+++ b/src/sksl/SkSLParser.cpp
@@ -0,0 +1,1389 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+ 
+#include "stdio.h"
+#include "SkSLParser.h"
+#include "SkSLToken.h"
+
+#define register
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunneeded-internal-declaration"
+#endif
+#include "lex.sksl.c"
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+#undef register
+
+#include "ast/SkSLASTBinaryExpression.h"
+#include "ast/SkSLASTBlock.h"
+#include "ast/SkSLASTBoolLiteral.h"
+#include "ast/SkSLASTBreakStatement.h"
+#include "ast/SkSLASTCallSuffix.h"
+#include "ast/SkSLASTContinueStatement.h"
+#include "ast/SkSLASTDiscardStatement.h"
+#include "ast/SkSLASTDoStatement.h"
+#include "ast/SkSLASTExpression.h"
+#include "ast/SkSLASTExpressionStatement.h"
+#include "ast/SkSLASTExtension.h"
+#include "ast/SkSLASTFieldSuffix.h"
+#include "ast/SkSLASTFloatLiteral.h"
+#include "ast/SkSLASTForStatement.h"
+#include "ast/SkSLASTFunction.h"
+#include "ast/SkSLASTIdentifier.h"
+#include "ast/SkSLASTIfStatement.h"
+#include "ast/SkSLASTIndexSuffix.h"
+#include "ast/SkSLASTInterfaceBlock.h"
+#include "ast/SkSLASTIntLiteral.h"
+#include "ast/SkSLASTParameter.h"
+#include "ast/SkSLASTPrefixExpression.h"
+#include "ast/SkSLASTReturnStatement.h"
+#include "ast/SkSLASTStatement.h"
+#include "ast/SkSLASTSuffixExpression.h"
+#include "ast/SkSLASTTernaryExpression.h"
+#include "ast/SkSLASTType.h"
+#include "ast/SkSLASTVarDeclaration.h"
+#include "ast/SkSLASTVarDeclarationStatement.h"
+#include "ast/SkSLASTWhileStatement.h"
+#include "ir/SkSLSymbolTable.h"
+
+namespace SkSL {
+
+Parser::Parser(std::string text, SymbolTable& types, ErrorReporter& errors) 
+: fPushback(Position(-1, -1), Token::INVALID_TOKEN, "")
+, fTypes(types)
+, fErrors(errors) {
+    sksllex_init(&fScanner);
+    fBuffer = sksl_scan_string(text.c_str(), fScanner);
+    skslset_lineno(1, fScanner);
+
+    if (false) {
+        // avoid unused warning
+        yyunput(0, nullptr, fScanner);
+    }
+}
+
+Parser::~Parser() {
+    sksl_delete_buffer(fBuffer, fScanner);
+}
+
+/* (precision | directive | declaration)* END_OF_FILE */
+std::vector<std::unique_ptr<ASTDeclaration>> Parser::file() {
+    std::vector<std::unique_ptr<ASTDeclaration>> result;
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::END_OF_FILE:
+                return result;
+            case Token::PRECISION:
+                this->precision();
+                break;
+            case Token::DIRECTIVE: {
+                std::unique_ptr<ASTDeclaration> decl = this->directive();
+                if (decl) {
+                    result.push_back(std::move(decl));
+                }
+                break;
+            }
+            default: {
+                std::unique_ptr<ASTDeclaration> decl = this->declaration();
+                if (!decl) {
+                    continue;
+                }
+                result.push_back(std::move(decl));
+            }
+        }
+    }
+}
+
+Token Parser::nextToken() {
+    if (fPushback.fKind != Token::INVALID_TOKEN) {
+        Token result = fPushback;
+        fPushback.fKind = Token::INVALID_TOKEN;
+        fPushback.fText = "";
+        return result;
+    }
+    int token = sksllex(fScanner);
+    return Token(Position(skslget_lineno(fScanner), -1), (Token::Kind) token, 
+                 token == Token::END_OF_FILE ? "<end of file>" : 
+                                               std::string(skslget_text(fScanner)));
+}
+
+void Parser::pushback(Token t) {
+    ASSERT(fPushback.fKind == Token::INVALID_TOKEN);
+    fPushback = t;
+}
+
+Token Parser::peek() {
+    fPushback = this->nextToken();
+    return fPushback;
+}
+
+bool Parser::expect(Token::Kind kind, std::string expected, Token* result) {
+    Token next = this->nextToken();
+    if (next.fKind == kind) {
+        if (result) {
+            *result = next;
+        }
+        return true;
+    } else {
+        this->error(next.fPosition, "expected " + expected + ", but found '" + next.fText + "'");
+        return false;
+    }
+}
+
+void Parser::error(Position p, std::string msg) {
+    fErrors.error(p, msg);
+}
+
+bool Parser::isType(std::string name) {
+    return nullptr != fTypes[name];
+}
+
+/* PRECISION (LOWP | MEDIUMP | HIGHP) type SEMICOLON */
+void Parser::precision() {
+    if (!this->expect(Token::PRECISION, "'precision'")) {
+        return;
+    }
+    Token p = this->nextToken();
+    switch (p.fKind) {
+        case Token::LOWP: // fall through
+        case Token::MEDIUMP: // fall through
+        case Token::HIGHP:
+            // ignored for now
+            break;
+        default:
+            this->error(p.fPosition, "expected 'lowp', 'mediump', or 'highp', but found '" + 
+                                     p.fText + "'");
+            return;
+    }
+    if (!this->type()) {
+        return;
+    }
+    this->expect(Token::SEMICOLON, "';'");
+}
+
+/* DIRECTIVE(#version) INT_LITERAL | DIRECTIVE(#extension) IDENTIFIER COLON IDENTIFIER */
+std::unique_ptr<ASTDeclaration> Parser::directive() {
+    Token start;
+    if (!this->expect(Token::DIRECTIVE, "a directive", &start)) {
+        return nullptr;
+    }
+    if (start.fText == "#version") {
+        this->expect(Token::INT_LITERAL, "a version number");
+        // ignored for now
+        return nullptr;
+    } else if (start.fText == "#extension") {
+        Token name;
+        if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+            return nullptr;
+        }
+        if (!this->expect(Token::COLON, "':'")) {
+            return nullptr;
+        }
+        // FIXME: need to start paying attention to this token
+        if (!this->expect(Token::IDENTIFIER, "an identifier")) {
+            return nullptr;
+        }
+        return std::unique_ptr<ASTDeclaration>(new ASTExtension(start.fPosition, 
+                                                                std::move(name.fText)));
+    } else {
+        this->error(start.fPosition, "unsupported directive '" + start.fText + "'");
+        return nullptr;
+    }
+}
+
+/* modifiers (structVarDeclaration | type IDENTIFIER ((LPAREN parameter 
+   (COMMA parameter)* RPAREN (block | SEMICOLON)) | SEMICOLON) | interfaceBlock) */
+std::unique_ptr<ASTDeclaration> Parser::declaration() {
+    ASTModifiers modifiers = this->modifiers();
+    Token lookahead = this->peek();
+    if (lookahead.fKind == Token::IDENTIFIER && !this->isType(lookahead.fText)) {
+        // we have an identifier that's not a type, could be the start of an interface block
+        return this->interfaceBlock(modifiers);
+    }
+    if (lookahead.fKind == Token::STRUCT) {
+        return this->structVarDeclaration(modifiers);
+    }
+    std::unique_ptr<ASTType> type(this->type());
+    if (!type) {
+        return nullptr;
+    }
+    if (type->fKind == ASTType::kStruct_Kind && peek().fKind == Token::SEMICOLON) {
+        this->nextToken();
+        return nullptr;
+    }
+    Token name;
+    if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+        return nullptr;
+    }
+    if (!modifiers.fFlags && this->peek().fKind == Token::LPAREN) {
+        this->nextToken();
+        std::vector<std::unique_ptr<ASTParameter>> parameters;
+        while (this->peek().fKind != Token::RPAREN) {
+            if (parameters.size() > 0) {
+                if (!this->expect(Token::COMMA, "','")) {
+                    return nullptr;
+                }
+            }
+            std::unique_ptr<ASTParameter> parameter = this->parameter();
+            if (!parameter) {
+                return nullptr;
+            }
+            parameters.push_back(std::move(parameter));
+        }
+        this->nextToken();
+        std::unique_ptr<ASTBlock> body;
+        if (this->peek().fKind == Token::SEMICOLON) {
+            this->nextToken();
+        } else {
+            body = this->block();
+            if (!body) {
+                return nullptr;
+            }
+        }
+        return std::unique_ptr<ASTDeclaration>(new ASTFunction(name.fPosition, std::move(type), 
+                                                               std::move(name.fText), 
+                                                               std::move(parameters), 
+                                                               std::move(body)));
+    } else {
+        return this->varDeclarationEnd(modifiers, std::move(type), name.fText);
+    }
+}
+
+/* modifiers type IDENTIFIER varDeclarationEnd */
+std::unique_ptr<ASTVarDeclaration> Parser::varDeclaration() {
+    ASTModifiers modifiers = this->modifiers();
+    std::unique_ptr<ASTType> type(this->type());
+    if (!type) {
+        return nullptr;
+    }
+    Token name;
+    if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+        return nullptr;
+    }
+    return this->varDeclarationEnd(modifiers, std::move(type), std::move(name.fText));
+}
+
+/* STRUCT IDENTIFIER LBRACE varDeclaration* RBRACE */
+std::unique_ptr<ASTType> Parser::structDeclaration() {
+    if (!this->expect(Token::STRUCT, "'struct'")) {
+        return nullptr;
+    }
+    Token name;
+    if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::LBRACE, "'{'")) {
+        return nullptr;
+    }
+    std::vector<Type::Field> fields;
+    while (this->peek().fKind != Token::RBRACE) {
+        std::unique_ptr<ASTVarDeclaration> decl = this->varDeclaration();
+        if (!decl) {
+            return nullptr;
+        }
+        for (size_t i = 0; i < decl->fNames.size(); i++) {
+            auto type = std::static_pointer_cast<Type>(fTypes[decl->fType->fName]);
+            for (int j = (int) decl->fSizes[i].size() - 1; j >= 0; j--) {
+                if (decl->fSizes[i][j]->fKind == ASTExpression::kInt_Kind) {
+                    this->error(decl->fPosition, "array size in struct field must be a constant");
+                }
+                uint64_t columns = ((ASTIntLiteral&) *decl->fSizes[i][j]).fValue;
+                std::string name = type->name() + "[" + to_string(columns) + "]";
+                type = std::shared_ptr<Type>(new Type(name, Type::kArray_Kind, std::move(type), 
+                                                      (int) columns));
+            }
+            fields.push_back(Type::Field(decl->fModifiers, decl->fNames[i], std::move(type)));
+            if (decl->fValues[i]) {
+                this->error(decl->fPosition, "initializers are not permitted on struct fields");
+            }
+        }
+    }
+    if (!this->expect(Token::RBRACE, "'}'")) {
+        return nullptr;
+    }
+    std::shared_ptr<Type> type(new Type(name.fText, fields));
+    fTypes.add(type->fName, type);
+    return std::unique_ptr<ASTType>(new ASTType(name.fPosition, type->fName, 
+                                                ASTType::kStruct_Kind));
+}
+
+/* structDeclaration ((IDENTIFIER varDeclarationEnd) | SEMICOLON) */
+std::unique_ptr<ASTVarDeclaration> Parser::structVarDeclaration(ASTModifiers modifiers) {
+    std::unique_ptr<ASTType> type = this->structDeclaration();
+    if (!type) {
+        return nullptr;
+    }
+    if (peek().fKind == Token::IDENTIFIER) {
+        Token name = this->nextToken();
+        std::unique_ptr<ASTVarDeclaration> result = this->varDeclarationEnd(modifiers, 
+                                                                            std::move(type), 
+                                                                            std::move(name.fText));
+        if (result) {
+            for (size_t i = 0; i < result->fValues.size(); i++) {
+                if (result->fValues[i]) {
+                    this->error(result->fValues[i]->fPosition, 
+                                "struct variables cannot be initialized");
+                }
+            }
+        }
+        return result;
+    }
+    this->expect(Token::SEMICOLON, "';'");
+    return nullptr;
+}
+
+/* (LBRACKET expression? RBRACKET)* (EQ expression)? (COMMA IDENTIFER 
+   (LBRACKET expression? RBRACKET)* (EQ expression)?)* SEMICOLON */
+std::unique_ptr<ASTVarDeclaration> Parser::varDeclarationEnd(ASTModifiers mods,
+                                                             std::unique_ptr<ASTType> type,
+                                                             std::string name) {
+    std::vector<std::string> names;
+    std::vector<std::vector<std::unique_ptr<ASTExpression>>> sizes;
+    names.push_back(name);
+    std::vector<std::unique_ptr<ASTExpression>> currentVarSizes;
+    while (this->peek().fKind == Token::LBRACKET) {
+        this->nextToken();
+        if (this->peek().fKind == Token::RBRACKET) {
+            this->nextToken();
+            currentVarSizes.push_back(nullptr);
+        } else {
+            std::unique_ptr<ASTExpression> size(this->expression());
+            if (!size) {
+                return nullptr;
+            }
+            currentVarSizes.push_back(std::move(size));
+            if (!this->expect(Token::RBRACKET, "']'")) {
+                return nullptr;
+            }
+        }
+    }
+    sizes.push_back(std::move(currentVarSizes));
+    std::vector<std::unique_ptr<ASTExpression>> values;
+    if (this->peek().fKind == Token::EQ) {
+        this->nextToken();
+        std::unique_ptr<ASTExpression> value(this->expression());
+        if (!value) {
+            return nullptr;
+        }
+        values.push_back(std::move(value));
+    } else {
+        values.push_back(nullptr);
+    }
+    while (this->peek().fKind == Token::COMMA) {
+        this->nextToken();
+        Token name;
+        if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+            return nullptr;
+        }
+        names.push_back(name.fText);
+        currentVarSizes.clear();
+        while (this->peek().fKind == Token::LBRACKET) {
+            this->nextToken();
+            if (this->peek().fKind == Token::RBRACKET) {
+                this->nextToken();
+                currentVarSizes.push_back(nullptr);
+            } else {
+                std::unique_ptr<ASTExpression> size(this->expression());
+                if (!size) {
+                    return nullptr;
+                }
+                currentVarSizes.push_back(std::move(size));
+                if (!this->expect(Token::RBRACKET, "']'")) {
+                    return nullptr;
+                }
+            }
+        }
+        sizes.push_back(std::move(currentVarSizes));
+        if (this->peek().fKind == Token::EQ) {
+            this->nextToken();
+            std::unique_ptr<ASTExpression> value(this->expression());
+            if (!value) {
+                return nullptr;
+            }
+            values.push_back(std::move(value));
+        } else {
+            values.push_back(nullptr);
+        }
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTVarDeclaration>(new ASTVarDeclaration(std::move(mods),
+                                                                    std::move(type),
+                                                                    std::move(names),
+                                                                    std::move(sizes),
+                                                                    std::move(values)));
+}
+
+/* modifiers type IDENTIFIER (LBRACKET INT_LITERAL RBRACKET)? */
+std::unique_ptr<ASTParameter> Parser::parameter() {
+    ASTModifiers modifiers = this->modifiersWithDefaults(ASTModifiers::kIn_Flag);
+    std::unique_ptr<ASTType> type = this->type();
+    if (!type) {
+        return nullptr;
+    }
+    Token name;
+    if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+        return nullptr;
+    }
+    std::vector<int> sizes;
+    while (this->peek().fKind == Token::LBRACKET) {
+        this->nextToken();
+        Token sizeToken;
+        if (!this->expect(Token::INT_LITERAL, "a positive integer", &sizeToken)) {
+            return nullptr;
+        }
+        sizes.push_back(SkSL::stoi(sizeToken.fText));
+        if (!this->expect(Token::RBRACKET, "']'")) {
+            return nullptr;
+        }
+    }
+    return std::unique_ptr<ASTParameter>(new ASTParameter(name.fPosition, modifiers, 
+                                                          std::move(type), name.fText, 
+                                                          std::move(sizes)));
+}
+
+/** (EQ INT_LITERAL)? */
+int Parser::layoutInt() {
+    if (!this->expect(Token::EQ, "'='")) {
+        return -1;
+    }
+    Token resultToken;
+    if (this->expect(Token::INT_LITERAL, "a non-negative integer", &resultToken)) {
+        return SkSL::stoi(resultToken.fText);
+    }
+    return -1;
+}
+
+/* LAYOUT LPAREN IDENTIFIER EQ INT_LITERAL (COMMA IDENTIFIER EQ INT_LITERAL)* 
+   RPAREN */
+ASTLayout Parser::layout() {
+    int location = -1;
+    int binding = -1;
+    int index = -1;
+    int set = -1;
+    int builtin = -1;
+    if (this->peek().fKind == Token::LAYOUT) {
+        this->nextToken();
+        if (!this->expect(Token::LPAREN, "'('")) {
+            return ASTLayout(location, binding, index, set, builtin);
+        }
+        for (;;) {
+            Token t = this->nextToken();
+            if (t.fText == "location") {
+                location = this->layoutInt();
+            } else if (t.fText == "binding") {
+                binding = this->layoutInt();
+            } else if (t.fText == "index") {
+                index = this->layoutInt();
+            } else if (t.fText == "set") {
+                set = this->layoutInt();
+            } else if (t.fText == "builtin") {
+                builtin = this->layoutInt();
+            } else {
+                this->error(t.fPosition, ("'" + t.fText + 
+                                          "' is not a valid layout qualifier").c_str());
+            }
+            if (this->peek().fKind == Token::RPAREN) {
+                this->nextToken();
+                break;
+            }
+            if (!this->expect(Token::COMMA, "','")) {
+                break;
+            }
+        }
+    }
+    return ASTLayout(location, binding, index, set, builtin);
+}
+
+/* layout? (UNIFORM | CONST | IN | OUT | INOUT | LOWP | 
+   MEDIUMP | HIGHP)* */
+ASTModifiers Parser::modifiers() {
+    ASTLayout layout = this->layout();
+    int flags = 0;
+    for (;;) {
+        // TODO: handle duplicate / incompatible flags
+        switch (peek().fKind) {
+            case Token::UNIFORM:
+                this->nextToken();
+                flags |= ASTModifiers::kUniform_Flag;
+                break;
+            case Token::CONST:
+                this->nextToken();
+                flags |= ASTModifiers::kConst_Flag;
+                break;
+            case Token::IN:
+                this->nextToken();
+                flags |= ASTModifiers::kIn_Flag;
+                break;
+            case Token::OUT:
+                this->nextToken();
+                flags |= ASTModifiers::kOut_Flag;
+                break;
+            case Token::INOUT:
+                this->nextToken();
+                flags |= ASTModifiers::kIn_Flag;
+                flags |= ASTModifiers::kOut_Flag;
+                break;
+            case Token::LOWP:
+                this->nextToken();
+                flags |= ASTModifiers::kLowp_Flag;
+                break;
+            case Token::MEDIUMP:
+                this->nextToken();
+                flags |= ASTModifiers::kMediump_Flag;
+                break;
+            case Token::HIGHP:
+                this->nextToken();
+                flags |= ASTModifiers::kHighp_Flag;
+                break;
+            default:
+                return ASTModifiers(layout, flags);
+        }
+    }
+}
+
+ASTModifiers Parser::modifiersWithDefaults(int defaultFlags) {
+    ASTModifiers result = this->modifiers();
+    if (!result.fFlags) {
+        return ASTModifiers(result.fLayout, defaultFlags);
+    }
+    return result;
+}
+
+/* ifStatement | forStatement | doStatement | whileStatement | block | expression */
+std::unique_ptr<ASTStatement> Parser::statement() {
+    Token start = this->peek();
+    switch (start.fKind) {
+        case Token::IF:
+            return this->ifStatement();
+        case Token::FOR:
+            return this->forStatement();
+        case Token::DO:
+            return this->doStatement();
+        case Token::WHILE:
+            return this->whileStatement();
+        case Token::RETURN:
+            return this->returnStatement();
+        case Token::BREAK:
+            return this->breakStatement();
+        case Token::CONTINUE:
+            return this->continueStatement();
+        case Token::DISCARD:
+            return this->discardStatement();
+        case Token::LBRACE:
+            return this->block();
+        case Token::SEMICOLON:
+            this->nextToken(); 
+            return std::unique_ptr<ASTStatement>(new ASTBlock(start.fPosition, {}));
+        case Token::CONST:   // fall through
+        case Token::HIGHP:   // fall through
+        case Token::MEDIUMP: // fall through
+        case Token::LOWP: {
+            auto decl = this->varDeclaration();
+            if (!decl) {
+                return nullptr;
+            }
+            return std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(std::move(decl)));
+        }
+        case Token::IDENTIFIER:
+            if (this->isType(start.fText)) {
+                auto decl = this->varDeclaration();
+                if (!decl) {
+                    return nullptr;
+                }
+                return std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(
+                                                                                  std::move(decl)));
+            }
+            // fall through
+        default:
+            return this->expressionStatement();
+    }    
+}
+
+/* IDENTIFIER(type) */
+std::unique_ptr<ASTType> Parser::type() {
+    Token type;
+    if (!this->expect(Token::IDENTIFIER, "a type", &type)) {
+        return nullptr;
+    }
+    if (!this->isType(type.fText)) {
+        this->error(type.fPosition, ("no type named '" + type.fText + "'").c_str());
+        return nullptr;
+    }
+    return std::unique_ptr<ASTType>(new ASTType(type.fPosition, std::move(type.fText), 
+                                                ASTType::kIdentifier_Kind));
+}
+
+/* IDENTIFIER LBRACE varDeclaration* RBRACE */
+std::unique_ptr<ASTDeclaration> Parser::interfaceBlock(ASTModifiers mods) {
+    Token name;
+    if (!this->expect(Token::IDENTIFIER, "an identifier", &name)) {
+        return nullptr;
+    }
+    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.fPosition, "no type named '" + name.fText + "'");
+        return nullptr;
+    }
+    this->nextToken();
+    std::vector<std::unique_ptr<ASTVarDeclaration>> decls; 
+    while (this->peek().fKind != Token::RBRACE) {
+        std::unique_ptr<ASTVarDeclaration> decl = this->varDeclaration();
+        if (!decl) {
+            return nullptr;
+        }
+        decls.push_back(std::move(decl));
+    }
+    this->nextToken();
+    std::string valueName;
+    if (this->peek().fKind == Token::IDENTIFIER) {
+        valueName = this->nextToken().fText;
+    }
+    this->expect(Token::SEMICOLON, "';'");
+    return std::unique_ptr<ASTDeclaration>(new ASTInterfaceBlock(name.fPosition, mods, 
+                                                                 name.fText, std::move(valueName),
+                                                                 std::move(decls)));
+}
+
+/* IF LPAREN expression RPAREN statement (ELSE statement)? */
+std::unique_ptr<ASTIfStatement> Parser::ifStatement() {
+    Token start;
+    if (!this->expect(Token::IF, "'if'", &start)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::LPAREN, "'('")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTExpression> test(this->expression());
+    if (!test) {
+        return nullptr;
+    }
+    if (!this->expect(Token::RPAREN, "')'")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTStatement> ifTrue(this->statement());
+    if (!ifTrue) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTStatement> ifFalse;
+    if (this->peek().fKind == Token::ELSE) {
+        this->nextToken();
+        ifFalse = this->statement();
+        if (!ifFalse) {
+            return nullptr;
+        }
+    }
+    return std::unique_ptr<ASTIfStatement>(new ASTIfStatement(start.fPosition, std::move(test), 
+                                                              std::move(ifTrue), 
+                                                              std::move(ifFalse)));
+}
+
+/* DO statement WHILE LPAREN expression RPAREN SEMICOLON */
+std::unique_ptr<ASTDoStatement> Parser::doStatement() {
+    Token start;
+    if (!this->expect(Token::DO, "'do'", &start)) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTStatement> statement(this->statement());
+    if (!statement) {
+        return nullptr;
+    }
+    if (!this->expect(Token::WHILE, "'while'")) {
+        return nullptr;
+    }
+    if (!this->expect(Token::LPAREN, "'('")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTExpression> test(this->expression());
+    if (!test) {
+        return nullptr;
+    }
+    if (!this->expect(Token::RPAREN, "')'")) {
+        return nullptr;
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTDoStatement>(new ASTDoStatement(start.fPosition, 
+                                                              std::move(statement),
+                                                              std::move(test)));
+}
+
+/* WHILE LPAREN expression RPAREN STATEMENT */
+std::unique_ptr<ASTWhileStatement> Parser::whileStatement() {
+    Token start;
+    if (!this->expect(Token::WHILE, "'while'", &start)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::LPAREN, "'('")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTExpression> test(this->expression());
+    if (!test) {
+        return nullptr;
+    }
+    if (!this->expect(Token::RPAREN, "')'")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTStatement> statement(this->statement());
+    if (!statement) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTWhileStatement>(new ASTWhileStatement(start.fPosition, 
+                                                                    std::move(test), 
+                                                                    std::move(statement)));
+}
+
+/* FOR LPAREN (declaration | expression)? SEMICOLON expression? SEMICOLON expression? RPAREN 
+   STATEMENT */
+std::unique_ptr<ASTForStatement> Parser::forStatement() {
+    Token start;
+    if (!this->expect(Token::FOR, "'for'", &start)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::LPAREN, "'('")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTStatement> initializer;
+    Token nextToken = this->peek();
+    switch (nextToken.fKind) {
+        case Token::SEMICOLON: 
+            break;
+        case Token::CONST:
+            initializer = std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(
+                                                                           this->varDeclaration()));
+            break;
+        case Token::IDENTIFIER: 
+            if (this->isType(nextToken.fText)) {
+                initializer = std::unique_ptr<ASTStatement>(new ASTVarDeclarationStatement(
+                                                                           this->varDeclaration()));
+                break;
+            }
+            // fall through
+        default:
+            initializer = this->expressionStatement();
+    }
+    std::unique_ptr<ASTExpression> test;
+    if (this->peek().fKind != Token::SEMICOLON) {
+        test = this->expression();
+        if (!test) {
+            return nullptr;
+        }
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTExpression> next;
+    if (this->peek().fKind != Token::SEMICOLON) {
+        next = this->expression();
+        if (!next) {
+            return nullptr;
+        }
+    }
+    if (!this->expect(Token::RPAREN, "')'")) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTStatement> statement(this->statement());
+    if (!statement) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTForStatement>(new ASTForStatement(start.fPosition, 
+                                                                std::move(initializer),
+                                                                std::move(test), std::move(next),
+                                                                std::move(statement)));
+}
+
+/* RETURN expression? SEMICOLON */
+std::unique_ptr<ASTReturnStatement> Parser::returnStatement() {
+    Token start;
+    if (!this->expect(Token::RETURN, "'return'", &start)) {
+        return nullptr;
+    }
+    std::unique_ptr<ASTExpression> expression;
+    if (this->peek().fKind != Token::SEMICOLON) {
+        expression = this->expression();
+        if (!expression) {
+            return nullptr;
+        }
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTReturnStatement>(new ASTReturnStatement(start.fPosition, 
+                                                                      std::move(expression)));
+}
+
+/* BREAK SEMICOLON */
+std::unique_ptr<ASTBreakStatement> Parser::breakStatement() {
+    Token start;
+    if (!this->expect(Token::BREAK, "'break'", &start)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTBreakStatement>(new ASTBreakStatement(start.fPosition));
+}
+
+/* CONTINUE SEMICOLON */
+std::unique_ptr<ASTContinueStatement> Parser::continueStatement() {
+    Token start;
+    if (!this->expect(Token::CONTINUE, "'continue'", &start)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTContinueStatement>(new ASTContinueStatement(start.fPosition));
+}
+
+/* DISCARD SEMICOLON */
+std::unique_ptr<ASTDiscardStatement> Parser::discardStatement() {
+    Token start;
+    if (!this->expect(Token::DISCARD, "'continue'", &start)) {
+        return nullptr;
+    }
+    if (!this->expect(Token::SEMICOLON, "';'")) {
+        return nullptr;
+    }
+    return std::unique_ptr<ASTDiscardStatement>(new ASTDiscardStatement(start.fPosition));
+}
+
+/* LBRACE statement* RBRACE */
+std::unique_ptr<ASTBlock> Parser::block() {
+    Token start;
+    if (!this->expect(Token::LBRACE, "'{'", &start)) {
+        return nullptr;
+    }
+    std::vector<std::unique_ptr<ASTStatement>> statements;
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::RBRACE: 
+                this->nextToken();
+                return std::unique_ptr<ASTBlock>(new ASTBlock(start.fPosition, 
+                                                              std::move(statements)));
+            case Token::END_OF_FILE: 
+                this->error(this->peek().fPosition, "expected '}', but found end of file");
+                return nullptr;
+            default: {
+                std::unique_ptr<ASTStatement> statement = this->statement();
+                if (!statement) {
+                    return nullptr;
+                }
+                statements.push_back(std::move(statement));
+            }
+        }
+    }
+}
+
+/* expression SEMICOLON */
+std::unique_ptr<ASTExpressionStatement> Parser::expressionStatement() {
+    std::unique_ptr<ASTExpression> expr = this->expression();
+    if (expr) {
+        if (this->expect(Token::SEMICOLON, "';'")) {
+            ASTExpressionStatement* result = new ASTExpressionStatement(std::move(expr));
+            return std::unique_ptr<ASTExpressionStatement>(result);
+        }
+    }
+    return nullptr;
+}
+
+/* assignmentExpression */
+std::unique_ptr<ASTExpression> Parser::expression() {
+    return this->assignmentExpression();
+}
+
+/* ternaryExpression ((EQEQ | STAREQ | SLASHEQ | PERCENTEQ | PLUSEQ | MINUSEQ | SHLEQ | SHREQ |
+   BITWISEANDEQ | BITWISEXOREQ | BITWISEOREQ | LOGICALANDEQ | LOGICALXOREQ | LOGICALOREQ)
+   assignmentExpression)*
+ */
+std::unique_ptr<ASTExpression> Parser::assignmentExpression() {
+    std::unique_ptr<ASTExpression> result = this->ternaryExpression();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::EQ:           // fall through
+            case Token::STAREQ:       // fall through
+            case Token::SLASHEQ:      // fall through
+            case Token::PERCENTEQ:    // fall through
+            case Token::PLUSEQ:       // fall through
+            case Token::MINUSEQ:      // fall through
+            case Token::SHLEQ:        // fall through
+            case Token::SHREQ:        // fall through
+            case Token::BITWISEANDEQ: // fall through
+            case Token::BITWISEXOREQ: // fall through
+            case Token::BITWISEOREQ:  // fall through
+            case Token::LOGICALANDEQ: // fall through
+            case Token::LOGICALXOREQ: // fall through
+            case Token::LOGICALOREQ: {
+                Token t = this->nextToken();
+                std::unique_ptr<ASTExpression> right = this->assignmentExpression();
+                if (!right) {
+                    return nullptr;
+                }
+                result = std::unique_ptr<ASTExpression>(new ASTBinaryExpression(std::move(result), 
+                                                                                t, 
+                                                                                std::move(right)));
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* logicalOrExpression ('?' expression ':' assignmentExpression)? */
+std::unique_ptr<ASTExpression> Parser::ternaryExpression() {
+    std::unique_ptr<ASTExpression> result = this->logicalOrExpression();
+    if (!result) {
+        return nullptr;
+    }
+    if (this->peek().fKind == Token::QUESTION) {
+        Token question = this->nextToken();
+        std::unique_ptr<ASTExpression> trueExpr = this->expression();
+        if (!trueExpr) {
+            return nullptr;
+        }
+        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)));
+        }
+        return nullptr;
+    }
+    return result;
+}
+
+/* logicalXorExpression (LOGICALOR logicalXorExpression)* */
+std::unique_ptr<ASTExpression> Parser::logicalOrExpression() {
+    std::unique_ptr<ASTExpression> result = this->logicalXorExpression();
+    if (!result) {
+        return nullptr;
+    }
+    while (this->peek().fKind == Token::LOGICALOR) {
+        Token t = this->nextToken();
+        std::unique_ptr<ASTExpression> right = this->logicalXorExpression();
+        if (!right) {
+            return nullptr;
+        }
+        result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+    }
+    return result;
+}
+
+/* logicalAndExpression (LOGICALXOR logicalAndExpression)* */
+std::unique_ptr<ASTExpression> Parser::logicalXorExpression() {
+    std::unique_ptr<ASTExpression> result = this->logicalAndExpression();
+    if (!result) {
+        return nullptr;
+    }
+    while (this->peek().fKind == Token::LOGICALXOR) {
+        Token t = this->nextToken();
+        std::unique_ptr<ASTExpression> right = this->logicalAndExpression();
+        if (!right) {
+            return nullptr;
+        }
+        result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+    }
+    return result;
+}
+
+/* bitwiseOrExpression (LOGICALAND bitwiseOrExpression)* */
+std::unique_ptr<ASTExpression> Parser::logicalAndExpression() {
+    std::unique_ptr<ASTExpression> result = this->bitwiseOrExpression();
+    if (!result) {
+        return nullptr;
+    }
+    while (this->peek().fKind == Token::LOGICALAND) {
+        Token t = this->nextToken();
+        std::unique_ptr<ASTExpression> right = this->bitwiseOrExpression();
+        if (!right) {
+            return nullptr;
+        }
+        result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+    }
+    return result;
+}
+
+/* bitwiseXorExpression (BITWISEOR bitwiseXorExpression)* */
+std::unique_ptr<ASTExpression> Parser::bitwiseOrExpression() {
+    std::unique_ptr<ASTExpression> result = this->bitwiseXorExpression();
+    if (!result) {
+        return nullptr;
+    }
+    while (this->peek().fKind == Token::BITWISEOR) {
+        Token t = this->nextToken();
+        std::unique_ptr<ASTExpression> right = this->bitwiseXorExpression();
+        if (!right) {
+            return nullptr;
+        }
+        result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+    }
+    return result;
+}
+
+/* bitwiseAndExpression (BITWISEXOR bitwiseAndExpression)* */
+std::unique_ptr<ASTExpression> Parser::bitwiseXorExpression() {
+    std::unique_ptr<ASTExpression> result = this->bitwiseAndExpression();
+    if (!result) {
+        return nullptr;
+    }
+    while (this->peek().fKind == Token::BITWISEXOR) {
+        Token t = this->nextToken();
+        std::unique_ptr<ASTExpression> right = this->bitwiseAndExpression();
+        if (!right) {
+            return nullptr;
+        }
+        result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+    }
+    return result;
+}
+
+/* equalityExpression (BITWISEAND equalityExpression)* */
+std::unique_ptr<ASTExpression> Parser::bitwiseAndExpression() {
+    std::unique_ptr<ASTExpression> result = this->equalityExpression();
+    if (!result) {
+        return nullptr;
+    }
+    while (this->peek().fKind == Token::BITWISEAND) {
+        Token t = this->nextToken();
+        std::unique_ptr<ASTExpression> right = this->equalityExpression();
+        if (!right) {
+            return nullptr;
+        }
+        result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+    }
+    return result;
+}
+
+/* relationalExpression ((EQEQ | NEQ) relationalExpression)* */
+std::unique_ptr<ASTExpression> Parser::equalityExpression() {
+    std::unique_ptr<ASTExpression> result = this->relationalExpression();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::EQEQ:   // fall through
+            case Token::NEQ: {
+                Token t = this->nextToken();
+                std::unique_ptr<ASTExpression> right = this->relationalExpression();
+                if (!right) {
+                    return nullptr;
+                }
+                result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+                break;
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* shiftExpression ((LT | GT | LTEQ | GTEQ) shiftExpression)* */
+std::unique_ptr<ASTExpression> Parser::relationalExpression() {
+    std::unique_ptr<ASTExpression> result = this->shiftExpression();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::LT:   // fall through
+            case Token::GT:   // fall through
+            case Token::LTEQ: // fall through
+            case Token::GTEQ: {
+                Token t = this->nextToken();
+                std::unique_ptr<ASTExpression> right = this->shiftExpression();
+                if (!right) {
+                    return nullptr;
+                }
+                result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+                break;
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* additiveExpression ((SHL | SHR) additiveExpression)* */
+std::unique_ptr<ASTExpression> Parser::shiftExpression() {
+    std::unique_ptr<ASTExpression> result = this->additiveExpression();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::SHL: // fall through
+            case Token::SHR: {
+                Token t = this->nextToken();
+                std::unique_ptr<ASTExpression> right = this->additiveExpression();
+                if (!right) {
+                    return nullptr;
+                }
+                result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+                break;
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* multiplicativeExpression ((PLUS | MINUS) multiplicativeExpression)* */
+std::unique_ptr<ASTExpression> Parser::additiveExpression() {
+    std::unique_ptr<ASTExpression> result = this->multiplicativeExpression();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::PLUS: // fall through
+            case Token::MINUS: {
+                Token t = this->nextToken();
+                std::unique_ptr<ASTExpression> right = this->multiplicativeExpression();
+                if (!right) {
+                    return nullptr;
+                }
+                result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+                break;
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* unaryExpression ((STAR | SLASH | PERCENT) unaryExpression)* */
+std::unique_ptr<ASTExpression> Parser::multiplicativeExpression() {
+    std::unique_ptr<ASTExpression> result = this->unaryExpression();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::STAR: // fall through
+            case Token::SLASH: // fall through
+            case Token::PERCENT: {
+                Token t = this->nextToken();
+                std::unique_ptr<ASTExpression> right = this->unaryExpression();
+                if (!right) {
+                    return nullptr;
+                }
+                result.reset(new ASTBinaryExpression(std::move(result), t, std::move(right)));
+                break;
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* postfixExpression | (PLUS | MINUS | NOT | PLUSPLUS | MINUSMINUS) unaryExpression */
+std::unique_ptr<ASTExpression> Parser::unaryExpression() {
+    switch (this->peek().fKind) {
+        case Token::PLUS:     // fall through
+        case Token::MINUS:    // fall through
+        case Token::NOT:      // fall through
+        case Token::PLUSPLUS: // fall through
+        case Token::MINUSMINUS: {
+            Token t = this->nextToken();
+            std::unique_ptr<ASTExpression> expr = this->unaryExpression();
+            if (!expr) {
+                return nullptr;
+            }
+            return std::unique_ptr<ASTExpression>(new ASTPrefixExpression(t, std::move(expr)));
+        }
+        default:
+            return this->postfixExpression();
+    }
+}
+
+/* term suffix* */
+std::unique_ptr<ASTExpression> Parser::postfixExpression() {
+    std::unique_ptr<ASTExpression> result = this->term();
+    if (!result) {
+        return nullptr;
+    }
+    for (;;) {
+        switch (this->peek().fKind) {
+            case Token::LBRACKET: // fall through
+            case Token::DOT:      // fall through
+            case Token::LPAREN:   // fall through
+            case Token::PLUSPLUS: // fall through
+            case Token::MINUSMINUS: {
+                std::unique_ptr<ASTSuffix> s = this->suffix();
+                if (!s) {
+                    return nullptr;
+                }
+                result.reset(new ASTSuffixExpression(std::move(result), std::move(s)));
+                break;
+            }
+            default:
+                return result;
+        }
+    }
+}
+
+/* LBRACKET expression RBRACKET | DOT IDENTIFIER | LPAREN parameters RPAREN | 
+   PLUSPLUS | MINUSMINUS */
+std::unique_ptr<ASTSuffix> Parser::suffix() {
+    Token next = this->nextToken();
+    switch (next.fKind) {
+        case Token::LBRACKET: {
+            std::unique_ptr<ASTExpression> e = this->expression();
+            if (!e) {
+                return nullptr;
+            }
+            this->expect(Token::RBRACKET, "']' to complete array access expression");
+            return std::unique_ptr<ASTSuffix>(new ASTIndexSuffix(std::move(e)));
+        }
+        case Token::DOT: {
+            Position pos = this->peek().fPosition;
+            std::string text;
+            if (this->identifier(&text)) {
+                return std::unique_ptr<ASTSuffix>(new ASTFieldSuffix(pos, std::move(text)));
+            }
+            return nullptr;
+        }
+        case Token::LPAREN: {
+            std::vector<std::unique_ptr<ASTExpression>> parameters;
+            if (this->peek().fKind != Token::RPAREN) {
+                for (;;) {
+                    std::unique_ptr<ASTExpression> expr = this->expression();
+                    if (!expr) {
+                        return nullptr;
+                    }
+                    parameters.push_back(std::move(expr));
+                    if (this->peek().fKind != Token::COMMA) {
+                        break;
+                    }
+                    this->nextToken();
+                }
+            }
+            this->expect(Token::RPAREN, "')' to complete function parameters");
+            return std::unique_ptr<ASTSuffix>(new ASTCallSuffix(next.fPosition, 
+                                                                std::move(parameters)));
+        }
+        case Token::PLUSPLUS:
+            return std::unique_ptr<ASTSuffix>(new ASTSuffix(next.fPosition, 
+                                                            ASTSuffix::kPostIncrement_Kind));
+        case Token::MINUSMINUS:
+            return std::unique_ptr<ASTSuffix>(new ASTSuffix(next.fPosition,
+                                                            ASTSuffix::kPostDecrement_Kind));
+        default: {
+            this->error(next.fPosition,  "expected expression suffix, but found '" + next.fText + 
+                                         "'\n");
+            return nullptr;
+        }
+    }
+}
+
+/* IDENTIFIER | intLiteral | floatLiteral | boolLiteral | '(' expression ')' */
+std::unique_ptr<ASTExpression> Parser::term() {
+    std::unique_ptr<ASTExpression> result;
+    Token t = this->peek();
+    switch (t.fKind) {
+        case Token::IDENTIFIER: {
+            std::string text;
+            if (this->identifier(&text)) {
+                result.reset(new ASTIdentifier(t.fPosition, std::move(text)));
+            }
+            break;
+        }
+        case Token::INT_LITERAL: {
+            int64_t i;
+            if (this->intLiteral(&i)) {
+                result.reset(new ASTIntLiteral(t.fPosition, i));
+            }
+            break;
+        }
+        case Token::FLOAT_LITERAL: {
+            double f;
+            if (this->floatLiteral(&f)) {
+                result.reset(new ASTFloatLiteral(t.fPosition, f));
+            }
+            break;
+        }
+        case Token::TRUE_LITERAL: // fall through
+        case Token::FALSE_LITERAL: {
+            bool b;
+            if (this->boolLiteral(&b)) {
+                result.reset(new ASTBoolLiteral(t.fPosition, b));
+            }
+            break;
+        }
+        case Token::LPAREN: {
+            this->nextToken();
+            result = this->expression();
+            if (result) {
+                this->expect(Token::RPAREN, "')' to complete expression");
+            }
+            break;
+        }
+        default:
+            this->nextToken();
+            this->error(t.fPosition,  "expected expression, but found '" + t.fText + "'\n");
+            result = nullptr;
+    }
+    return result;
+}
+
+/* INT_LITERAL */
+bool Parser::intLiteral(int64_t* dest) {
+    Token t;
+    if (this->expect(Token::INT_LITERAL, "integer literal", &t)) {
+        *dest = SkSL::stol(t.fText);
+        return true;
+    }
+    return false;
+}
+
+/* FLOAT_LITERAL */
+bool Parser::floatLiteral(double* dest) {
+    Token t;
+    if (this->expect(Token::FLOAT_LITERAL, "float literal", &t)) {
+        *dest = SkSL::stod(t.fText);
+        return true;
+    }
+    return false;
+}
+
+/* TRUE_LITERAL | FALSE_LITERAL */
+bool Parser::boolLiteral(bool* dest) {
+    Token t = this->nextToken();
+    switch (t.fKind) {
+        case Token::TRUE_LITERAL:
+            *dest = true;
+            return true;
+        case Token::FALSE_LITERAL:
+            *dest = false;
+            return true;
+        default:
+            this->error(t.fPosition, "expected 'true' or 'false', but found '" + t.fText + "'\n");
+            return false;
+    }
+}
+
+/* IDENTIFIER */
+bool Parser::identifier(std::string* dest) {
+    Token t;
+    if (this->expect(Token::IDENTIFIER, "identifier", &t)) {
+        *dest = t.fText; 
+        return true;
+    }
+    return false;
+}
+
+} // namespace
diff --git a/src/sksl/SkSLParser.h b/src/sksl/SkSLParser.h
new file mode 100644
index 0000000..45629a3
--- /dev/null
+++ b/src/sksl/SkSLParser.h
@@ -0,0 +1,209 @@
+/*
+ * 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_PARSER
+#define SKSL_PARSER
+
+#include <string>
+#include <vector>
+#include <memory>
+#include <unordered_set>
+#include "SkSLErrorReporter.h"
+#include "SkSLToken.h"
+
+struct yy_buffer_state;
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+
+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 ASTLayout;
+struct ASTModifiers;
+struct ASTParameter;
+struct ASTReturnStatement;
+struct ASTStatement;
+struct ASTSuffix;
+struct ASTType;
+struct ASTWhileStatement;
+struct ASTVarDeclaration;
+class SymbolTable;
+
+/**
+ * Consumes .sksl text and produces an abstract syntax tree describing the contents.
+ */
+class Parser {
+public:
+    Parser(std::string text, SymbolTable& types, ErrorReporter& errors);
+
+    ~Parser();
+
+    /**
+     * 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.
+     */
+    std::vector<std::unique_ptr<ASTDeclaration>> file();
+
+private:
+    /**
+     * Return the next token from the parse stream.
+     */
+    Token nextToken();
+
+    /**
+     * Push a token back onto the parse stream, so that it is the next one read. Only a single level
+     * of pushback is supported (that is, it is an error to call pushback() twice in a row without
+     * an intervening nextToken()).
+     */
+    void pushback(Token t);
+
+    /**
+     * Returns the next token without consuming it from the stream. 
+     */
+    Token peek();
+
+    /**
+     * Reads the next token and generates an error if it is not the expected type. The 'expected'
+     * string is part of the error message, which reads:
+     *
+     * "expected <expected>, but found '<actual text>'"
+     *
+     * If 'result' is non-null, it is set to point to the token that was read.
+     * Returns true if the read token was as expected, false otherwise.
+     */
+    bool expect(Token::Kind kind, std::string expected, Token* result = nullptr);
+
+    void error(Position p, std::string msg);
+    
+    /**
+     * Returns true if the 'name' identifier refers to a type name. For instance, isType("int") will
+     * always return true.
+     */
+    bool isType(std::string name);
+
+    // 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.
+
+    void precision();
+
+    std::unique_ptr<ASTDeclaration> directive();
+
+    std::unique_ptr<ASTDeclaration> declaration();
+
+    std::unique_ptr<ASTVarDeclaration> varDeclaration();
+
+    std::unique_ptr<ASTType> structDeclaration();
+
+    std::unique_ptr<ASTVarDeclaration> structVarDeclaration(ASTModifiers modifiers);
+
+    std::unique_ptr<ASTVarDeclaration> varDeclarationEnd(ASTModifiers modifiers,
+                                                         std::unique_ptr<ASTType> type, 
+                                                         std::string name);
+
+    std::unique_ptr<ASTParameter> parameter();
+
+    int layoutInt();
+    
+    ASTLayout layout();
+
+    ASTModifiers modifiers();
+
+    ASTModifiers modifiersWithDefaults(int defaultFlags);
+
+    std::unique_ptr<ASTStatement> statement();
+
+    std::unique_ptr<ASTType> type();
+
+    std::unique_ptr<ASTDeclaration> interfaceBlock(ASTModifiers mods);
+
+    std::unique_ptr<ASTIfStatement> ifStatement();
+
+    std::unique_ptr<ASTDoStatement> doStatement();
+
+    std::unique_ptr<ASTWhileStatement> whileStatement();
+
+    std::unique_ptr<ASTForStatement> forStatement();
+
+    std::unique_ptr<ASTReturnStatement> returnStatement();
+
+    std::unique_ptr<ASTBreakStatement> breakStatement();
+
+    std::unique_ptr<ASTContinueStatement> continueStatement();
+
+    std::unique_ptr<ASTDiscardStatement> discardStatement();
+
+    std::unique_ptr<ASTBlock> block();
+
+    std::unique_ptr<ASTExpressionStatement> expressionStatement();
+
+    std::unique_ptr<ASTExpression> expression();
+
+    std::unique_ptr<ASTExpression> assignmentExpression();
+    
+    std::unique_ptr<ASTExpression> ternaryExpression();
+
+    std::unique_ptr<ASTExpression> logicalOrExpression();
+
+    std::unique_ptr<ASTExpression> logicalXorExpression();
+
+    std::unique_ptr<ASTExpression> logicalAndExpression();
+
+    std::unique_ptr<ASTExpression> bitwiseOrExpression();
+
+    std::unique_ptr<ASTExpression> bitwiseXorExpression();
+
+    std::unique_ptr<ASTExpression> bitwiseAndExpression();
+
+    std::unique_ptr<ASTExpression> equalityExpression();
+
+    std::unique_ptr<ASTExpression> relationalExpression();
+
+    std::unique_ptr<ASTExpression> shiftExpression();
+
+    std::unique_ptr<ASTExpression> additiveExpression();
+
+    std::unique_ptr<ASTExpression> multiplicativeExpression();
+
+    std::unique_ptr<ASTExpression> unaryExpression();
+
+    std::unique_ptr<ASTExpression> postfixExpression();
+
+    std::unique_ptr<ASTSuffix> suffix();
+
+    std::unique_ptr<ASTExpression> term();
+
+    bool intLiteral(int64_t* dest);
+
+    bool floatLiteral(double* dest);
+
+    bool boolLiteral(bool* dest);
+
+    bool identifier(std::string* dest);
+
+
+    void* fScanner;
+    YY_BUFFER_STATE fBuffer;
+    Token fPushback;
+    SymbolTable& fTypes;
+    ErrorReporter& fErrors;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLPosition.h b/src/sksl/SkSLPosition.h
new file mode 100644
index 0000000..979f630
--- /dev/null
+++ b/src/sksl/SkSLPosition.h
@@ -0,0 +1,38 @@
+/*
+ * 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_POSITION
+#define SKSL_POSITION
+
+#include "SkSLUtil.h"
+
+namespace SkSL {
+
+/**
+ * Represents a position in the source code. Both line and column are one-based. Column is currently
+ * ignored.
+ */
+struct Position {
+    Position() 
+    : fLine(-1)
+    , fColumn(-1) {}
+    
+    Position(int line, int column)
+    : fLine(line)
+    , fColumn(column) {}
+
+    std::string description() const {
+        return to_string(fLine);
+    }
+
+    int fLine;
+    int fColumn;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
new file mode 100644
index 0000000..037abc0
--- /dev/null
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -0,0 +1,2628 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+ 
+#include "SkSLSPIRVCodeGenerator.h"
+
+#include "string.h"
+
+#include "GLSL.std.450.h"
+
+#include "ir/SkSLExpressionStatement.h"
+#include "ir/SkSLExtension.h"
+#include "ir/SkSLIndexExpression.h"
+#include "ir/SkSLVariableReference.h"
+
+namespace SkSL {
+
+#define SPIRV_DEBUG 0
+
+static const int32_t SKSL_MAGIC  = 0x0; // FIXME: we should probably register a magic number
+
+void SPIRVCodeGenerator::setupIntrinsics() {
+#define ALL_GLSL(x) std::make_tuple(kGLSL_STD_450_IntrinsicKind, GLSLstd450 ## x, GLSLstd450 ## x, \
+                                    GLSLstd450 ## x, GLSLstd450 ## x)
+#define BY_TYPE_GLSL(ifFloat, ifInt, ifUInt) std::make_tuple(kGLSL_STD_450_IntrinsicKind, \
+                                                             GLSLstd450 ## ifFloat, \
+                                                             GLSLstd450 ## ifInt, \
+                                                             GLSLstd450 ## ifUInt, \
+                                                             SpvOpUndef)
+#define SPECIAL(x) std::make_tuple(kSpecial_IntrinsicKind, k ## x ## _SpecialIntrinsic, \
+                                   k ## x ## _SpecialIntrinsic, k ## x ## _SpecialIntrinsic, \
+                                   k ## x ## _SpecialIntrinsic)
+    fIntrinsicMap["round"]         = ALL_GLSL(Round);
+    fIntrinsicMap["roundEven"]     = ALL_GLSL(RoundEven);
+    fIntrinsicMap["trunc"]         = ALL_GLSL(Trunc);
+    fIntrinsicMap["abs"]           = BY_TYPE_GLSL(FAbs, SAbs, SAbs);
+    fIntrinsicMap["sign"]          = BY_TYPE_GLSL(FSign, SSign, SSign);
+    fIntrinsicMap["floor"]         = ALL_GLSL(Floor);
+    fIntrinsicMap["ceil"]          = ALL_GLSL(Ceil);
+    fIntrinsicMap["fract"]         = ALL_GLSL(Fract);
+    fIntrinsicMap["radians"]       = ALL_GLSL(Radians);
+    fIntrinsicMap["degrees"]       = ALL_GLSL(Degrees);
+    fIntrinsicMap["sin"]           = ALL_GLSL(Sin);
+    fIntrinsicMap["cos"]           = ALL_GLSL(Cos);
+    fIntrinsicMap["tan"]           = ALL_GLSL(Tan);
+    fIntrinsicMap["asin"]          = ALL_GLSL(Asin);
+    fIntrinsicMap["acos"]          = ALL_GLSL(Acos);
+    fIntrinsicMap["atan"]          = SPECIAL(Atan);
+    fIntrinsicMap["sinh"]          = ALL_GLSL(Sinh);
+    fIntrinsicMap["cosh"]          = ALL_GLSL(Cosh);
+    fIntrinsicMap["tanh"]          = ALL_GLSL(Tanh);
+    fIntrinsicMap["asinh"]         = ALL_GLSL(Asinh);
+    fIntrinsicMap["acosh"]         = ALL_GLSL(Acosh);
+    fIntrinsicMap["atanh"]         = ALL_GLSL(Atanh);
+    fIntrinsicMap["pow"]           = ALL_GLSL(Pow);
+    fIntrinsicMap["exp"]           = ALL_GLSL(Exp);
+    fIntrinsicMap["log"]           = ALL_GLSL(Log);
+    fIntrinsicMap["exp2"]          = ALL_GLSL(Exp2);
+    fIntrinsicMap["log2"]          = ALL_GLSL(Log2);
+    fIntrinsicMap["sqrt"]          = ALL_GLSL(Sqrt);
+    fIntrinsicMap["inversesqrt"]   = ALL_GLSL(InverseSqrt);
+    fIntrinsicMap["determinant"]   = ALL_GLSL(Determinant);
+    fIntrinsicMap["matrixInverse"] = ALL_GLSL(MatrixInverse);
+    fIntrinsicMap["mod"]           = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpFMod, SpvOpSMod, 
+                                                     SpvOpUMod, SpvOpUndef);
+    fIntrinsicMap["min"]           = BY_TYPE_GLSL(FMin, SMin, UMin);
+    fIntrinsicMap["max"]           = BY_TYPE_GLSL(FMax, SMax, UMax);
+    fIntrinsicMap["clamp"]         = BY_TYPE_GLSL(FClamp, SClamp, UClamp);
+    fIntrinsicMap["dot"]           = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDot, SpvOpUndef,
+                                                     SpvOpUndef, SpvOpUndef);
+    fIntrinsicMap["mix"]           = ALL_GLSL(FMix);
+    fIntrinsicMap["step"]          = ALL_GLSL(Step);
+    fIntrinsicMap["smoothstep"]    = ALL_GLSL(SmoothStep);
+    fIntrinsicMap["fma"]           = ALL_GLSL(Fma);
+    fIntrinsicMap["frexp"]         = ALL_GLSL(Frexp);
+    fIntrinsicMap["ldexp"]         = ALL_GLSL(Ldexp);
+
+#define PACK(type) fIntrinsicMap["pack" #type] = ALL_GLSL(Pack ## type); \
+                   fIntrinsicMap["unpack" #type] = ALL_GLSL(Unpack ## type)
+    PACK(Snorm4x8);
+    PACK(Unorm4x8);
+    PACK(Snorm2x16);
+    PACK(Unorm2x16);
+    PACK(Half2x16);
+    PACK(Double2x32);
+    fIntrinsicMap["length"]      = ALL_GLSL(Length);
+    fIntrinsicMap["distance"]    = ALL_GLSL(Distance);
+    fIntrinsicMap["cross"]       = ALL_GLSL(Cross);
+    fIntrinsicMap["normalize"]   = ALL_GLSL(Normalize);
+    fIntrinsicMap["faceForward"] = ALL_GLSL(FaceForward);
+    fIntrinsicMap["reflect"]     = ALL_GLSL(Reflect);
+    fIntrinsicMap["refract"]     = ALL_GLSL(Refract);
+    fIntrinsicMap["findLSB"]     = ALL_GLSL(FindILsb);
+    fIntrinsicMap["findMSB"]     = BY_TYPE_GLSL(FindSMsb, FindSMsb, FindUMsb);
+    fIntrinsicMap["dFdx"]        = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDPdx, SpvOpUndef,
+                                                   SpvOpUndef, SpvOpUndef);
+    fIntrinsicMap["dFdy"]        = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDPdy, SpvOpUndef,
+                                                   SpvOpUndef, SpvOpUndef);
+    fIntrinsicMap["dFdy"]        = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpDPdy, SpvOpUndef,
+                                                   SpvOpUndef, SpvOpUndef);
+    fIntrinsicMap["texture"]     = SPECIAL(Texture);
+    fIntrinsicMap["texture2D"]   = SPECIAL(Texture2D);
+    fIntrinsicMap["textureProj"] = SPECIAL(TextureProj);
+
+    fIntrinsicMap["any"]              = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpUndef, 
+                                                        SpvOpUndef, SpvOpUndef, SpvOpAny);
+    fIntrinsicMap["all"]              = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpUndef, 
+                                                        SpvOpUndef, SpvOpUndef, SpvOpAll);
+    fIntrinsicMap["equal"]            = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpFOrdEqual, 
+                                                        SpvOpIEqual, SpvOpIEqual, 
+                                                        SpvOpLogicalEqual);
+    fIntrinsicMap["notEqual"]         = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpFOrdNotEqual, 
+                                                        SpvOpINotEqual, SpvOpINotEqual, 
+                                                        SpvOpLogicalNotEqual);
+    fIntrinsicMap["lessThan"]         = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpSLessThan, 
+                                                        SpvOpULessThan, SpvOpFOrdLessThan, 
+                                                        SpvOpUndef);
+    fIntrinsicMap["lessThanEqual"]    = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpSLessThanEqual, 
+                                                        SpvOpULessThanEqual, SpvOpFOrdLessThanEqual,
+                                                        SpvOpUndef);
+    fIntrinsicMap["greaterThan"]      = std::make_tuple(kSPIRV_IntrinsicKind, SpvOpSGreaterThan, 
+                                                        SpvOpUGreaterThan, SpvOpFOrdGreaterThan, 
+                                                        SpvOpUndef);
+    fIntrinsicMap["greaterThanEqual"] = std::make_tuple(kSPIRV_IntrinsicKind, 
+                                                        SpvOpSGreaterThanEqual, 
+                                                        SpvOpUGreaterThanEqual, 
+                                                        SpvOpFOrdGreaterThanEqual,
+                                                        SpvOpUndef);
+
+// interpolateAt* not yet supported...
+}
+
+void SPIRVCodeGenerator::writeWord(int32_t word, std::ostream& out) {
+#if SPIRV_DEBUG
+    out << "(" << word << ") ";
+#else
+    out.write((const char*) &word, sizeof(word));
+#endif
+}
+
+static bool is_float(const Type& type) {
+    if (type.kind() == Type::kVector_Kind) {
+        return is_float(*type.componentType());
+    }
+    return type == *kFloat_Type || type == *kDouble_Type;
+}
+
+static bool is_signed(const Type& type) {
+    if (type.kind() == Type::kVector_Kind) {
+        return is_signed(*type.componentType());
+    }
+    return type == *kInt_Type;
+}
+
+static bool is_unsigned(const Type& type) {
+    if (type.kind() == Type::kVector_Kind) {
+        return is_unsigned(*type.componentType());
+    }
+    return type == *kUInt_Type;
+}
+
+static bool is_bool(const Type& type) {
+    if (type.kind() == Type::kVector_Kind) {
+        return is_bool(*type.componentType());
+    }
+    return type == *kBool_Type;
+}
+
+static bool is_out(std::shared_ptr<Variable> var) {
+    return (var->fModifiers.fFlags & Modifiers::kOut_Flag) != 0;
+}
+
+#if SPIRV_DEBUG
+static std::string opcode_text(SpvOp_ opCode) {
+    switch (opCode) {
+        case SpvOpNop:
+            return "Nop";
+        case SpvOpUndef:
+            return "Undef";
+        case SpvOpSourceContinued:
+            return "SourceContinued";
+        case SpvOpSource:
+            return "Source";
+        case SpvOpSourceExtension:
+            return "SourceExtension";
+        case SpvOpName:
+            return "Name";
+        case SpvOpMemberName:
+            return "MemberName";
+        case SpvOpString:
+            return "String";
+        case SpvOpLine:
+            return "Line";
+        case SpvOpExtension:
+            return "Extension";
+        case SpvOpExtInstImport:
+            return "ExtInstImport";
+        case SpvOpExtInst:
+            return "ExtInst";
+        case SpvOpMemoryModel:
+            return "MemoryModel";
+        case SpvOpEntryPoint:
+            return "EntryPoint";
+        case SpvOpExecutionMode:
+            return "ExecutionMode";
+        case SpvOpCapability:
+            return "Capability";
+        case SpvOpTypeVoid:
+            return "TypeVoid";
+        case SpvOpTypeBool:
+            return "TypeBool";
+        case SpvOpTypeInt:
+            return "TypeInt";
+        case SpvOpTypeFloat:
+            return "TypeFloat";
+        case SpvOpTypeVector:
+            return "TypeVector";
+        case SpvOpTypeMatrix:
+            return "TypeMatrix";
+        case SpvOpTypeImage:
+            return "TypeImage";
+        case SpvOpTypeSampler:
+            return "TypeSampler";
+        case SpvOpTypeSampledImage:
+            return "TypeSampledImage";
+        case SpvOpTypeArray:
+            return "TypeArray";
+        case SpvOpTypeRuntimeArray:
+            return "TypeRuntimeArray";
+        case SpvOpTypeStruct:
+            return "TypeStruct";
+        case SpvOpTypeOpaque:
+            return "TypeOpaque";
+        case SpvOpTypePointer:
+            return "TypePointer";
+        case SpvOpTypeFunction:
+            return "TypeFunction";
+        case SpvOpTypeEvent:
+            return "TypeEvent";
+        case SpvOpTypeDeviceEvent:
+            return "TypeDeviceEvent";
+        case SpvOpTypeReserveId:
+            return "TypeReserveId";
+        case SpvOpTypeQueue:
+            return "TypeQueue";
+        case SpvOpTypePipe:
+            return "TypePipe";
+        case SpvOpTypeForwardPointer:
+            return "TypeForwardPointer";
+        case SpvOpConstantTrue:
+            return "ConstantTrue";
+        case SpvOpConstantFalse:
+            return "ConstantFalse";
+        case SpvOpConstant:
+            return "Constant";
+        case SpvOpConstantComposite:
+            return "ConstantComposite";
+        case SpvOpConstantSampler:
+            return "ConstantSampler";
+        case SpvOpConstantNull:
+            return "ConstantNull";
+        case SpvOpSpecConstantTrue:
+            return "SpecConstantTrue";
+        case SpvOpSpecConstantFalse:
+            return "SpecConstantFalse";
+        case SpvOpSpecConstant:
+            return "SpecConstant";
+        case SpvOpSpecConstantComposite:
+            return "SpecConstantComposite";
+        case SpvOpSpecConstantOp:
+            return "SpecConstantOp";
+        case SpvOpFunction:
+            return "Function";
+        case SpvOpFunctionParameter:
+            return "FunctionParameter";
+        case SpvOpFunctionEnd:
+            return "FunctionEnd";
+        case SpvOpFunctionCall:
+            return "FunctionCall";
+        case SpvOpVariable:
+            return "Variable";
+        case SpvOpImageTexelPointer:
+            return "ImageTexelPointer";
+        case SpvOpLoad:
+            return "Load";
+        case SpvOpStore:
+            return "Store";
+        case SpvOpCopyMemory:
+            return "CopyMemory";
+        case SpvOpCopyMemorySized:
+            return "CopyMemorySized";
+        case SpvOpAccessChain:
+            return "AccessChain";
+        case SpvOpInBoundsAccessChain:
+            return "InBoundsAccessChain";
+        case SpvOpPtrAccessChain:
+            return "PtrAccessChain";
+        case SpvOpArrayLength:
+            return "ArrayLength";
+        case SpvOpGenericPtrMemSemantics:
+            return "GenericPtrMemSemantics";
+        case SpvOpInBoundsPtrAccessChain:
+            return "InBoundsPtrAccessChain";
+        case SpvOpDecorate:
+            return "Decorate";
+        case SpvOpMemberDecorate:
+            return "MemberDecorate";
+        case SpvOpDecorationGroup:
+            return "DecorationGroup";
+        case SpvOpGroupDecorate:
+            return "GroupDecorate";
+        case SpvOpGroupMemberDecorate:
+            return "GroupMemberDecorate";
+        case SpvOpVectorExtractDynamic:
+            return "VectorExtractDynamic";
+        case SpvOpVectorInsertDynamic:
+            return "VectorInsertDynamic";
+        case SpvOpVectorShuffle:
+            return "VectorShuffle";
+        case SpvOpCompositeConstruct:
+            return "CompositeConstruct";
+        case SpvOpCompositeExtract:
+            return "CompositeExtract";
+        case SpvOpCompositeInsert:
+            return "CompositeInsert";
+        case SpvOpCopyObject:
+            return "CopyObject";
+        case SpvOpTranspose:
+            return "Transpose";
+        case SpvOpSampledImage:
+            return "SampledImage";
+        case SpvOpImageSampleImplicitLod:
+            return "ImageSampleImplicitLod";
+        case SpvOpImageSampleExplicitLod:
+            return "ImageSampleExplicitLod";
+        case SpvOpImageSampleDrefImplicitLod:
+            return "ImageSampleDrefImplicitLod";
+        case SpvOpImageSampleDrefExplicitLod:
+            return "ImageSampleDrefExplicitLod";
+        case SpvOpImageSampleProjImplicitLod:
+            return "ImageSampleProjImplicitLod";
+        case SpvOpImageSampleProjExplicitLod:
+            return "ImageSampleProjExplicitLod";
+        case SpvOpImageSampleProjDrefImplicitLod:
+            return "ImageSampleProjDrefImplicitLod";
+        case SpvOpImageSampleProjDrefExplicitLod:
+            return "ImageSampleProjDrefExplicitLod";
+        case SpvOpImageFetch:
+            return "ImageFetch";
+        case SpvOpImageGather:
+            return "ImageGather";
+        case SpvOpImageDrefGather:
+            return "ImageDrefGather";
+        case SpvOpImageRead:
+            return "ImageRead";
+        case SpvOpImageWrite:
+            return "ImageWrite";
+        case SpvOpImage:
+            return "Image";
+        case SpvOpImageQueryFormat:
+            return "ImageQueryFormat";
+        case SpvOpImageQueryOrder:
+            return "ImageQueryOrder";
+        case SpvOpImageQuerySizeLod:
+            return "ImageQuerySizeLod";
+        case SpvOpImageQuerySize:
+            return "ImageQuerySize";
+        case SpvOpImageQueryLod:
+            return "ImageQueryLod";
+        case SpvOpImageQueryLevels:
+            return "ImageQueryLevels";
+        case SpvOpImageQuerySamples:
+            return "ImageQuerySamples";
+        case SpvOpConvertFToU:
+            return "ConvertFToU";
+        case SpvOpConvertFToS:
+            return "ConvertFToS";
+        case SpvOpConvertSToF:
+            return "ConvertSToF";
+        case SpvOpConvertUToF:
+            return "ConvertUToF";
+        case SpvOpUConvert:
+            return "UConvert";
+        case SpvOpSConvert:
+            return "SConvert";
+        case SpvOpFConvert:
+            return "FConvert";
+        case SpvOpQuantizeToF16:
+            return "QuantizeToF16";
+        case SpvOpConvertPtrToU:
+            return "ConvertPtrToU";
+        case SpvOpSatConvertSToU:
+            return "SatConvertSToU";
+        case SpvOpSatConvertUToS:
+            return "SatConvertUToS";
+        case SpvOpConvertUToPtr:
+            return "ConvertUToPtr";
+        case SpvOpPtrCastToGeneric:
+            return "PtrCastToGeneric";
+        case SpvOpGenericCastToPtr:
+            return "GenericCastToPtr";
+        case SpvOpGenericCastToPtrExplicit:
+            return "GenericCastToPtrExplicit";
+        case SpvOpBitcast:
+            return "Bitcast";
+        case SpvOpSNegate:
+            return "SNegate";
+        case SpvOpFNegate:
+            return "FNegate";
+        case SpvOpIAdd:
+            return "IAdd";
+        case SpvOpFAdd:
+            return "FAdd";
+        case SpvOpISub:
+            return "ISub";
+        case SpvOpFSub:
+            return "FSub";
+        case SpvOpIMul:
+            return "IMul";
+        case SpvOpFMul:
+            return "FMul";
+        case SpvOpUDiv:
+            return "UDiv";
+        case SpvOpSDiv:
+            return "SDiv";
+        case SpvOpFDiv:
+            return "FDiv";
+        case SpvOpUMod:
+            return "UMod";
+        case SpvOpSRem:
+            return "SRem";
+        case SpvOpSMod:
+            return "SMod";
+        case SpvOpFRem:
+            return "FRem";
+        case SpvOpFMod:
+            return "FMod";
+        case SpvOpVectorTimesScalar:
+            return "VectorTimesScalar";
+        case SpvOpMatrixTimesScalar:
+            return "MatrixTimesScalar";
+        case SpvOpVectorTimesMatrix:
+            return "VectorTimesMatrix";
+        case SpvOpMatrixTimesVector:
+            return "MatrixTimesVector";
+        case SpvOpMatrixTimesMatrix:
+            return "MatrixTimesMatrix";
+        case SpvOpOuterProduct:
+            return "OuterProduct";
+        case SpvOpDot:
+            return "Dot";
+        case SpvOpIAddCarry:
+            return "IAddCarry";
+        case SpvOpISubBorrow:
+            return "ISubBorrow";
+        case SpvOpUMulExtended:
+            return "UMulExtended";
+        case SpvOpSMulExtended:
+            return "SMulExtended";
+        case SpvOpAny:
+            return "Any";
+        case SpvOpAll:
+            return "All";
+        case SpvOpIsNan:
+            return "IsNan";
+        case SpvOpIsInf:
+            return "IsInf";
+        case SpvOpIsFinite:
+            return "IsFinite";
+        case SpvOpIsNormal:
+            return "IsNormal";
+        case SpvOpSignBitSet:
+            return "SignBitSet";
+        case SpvOpLessOrGreater:
+            return "LessOrGreater";
+        case SpvOpOrdered:
+            return "Ordered";
+        case SpvOpUnordered:
+            return "Unordered";
+        case SpvOpLogicalEqual:
+            return "LogicalEqual";
+        case SpvOpLogicalNotEqual:
+            return "LogicalNotEqual";
+        case SpvOpLogicalOr:
+            return "LogicalOr";
+        case SpvOpLogicalAnd:
+            return "LogicalAnd";
+        case SpvOpLogicalNot:
+            return "LogicalNot";
+        case SpvOpSelect:
+            return "Select";
+        case SpvOpIEqual:
+            return "IEqual";
+        case SpvOpINotEqual:
+            return "INotEqual";
+        case SpvOpUGreaterThan:
+            return "UGreaterThan";
+        case SpvOpSGreaterThan:
+            return "SGreaterThan";
+        case SpvOpUGreaterThanEqual:
+            return "UGreaterThanEqual";
+        case SpvOpSGreaterThanEqual:
+            return "SGreaterThanEqual";
+        case SpvOpULessThan:
+            return "ULessThan";
+        case SpvOpSLessThan:
+            return "SLessThan";
+        case SpvOpULessThanEqual:
+            return "ULessThanEqual";
+        case SpvOpSLessThanEqual:
+            return "SLessThanEqual";
+        case SpvOpFOrdEqual:
+            return "FOrdEqual";
+        case SpvOpFUnordEqual:
+            return "FUnordEqual";
+        case SpvOpFOrdNotEqual:
+            return "FOrdNotEqual";
+        case SpvOpFUnordNotEqual:
+            return "FUnordNotEqual";
+        case SpvOpFOrdLessThan:
+            return "FOrdLessThan";
+        case SpvOpFUnordLessThan:
+            return "FUnordLessThan";
+        case SpvOpFOrdGreaterThan:
+            return "FOrdGreaterThan";
+        case SpvOpFUnordGreaterThan:
+            return "FUnordGreaterThan";
+        case SpvOpFOrdLessThanEqual:
+            return "FOrdLessThanEqual";
+        case SpvOpFUnordLessThanEqual:
+            return "FUnordLessThanEqual";
+        case SpvOpFOrdGreaterThanEqual:
+            return "FOrdGreaterThanEqual";
+        case SpvOpFUnordGreaterThanEqual:
+            return "FUnordGreaterThanEqual";
+        case SpvOpShiftRightLogical:
+            return "ShiftRightLogical";
+        case SpvOpShiftRightArithmetic:
+            return "ShiftRightArithmetic";
+        case SpvOpShiftLeftLogical:
+            return "ShiftLeftLogical";
+        case SpvOpBitwiseOr:
+            return "BitwiseOr";
+        case SpvOpBitwiseXor:
+            return "BitwiseXor";
+        case SpvOpBitwiseAnd:
+            return "BitwiseAnd";
+        case SpvOpNot:
+            return "Not";
+        case SpvOpBitFieldInsert:
+            return "BitFieldInsert";
+        case SpvOpBitFieldSExtract:
+            return "BitFieldSExtract";
+        case SpvOpBitFieldUExtract:
+            return "BitFieldUExtract";
+        case SpvOpBitReverse:
+            return "BitReverse";
+        case SpvOpBitCount:
+            return "BitCount";
+        case SpvOpDPdx:
+            return "DPdx";
+        case SpvOpDPdy:
+            return "DPdy";
+        case SpvOpFwidth:
+            return "Fwidth";
+        case SpvOpDPdxFine:
+            return "DPdxFine";
+        case SpvOpDPdyFine:
+            return "DPdyFine";
+        case SpvOpFwidthFine:
+            return "FwidthFine";
+        case SpvOpDPdxCoarse:
+            return "DPdxCoarse";
+        case SpvOpDPdyCoarse:
+            return "DPdyCoarse";
+        case SpvOpFwidthCoarse:
+            return "FwidthCoarse";
+        case SpvOpEmitVertex:
+            return "EmitVertex";
+        case SpvOpEndPrimitive:
+            return "EndPrimitive";
+        case SpvOpEmitStreamVertex:
+            return "EmitStreamVertex";
+        case SpvOpEndStreamPrimitive:
+            return "EndStreamPrimitive";
+        case SpvOpControlBarrier:
+            return "ControlBarrier";
+        case SpvOpMemoryBarrier:
+            return "MemoryBarrier";
+        case SpvOpAtomicLoad:
+            return "AtomicLoad";
+        case SpvOpAtomicStore:
+            return "AtomicStore";
+        case SpvOpAtomicExchange:
+            return "AtomicExchange";
+        case SpvOpAtomicCompareExchange:
+            return "AtomicCompareExchange";
+        case SpvOpAtomicCompareExchangeWeak:
+            return "AtomicCompareExchangeWeak";
+        case SpvOpAtomicIIncrement:
+            return "AtomicIIncrement";
+        case SpvOpAtomicIDecrement:
+            return "AtomicIDecrement";
+        case SpvOpAtomicIAdd:
+            return "AtomicIAdd";
+        case SpvOpAtomicISub:
+            return "AtomicISub";
+        case SpvOpAtomicSMin:
+            return "AtomicSMin";
+        case SpvOpAtomicUMin:
+            return "AtomicUMin";
+        case SpvOpAtomicSMax:
+            return "AtomicSMax";
+        case SpvOpAtomicUMax:
+            return "AtomicUMax";
+        case SpvOpAtomicAnd:
+            return "AtomicAnd";
+        case SpvOpAtomicOr:
+            return "AtomicOr";
+        case SpvOpAtomicXor:
+            return "AtomicXor";
+        case SpvOpPhi:
+            return "Phi";
+        case SpvOpLoopMerge:
+            return "LoopMerge";
+        case SpvOpSelectionMerge:
+            return "SelectionMerge";
+        case SpvOpLabel:
+            return "Label";
+        case SpvOpBranch:
+            return "Branch";
+        case SpvOpBranchConditional:
+            return "BranchConditional";
+        case SpvOpSwitch:
+            return "Switch";
+        case SpvOpKill:
+            return "Kill";
+        case SpvOpReturn:
+            return "Return";
+        case SpvOpReturnValue:
+            return "ReturnValue";
+        case SpvOpUnreachable:
+            return "Unreachable";
+        case SpvOpLifetimeStart:
+            return "LifetimeStart";
+        case SpvOpLifetimeStop:
+            return "LifetimeStop";
+        case SpvOpGroupAsyncCopy:
+            return "GroupAsyncCopy";
+        case SpvOpGroupWaitEvents:
+            return "GroupWaitEvents";
+        case SpvOpGroupAll:
+            return "GroupAll";
+        case SpvOpGroupAny:
+            return "GroupAny";
+        case SpvOpGroupBroadcast:
+            return "GroupBroadcast";
+        case SpvOpGroupIAdd:
+            return "GroupIAdd";
+        case SpvOpGroupFAdd:
+            return "GroupFAdd";
+        case SpvOpGroupFMin:
+            return "GroupFMin";
+        case SpvOpGroupUMin:
+            return "GroupUMin";
+        case SpvOpGroupSMin:
+            return "GroupSMin";
+        case SpvOpGroupFMax:
+            return "GroupFMax";
+        case SpvOpGroupUMax:
+            return "GroupUMax";
+        case SpvOpGroupSMax:
+            return "GroupSMax";
+        case SpvOpReadPipe:
+            return "ReadPipe";
+        case SpvOpWritePipe:
+            return "WritePipe";
+        case SpvOpReservedReadPipe:
+            return "ReservedReadPipe";
+        case SpvOpReservedWritePipe:
+            return "ReservedWritePipe";
+        case SpvOpReserveReadPipePackets:
+            return "ReserveReadPipePackets";
+        case SpvOpReserveWritePipePackets:
+            return "ReserveWritePipePackets";
+        case SpvOpCommitReadPipe:
+            return "CommitReadPipe";
+        case SpvOpCommitWritePipe:
+            return "CommitWritePipe";
+        case SpvOpIsValidReserveId:
+            return "IsValidReserveId";
+        case SpvOpGetNumPipePackets:
+            return "GetNumPipePackets";
+        case SpvOpGetMaxPipePackets:
+            return "GetMaxPipePackets";
+        case SpvOpGroupReserveReadPipePackets:
+            return "GroupReserveReadPipePackets";
+        case SpvOpGroupReserveWritePipePackets:
+            return "GroupReserveWritePipePackets";
+        case SpvOpGroupCommitReadPipe:
+            return "GroupCommitReadPipe";
+        case SpvOpGroupCommitWritePipe:
+            return "GroupCommitWritePipe";
+        case SpvOpEnqueueMarker:
+            return "EnqueueMarker";
+        case SpvOpEnqueueKernel:
+            return "EnqueueKernel";
+        case SpvOpGetKernelNDrangeSubGroupCount:
+            return "GetKernelNDrangeSubGroupCount";
+        case SpvOpGetKernelNDrangeMaxSubGroupSize:
+            return "GetKernelNDrangeMaxSubGroupSize";
+        case SpvOpGetKernelWorkGroupSize:
+            return "GetKernelWorkGroupSize";
+        case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
+            return "GetKernelPreferredWorkGroupSizeMultiple";
+        case SpvOpRetainEvent:
+            return "RetainEvent";
+        case SpvOpReleaseEvent:
+            return "ReleaseEvent";
+        case SpvOpCreateUserEvent:
+            return "CreateUserEvent";
+        case SpvOpIsValidEvent:
+            return "IsValidEvent";
+        case SpvOpSetUserEventStatus:
+            return "SetUserEventStatus";
+        case SpvOpCaptureEventProfilingInfo:
+            return "CaptureEventProfilingInfo";
+        case SpvOpGetDefaultQueue:
+            return "GetDefaultQueue";
+        case SpvOpBuildNDRange:
+            return "BuildNDRange";
+        case SpvOpImageSparseSampleImplicitLod:
+            return "ImageSparseSampleImplicitLod";
+        case SpvOpImageSparseSampleExplicitLod:
+            return "ImageSparseSampleExplicitLod";
+        case SpvOpImageSparseSampleDrefImplicitLod:
+            return "ImageSparseSampleDrefImplicitLod";
+        case SpvOpImageSparseSampleDrefExplicitLod:
+            return "ImageSparseSampleDrefExplicitLod";
+        case SpvOpImageSparseSampleProjImplicitLod:
+            return "ImageSparseSampleProjImplicitLod";
+        case SpvOpImageSparseSampleProjExplicitLod:
+            return "ImageSparseSampleProjExplicitLod";
+        case SpvOpImageSparseSampleProjDrefImplicitLod:
+            return "ImageSparseSampleProjDrefImplicitLod";
+        case SpvOpImageSparseSampleProjDrefExplicitLod:
+            return "ImageSparseSampleProjDrefExplicitLod";
+        case SpvOpImageSparseFetch:
+            return "ImageSparseFetch";
+        case SpvOpImageSparseGather:
+            return "ImageSparseGather";
+        case SpvOpImageSparseDrefGather:
+            return "ImageSparseDrefGather";
+        case SpvOpImageSparseTexelsResident:
+            return "ImageSparseTexelsResident";
+        case SpvOpNoLine:
+            return "NoLine";
+        case SpvOpAtomicFlagTestAndSet:
+            return "AtomicFlagTestAndSet";
+        case SpvOpAtomicFlagClear:
+            return "AtomicFlagClear";
+        case SpvOpImageSparseRead:
+            return "ImageSparseRead";
+        default:
+            ABORT("unsupported SPIR-V op");    
+    }
+}
+#endif
+
+void SPIRVCodeGenerator::writeOpCode(SpvOp_ opCode, int length, std::ostream& out) {
+    ASSERT(opCode != SpvOpUndef);
+    switch (opCode) {
+        case SpvOpReturn:      // fall through
+        case SpvOpReturnValue: // fall through
+        case SpvOpBranch:      // fall through
+        case SpvOpBranchConditional:
+            ASSERT(fCurrentBlock);
+            fCurrentBlock = 0;
+            break;
+        case SpvOpConstant:          // fall through
+        case SpvOpConstantTrue:      // fall through
+        case SpvOpConstantFalse:     // fall through
+        case SpvOpConstantComposite: // fall through
+        case SpvOpTypeVoid:          // fall through
+        case SpvOpTypeInt:           // fall through
+        case SpvOpTypeFloat:         // fall through
+        case SpvOpTypeBool:          // fall through
+        case SpvOpTypeVector:        // fall through
+        case SpvOpTypeMatrix:        // fall through
+        case SpvOpTypeArray:         // fall through
+        case SpvOpTypePointer:       // fall through
+        case SpvOpTypeFunction:      // fall through
+        case SpvOpTypeRuntimeArray:  // fall through
+        case SpvOpTypeStruct:        // fall through
+        case SpvOpTypeImage:         // fall through
+        case SpvOpTypeSampledImage:  // fall through
+        case SpvOpVariable:          // fall through
+        case SpvOpFunction:          // fall through
+        case SpvOpFunctionParameter: // fall through
+        case SpvOpFunctionEnd:       // fall through
+        case SpvOpExecutionMode:     // fall through
+        case SpvOpMemoryModel:       // fall through
+        case SpvOpCapability:        // fall through
+        case SpvOpExtInstImport:     // fall through
+        case SpvOpEntryPoint:        // fall through
+        case SpvOpSource:            // fall through
+        case SpvOpSourceExtension:   // fall through
+        case SpvOpName:              // fall through
+        case SpvOpMemberName:        // fall through
+        case SpvOpDecorate:          // fall through
+        case SpvOpMemberDecorate:
+            break;
+        default:
+            ASSERT(fCurrentBlock);
+    }
+#if SPIRV_DEBUG
+    out << std::endl << opcode_text(opCode) << " ";
+#else
+    this->writeWord((length << 16) | opCode, out);
+#endif
+}
+
+void SPIRVCodeGenerator::writeLabel(SpvId label, std::ostream& out) {
+    fCurrentBlock = label;
+    this->writeInstruction(SpvOpLabel, label, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, std::ostream& out) {
+    this->writeOpCode(opCode, 1, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, std::ostream& out) {
+    this->writeOpCode(opCode, 2, out);
+    this->writeWord(word1, out);
+}
+
+void SPIRVCodeGenerator::writeString(const char* string, std::ostream& out) {
+    size_t length = strlen(string);
+    out << string;
+    switch (length % 4) {
+        case 1:
+            out << (char) 0;
+            // fall through
+        case 2:
+            out << (char) 0;
+            // fall through
+        case 3:
+            out << (char) 0;
+            break;
+        default:
+            this->writeWord(0, out);
+    }
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, const char* string, std::ostream& out) {
+    int32_t length = (int32_t) strlen(string);
+    this->writeOpCode(opCode, 1 + (length + 4) / 4, out);
+    this->writeString(string, out);
+}
+
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, const char* string, 
+                                          std::ostream& out) {
+    int32_t length = (int32_t) strlen(string);
+    this->writeOpCode(opCode, 2 + (length + 4) / 4, out);
+    this->writeWord(word1, out);
+    this->writeString(string, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          const char* string, std::ostream& out) {
+    int32_t length = (int32_t) strlen(string);
+    this->writeOpCode(opCode, 3 + (length + 4) / 4, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeString(string, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          std::ostream& out) {
+    this->writeOpCode(opCode, 3, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          int32_t word3, std::ostream& out) {
+    this->writeOpCode(opCode, 4, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeWord(word3, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          int32_t word3, int32_t word4, std::ostream& out) {
+    this->writeOpCode(opCode, 5, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeWord(word3, out);
+    this->writeWord(word4, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          int32_t word3, int32_t word4, int32_t word5, 
+                                          std::ostream& out) {
+    this->writeOpCode(opCode, 6, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeWord(word3, out);
+    this->writeWord(word4, out);
+    this->writeWord(word5, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          int32_t word3, int32_t word4, int32_t word5,
+                                          int32_t word6, std::ostream& out) {
+    this->writeOpCode(opCode, 7, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeWord(word3, out);
+    this->writeWord(word4, out);
+    this->writeWord(word5, out);
+    this->writeWord(word6, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          int32_t word3, int32_t word4, int32_t word5,
+                                          int32_t word6, int32_t word7, std::ostream& out) {
+    this->writeOpCode(opCode, 8, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeWord(word3, out);
+    this->writeWord(word4, out);
+    this->writeWord(word5, out);
+    this->writeWord(word6, out);
+    this->writeWord(word7, out);
+}
+
+void SPIRVCodeGenerator::writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, 
+                                          int32_t word3, int32_t word4, int32_t word5,
+                                          int32_t word6, int32_t word7, int32_t word8,
+                                          std::ostream& out) {
+    this->writeOpCode(opCode, 9, out);
+    this->writeWord(word1, out);
+    this->writeWord(word2, out);
+    this->writeWord(word3, out);
+    this->writeWord(word4, out);
+    this->writeWord(word5, out);
+    this->writeWord(word6, out);
+    this->writeWord(word7, out);
+    this->writeWord(word8, out);
+}
+
+void SPIRVCodeGenerator::writeCapabilities(std::ostream& out) {
+    for (uint64_t i = 0, bit = 1; i <= kLast_Capability; i++, bit <<= 1) {
+        if (fCapabilities & bit) {
+            this->writeInstruction(SpvOpCapability, (SpvId) i, out);
+        }
+    }
+}
+
+SpvId SPIRVCodeGenerator::nextId() {
+    return fIdCount++;
+}
+
+void SPIRVCodeGenerator::writeStruct(const Type& type, SpvId resultId) {
+    this->writeInstruction(SpvOpName, resultId, type.name().c_str(), fNameBuffer);
+    // go ahead and write all of the field types, so we don't inadvertently write them while we're
+    // in the middle of writing the struct instruction
+    std::vector<SpvId> types;
+    for (const auto& f : type.fields()) {
+        types.push_back(this->getType(*f.fType));
+    }
+    this->writeOpCode(SpvOpTypeStruct, 2 + (int32_t) types.size(), fConstantBuffer);
+    this->writeWord(resultId, fConstantBuffer);
+    for (SpvId id : types) {
+        this->writeWord(id, fConstantBuffer);
+    }
+    size_t offset = 0;
+    for (int32_t i = 0; i < (int32_t) type.fields().size(); i++) {
+        size_t size = type.fields()[i].fType->size();
+        size_t alignment = type.fields()[i].fType->alignment();
+        size_t mod = offset % alignment;
+        if (mod != 0) {
+            offset += alignment - mod;
+        }
+        this->writeInstruction(SpvOpMemberName, resultId, i, type.fields()[i].fName.c_str(),
+                               fNameBuffer);
+        this->writeLayout(type.fields()[i].fModifiers.fLayout, resultId, i);
+        if (type.fields()[i].fModifiers.fLayout.fBuiltin < 0) {
+            this->writeInstruction(SpvOpMemberDecorate, resultId, (SpvId) i, SpvDecorationOffset, 
+                                   (SpvId) offset, fDecorationBuffer);
+        }
+        if (type.fields()[i].fType->kind() == Type::kMatrix_Kind) {
+            this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationColMajor, 
+                                   fDecorationBuffer);
+            this->writeInstruction(SpvOpMemberDecorate, resultId, i, SpvDecorationMatrixStride, 
+                                   (SpvId) type.fields()[i].fType->stride(), fDecorationBuffer);
+        }
+        offset += size;
+        Type::Kind kind = type.fields()[i].fType->kind();
+        if ((kind == Type::kArray_Kind || kind == Type::kStruct_Kind) && offset % alignment != 0) {
+            offset += alignment - offset % alignment;
+        }
+        ASSERT(offset % alignment == 0);
+    }
+}
+
+SpvId SPIRVCodeGenerator::getType(const Type& type) {
+    auto entry = fTypeMap.find(type.name());
+    if (entry == fTypeMap.end()) {
+        SpvId result = this->nextId();
+        switch (type.kind()) {
+            case Type::kScalar_Kind:
+                if (type == *kBool_Type) {
+                    this->writeInstruction(SpvOpTypeBool, result, fConstantBuffer);
+                } else if (type == *kInt_Type) {
+                    this->writeInstruction(SpvOpTypeInt, result, 32, 1, fConstantBuffer);
+                } else if (type == *kUInt_Type) {
+                    this->writeInstruction(SpvOpTypeInt, result, 32, 0, fConstantBuffer);
+                } else if (type == *kFloat_Type) {
+                    this->writeInstruction(SpvOpTypeFloat, result, 32, fConstantBuffer);
+                } else if (type == *kDouble_Type) {
+                    this->writeInstruction(SpvOpTypeFloat, result, 64, fConstantBuffer);
+                } else {
+                    ASSERT(false);
+                }
+                break;
+            case Type::kVector_Kind:
+                this->writeInstruction(SpvOpTypeVector, result, 
+                                       this->getType(*type.componentType()),
+                                       type.columns(), fConstantBuffer);
+                break;
+            case Type::kMatrix_Kind:
+                this->writeInstruction(SpvOpTypeMatrix, result, this->getType(*index_type(type)), 
+                                       type.columns(), fConstantBuffer);
+                break;
+            case Type::kStruct_Kind:
+                this->writeStruct(type, result);
+                break;
+            case Type::kArray_Kind: {
+                if (type.columns() > 0) {
+                    IntLiteral count(Position(), type.columns());
+                    this->writeInstruction(SpvOpTypeArray, result, 
+                                           this->getType(*type.componentType()), 
+                                           this->writeIntLiteral(count), fConstantBuffer);
+                    this->writeInstruction(SpvOpDecorate, result, SpvDecorationArrayStride, 
+                                           (int32_t) type.stride(), fDecorationBuffer);
+                } else {
+                    ABORT("runtime-sized arrays are not yet supported");
+                    this->writeInstruction(SpvOpTypeRuntimeArray, result, 
+                                           this->getType(*type.componentType()), fConstantBuffer);
+                }
+                break;
+            }
+            case Type::kSampler_Kind: {
+                SpvId image = this->nextId();
+                this->writeInstruction(SpvOpTypeImage, image, this->getType(*kFloat_Type), 
+                                       type.dimensions(), type.isDepth(), type.isArrayed(),
+                                       type.isMultisampled(), type.isSampled(), 
+                                       SpvImageFormatUnknown, fConstantBuffer);
+                this->writeInstruction(SpvOpTypeSampledImage, result, image, fConstantBuffer);
+                break;
+            }
+            default:
+                if (type == *kVoid_Type) {
+                    this->writeInstruction(SpvOpTypeVoid, result, fConstantBuffer);
+                } else {
+                    ABORT("invalid type: %s", type.description().c_str());
+                }
+        }
+        fTypeMap[type.name()] = result;
+        return result;
+    }
+    return entry->second;
+}
+
+SpvId SPIRVCodeGenerator::getFunctionType(std::shared_ptr<FunctionDeclaration> function) {
+    std::string key = function->fReturnType->description() + "(";
+    std::string separator = "";
+    for (size_t i = 0; i < function->fParameters.size(); i++) {
+        key += separator;
+        separator = ", ";
+        key += function->fParameters[i]->fType->description();
+    }
+    key += ")";
+    auto entry = fTypeMap.find(key);
+    if (entry == fTypeMap.end()) {
+        SpvId result = this->nextId();
+        int32_t length = 3 + (int32_t) function->fParameters.size();
+        SpvId returnType = this->getType(*function->fReturnType);
+        std::vector<SpvId> parameterTypes;
+        for (size_t i = 0; i < function->fParameters.size(); i++) {
+            // glslang seems to treat all function arguments as pointers whether they need to be or 
+            // not. I  was initially puzzled by this until I ran bizarre failures with certain 
+            // patterns of function calls and control constructs, as exemplified by this minimal 
+            // failure case:
+            //
+            // void sphere(float x) {
+            // }
+            // 
+            // void map() {
+            //     sphere(1.0);
+            // }
+            // 
+            // void main() {
+            //     for (int i = 0; i < 1; i++) {
+            //         map();
+            //     }
+            // }
+            //
+            // As of this writing, compiling this in the "obvious" way (with sphere taking a float) 
+            // crashes. Making it take a float* and storing the argument in a temporary variable, 
+            // as glslang does, fixes it. It's entirely possible I simply missed whichever part of
+            // the spec makes this make sense.
+//            if (is_out(function->fParameters[i])) {
+                parameterTypes.push_back(this->getPointerType(function->fParameters[i]->fType,
+                                                              SpvStorageClassFunction));
+//            } else {
+//                parameterTypes.push_back(this->getType(*function->fParameters[i]->fType));
+//            }
+        }
+        this->writeOpCode(SpvOpTypeFunction, length, fConstantBuffer);
+        this->writeWord(result, fConstantBuffer);
+        this->writeWord(returnType, fConstantBuffer);
+        for (SpvId id : parameterTypes) {
+            this->writeWord(id, fConstantBuffer);
+        }
+        fTypeMap[key] = result;
+        return result;
+    }
+    return entry->second;
+}
+
+SpvId SPIRVCodeGenerator::getPointerType(std::shared_ptr<Type> type, 
+                                         SpvStorageClass_ storageClass) {
+    std::string key = type->description() + "*" + to_string(storageClass);
+    auto entry = fTypeMap.find(key);
+    if (entry == fTypeMap.end()) {
+        SpvId result = this->nextId();
+        this->writeInstruction(SpvOpTypePointer, result, storageClass, 
+                               this->getType(*type), fConstantBuffer);
+        fTypeMap[key] = result;
+        return result;
+    }
+    return entry->second;
+}
+
+SpvId SPIRVCodeGenerator::writeExpression(Expression& expr, std::ostream& out) {
+    switch (expr.fKind) {
+        case Expression::kBinary_Kind:
+            return this->writeBinaryExpression((BinaryExpression&) expr, out);
+        case Expression::kBoolLiteral_Kind:
+            return this->writeBoolLiteral((BoolLiteral&) expr);
+        case Expression::kConstructor_Kind:
+            return this->writeConstructor((Constructor&) expr, out);
+        case Expression::kIntLiteral_Kind:
+            return this->writeIntLiteral((IntLiteral&) expr);
+        case Expression::kFieldAccess_Kind:
+            return this->writeFieldAccess(((FieldAccess&) expr), out);
+        case Expression::kFloatLiteral_Kind:
+            return this->writeFloatLiteral(((FloatLiteral&) expr));
+        case Expression::kFunctionCall_Kind:
+            return this->writeFunctionCall((FunctionCall&) expr, out);
+        case Expression::kPrefix_Kind:
+            return this->writePrefixExpression((PrefixExpression&) expr, out);
+        case Expression::kPostfix_Kind:
+            return this->writePostfixExpression((PostfixExpression&) expr, out);
+        case Expression::kSwizzle_Kind:
+            return this->writeSwizzle((Swizzle&) expr, out);
+        case Expression::kVariableReference_Kind:
+            return this->writeVariableReference((VariableReference&) expr, out);
+        case Expression::kTernary_Kind:
+            return this->writeTernaryExpression((TernaryExpression&) expr, out);
+        case Expression::kIndex_Kind:
+            return this->writeIndexExpression((IndexExpression&) expr, out);
+        default:
+            ABORT("unsupported expression: %s", expr.description().c_str());
+    }
+    return -1;
+}
+
+SpvId SPIRVCodeGenerator::writeIntrinsicCall(FunctionCall& c, std::ostream& out) {
+    auto intrinsic = fIntrinsicMap.find(c.fFunction->fName);
+    ASSERT(intrinsic != fIntrinsicMap.end());
+    std::shared_ptr<Type> type = c.fArguments[0]->fType;
+    int32_t intrinsicId;
+    if (std::get<0>(intrinsic->second) == kSpecial_IntrinsicKind || is_float(*type)) {
+        intrinsicId = std::get<1>(intrinsic->second);
+    } else if (is_signed(*type)) {
+        intrinsicId = std::get<2>(intrinsic->second);
+    } else if (is_unsigned(*type)) {
+        intrinsicId = std::get<3>(intrinsic->second);
+    } else if (is_bool(*type)) {
+        intrinsicId = std::get<4>(intrinsic->second);
+    } else {
+        ABORT("invalid call %s, cannot operate on '%s'", c.description().c_str(),
+              type->description().c_str());
+    }
+    switch (std::get<0>(intrinsic->second)) {
+        case kGLSL_STD_450_IntrinsicKind: {
+            SpvId result = this->nextId();
+            std::vector<SpvId> arguments;
+            for (size_t i = 0; i < c.fArguments.size(); i++) {
+                arguments.push_back(this->writeExpression(*c.fArguments[i], out));
+            }
+            this->writeOpCode(SpvOpExtInst, 5 + (int32_t) arguments.size(), out);
+            this->writeWord(this->getType(*c.fType), out);
+            this->writeWord(result, out);
+            this->writeWord(fGLSLExtendedInstructions, out);
+            this->writeWord(intrinsicId, out);
+            for (SpvId id : arguments) {
+                this->writeWord(id, out);
+            }
+            return result;
+        }
+        case kSPIRV_IntrinsicKind: {
+            SpvId result = this->nextId();
+            std::vector<SpvId> arguments;
+            for (size_t i = 0; i < c.fArguments.size(); i++) {
+                arguments.push_back(this->writeExpression(*c.fArguments[i], out));
+            }
+            this->writeOpCode((SpvOp_) intrinsicId, 3 + (int32_t) arguments.size(), out);
+            this->writeWord(this->getType(*c.fType), out);
+            this->writeWord(result, out);
+            for (SpvId id : arguments) {
+                this->writeWord(id, out);
+            }
+            return result;
+        }
+        case kSpecial_IntrinsicKind:
+            return this->writeSpecialIntrinsic(c, (SpecialIntrinsic) intrinsicId, out);
+        default:
+            ABORT("unsupported intrinsic kind");
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeSpecialIntrinsic(FunctionCall& c, SpecialIntrinsic kind, 
+                                                std::ostream& out) {
+    SpvId result = this->nextId();
+    switch (kind) {
+        case kAtan_SpecialIntrinsic: {
+            std::vector<SpvId> arguments;
+            for (size_t i = 0; i < c.fArguments.size(); i++) {
+                arguments.push_back(this->writeExpression(*c.fArguments[i], out));
+            }
+            this->writeOpCode(SpvOpExtInst, 5 + (int32_t) arguments.size(), out);
+            this->writeWord(this->getType(*c.fType), out);
+            this->writeWord(result, out);
+            this->writeWord(fGLSLExtendedInstructions, out);
+            this->writeWord(arguments.size() == 2 ? GLSLstd450Atan2 : GLSLstd450Atan, out);
+            for (SpvId id : arguments) {
+                this->writeWord(id, out);
+            }
+            return result;            
+        }
+        case kTexture_SpecialIntrinsic: {
+            SpvId type = this->getType(*c.fType);
+            SpvId sampler = this->writeExpression(*c.fArguments[0], out);
+            SpvId uv = this->writeExpression(*c.fArguments[1], out);
+            if (c.fArguments.size() == 3) {
+                this->writeInstruction(SpvOpImageSampleImplicitLod, type, result, sampler, uv,
+                                       SpvImageOperandsBiasMask,
+                                       this->writeExpression(*c.fArguments[2], out),
+                                       out);
+            } else {
+                ASSERT(c.fArguments.size() == 2);
+                this->writeInstruction(SpvOpImageSampleImplicitLod, type, result, sampler, uv, out);
+            }
+            break;
+        }
+        case kTextureProj_SpecialIntrinsic: {
+            SpvId type = this->getType(*c.fType);
+            SpvId sampler = this->writeExpression(*c.fArguments[0], out);
+            SpvId uv = this->writeExpression(*c.fArguments[1], out);
+            if (c.fArguments.size() == 3) {
+                this->writeInstruction(SpvOpImageSampleProjImplicitLod, type, result, sampler, uv,
+                                       SpvImageOperandsBiasMask,
+                                       this->writeExpression(*c.fArguments[2], out),
+                                       out);
+            } else {
+                ASSERT(c.fArguments.size() == 2);
+                this->writeInstruction(SpvOpImageSampleProjImplicitLod, type, result, sampler, uv, 
+                                       out);
+            }
+            break;
+        }
+        case kTexture2D_SpecialIntrinsic: {
+            SpvId img = this->writeExpression(*c.fArguments[0], out);
+            SpvId coords = this->writeExpression(*c.fArguments[1], out);
+            this->writeInstruction(SpvOpImageSampleImplicitLod,
+                                   this->getType(*c.fType),
+                                   result, 
+                                   img,
+                                   coords,
+                                   out);
+            break;
+        }
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeFunctionCall(FunctionCall& c, std::ostream& out) {
+    const auto& entry = fFunctionMap.find(c.fFunction);
+    if (entry == fFunctionMap.end()) {
+        return this->writeIntrinsicCall(c, out);
+    }
+    // stores (variable, type, lvalue) pairs to extract and save after the function call is complete
+    std::vector<std::tuple<SpvId, SpvId, std::unique_ptr<LValue>>> lvalues;
+    std::vector<SpvId> arguments;
+    for (size_t i = 0; i < c.fArguments.size(); i++) {
+        // id of temporary variable that we will use to hold this argument, or 0 if it is being 
+        // passed directly
+        SpvId tmpVar;
+        // if we need a temporary var to store this argument, this is the value to store in the var
+        SpvId tmpValueId;
+        if (is_out(c.fFunction->fParameters[i])) {
+            std::unique_ptr<LValue> lv = this->getLValue(*c.fArguments[i], out);
+            SpvId ptr = lv->getPointer();
+            if (ptr) {
+                arguments.push_back(ptr);
+                continue;
+            } else {
+                // lvalue cannot simply be read and written via a pointer (e.g. a swizzle). Need to
+                // copy it into a temp, call the function, read the value out of the temp, and then
+                // update the lvalue.
+                tmpValueId = lv->load(out);
+                tmpVar = this->nextId();
+                lvalues.push_back(std::make_tuple(tmpVar, this->getType(*c.fArguments[i]->fType),
+                                  std::move(lv)));
+            }
+        } else {
+            // see getFunctionType for an explanation of why we're always using pointer parameters
+            tmpValueId = this->writeExpression(*c.fArguments[i], out);
+            tmpVar = this->nextId();
+        }
+        this->writeInstruction(SpvOpVariable, 
+                               this->getPointerType(c.fArguments[i]->fType, 
+                                                    SpvStorageClassFunction),
+                               tmpVar, 
+                               SpvStorageClassFunction,
+                               out);
+        this->writeInstruction(SpvOpStore, tmpVar, tmpValueId, out);
+        arguments.push_back(tmpVar);
+    }
+    SpvId result = this->nextId();
+    this->writeOpCode(SpvOpFunctionCall, 4 + (int32_t) c.fArguments.size(), out);
+    this->writeWord(this->getType(*c.fType), out);
+    this->writeWord(result, out);
+    this->writeWord(entry->second, out);
+    for (SpvId id : arguments) {
+        this->writeWord(id, out);
+    }
+    // now that the call is complete, we may need to update some lvalues with the new values of out
+    // arguments
+    for (const auto& tuple : lvalues) {
+        SpvId load = this->nextId();
+        this->writeInstruction(SpvOpLoad, std::get<1>(tuple), load, std::get<0>(tuple), out);
+        std::get<2>(tuple)->store(load, out);
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeConstantVector(Constructor& c) {
+    ASSERT(c.fType->kind() == Type::kVector_Kind && c.isConstant());
+    SpvId result = this->nextId();
+    std::vector<SpvId> arguments;
+    for (size_t i = 0; i < c.fArguments.size(); i++) {
+        arguments.push_back(this->writeExpression(*c.fArguments[i], fConstantBuffer));
+    }
+    SpvId type = this->getType(*c.fType);
+    if (c.fArguments.size() == 1) {
+        // with a single argument, a vector will have all of its entries equal to the argument
+        this->writeOpCode(SpvOpConstantComposite, 3 + c.fType->columns(), fConstantBuffer);
+        this->writeWord(type, fConstantBuffer);
+        this->writeWord(result, fConstantBuffer);
+        for (int i = 0; i < c.fType->columns(); i++) {
+            this->writeWord(arguments[0], fConstantBuffer);
+        }
+    } else {
+        this->writeOpCode(SpvOpConstantComposite, 3 + (int32_t) c.fArguments.size(), 
+                          fConstantBuffer);
+        this->writeWord(type, fConstantBuffer);
+        this->writeWord(result, fConstantBuffer);
+        for (SpvId id : arguments) {
+            this->writeWord(id, fConstantBuffer);
+        }
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeFloatConstructor(Constructor& c, std::ostream& out) {
+    ASSERT(c.fType == kFloat_Type);
+    ASSERT(c.fArguments.size() == 1);
+    ASSERT(c.fArguments[0]->fType->isNumber());
+    SpvId result = this->nextId();
+    SpvId parameter = this->writeExpression(*c.fArguments[0], out);
+    if (c.fArguments[0]->fType == kInt_Type) {
+        this->writeInstruction(SpvOpConvertSToF, this->getType(*c.fType), result, parameter, 
+                               out);
+    } else if (c.fArguments[0]->fType == kUInt_Type) {
+        this->writeInstruction(SpvOpConvertUToF, this->getType(*c.fType), result, parameter, 
+                               out);
+    } else if (c.fArguments[0]->fType == kFloat_Type) {
+        return parameter;
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeIntConstructor(Constructor& c, std::ostream& out) {
+    ASSERT(c.fType == kInt_Type);
+    ASSERT(c.fArguments.size() == 1);
+    ASSERT(c.fArguments[0]->fType->isNumber());
+    SpvId result = this->nextId();
+    SpvId parameter = this->writeExpression(*c.fArguments[0], out);
+    if (c.fArguments[0]->fType == kFloat_Type) {
+        this->writeInstruction(SpvOpConvertFToS, this->getType(*c.fType), result, parameter, 
+                               out);
+    } else if (c.fArguments[0]->fType == kUInt_Type) {
+        this->writeInstruction(SpvOpSatConvertUToS, this->getType(*c.fType), result, parameter, 
+                               out);
+    } else if (c.fArguments[0]->fType == kInt_Type) {
+        return parameter;
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeMatrixConstructor(Constructor& c, std::ostream& out) {
+    ASSERT(c.fType->kind() == Type::kMatrix_Kind);
+    // go ahead and write the arguments so we don't try to write new instructions in the middle of
+    // an instruction
+    std::vector<SpvId> arguments;
+    for (size_t i = 0; i < c.fArguments.size(); i++) {
+        arguments.push_back(this->writeExpression(*c.fArguments[i], out));
+    }
+    SpvId result = this->nextId();
+    int rows = c.fType->rows();
+    int columns = c.fType->columns();
+    // FIXME this won't work to create a matrix from another matrix
+    if (arguments.size() == 1) {
+        // with a single argument, a matrix will have all of its diagonal entries equal to the 
+        // argument and its other values equal to zero
+        // FIXME this won't work for int matrices
+        FloatLiteral zero(Position(), 0);
+        SpvId zeroId = this->writeFloatLiteral(zero);
+        std::vector<SpvId> columnIds;
+        for (int column = 0; column < columns; column++) {
+            this->writeOpCode(SpvOpCompositeConstruct, 3 + c.fType->rows(), 
+                              out);
+            this->writeWord(this->getType(*c.fType->componentType()->toCompound(rows, 1)), out);
+            SpvId columnId = this->nextId();
+            this->writeWord(columnId, out);
+            columnIds.push_back(columnId);
+            for (int row = 0; row < c.fType->columns(); row++) {
+                this->writeWord(row == column ? arguments[0] : zeroId, out);
+            }
+        }
+        this->writeOpCode(SpvOpCompositeConstruct, 3 + columns, 
+                          out);
+        this->writeWord(this->getType(*c.fType), out);
+        this->writeWord(result, out);
+        for (SpvId id : columnIds) {
+            this->writeWord(id, out);
+        }
+    } else {
+        std::vector<SpvId> columnIds;
+        int currentCount = 0;
+        for (size_t i = 0; i < arguments.size(); i++) {
+            if (c.fArguments[i]->fType->kind() == Type::kVector_Kind) {
+                ASSERT(currentCount == 0);
+                columnIds.push_back(arguments[i]);
+                currentCount = 0;
+            } else {
+                ASSERT(c.fArguments[i]->fType->kind() == Type::kScalar_Kind);
+                if (currentCount == 0) {
+                    this->writeOpCode(SpvOpCompositeConstruct, 3 + c.fType->rows(), out);
+                    this->writeWord(this->getType(*c.fType->componentType()->toCompound(rows, 1)), 
+                                    out);
+                    SpvId id = this->nextId();
+                    this->writeWord(id, out);
+                    columnIds.push_back(id);
+                }
+                this->writeWord(arguments[i], out);
+                currentCount = (currentCount + 1) % rows;
+            }
+        }
+        ASSERT(columnIds.size() == (size_t) columns);
+        this->writeOpCode(SpvOpCompositeConstruct, 3 + columns, out);
+        this->writeWord(this->getType(*c.fType), out);
+        this->writeWord(result, out);
+        for (SpvId id : columnIds) {
+            this->writeWord(id, out);
+        }
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeVectorConstructor(Constructor& c, std::ostream& out) {
+    ASSERT(c.fType->kind() == Type::kVector_Kind);
+    if (c.isConstant()) {
+        return this->writeConstantVector(c);
+    }
+    // go ahead and write the arguments so we don't try to write new instructions in the middle of
+    // an instruction
+    std::vector<SpvId> arguments;
+    for (size_t i = 0; i < c.fArguments.size(); i++) {
+        arguments.push_back(this->writeExpression(*c.fArguments[i], out));
+    }
+    SpvId result = this->nextId();
+    if (arguments.size() == 1 && c.fArguments[0]->fType->kind() == Type::kScalar_Kind) {
+        this->writeOpCode(SpvOpCompositeConstruct, 3 + c.fType->columns(), out);
+        this->writeWord(this->getType(*c.fType), out);
+        this->writeWord(result, out);
+        for (int i = 0; i < c.fType->columns(); i++) {
+            this->writeWord(arguments[0], out);
+        }
+    } else {
+        this->writeOpCode(SpvOpCompositeConstruct, 3 + (int32_t) c.fArguments.size(), out);
+        this->writeWord(this->getType(*c.fType), out);
+        this->writeWord(result, out);
+        for (SpvId id : arguments) {
+            this->writeWord(id, out);
+        }
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeConstructor(Constructor& c, std::ostream& out) {
+    if (c.fType == kFloat_Type) {
+        return this->writeFloatConstructor(c, out);
+    } else if (c.fType == kInt_Type) {
+        return this->writeIntConstructor(c, out);
+    }
+    switch (c.fType->kind()) {
+        case Type::kVector_Kind:
+            return this->writeVectorConstructor(c, out);
+        case Type::kMatrix_Kind:
+            return this->writeMatrixConstructor(c, out);
+        default:
+            ABORT("unsupported constructor: %s", c.description().c_str());
+    }
+}
+
+SpvStorageClass_ get_storage_class(const Modifiers& modifiers) {
+    if (modifiers.fFlags & Modifiers::kIn_Flag) {
+        return SpvStorageClassInput;
+    } else if (modifiers.fFlags & Modifiers::kOut_Flag) {
+        return SpvStorageClassOutput;
+    } else if (modifiers.fFlags & Modifiers::kUniform_Flag) {
+        return SpvStorageClassUniform;
+    } else {
+        return SpvStorageClassFunction;
+    }
+}
+
+SpvStorageClass_ get_storage_class(Expression& expr) {
+    switch (expr.fKind) {
+        case Expression::kVariableReference_Kind:
+            return get_storage_class(((VariableReference&) expr).fVariable->fModifiers);
+        case Expression::kFieldAccess_Kind:
+            return get_storage_class(*((FieldAccess&) expr).fBase);
+        case Expression::kIndex_Kind:
+            return get_storage_class(*((IndexExpression&) expr).fBase);
+        default:
+            return SpvStorageClassFunction;
+    }
+}
+
+std::vector<SpvId> SPIRVCodeGenerator::getAccessChain(Expression& expr, std::ostream& out) {
+    std::vector<SpvId> chain;
+    switch (expr.fKind) {
+        case Expression::kIndex_Kind: {
+            IndexExpression& indexExpr = (IndexExpression&) expr;
+            chain = this->getAccessChain(*indexExpr.fBase, out);
+            chain.push_back(this->writeExpression(*indexExpr.fIndex, out));
+            break;
+        }
+        case Expression::kFieldAccess_Kind: {
+            FieldAccess& fieldExpr = (FieldAccess&) expr;
+            chain = this->getAccessChain(*fieldExpr.fBase, out);
+            IntLiteral index(Position(), fieldExpr.fFieldIndex);
+            chain.push_back(this->writeIntLiteral(index));
+            break;
+        }
+        default:
+            chain.push_back(this->getLValue(expr, out)->getPointer());
+    }
+    return chain;
+}
+
+class PointerLValue : public SPIRVCodeGenerator::LValue {
+public:
+    PointerLValue(SPIRVCodeGenerator& gen, SpvId pointer, SpvId type) 
+    : fGen(gen)
+    , fPointer(pointer)
+    , fType(type) {}
+
+    virtual SpvId getPointer() override {
+        return fPointer;
+    }
+
+    virtual SpvId load(std::ostream& out) override {
+        SpvId result = fGen.nextId();
+        fGen.writeInstruction(SpvOpLoad, fType, result, fPointer, out);
+        return result;
+    }
+
+    virtual void store(SpvId value, std::ostream& out) override {
+        fGen.writeInstruction(SpvOpStore, fPointer, value, out);
+    }
+
+private:
+    SPIRVCodeGenerator& fGen;
+    const SpvId fPointer;
+    const SpvId fType;
+};
+
+class SwizzleLValue : public SPIRVCodeGenerator::LValue {
+public:
+    SwizzleLValue(SPIRVCodeGenerator& gen, SpvId vecPointer, const std::vector<int>& components, 
+                  const Type& baseType, const Type& swizzleType)
+    : fGen(gen)
+    , fVecPointer(vecPointer)
+    , fComponents(components)
+    , fBaseType(baseType)
+    , fSwizzleType(swizzleType) {}
+
+    virtual SpvId getPointer() override {
+        return 0;
+    }
+
+    virtual SpvId load(std::ostream& out) override {
+        SpvId base = fGen.nextId();
+        fGen.writeInstruction(SpvOpLoad, fGen.getType(fBaseType), base, fVecPointer, out);
+        SpvId result = fGen.nextId();
+        fGen.writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) fComponents.size(), out);
+        fGen.writeWord(fGen.getType(fSwizzleType), out);
+        fGen.writeWord(result, out);
+        fGen.writeWord(base, out);
+        fGen.writeWord(base, out);
+        for (int component : fComponents) {
+            fGen.writeWord(component, out);
+        }
+        return result;
+    }
+
+    virtual void store(SpvId value, std::ostream& out) override {
+        // use OpVectorShuffle to mix and match the vector components. We effectively create
+        // a virtual vector out of the concatenation of the left and right vectors, and then
+        // select components from this virtual vector to make the result vector. For 
+        // instance, given:
+        // vec3 L = ...;
+        // vec3 R = ...;
+        // L.xz = R.xy;
+        // we end up with the virtual vector (L.x, L.y, L.z, R.x, R.y, R.z). Then we want 
+        // our result vector to look like (R.x, L.y, R.y), so we need to select indices
+        // (3, 1, 4).
+        SpvId base = fGen.nextId();
+        fGen.writeInstruction(SpvOpLoad, fGen.getType(fBaseType), base, fVecPointer, out);
+        SpvId shuffle = fGen.nextId();
+        fGen.writeOpCode(SpvOpVectorShuffle, 5 + fBaseType.columns(), out);
+        fGen.writeWord(fGen.getType(fBaseType), out);
+        fGen.writeWord(shuffle, out);
+        fGen.writeWord(base, out);
+        fGen.writeWord(value, out);
+        for (int i = 0; i < fBaseType.columns(); i++) {
+            // current offset into the virtual vector, defaults to pulling the unmodified
+            // value from the left side
+            int offset = i;
+            // check to see if we are writing this component
+            for (size_t j = 0; j < fComponents.size(); j++) {
+                if (fComponents[j] == i) {
+                    // we're writing to this component, so adjust the offset to pull from 
+                    // the correct component of the right side instead of preserving the
+                    // value from the left
+                    offset = (int) (j + fBaseType.columns());
+                    break;
+                }
+            }
+            fGen.writeWord(offset, out);
+        }
+        fGen.writeInstruction(SpvOpStore, fVecPointer, shuffle, out);
+    }
+
+private:
+    SPIRVCodeGenerator& fGen;
+    const SpvId fVecPointer;
+    const std::vector<int>& fComponents;
+    const Type& fBaseType;
+    const Type& fSwizzleType;
+};
+
+std::unique_ptr<SPIRVCodeGenerator::LValue> SPIRVCodeGenerator::getLValue(Expression& expr, 
+                                                                          std::ostream& out) {
+    switch (expr.fKind) {
+        case Expression::kVariableReference_Kind: {
+            std::shared_ptr<Variable> var = ((VariableReference&) expr).fVariable;
+            auto entry = fVariableMap.find(var);
+            ASSERT(entry != fVariableMap.end());
+            return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
+                                                                       *this,
+                                                                       entry->second, 
+                                                                       this->getType(*expr.fType)));
+        }
+        case Expression::kIndex_Kind: // fall through
+        case Expression::kFieldAccess_Kind: {
+            std::vector<SpvId> chain = this->getAccessChain(expr, out);
+            SpvId member = this->nextId();
+            this->writeOpCode(SpvOpAccessChain, (SpvId) (3 + chain.size()), out);
+            this->writeWord(this->getPointerType(expr.fType, get_storage_class(expr)), out); 
+            this->writeWord(member, out);
+            for (SpvId idx : chain) {
+                this->writeWord(idx, out);
+            }
+            return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
+                                                                       *this,
+                                                                       member, 
+                                                                       this->getType(*expr.fType)));
+        }
+
+        case Expression::kSwizzle_Kind: {
+            Swizzle& swizzle = (Swizzle&) expr;
+            size_t count = swizzle.fComponents.size();
+            SpvId base = this->getLValue(*swizzle.fBase, out)->getPointer();
+            ASSERT(base);
+            if (count == 1) {
+                IntLiteral index(Position(), swizzle.fComponents[0]);
+                SpvId member = this->nextId();
+                this->writeInstruction(SpvOpAccessChain,
+                                       this->getPointerType(swizzle.fType, 
+                                                            get_storage_class(*swizzle.fBase)), 
+                                       member, 
+                                       base, 
+                                       this->writeIntLiteral(index), 
+                                       out);
+                return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
+                                                                       *this,
+                                                                       member, 
+                                                                       this->getType(*expr.fType)));
+            } else {
+                return std::unique_ptr<SPIRVCodeGenerator::LValue>(new SwizzleLValue(
+                                                                              *this, 
+                                                                              base, 
+                                                                              swizzle.fComponents, 
+                                                                              *swizzle.fBase->fType,
+                                                                              *expr.fType));
+            }
+        }
+
+        default:
+            // expr isn't actually an lvalue, create a dummy variable for it. This case happens due
+            // to the need to store values in temporary variables during function calls (see 
+            // comments in getFunctionType); erroneous uses of rvalues as lvalues should have been
+            // caught by IRGenerator
+            SpvId result = this->nextId();
+            SpvId type = this->getPointerType(expr.fType, SpvStorageClassFunction);
+            this->writeInstruction(SpvOpVariable, type, result, SpvStorageClassFunction, out);
+            this->writeInstruction(SpvOpStore, result, this->writeExpression(expr, out), out);
+            return std::unique_ptr<SPIRVCodeGenerator::LValue>(new PointerLValue(
+                                                                       *this,
+                                                                       result, 
+                                                                       this->getType(*expr.fType)));
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeVariableReference(VariableReference& ref, std::ostream& out) {
+    auto entry = fVariableMap.find(ref.fVariable);
+    ASSERT(entry != fVariableMap.end());
+    SpvId var = entry->second;
+    SpvId result = this->nextId();
+    this->writeInstruction(SpvOpLoad, this->getType(*ref.fVariable->fType), result, var, out);
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeIndexExpression(IndexExpression& expr, std::ostream& out) {
+    return getLValue(expr, out)->load(out);
+}
+
+SpvId SPIRVCodeGenerator::writeFieldAccess(FieldAccess& f, std::ostream& out) {
+    return getLValue(f, out)->load(out);
+}
+
+SpvId SPIRVCodeGenerator::writeSwizzle(Swizzle& swizzle, std::ostream& out) {
+    SpvId base = this->writeExpression(*swizzle.fBase, out);
+    SpvId result = this->nextId();
+    size_t count = swizzle.fComponents.size();
+    if (count == 1) {
+        this->writeInstruction(SpvOpCompositeExtract, this->getType(*swizzle.fType), result, base, 
+                               swizzle.fComponents[0], out); 
+    } else {
+        this->writeOpCode(SpvOpVectorShuffle, 5 + (int32_t) count, out);
+        this->writeWord(this->getType(*swizzle.fType), out);
+        this->writeWord(result, out);
+        this->writeWord(base, out);
+        this->writeWord(base, out);
+        for (int component : swizzle.fComponents) {
+            this->writeWord(component, out);
+        }
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeBinaryOperation(const Type& resultType, 
+                                               const Type& operandType, SpvId lhs, 
+                                               SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, 
+                                               SpvOp_ ifUInt, SpvOp_ ifBool, std::ostream& out) {
+    SpvId result = this->nextId();
+    if (is_float(operandType)) {
+        this->writeInstruction(ifFloat, this->getType(resultType), result, lhs, rhs, out);
+    } else if (is_signed(operandType)) {
+        this->writeInstruction(ifInt, this->getType(resultType), result, lhs, rhs, out);
+    } else if (is_unsigned(operandType)) {
+        this->writeInstruction(ifUInt, this->getType(resultType), result, lhs, rhs, out);
+    } else if (operandType == *kBool_Type) {
+        this->writeInstruction(ifBool, this->getType(resultType), result, lhs, rhs, out);
+    } else {
+        ABORT("invalid operandType: %s", operandType.description().c_str());
+    }
+    return result;
+}
+
+bool is_assignment(Token::Kind op) {
+    switch (op) {
+        case Token::EQ:           // fall through
+        case Token::PLUSEQ:       // fall through
+        case Token::MINUSEQ:      // fall through
+        case Token::STAREQ:       // fall through
+        case Token::SLASHEQ:      // fall through
+        case Token::PERCENTEQ:    // fall through
+        case Token::SHLEQ:        // fall through
+        case Token::SHREQ:        // fall through
+        case Token::BITWISEOREQ:  // fall through
+        case Token::BITWISEXOREQ: // fall through
+        case Token::BITWISEANDEQ: // fall through
+        case Token::LOGICALOREQ:  // fall through
+        case Token::LOGICALXOREQ: // fall through
+        case Token::LOGICALANDEQ:
+            return true;
+        default:
+            return false;
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeBinaryExpression(BinaryExpression& b, std::ostream& out) {
+    // handle cases where we don't necessarily evaluate both LHS and RHS
+    switch (b.fOperator) {
+        case Token::EQ: {
+            SpvId rhs = this->writeExpression(*b.fRight, out);
+            this->getLValue(*b.fLeft, out)->store(rhs, out);
+            return rhs;
+        }
+        case Token::LOGICALAND:
+            return this->writeLogicalAnd(b, out);
+        case Token::LOGICALOR:
+            return this->writeLogicalOr(b, out);
+        default:
+            break;
+    }
+
+    // "normal" operators
+    const Type& resultType = *b.fType;
+    std::unique_ptr<LValue> lvalue;
+    SpvId lhs;
+    if (is_assignment(b.fOperator)) {
+        lvalue = this->getLValue(*b.fLeft, out);
+        lhs = lvalue->load(out);
+    } else {
+        lvalue = nullptr;
+        lhs = this->writeExpression(*b.fLeft, out);
+    }
+    SpvId rhs = this->writeExpression(*b.fRight, out);
+    // component type we are operating on: float, int, uint
+    const Type* operandType;
+    // IR allows mismatched types in expressions (e.g. vec2 * float), but they need special handling
+    // in SPIR-V
+    if (b.fLeft->fType != b.fRight->fType) {
+        if (b.fLeft->fType->kind() == Type::kVector_Kind && 
+            b.fRight->fType->isNumber()) {
+            // promote number to vector
+            SpvId vec = this->nextId();
+            this->writeOpCode(SpvOpCompositeConstruct, 3 + b.fType->columns(), out);
+            this->writeWord(this->getType(resultType), out);
+            this->writeWord(vec, out);
+            for (int i = 0; i < resultType.columns(); i++) {
+                this->writeWord(rhs, out);
+            }
+            rhs = vec;
+            operandType = b.fRight->fType.get();
+        } else if (b.fRight->fType->kind() == Type::kVector_Kind && 
+                   b.fLeft->fType->isNumber()) {
+            // promote number to vector
+            SpvId vec = this->nextId();
+            this->writeOpCode(SpvOpCompositeConstruct, 3 + b.fType->columns(), out);
+            this->writeWord(this->getType(resultType), out);
+            this->writeWord(vec, out);
+            for (int i = 0; i < resultType.columns(); i++) {
+                this->writeWord(lhs, out);
+            }
+            lhs = vec;
+            ASSERT(!lvalue);
+            operandType = b.fLeft->fType.get();
+        } else if (b.fLeft->fType->kind() == Type::kMatrix_Kind) {
+            SpvOp_ op;
+            if (b.fRight->fType->kind() == Type::kMatrix_Kind) {
+                op = SpvOpMatrixTimesMatrix;
+            } else if (b.fRight->fType->kind() == Type::kVector_Kind) {
+                op = SpvOpMatrixTimesVector;
+            } else {
+                ASSERT(b.fRight->fType->kind() == Type::kScalar_Kind);
+                op = SpvOpMatrixTimesScalar;
+            }
+            SpvId result = this->nextId();
+            this->writeInstruction(op, this->getType(*b.fType), result, lhs, rhs, out);
+            if (b.fOperator == Token::STAREQ) {
+                lvalue->store(result, out);
+            } else {
+                ASSERT(b.fOperator == Token::STAR);
+            }
+            return result;
+        } else if (b.fRight->fType->kind() == Type::kMatrix_Kind) {
+            SpvId result = this->nextId();
+            if (b.fLeft->fType->kind() == Type::kVector_Kind) {
+                this->writeInstruction(SpvOpVectorTimesMatrix, this->getType(*b.fType), result, 
+                                       lhs, rhs, out);
+            } else {
+                ASSERT(b.fLeft->fType->kind() == Type::kScalar_Kind);
+                this->writeInstruction(SpvOpMatrixTimesScalar, this->getType(*b.fType), result, rhs, 
+                                       lhs, out);
+            }
+            if (b.fOperator == Token::STAREQ) {
+                lvalue->store(result, out);
+            } else {
+                ASSERT(b.fOperator == Token::STAR);
+            }
+            return result;
+        } else {
+            ABORT("unsupported binary expression: %s", b.description().c_str());
+        }
+    } else {
+        operandType = b.fLeft->fType.get();
+        ASSERT(*operandType == *b.fRight->fType);
+    }
+    switch (b.fOperator) {
+        case Token::EQEQ:
+            ASSERT(resultType == *kBool_Type);
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdEqual, 
+                                              SpvOpIEqual, SpvOpIEqual, SpvOpLogicalEqual, out);
+        case Token::NEQ:
+            ASSERT(resultType == *kBool_Type);
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdNotEqual, 
+                                              SpvOpINotEqual, SpvOpINotEqual, SpvOpLogicalNotEqual, 
+                                              out);
+        case Token::GT:
+            ASSERT(resultType == *kBool_Type);
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, 
+                                              SpvOpFOrdGreaterThan, SpvOpSGreaterThan, 
+                                              SpvOpUGreaterThan, SpvOpUndef, out);
+        case Token::LT:
+            ASSERT(resultType == *kBool_Type);
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFOrdLessThan, 
+                                              SpvOpSLessThan, SpvOpULessThan, SpvOpUndef, out);
+        case Token::GTEQ:
+            ASSERT(resultType == *kBool_Type);
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, 
+                                              SpvOpFOrdGreaterThanEqual, SpvOpSGreaterThanEqual, 
+                                              SpvOpUGreaterThanEqual, SpvOpUndef, out);
+        case Token::LTEQ:
+            ASSERT(resultType == *kBool_Type);
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, 
+                                              SpvOpFOrdLessThanEqual, SpvOpSLessThanEqual, 
+                                              SpvOpULessThanEqual, SpvOpUndef, out);
+        case Token::PLUS:
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFAdd, 
+                                              SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out);
+        case Token::MINUS:
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFSub, 
+                                              SpvOpISub, SpvOpISub, SpvOpUndef, out);
+        case Token::STAR:
+            if (b.fLeft->fType->kind() == Type::kMatrix_Kind && 
+                b.fRight->fType->kind() == Type::kMatrix_Kind) {
+                // matrix multiply
+                SpvId result = this->nextId();
+                this->writeInstruction(SpvOpMatrixTimesMatrix, this->getType(resultType), result,
+                                       lhs, rhs, out);
+                return result;
+            }
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMul, 
+                                              SpvOpIMul, SpvOpIMul, SpvOpUndef, out);
+        case Token::SLASH:
+            return this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFDiv, 
+                                              SpvOpSDiv, SpvOpUDiv, SpvOpUndef, out);
+        case Token::PLUSEQ: {
+            SpvId result = this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFAdd, 
+                                                      SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out);
+            ASSERT(lvalue);
+            lvalue->store(result, out);
+            return result;
+        }
+        case Token::MINUSEQ: {
+            SpvId result = this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFSub, 
+                                                      SpvOpISub, SpvOpISub, SpvOpUndef, out);
+            ASSERT(lvalue);
+            lvalue->store(result, out);
+            return result;
+        }
+        case Token::STAREQ: {
+            if (b.fLeft->fType->kind() == Type::kMatrix_Kind && 
+                b.fRight->fType->kind() == Type::kMatrix_Kind) {
+                // matrix multiply
+                SpvId result = this->nextId();
+                this->writeInstruction(SpvOpMatrixTimesMatrix, this->getType(resultType), result,
+                                       lhs, rhs, out);
+                ASSERT(lvalue);
+                lvalue->store(result, out);
+                return result;
+            }
+            SpvId result = this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFMul, 
+                                                      SpvOpIMul, SpvOpIMul, SpvOpUndef, out);
+            ASSERT(lvalue);
+            lvalue->store(result, out);
+            return result;
+        }
+        case Token::SLASHEQ: {
+            SpvId result = this->writeBinaryOperation(resultType, *operandType, lhs, rhs, SpvOpFDiv, 
+                                                      SpvOpSDiv, SpvOpUDiv, SpvOpUndef, out);
+            ASSERT(lvalue);
+            lvalue->store(result, out);
+            return result;
+        }
+        default:
+            // FIXME: missing support for some operators (bitwise, &&=, ||=, shift...)
+            ABORT("unsupported binary expression: %s", b.description().c_str());
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeLogicalAnd(BinaryExpression& a, std::ostream& out) {
+    ASSERT(a.fOperator == Token::LOGICALAND);
+    BoolLiteral falseLiteral(Position(), false);
+    SpvId falseConstant = this->writeBoolLiteral(falseLiteral);
+    SpvId lhs = this->writeExpression(*a.fLeft, out);
+    SpvId rhsLabel = this->nextId();
+    SpvId end = this->nextId();
+    SpvId lhsBlock = fCurrentBlock;
+    this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+    this->writeInstruction(SpvOpBranchConditional, lhs, rhsLabel, end, out);
+    this->writeLabel(rhsLabel, out);
+    SpvId rhs = this->writeExpression(*a.fRight, out);
+    SpvId rhsBlock = fCurrentBlock;
+    this->writeInstruction(SpvOpBranch, end, out);
+    this->writeLabel(end, out);
+    SpvId result = this->nextId();
+    this->writeInstruction(SpvOpPhi, this->getType(*kBool_Type), result, falseConstant, lhsBlock,
+                           rhs, rhsBlock, out);
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeLogicalOr(BinaryExpression& o, std::ostream& out) {
+    ASSERT(o.fOperator == Token::LOGICALOR);
+    BoolLiteral trueLiteral(Position(), true);
+    SpvId trueConstant = this->writeBoolLiteral(trueLiteral);
+    SpvId lhs = this->writeExpression(*o.fLeft, out);
+    SpvId rhsLabel = this->nextId();
+    SpvId end = this->nextId();
+    SpvId lhsBlock = fCurrentBlock;
+    this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+    this->writeInstruction(SpvOpBranchConditional, lhs, end, rhsLabel, out);
+    this->writeLabel(rhsLabel, out);
+    SpvId rhs = this->writeExpression(*o.fRight, out);
+    SpvId rhsBlock = fCurrentBlock;
+    this->writeInstruction(SpvOpBranch, end, out);
+    this->writeLabel(end, out);
+    SpvId result = this->nextId();
+    this->writeInstruction(SpvOpPhi, this->getType(*kBool_Type), result, trueConstant, lhsBlock,
+                           rhs, rhsBlock, out);
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeTernaryExpression(TernaryExpression& t, std::ostream& out) {
+    SpvId test = this->writeExpression(*t.fTest, out);
+    if (t.fIfTrue->isConstant() && t.fIfFalse->isConstant()) {
+        // both true and false are constants, can just use OpSelect
+        SpvId result = this->nextId();
+        SpvId trueId = this->writeExpression(*t.fIfTrue, out);
+        SpvId falseId = this->writeExpression(*t.fIfFalse, out);
+        this->writeInstruction(SpvOpSelect, this->getType(*t.fType), result, test, trueId, falseId, 
+                               out);
+        return result;
+    }
+    // was originally using OpPhi to choose the result, but for some reason that is crashing on 
+    // Adreno. Switched to storing the result in a temp variable as glslang does.
+    SpvId var = this->nextId();
+    this->writeInstruction(SpvOpVariable, this->getPointerType(t.fType, SpvStorageClassFunction), 
+                           var, SpvStorageClassFunction, out);
+    SpvId trueLabel = this->nextId();
+    SpvId falseLabel = this->nextId();
+    SpvId end = this->nextId();
+    this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+    this->writeInstruction(SpvOpBranchConditional, test, trueLabel, falseLabel, out);
+    this->writeLabel(trueLabel, out);
+    this->writeInstruction(SpvOpStore, var, this->writeExpression(*t.fIfTrue, out), out);
+    this->writeInstruction(SpvOpBranch, end, out);
+    this->writeLabel(falseLabel, out);
+    this->writeInstruction(SpvOpStore, var, this->writeExpression(*t.fIfFalse, out), out);
+    this->writeInstruction(SpvOpBranch, end, out);
+    this->writeLabel(end, out);
+    SpvId result = this->nextId();
+    this->writeInstruction(SpvOpLoad, this->getType(*t.fType), result, var, out);
+    return result;
+}
+
+Expression* literal_1(const Type& type) {
+    static IntLiteral int1(Position(), 1);
+    static FloatLiteral float1(Position(), 1.0);
+    if (type == *kInt_Type) {
+        return &int1;
+    }
+    else if (type == *kFloat_Type) {
+        return &float1;
+    } else {
+        ABORT("math is unsupported on type '%s'")
+    }
+}
+
+SpvId SPIRVCodeGenerator::writePrefixExpression(PrefixExpression& p, std::ostream& out) {
+    if (p.fOperator == Token::MINUS) {
+        SpvId result = this->nextId();
+        SpvId typeId = this->getType(*p.fType);
+        SpvId expr = this->writeExpression(*p.fOperand, out);
+        if (is_float(*p.fType)) {
+            this->writeInstruction(SpvOpFNegate, typeId, result, expr, out);
+        } else if (is_signed(*p.fType)) {
+            this->writeInstruction(SpvOpSNegate, typeId, result, expr, out);
+        } else {
+            ABORT("unsupported prefix expression %s", p.description().c_str());
+        };
+        return result;
+    }
+    switch (p.fOperator) {
+        case Token::PLUS:
+            return this->writeExpression(*p.fOperand, out);
+        case Token::PLUSPLUS: {
+            std::unique_ptr<LValue> lv = this->getLValue(*p.fOperand, out);
+            SpvId one = this->writeExpression(*literal_1(*p.fType), out);
+            SpvId result = this->writeBinaryOperation(*p.fType, *p.fType, lv->load(out), one, 
+                                                      SpvOpFAdd, SpvOpIAdd, SpvOpIAdd, SpvOpUndef, 
+                                                      out);
+            lv->store(result, out);
+            return result;
+        }
+        case Token::MINUSMINUS: {
+            std::unique_ptr<LValue> lv = this->getLValue(*p.fOperand, out);
+            SpvId one = this->writeExpression(*literal_1(*p.fType), out);
+            SpvId result = this->writeBinaryOperation(*p.fType, *p.fType, lv->load(out), one, 
+                                                      SpvOpFSub, SpvOpISub, SpvOpISub, SpvOpUndef, 
+                                                      out);
+            lv->store(result, out);
+            return result;
+        }
+        case Token::NOT: {
+            ASSERT(p.fOperand->fType == kBool_Type);
+            SpvId result = this->nextId();
+            this->writeInstruction(SpvOpLogicalNot, this->getType(*p.fOperand->fType), result,
+                                   this->writeExpression(*p.fOperand, out), out);
+            return result;
+        }
+        default:
+            ABORT("unsupported prefix expression: %s", p.description().c_str());
+    }
+}
+
+SpvId SPIRVCodeGenerator::writePostfixExpression(PostfixExpression& p, std::ostream& out) {
+    std::unique_ptr<LValue> lv = this->getLValue(*p.fOperand, out);
+    SpvId result = lv->load(out);
+    SpvId one = this->writeExpression(*literal_1(*p.fType), out);
+    switch (p.fOperator) {
+        case Token::PLUSPLUS: {
+            SpvId temp = this->writeBinaryOperation(*p.fType, *p.fType, result, one, SpvOpFAdd, 
+                                                    SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out);
+            lv->store(temp, out);
+            return result;
+        }
+        case Token::MINUSMINUS: {
+            SpvId temp = this->writeBinaryOperation(*p.fType, *p.fType, result, one, SpvOpFSub, 
+                                                    SpvOpISub, SpvOpISub, SpvOpUndef, out);
+            lv->store(temp, out);
+            return result;
+        }
+        default:
+            ABORT("unsupported postfix expression %s", p.description().c_str());
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeBoolLiteral(BoolLiteral& b) {
+    if (b.fValue) {
+        if (fBoolTrue == 0) {
+            fBoolTrue = this->nextId();
+            this->writeInstruction(SpvOpConstantTrue, this->getType(*b.fType), fBoolTrue, 
+                                   fConstantBuffer);
+        }
+        return fBoolTrue;
+    } else {
+        if (fBoolFalse == 0) {
+            fBoolFalse = this->nextId();
+            this->writeInstruction(SpvOpConstantFalse, this->getType(*b.fType), fBoolFalse, 
+                                   fConstantBuffer);
+        }
+        return fBoolFalse;
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeIntLiteral(IntLiteral& i) {
+    if (i.fType == kInt_Type) {
+        auto entry = fIntConstants.find(i.fValue);
+        if (entry == fIntConstants.end()) {
+            SpvId result = this->nextId();
+            this->writeInstruction(SpvOpConstant, this->getType(*i.fType), result, (SpvId) i.fValue, 
+                                   fConstantBuffer);
+            fIntConstants[i.fValue] = result;
+            return result;
+        }
+        return entry->second;
+    } else {
+        ASSERT(i.fType == kUInt_Type);
+        auto entry = fUIntConstants.find(i.fValue);
+        if (entry == fUIntConstants.end()) {
+            SpvId result = this->nextId();
+            this->writeInstruction(SpvOpConstant, this->getType(*i.fType), result, (SpvId) i.fValue, 
+                                   fConstantBuffer);
+            fUIntConstants[i.fValue] = result;
+            return result;
+        }
+        return entry->second;
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeFloatLiteral(FloatLiteral& f) {
+    if (f.fType == kFloat_Type) {
+        float value = (float) f.fValue;
+        auto entry = fFloatConstants.find(value);
+        if (entry == fFloatConstants.end()) {
+            SpvId result = this->nextId();
+            uint32_t bits;
+            ASSERT(sizeof(bits) == sizeof(value));
+            memcpy(&bits, &value, sizeof(bits));
+            this->writeInstruction(SpvOpConstant, this->getType(*f.fType), result, bits, 
+                                   fConstantBuffer);
+            fFloatConstants[value] = result;
+            return result;
+        }
+        return entry->second;
+    } else {
+        ASSERT(f.fType == kDouble_Type);
+        auto entry = fDoubleConstants.find(f.fValue);
+        if (entry == fDoubleConstants.end()) {
+            SpvId result = this->nextId();
+            uint64_t bits;
+            ASSERT(sizeof(bits) == sizeof(f.fValue));
+            memcpy(&bits, &f.fValue, sizeof(bits));
+            this->writeInstruction(SpvOpConstant, this->getType(*f.fType), result, 
+                                   bits & 0xffffffff, bits >> 32, fConstantBuffer);
+            fDoubleConstants[f.fValue] = result;
+            return result;
+        }
+        return entry->second;
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeFunctionStart(std::shared_ptr<FunctionDeclaration> f, 
+                                             std::ostream& out) {
+    SpvId result = fFunctionMap[f];
+    this->writeInstruction(SpvOpFunction, this->getType(*f->fReturnType), result, 
+                           SpvFunctionControlMaskNone, this->getFunctionType(f), out);
+    this->writeInstruction(SpvOpName, result, f->fName.c_str(), fNameBuffer);
+    for (size_t i = 0; i < f->fParameters.size(); i++) {
+        SpvId id = this->nextId();
+        fVariableMap[f->fParameters[i]] = id;
+        SpvId type;
+        type = this->getPointerType(f->fParameters[i]->fType, SpvStorageClassFunction);
+        this->writeInstruction(SpvOpFunctionParameter, type, id, out);
+    }
+    return result;
+}
+
+SpvId SPIRVCodeGenerator::writeFunction(FunctionDefinition& f, std::ostream& out) {
+    SpvId result = this->writeFunctionStart(f.fDeclaration, out);
+    this->writeLabel(this->nextId(), out);
+    if (f.fDeclaration->fName == "main") {
+        out << fGlobalInitializersBuffer.str();
+    }
+    std::stringstream bodyBuffer;
+    this->writeBlock(*f.fBody, bodyBuffer);
+    out << fVariableBuffer.str();
+    fVariableBuffer.str("");
+    out << bodyBuffer.str();
+    if (fCurrentBlock) {
+        this->writeInstruction(SpvOpReturn, out);
+    }
+    this->writeInstruction(SpvOpFunctionEnd, out);
+    return result;
+}
+
+void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target) {
+    if (layout.fLocation >= 0) {
+        this->writeInstruction(SpvOpDecorate, target, SpvDecorationLocation, layout.fLocation, 
+                               fDecorationBuffer);
+    }
+    if (layout.fBinding >= 0) {
+        this->writeInstruction(SpvOpDecorate, target, SpvDecorationBinding, layout.fBinding, 
+                               fDecorationBuffer);
+    }
+    if (layout.fIndex >= 0) {
+        this->writeInstruction(SpvOpDecorate, target, SpvDecorationIndex, layout.fIndex, 
+                               fDecorationBuffer);
+    }
+    if (layout.fSet >= 0) {
+        this->writeInstruction(SpvOpDecorate, target, SpvDecorationDescriptorSet, layout.fSet, 
+                               fDecorationBuffer);
+    }
+    if (layout.fBuiltin >= 0) {
+        this->writeInstruction(SpvOpDecorate, target, SpvDecorationBuiltIn, layout.fBuiltin, 
+                               fDecorationBuffer);
+    }
+}
+
+void SPIRVCodeGenerator::writeLayout(const Layout& layout, SpvId target, int member) {
+    if (layout.fLocation >= 0) {
+        this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationLocation, 
+                               layout.fLocation, fDecorationBuffer);
+    }
+    if (layout.fBinding >= 0) {
+        this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBinding, 
+                               layout.fBinding, fDecorationBuffer);
+    }
+    if (layout.fIndex >= 0) {
+        this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationIndex, 
+                               layout.fIndex, fDecorationBuffer);
+    }
+    if (layout.fSet >= 0) {
+        this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationDescriptorSet, 
+                               layout.fSet, fDecorationBuffer);
+    }
+    if (layout.fBuiltin >= 0) {
+        this->writeInstruction(SpvOpMemberDecorate, target, member, SpvDecorationBuiltIn, 
+                               layout.fBuiltin, fDecorationBuffer);
+    }
+}
+
+SpvId SPIRVCodeGenerator::writeInterfaceBlock(InterfaceBlock& intf) {
+    SpvId type = this->getType(*intf.fVariable->fType);
+    SpvId result = this->nextId();
+    this->writeInstruction(SpvOpDecorate, type, SpvDecorationBlock, fDecorationBuffer);
+    SpvStorageClass_ storageClass = get_storage_class(intf.fVariable->fModifiers);
+    SpvId ptrType = this->nextId();
+    this->writeInstruction(SpvOpTypePointer, ptrType, storageClass, type, fConstantBuffer);
+    this->writeInstruction(SpvOpVariable, ptrType, result, storageClass, fConstantBuffer);
+    this->writeLayout(intf.fVariable->fModifiers.fLayout, result);
+    fVariableMap[intf.fVariable] = result;
+    return result;
+}
+
+void SPIRVCodeGenerator::writeGlobalVars(VarDeclaration& decl, std::ostream& out) {
+    for (size_t i = 0; i < decl.fVars.size(); i++) {
+        if (!decl.fVars[i]->fIsReadFrom && !decl.fVars[i]->fIsWrittenTo) {
+            continue;
+        }
+        SpvStorageClass_ storageClass;
+        if (decl.fVars[i]->fModifiers.fFlags & Modifiers::kIn_Flag) {
+            storageClass = SpvStorageClassInput;
+        } else if (decl.fVars[i]->fModifiers.fFlags & Modifiers::kOut_Flag) {
+            storageClass = SpvStorageClassOutput;
+        } else if (decl.fVars[i]->fModifiers.fFlags & Modifiers::kUniform_Flag) {
+            if (decl.fVars[i]->fType->kind() == Type::kSampler_Kind) {
+                storageClass = SpvStorageClassUniformConstant;
+            } else {
+                storageClass = SpvStorageClassUniform;
+            }
+        } else {
+            storageClass = SpvStorageClassPrivate;
+        }
+        SpvId id = this->nextId();
+        fVariableMap[decl.fVars[i]] = id;
+        SpvId type = this->getPointerType(decl.fVars[i]->fType, storageClass);
+        this->writeInstruction(SpvOpVariable, type, id, storageClass, fConstantBuffer);
+        this->writeInstruction(SpvOpName, id, decl.fVars[i]->fName.c_str(), fNameBuffer);
+        if (decl.fVars[i]->fType->kind() == Type::kMatrix_Kind) {
+            this->writeInstruction(SpvOpMemberDecorate, id, (SpvId) i, SpvDecorationColMajor, 
+                                   fDecorationBuffer);
+            this->writeInstruction(SpvOpMemberDecorate, id, (SpvId) i, SpvDecorationMatrixStride, 
+                                   (SpvId) decl.fVars[i]->fType->stride(), fDecorationBuffer);
+        }
+        if (decl.fValues[i]) {
+			ASSERT(!fCurrentBlock);
+			fCurrentBlock = -1;
+            SpvId value = this->writeExpression(*decl.fValues[i], fGlobalInitializersBuffer);
+            this->writeInstruction(SpvOpStore, id, value, fGlobalInitializersBuffer);
+			fCurrentBlock = 0;
+        }
+        this->writeLayout(decl.fVars[i]->fModifiers.fLayout, id);
+    }
+}
+
+void SPIRVCodeGenerator::writeVarDeclaration(VarDeclaration& decl, std::ostream& out) {
+    for (size_t i = 0; i < decl.fVars.size(); i++) {
+        SpvId id = this->nextId();
+        fVariableMap[decl.fVars[i]] = id;
+        SpvId type = this->getPointerType(decl.fVars[i]->fType, SpvStorageClassFunction);
+        this->writeInstruction(SpvOpVariable, type, id, SpvStorageClassFunction, fVariableBuffer);
+        this->writeInstruction(SpvOpName, id, decl.fVars[i]->fName.c_str(), fNameBuffer);
+        if (decl.fValues[i]) {
+            SpvId value = this->writeExpression(*decl.fValues[i], out);
+            this->writeInstruction(SpvOpStore, id, value, out);
+        }
+    }
+}
+
+void SPIRVCodeGenerator::writeStatement(Statement& s, std::ostream& out) {
+    switch (s.fKind) {
+        case Statement::kBlock_Kind:
+            this->writeBlock((Block&) s, out);
+            break;
+        case Statement::kExpression_Kind:
+            this->writeExpression(*((ExpressionStatement&) s).fExpression, out);
+            break;
+        case Statement::kReturn_Kind: 
+            this->writeReturnStatement((ReturnStatement&) s, out);
+            break;
+        case Statement::kVarDeclaration_Kind:
+            this->writeVarDeclaration(*((VarDeclarationStatement&) s).fDeclaration, out);
+            break;
+        case Statement::kIf_Kind:
+            this->writeIfStatement((IfStatement&) s, out);
+            break;
+        case Statement::kFor_Kind:
+            this->writeForStatement((ForStatement&) s, out);
+            break;
+        case Statement::kBreak_Kind:
+            this->writeInstruction(SpvOpBranch, fBreakTarget.top(), out);
+            break;
+        case Statement::kContinue_Kind:
+            this->writeInstruction(SpvOpBranch, fContinueTarget.top(), out);
+            break;
+        case Statement::kDiscard_Kind:
+            this->writeInstruction(SpvOpKill, out);
+            break;
+        default:
+            ABORT("unsupported statement: %s", s.description().c_str());
+    }
+}
+
+void SPIRVCodeGenerator::writeBlock(Block& b, std::ostream& out) {
+    for (size_t i = 0; i < b.fStatements.size(); i++) {
+        this->writeStatement(*b.fStatements[i], out);
+    }
+}
+
+void SPIRVCodeGenerator::writeIfStatement(IfStatement& stmt, std::ostream& out) {
+    SpvId test = this->writeExpression(*stmt.fTest, out);
+    SpvId ifTrue = this->nextId();
+    SpvId ifFalse = this->nextId();
+    if (stmt.fIfFalse) {
+        SpvId end = this->nextId();
+        this->writeInstruction(SpvOpSelectionMerge, end, SpvSelectionControlMaskNone, out);
+        this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out);
+        this->writeLabel(ifTrue, out);
+        this->writeStatement(*stmt.fIfTrue, out);
+        if (fCurrentBlock) {
+            this->writeInstruction(SpvOpBranch, end, out);
+        }
+        this->writeLabel(ifFalse, out);
+        this->writeStatement(*stmt.fIfFalse, out);
+        if (fCurrentBlock) {
+            this->writeInstruction(SpvOpBranch, end, out);
+        }
+        this->writeLabel(end, out);
+    } else {
+        this->writeInstruction(SpvOpSelectionMerge, ifFalse, SpvSelectionControlMaskNone, out);
+        this->writeInstruction(SpvOpBranchConditional, test, ifTrue, ifFalse, out);
+        this->writeLabel(ifTrue, out);
+        this->writeStatement(*stmt.fIfTrue, out);
+        if (fCurrentBlock) {
+            this->writeInstruction(SpvOpBranch, ifFalse, out);
+        }
+        this->writeLabel(ifFalse, out);
+    }
+}
+
+void SPIRVCodeGenerator::writeForStatement(ForStatement& f, std::ostream& out) {
+    if (f.fInitializer) {
+        this->writeStatement(*f.fInitializer, out);
+    }
+    SpvId header = this->nextId();
+    SpvId start = this->nextId();
+    SpvId body = this->nextId();
+    SpvId next = this->nextId();
+    fContinueTarget.push(next);
+    SpvId end = this->nextId();
+    fBreakTarget.push(end);
+    this->writeInstruction(SpvOpBranch, header, out);
+    this->writeLabel(header, out);
+    this->writeInstruction(SpvOpLoopMerge, end, next, SpvLoopControlMaskNone, out);
+	this->writeInstruction(SpvOpBranch, start, out);
+    this->writeLabel(start, out);
+    SpvId test = this->writeExpression(*f.fTest, out);
+    this->writeInstruction(SpvOpBranchConditional, test, body, end, out);
+    this->writeLabel(body, out);
+    this->writeStatement(*f.fStatement, out);
+    if (fCurrentBlock) {
+        this->writeInstruction(SpvOpBranch, next, out);
+    }
+    this->writeLabel(next, out);
+    if (f.fNext) {
+        this->writeExpression(*f.fNext, out);
+    }
+    this->writeInstruction(SpvOpBranch, header, out);
+    this->writeLabel(end, out);
+    fBreakTarget.pop();
+    fContinueTarget.pop();
+}
+
+void SPIRVCodeGenerator::writeReturnStatement(ReturnStatement& r, std::ostream& out) {
+    if (r.fExpression) {
+        this->writeInstruction(SpvOpReturnValue, this->writeExpression(*r.fExpression, out), 
+                               out);
+    } else {
+        this->writeInstruction(SpvOpReturn, out);
+    }
+}
+
+void SPIRVCodeGenerator::writeInstructions(Program& program, std::ostream& out) {
+    fGLSLExtendedInstructions = this->nextId();
+    std::stringstream body;
+    std::vector<SpvId> interfaceVars;
+    // assign IDs to functions
+    for (size_t i = 0; i < program.fElements.size(); i++) {
+        if (program.fElements[i]->fKind == ProgramElement::kFunction_Kind) {
+            FunctionDefinition& f = (FunctionDefinition&) *program.fElements[i];
+            fFunctionMap[f.fDeclaration] = this->nextId();
+        }
+    }
+    for (size_t i = 0; i < program.fElements.size(); i++) {
+        if (program.fElements[i]->fKind == ProgramElement::kInterfaceBlock_Kind) {
+            InterfaceBlock& intf = (InterfaceBlock&) *program.fElements[i];
+            SpvId id = this->writeInterfaceBlock(intf);
+            if ((intf.fVariable->fModifiers.fFlags & Modifiers::kIn_Flag) ||
+                (intf.fVariable->fModifiers.fFlags & Modifiers::kOut_Flag)) {
+                interfaceVars.push_back(id);
+            }
+        }
+    }
+    for (size_t i = 0; i < program.fElements.size(); i++) {
+        if (program.fElements[i]->fKind == ProgramElement::kVar_Kind) {
+            this->writeGlobalVars(((VarDeclaration&) *program.fElements[i]), body);
+        }
+    }
+    for (size_t i = 0; i < program.fElements.size(); i++) {
+        if (program.fElements[i]->fKind == ProgramElement::kFunction_Kind) {
+            this->writeFunction(((FunctionDefinition&) *program.fElements[i]), body);
+        }
+    }
+    std::shared_ptr<FunctionDeclaration> main = nullptr;
+    for (auto entry : fFunctionMap) {
+		if (entry.first->fName == "main") {
+            main = entry.first;
+        }
+    }
+    ASSERT(main);
+    for (auto entry : fVariableMap) {
+        std::shared_ptr<Variable> var = entry.first;
+        if (var->fStorage == Variable::kGlobal_Storage && 
+                ((var->fModifiers.fFlags & Modifiers::kIn_Flag) ||
+                 (var->fModifiers.fFlags & Modifiers::kOut_Flag))) {
+            interfaceVars.push_back(entry.second);
+        }
+    }
+    this->writeCapabilities(out);
+    this->writeInstruction(SpvOpExtInstImport, fGLSLExtendedInstructions, "GLSL.std.450", out);
+    this->writeInstruction(SpvOpMemoryModel, SpvAddressingModelLogical, SpvMemoryModelGLSL450, out);
+    this->writeOpCode(SpvOpEntryPoint, (SpvId) (3 + (strlen(main->fName.c_str()) + 4) / 4) + 
+                      (int32_t) interfaceVars.size(), out);
+    switch (program.fKind) {
+        case Program::kVertex_Kind:
+            this->writeWord(SpvExecutionModelVertex, out);
+            break;
+        case Program::kFragment_Kind:
+            this->writeWord(SpvExecutionModelFragment, out);
+            break;
+    }
+    this->writeWord(fFunctionMap[main], out);
+    this->writeString(main->fName.c_str(), out);
+    for (int var : interfaceVars) {
+        this->writeWord(var, out);
+    }
+    if (program.fKind == Program::kFragment_Kind) {
+        this->writeInstruction(SpvOpExecutionMode, 
+                               fFunctionMap[main], 
+                               SpvExecutionModeOriginUpperLeft,
+                               out);
+    }
+    for (size_t i = 0; i < program.fElements.size(); i++) {
+        if (program.fElements[i]->fKind == ProgramElement::kExtension_Kind) {
+            this->writeInstruction(SpvOpSourceExtension, 
+                                   ((Extension&) *program.fElements[i]).fName.c_str(), 
+                                   out);
+        }
+    }
+    
+    out << fNameBuffer.str();
+    out << fDecorationBuffer.str();
+    out << fConstantBuffer.str();
+    out << fExternalFunctionsBuffer.str();
+    out << body.str();
+}
+
+void SPIRVCodeGenerator::generateCode(Program& program, std::ostream& out) {
+    this->writeWord(SpvMagicNumber, out);
+    this->writeWord(SpvVersion, out);
+    this->writeWord(SKSL_MAGIC, out);
+    std::stringstream buffer;
+    this->writeInstructions(program, buffer);
+    this->writeWord(fIdCount, out);
+    this->writeWord(0, out); // reserved, always zero
+    out << buffer.str();
+}
+
+}
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.h b/src/sksl/SkSLSPIRVCodeGenerator.h
new file mode 100644
index 0000000..885c6b8
--- /dev/null
+++ b/src/sksl/SkSLSPIRVCodeGenerator.h
@@ -0,0 +1,264 @@
+/*
+ * 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_SPIRVCODEGENERATOR
+#define SKSL_SPIRVCODEGENERATOR
+
+#include <sstream>
+#include <stack>
+#include <tuple>
+#include <unordered_map>
+
+#include "SkSLCodeGenerator.h"
+#include "ir/SkSLBinaryExpression.h"
+#include "ir/SkSLBoolLiteral.h"
+#include "ir/SkSLConstructor.h"
+#include "ir/SkSLFloatLiteral.h"
+#include "ir/SkSLIfStatement.h"
+#include "ir/SkSLIndexExpression.h"
+#include "ir/SkSLInterfaceBlock.h"
+#include "ir/SkSLIntLiteral.h"
+#include "ir/SkSLFieldAccess.h"
+#include "ir/SkSLForStatement.h"
+#include "ir/SkSLFunctionCall.h"
+#include "ir/SkSLFunctionDeclaration.h"
+#include "ir/SkSLFunctionDefinition.h"
+#include "ir/SkSLPrefixExpression.h"
+#include "ir/SkSLPostfixExpression.h"
+#include "ir/SkSLProgramElement.h"
+#include "ir/SkSLReturnStatement.h"
+#include "ir/SkSLStatement.h"
+#include "ir/SkSLSwizzle.h"
+#include "ir/SkSLTernaryExpression.h"
+#include "ir/SkSLVarDeclaration.h"
+#include "ir/SkSLVarDeclarationStatement.h"
+#include "ir/SkSLVariableReference.h"
+#include "spirv.h"
+
+namespace SkSL {
+
+#define kLast_Capability SpvCapabilityMultiViewport
+
+/**
+ * Converts a Program into a SPIR-V binary.
+ */
+class SPIRVCodeGenerator : public CodeGenerator {
+public:
+    class LValue {
+    public:
+        virtual ~LValue() {}
+        
+        // returns a pointer to the lvalue, if possible. If the lvalue cannot be directly referenced
+        // by a pointer (e.g. vector swizzles), returns 0.
+        virtual SpvId getPointer() = 0;
+
+        virtual SpvId load(std::ostream& out) = 0;
+
+        virtual void store(SpvId value, std::ostream& out) = 0;
+    };
+
+    SPIRVCodeGenerator()
+    : fCapabilities(1 << SpvCapabilityShader)
+    , fIdCount(1)
+    , fBoolTrue(0)
+    , fBoolFalse(0)
+    , fCurrentBlock(0) {
+        this->setupIntrinsics();
+    }
+
+    void generateCode(Program& program, std::ostream& out) override;
+
+private:
+    enum IntrinsicKind {
+        kGLSL_STD_450_IntrinsicKind,
+        kSPIRV_IntrinsicKind,
+        kSpecial_IntrinsicKind
+    };
+
+    enum SpecialIntrinsic {
+        kAtan_SpecialIntrinsic,
+        kTexture_SpecialIntrinsic,
+        kTexture2D_SpecialIntrinsic,
+        kTextureProj_SpecialIntrinsic
+    };
+
+    void setupIntrinsics();
+
+    SpvId nextId();
+
+    SpvId getType(const Type& type);
+
+    SpvId getFunctionType(std::shared_ptr<FunctionDeclaration> function);
+
+    SpvId getPointerType(std::shared_ptr<Type> type, SpvStorageClass_ storageClass);
+
+    std::vector<SpvId> getAccessChain(Expression& expr, std::ostream& out);
+
+    void writeLayout(const Layout& layout, SpvId target);
+
+    void writeLayout(const Layout& layout, SpvId target, int member);
+
+    void writeStruct(const Type& type, SpvId resultId);
+
+    void writeProgramElement(ProgramElement& pe, std::ostream& out);
+
+    SpvId writeInterfaceBlock(InterfaceBlock& intf);
+
+    SpvId writeFunctionStart(std::shared_ptr<FunctionDeclaration> f, std::ostream& out);
+    
+    SpvId writeFunctionDeclaration(std::shared_ptr<FunctionDeclaration> f, std::ostream& out);
+
+    SpvId writeFunction(FunctionDefinition& f, std::ostream& out);
+
+    void writeGlobalVars(VarDeclaration& v, std::ostream& out);
+
+    void writeVarDeclaration(VarDeclaration& decl, std::ostream& out);
+
+    SpvId writeVariableReference(VariableReference& ref, std::ostream& out);
+
+    std::unique_ptr<LValue> getLValue(Expression& value, std::ostream& out);
+
+    SpvId writeExpression(Expression& expr, std::ostream& out);
+    
+    SpvId writeIntrinsicCall(FunctionCall& c, std::ostream& out);
+
+    SpvId writeFunctionCall(FunctionCall& c, std::ostream& out);
+
+    SpvId writeSpecialIntrinsic(FunctionCall& c, SpecialIntrinsic kind, std::ostream& out);
+
+    SpvId writeConstantVector(Constructor& c);
+
+    SpvId writeFloatConstructor(Constructor& c, std::ostream& out);
+
+    SpvId writeIntConstructor(Constructor& c, std::ostream& out);
+    
+    SpvId writeMatrixConstructor(Constructor& c, std::ostream& out);
+
+    SpvId writeVectorConstructor(Constructor& c, std::ostream& out);
+
+    SpvId writeConstructor(Constructor& c, std::ostream& out);
+
+    SpvId writeFieldAccess(FieldAccess& f, std::ostream& out);
+
+    SpvId writeSwizzle(Swizzle& swizzle, std::ostream& out);
+
+    SpvId writeBinaryOperation(const Type& resultType, const Type& operandType, SpvId lhs, 
+                               SpvId rhs, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt, 
+                               SpvOp_ ifBool, std::ostream& out);
+
+    SpvId writeBinaryOperation(BinaryExpression& expr, SpvOp_ ifFloat, SpvOp_ ifInt, SpvOp_ ifUInt,
+                               std::ostream& out);
+
+    SpvId writeBinaryExpression(BinaryExpression& b, std::ostream& out);
+
+    SpvId writeTernaryExpression(TernaryExpression& t, std::ostream& out);
+
+    SpvId writeIndexExpression(IndexExpression& expr, std::ostream& out);
+
+    SpvId writeLogicalAnd(BinaryExpression& b, std::ostream& out);
+
+    SpvId writeLogicalOr(BinaryExpression& o, std::ostream& out);
+
+    SpvId writePrefixExpression(PrefixExpression& p, std::ostream& out);
+
+    SpvId writePostfixExpression(PostfixExpression& p, std::ostream& out);
+
+    SpvId writeBoolLiteral(BoolLiteral& b);
+
+    SpvId writeIntLiteral(IntLiteral& i);
+
+    SpvId writeFloatLiteral(FloatLiteral& f);
+
+    void writeStatement(Statement& s, std::ostream& out);
+
+    void writeBlock(Block& b, std::ostream& out);
+
+    void writeIfStatement(IfStatement& stmt, std::ostream& out);
+
+    void writeForStatement(ForStatement& f, std::ostream& out);
+
+    void writeReturnStatement(ReturnStatement& r, std::ostream& out);
+
+    void writeCapabilities(std::ostream& out);
+
+    void writeInstructions(Program& program, std::ostream& out);
+
+    void writeOpCode(SpvOp_ opCode, int length, std::ostream& out);
+
+    void writeWord(int32_t word, std::ostream& out);
+
+    void writeString(const char* string, std::ostream& out);
+
+    void writeLabel(SpvId id, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, const char* string, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, const char* string, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, const char* string,
+                          std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, 
+                          std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+                          std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+                          int32_t word5, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+                          int32_t word5, int32_t word6, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+                          int32_t word5, int32_t word6, int32_t word7, std::ostream& out);
+
+    void writeInstruction(SpvOp_ opCode, int32_t word1, int32_t word2, int32_t word3, int32_t word4,
+                          int32_t word5, int32_t word6, int32_t word7, int32_t word8, 
+                          std::ostream& out);
+
+    uint64_t fCapabilities;
+    SpvId fIdCount;
+    SpvId fGLSLExtendedInstructions;
+    typedef std::tuple<IntrinsicKind, int32_t, int32_t, int32_t, int32_t> Intrinsic;
+    std::unordered_map<std::string, Intrinsic> fIntrinsicMap;
+    std::unordered_map<std::shared_ptr<FunctionDeclaration>, SpvId> fFunctionMap;
+    std::unordered_map<std::shared_ptr<Variable>, SpvId> fVariableMap;
+    std::unordered_map<std::shared_ptr<Variable>, int32_t> fInterfaceBlockMap;
+    std::unordered_map<std::string, SpvId> fTypeMap;
+    std::stringstream fCapabilitiesBuffer;
+    std::stringstream fGlobalInitializersBuffer;
+    std::stringstream fConstantBuffer;
+    std::stringstream fExternalFunctionsBuffer;
+    std::stringstream fVariableBuffer;
+    std::stringstream fNameBuffer;
+    std::stringstream fDecorationBuffer;
+
+    SpvId fBoolTrue;
+    SpvId fBoolFalse;
+    std::unordered_map<int64_t, SpvId> fIntConstants;
+    std::unordered_map<uint64_t, SpvId> fUIntConstants;
+    std::unordered_map<float, SpvId> fFloatConstants;
+    std::unordered_map<double, SpvId> fDoubleConstants;
+    // label of the current block, or 0 if we are not in a block
+    SpvId fCurrentBlock;
+    std::stack<SpvId> fBreakTarget;
+    std::stack<SpvId> fContinueTarget;
+
+    friend class PointerLValue;
+    friend class SwizzleLValue;
+};
+
+}
+
+#endif
diff --git a/src/sksl/SkSLToken.h b/src/sksl/SkSLToken.h
new file mode 100644
index 0000000..538ae50
--- /dev/null
+++ b/src/sksl/SkSLToken.h
@@ -0,0 +1,156 @@
+/*
+ * 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_TOKEN
+#define SKSL_TOKEN
+
+#include "SkSLPosition.h"
+#include "SkSLUtil.h"
+ 
+namespace SkSL {
+
+/**
+ * Represents a lexical analysis token. Token is generally only used during the parse process, but
+ * Token::Kind is also used to represent operator kinds.
+ */
+struct Token {
+    enum Kind {
+        END_OF_FILE,
+        IDENTIFIER,
+        INT_LITERAL,
+        FLOAT_LITERAL,
+        TRUE_LITERAL,
+        FALSE_LITERAL,
+        LPAREN,
+        RPAREN,
+        LBRACE,
+        RBRACE,
+        LBRACKET,
+        RBRACKET,
+        DOT,
+        COMMA,
+        PLUSPLUS,
+        MINUSMINUS,
+        PLUS,
+        MINUS,
+        STAR,
+        SLASH,
+        PERCENT,
+        SHL,
+        SHR,
+        BITWISEOR,
+        BITWISEXOR,
+        BITWISEAND,
+        LOGICALOR,
+        LOGICALXOR,
+        LOGICALAND,
+        NOT,
+        QUESTION,
+        COLON,
+        EQ,
+        EQEQ,
+        NEQ,
+        GT,
+        LT,
+        GTEQ,
+        LTEQ,
+        PLUSEQ,
+        MINUSEQ,
+        STAREQ,
+        SLASHEQ,
+        PERCENTEQ,
+        SHLEQ,
+        SHREQ,
+        BITWISEOREQ,
+        BITWISEXOREQ,
+        BITWISEANDEQ,
+        LOGICALOREQ,
+        LOGICALXOREQ,
+        LOGICALANDEQ,
+        SEMICOLON,
+        IF,
+        ELSE,
+        FOR,
+        WHILE,
+        DO,
+        RETURN,
+        BREAK,
+        CONTINUE,
+        DISCARD,
+        IN,
+        OUT,
+        INOUT,
+        CONST,
+        LOWP,
+        MEDIUMP,
+        HIGHP,
+        UNIFORM,
+        STRUCT,
+        LAYOUT,
+        DIRECTIVE,
+        PRECISION,
+        INVALID_TOKEN
+    };
+
+    static std::string OperatorName(Kind kind) {
+        switch (kind) {
+            case Token::PLUS:         return "+";
+            case Token::MINUS:        return "-";
+            case Token::STAR:         return "*";
+            case Token::SLASH:        return "/";
+            case Token::PERCENT:      return "%";
+            case Token::SHL:          return "<<";
+            case Token::SHR:          return ">>";
+            case Token::LOGICALAND:   return "&&";
+            case Token::LOGICALOR:    return "||";
+            case Token::LOGICALXOR:   return "^^";
+            case Token::BITWISEAND:   return "&";
+            case Token::BITWISEOR:    return "|";
+            case Token::BITWISEXOR:   return "^";
+            case Token::EQ:           return "=";
+            case Token::EQEQ:         return "==";
+            case Token::NEQ:          return "!=";
+            case Token::LT:           return "<";
+            case Token::GT:           return ">";
+            case Token::LTEQ:         return "<=";
+            case Token::GTEQ:         return ">=";
+            case Token::PLUSEQ:       return "+=";
+            case Token::MINUSEQ:      return "-=";
+            case Token::STAREQ:       return "*=";
+            case Token::SLASHEQ:      return "/=";
+            case Token::PERCENTEQ:    return "%=";
+            case Token::SHLEQ:        return "<<=";
+            case Token::SHREQ:        return ">>=";
+            case Token::LOGICALANDEQ: return "&&=";
+            case Token::LOGICALOREQ:  return "||=";
+            case Token::LOGICALXOREQ: return "^^=";
+            case Token::BITWISEANDEQ: return "&=";
+            case Token::BITWISEOREQ:  return "|=";
+            case Token::BITWISEXOREQ: return "^=";
+            case Token::PLUSPLUS:     return "++";
+            case Token::MINUSMINUS:   return "--";
+            case Token::NOT:          return "!";
+            default:
+                ABORT("unsupported operator: %d\n", kind); 
+        }        
+    }
+
+    Token() {
+    }
+
+    Token(Position position, Kind kind, std::string text)
+    : fPosition(position)
+    , fKind(kind)
+    , fText(std::move(text)) {}
+
+    Position fPosition;
+    Kind fKind;
+    std::string fText;
+};
+
+} // namespace
+#endif
diff --git a/src/sksl/SkSLUtil.cpp b/src/sksl/SkSLUtil.cpp
new file mode 100644
index 0000000..327bffe
--- /dev/null
+++ b/src/sksl/SkSLUtil.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkSLUtil.h"
+
+namespace SkSL {
+
+int stoi(std::string s) {
+    return atoi(s.c_str());
+}
+
+double stod(std::string s) {
+    return atof(s.c_str());
+}
+
+long stol(std::string s) {
+    return atol(s.c_str());
+}
+
+void sksl_abort() {
+#ifdef SKIA
+    sk_abort_no_print();
+    exit(1);
+#else
+    abort();
+#endif
+}
+
+} // namespace
diff --git a/src/sksl/SkSLUtil.h b/src/sksl/SkSLUtil.h
new file mode 100644
index 0000000..5536d93
--- /dev/null
+++ b/src/sksl/SkSLUtil.h
@@ -0,0 +1,60 @@
+/*
+ * 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_UTIL
+#define SKSL_UTIL
+
+#include <string>
+#include <sstream>
+#include "stdlib.h"
+#include "assert.h"
+#include "SkTypes.h"    
+
+namespace SkSL {
+
+// our own definitions of certain std:: functions, because they are not always present on Android
+
+template <typename T> std::string to_string(T value) {
+#ifdef SK_BUILD_FOR_ANDROID
+    std::stringstream buffer;
+    buffer << value;
+    return buffer.str();
+#else
+    return std::to_string(value);
+#endif
+}
+
+#if _MSC_VER
+#define NORETURN __declspec(noreturn)
+#else
+#define NORETURN __attribute__((__noreturn__))
+#endif
+int stoi(std::string s);
+
+double stod(std::string s);
+
+long stol(std::string s);
+
+NORETURN void sksl_abort();
+
+} // namespace
+
+#ifdef DEBUG
+#define ASSERT(x) assert(x)
+#define ASSERT_RESULT(x) ASSERT(x);
+#else
+#define ASSERT(x)
+#define ASSERT_RESULT(x) x
+#endif
+
+#ifdef SKIA
+#define ABORT(...) { SkDebugf(__VA_ARGS__); sksl_abort(); }
+#else
+#define ABORT(...) { sksl_abort(); }
+#endif
+
+#endif
diff --git a/src/sksl/ast/SkSLASTBinaryExpression.h b/src/sksl/ast/SkSLASTBinaryExpression.h
new file mode 100644
index 0000000..88feba6
--- /dev/null
+++ b/src/sksl/ast/SkSLASTBinaryExpression.h
@@ -0,0 +1,42 @@
+/*
+ * 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 "SkSLASTExpression.h"
+#include "../SkSLToken.h"
+#include <sstream>
+
+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.fPosition, kBinary_Kind)
+    , fLeft(std::move(left))
+    , fOperator(op.fKind)
+    , fRight(std::move(right)) {}
+
+    std::string description() const override {
+        return "(" + fLeft->description() + " " + Token::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
new file mode 100644
index 0000000..09450a3
--- /dev/null
+++ b/src/sksl/ast/SkSLASTBlock.h
@@ -0,0 +1,40 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * Represents a curly-braced block of statements. 
+ */
+struct ASTBlock : public ASTStatement {
+    ASTBlock(Position position, std::vector<std::unique_ptr<ASTStatement>> statements)
+    : INHERITED(position, kBlock_Kind)
+    , fStatements(std::move(statements)) {}
+
+    std::string description() const override {
+        std::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
new file mode 100644
index 0000000..ff58822
--- /dev/null
+++ b/src/sksl/ast/SkSLASTBoolLiteral.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkSLASTExpression.h"
+
+namespace SkSL {
+
+/**
+ * Represents "true" or "false". 
+ */
+struct ASTBoolLiteral : public ASTExpression {
+    ASTBoolLiteral(Position position, bool value)
+    : INHERITED(position, kBool_Kind)
+    , fValue(value) {}
+
+    std::string description() const override {
+        return 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
new file mode 100644
index 0000000..ede548c
--- /dev/null
+++ b/src/sksl/ast/SkSLASTBreakStatement.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'break' statement. 
+ */
+struct ASTBreakStatement : public ASTStatement {
+    ASTBreakStatement(Position position)
+    : INHERITED(position, kBreak_Kind) {}
+
+    std::string description() const override {
+        return "break;";
+    }
+
+    typedef ASTStatement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTCallSuffix.h b/src/sksl/ast/SkSLASTCallSuffix.h
new file mode 100644
index 0000000..5cff6f6
--- /dev/null
+++ b/src/sksl/ast/SkSLASTCallSuffix.h
@@ -0,0 +1,44 @@
+/*
+ * 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 <sstream>
+#include <vector>
+#include "SkSLASTSuffix.h"
+
+namespace SkSL {
+
+/**
+ * A parenthesized list of arguments following an expression, indicating a function call. 
+ */
+struct ASTCallSuffix : public ASTSuffix {
+    ASTCallSuffix(Position position, std::vector<std::unique_ptr<ASTExpression>> arguments) 
+    : INHERITED(position, ASTSuffix::kCall_Kind)
+    , fArguments(std::move(arguments)) {}
+
+    std::string description() const override {
+        std::string result("(");
+        std::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
new file mode 100644
index 0000000..d5ab7a5
--- /dev/null
+++ b/src/sksl/ast/SkSLASTContinueStatement.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'continue' statement. 
+ */
+struct ASTContinueStatement : public ASTStatement {
+    ASTContinueStatement(Position position)
+    : INHERITED(position, kContinue_Kind) {}
+
+    std::string description() const override {
+        return "continue;";
+    }
+
+    typedef ASTStatement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTDeclaration.h b/src/sksl/ast/SkSLASTDeclaration.h
new file mode 100644
index 0000000..8b55ecf
--- /dev/null
+++ b/src/sksl/ast/SkSLASTDeclaration.h
@@ -0,0 +1,37 @@
+/*
+ * 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 "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
+    };
+
+    ASTDeclaration(Position position, Kind kind)
+    : INHERITED(position)
+    , fKind(kind) {}
+
+    Kind fKind;
+
+    typedef ASTPositionNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTDiscardStatement.h b/src/sksl/ast/SkSLASTDiscardStatement.h
new file mode 100644
index 0000000..4eaeec9
--- /dev/null
+++ b/src/sksl/ast/SkSLASTDiscardStatement.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'discard' statement. 
+ */
+struct ASTDiscardStatement : public ASTStatement {
+    ASTDiscardStatement(Position position)
+    : INHERITED(position, kDiscard_Kind) {}
+
+    std::string description() const override {
+        return "discard;";
+    }
+
+    typedef ASTStatement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTDoStatement.h b/src/sksl/ast/SkSLASTDoStatement.h
new file mode 100644
index 0000000..a952d62
--- /dev/null
+++ b/src/sksl/ast/SkSLASTDoStatement.h
@@ -0,0 +1,37 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'do' loop. 
+ */
+struct ASTDoStatement : public ASTStatement {
+    ASTDoStatement(Position position, std::unique_ptr<ASTStatement> statement,
+                   std::unique_ptr<ASTExpression> test)
+    : INHERITED(position, kDo_Kind)
+    , fStatement(std::move(statement))
+    , fTest(std::move(test)) {}
+
+    std::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/SkSLASTExpression.h b/src/sksl/ast/SkSLASTExpression.h
new file mode 100644
index 0000000..8a48271
--- /dev/null
+++ b/src/sksl/ast/SkSLASTExpression.h
@@ -0,0 +1,41 @@
+/*
+ * 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 "SkSLASTPositionNode.h"
+
+namespace SkSL {
+
+/**
+ * Abstract supertype of all expressions. 
+ */
+struct ASTExpression : public ASTPositionNode {
+    enum Kind {
+        kFloat_Kind,
+        kIdentifier_Kind,
+        kInt_Kind,
+        kBool_Kind,
+        kPrefix_Kind,
+        kSuffix_Kind,
+        kBinary_Kind,
+        kTernary_Kind
+    };
+
+    ASTExpression(Position position, Kind kind)
+    : INHERITED(position)
+    , fKind(kind) {}
+
+    const Kind fKind;
+
+    typedef ASTPositionNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTExpressionStatement.h b/src/sksl/ast/SkSLASTExpressionStatement.h
new file mode 100644
index 0000000..450cca2
--- /dev/null
+++ b/src/sksl/ast/SkSLASTExpressionStatement.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A lone expression being used as a statement. 
+ */
+struct ASTExpressionStatement : public ASTStatement {
+    ASTExpressionStatement(std::unique_ptr<ASTExpression> expression)
+    : INHERITED(expression->fPosition, kExpression_Kind)
+    , fExpression(std::move(expression)) {}
+
+    std::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
new file mode 100644
index 0000000..896ac46
--- /dev/null
+++ b/src/sksl/ast/SkSLASTExtension.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkSLASTDeclaration.h"
+
+namespace SkSL {
+
+/** 
+ * An extension declaration. 
+ */
+struct ASTExtension : public ASTDeclaration {
+    ASTExtension(Position position, std::string name)
+    : INHERITED(position, kExtension_Kind)
+    , fName(std::move(name)) {}
+
+    std::string description() const override {
+        return "#extension " + fName + " : enable";
+    }
+
+    const std::string fName;
+
+    typedef ASTDeclaration INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTFieldSuffix.h b/src/sksl/ast/SkSLASTFieldSuffix.h
new file mode 100644
index 0000000..cf141d8
--- /dev/null
+++ b/src/sksl/ast/SkSLASTFieldSuffix.h
@@ -0,0 +1,35 @@
+/*
+ * 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 "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(Position position, std::string field) 
+    : INHERITED(position, ASTSuffix::kField_Kind)
+    , fField(std::move(field)) {}
+
+    std::string description() const override {
+        return "." + fField;
+    }
+
+    std::string fField;
+
+    typedef ASTSuffix INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTFloatLiteral.h b/src/sksl/ast/SkSLASTFloatLiteral.h
new file mode 100644
index 0000000..89d43cc
--- /dev/null
+++ b/src/sksl/ast/SkSLASTFloatLiteral.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkSLASTExpression.h"
+
+namespace SkSL {
+
+/**
+ * A literal floating point number. 
+ */
+struct ASTFloatLiteral : public ASTExpression {
+    ASTFloatLiteral(Position position, double value)
+    : INHERITED(position, kFloat_Kind)
+    , fValue(value) {}
+
+    std::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
new file mode 100644
index 0000000..f4f68c8
--- /dev/null
+++ b/src/sksl/ast/SkSLASTForStatement.h
@@ -0,0 +1,56 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'for' loop. 
+ */
+struct ASTForStatement : public ASTStatement {
+    ASTForStatement(Position position, std::unique_ptr<ASTStatement> initializer, 
+                   std::unique_ptr<ASTExpression> test, std::unique_ptr<ASTExpression> next,
+                   std::unique_ptr<ASTStatement> statement)
+    : INHERITED(position, kFor_Kind)
+    , fInitializer(std::move(initializer))
+    , fTest(std::move(test))
+    , fNext(std::move(next))
+    , fStatement(std::move(statement)) {}
+
+    std::string description() const override {
+        std::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
new file mode 100644
index 0000000..c5c3b9a
--- /dev/null
+++ b/src/sksl/ast/SkSLASTFunction.h
@@ -0,0 +1,57 @@
+/*
+ * 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 "SkSLASTBlock.h"
+#include "SkSLASTDeclaration.h"
+#include "SkSLASTParameter.h"
+#include "SkSLASTType.h"
+
+namespace SkSL {
+
+/**
+ * A function declaration or definition. The fBody field will be null for declarations. 
+ */
+struct ASTFunction : public ASTDeclaration {
+    ASTFunction(Position position, std::unique_ptr<ASTType> returnType, std::string name,
+                std::vector<std::unique_ptr<ASTParameter>> parameters, 
+                std::unique_ptr<ASTBlock> body)
+    : INHERITED(position, kFunction_Kind)
+    , fReturnType(std::move(returnType))
+    , fName(std::move(name))
+    , fParameters(std::move(parameters))
+    , fBody(std::move(body)) {}
+
+    std::string description() const override {
+        std::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 std::unique_ptr<ASTType> fReturnType;
+    const std::string 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
new file mode 100644
index 0000000..d67f64d
--- /dev/null
+++ b/src/sksl/ast/SkSLASTIdentifier.h
@@ -0,0 +1,34 @@
+/*
+ * 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 "SkSLASTExpression.h"
+
+namespace SkSL {
+
+/**
+ * An identifier in an expression context. 
+ */
+struct ASTIdentifier : public ASTExpression {
+    ASTIdentifier(Position position, std::string text)
+    : INHERITED(position, kIdentifier_Kind)
+    , fText(std::move(text)) {}
+
+    std::string description() const override {
+        return fText;
+    }
+
+    const std::string fText;
+
+    typedef ASTExpression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTIfStatement.h b/src/sksl/ast/SkSLASTIfStatement.h
new file mode 100644
index 0000000..06f663d
--- /dev/null
+++ b/src/sksl/ast/SkSLASTIfStatement.h
@@ -0,0 +1,47 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * An 'if' statement. 
+ */
+struct ASTIfStatement : public ASTStatement {
+    ASTIfStatement(Position position, std::unique_ptr<ASTExpression> test, 
+                   std::unique_ptr<ASTStatement> ifTrue, std::unique_ptr<ASTStatement> ifFalse)
+    : INHERITED(position, kIf_Kind)
+    , fTest(std::move(test))
+    , fIfTrue(std::move(ifTrue))
+    , fIfFalse(std::move(ifFalse)) {}
+
+    std::string description() const override {
+        std::string result("if (");
+        result += fTest->description();
+        result += ") ";
+        result += fIfTrue->description();
+        if (fIfFalse) {
+            result += " else ";
+            result += fIfFalse->description();
+        }
+        return result;        
+    }
+
+    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
new file mode 100644
index 0000000..44d91fa
--- /dev/null
+++ b/src/sksl/ast/SkSLASTIndexSuffix.h
@@ -0,0 +1,35 @@
+/*
+ * 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 "SkSLASTExpression.h"
+#include "SkSLASTSuffix.h"
+
+namespace SkSL {
+
+/**
+ * A bracketed expression, as in '[0]', indicating an array access. 
+ */
+struct ASTIndexSuffix : public ASTSuffix {
+    ASTIndexSuffix(std::unique_ptr<ASTExpression> expression) 
+    : INHERITED(expression->fPosition, ASTSuffix::kIndex_Kind)
+    , fExpression(std::move(expression)) {}
+
+    std::string description() const override {
+        return "[" + fExpression->description() + "]";
+    }
+
+    std::unique_ptr<ASTExpression> fExpression;
+
+    typedef ASTSuffix INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTIntLiteral.h b/src/sksl/ast/SkSLASTIntLiteral.h
new file mode 100644
index 0000000..2598847
--- /dev/null
+++ b/src/sksl/ast/SkSLASTIntLiteral.h
@@ -0,0 +1,35 @@
+/*
+ * 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 "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(Position position, uint64_t value)
+    : INHERITED(position, kInt_Kind)
+    , fValue(value) {}
+
+    std::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
new file mode 100644
index 0000000..f501b12
--- /dev/null
+++ b/src/sksl/ast/SkSLASTInterfaceBlock.h
@@ -0,0 +1,58 @@
+/*
+ * 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 "SkSLASTVarDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * An interface block, as in:
+ *
+ * out gl_PerVertex {
+ *   layout(builtin=0) vec4 gl_Position;
+ *   layout(builtin=1) float gl_PointSize;
+ * };
+ */
+struct ASTInterfaceBlock : public ASTDeclaration {
+    // valueName is empty when it was not present in the source
+    ASTInterfaceBlock(Position position,
+                      ASTModifiers modifiers, 
+                      std::string interfaceName, 
+                      std::string valueName, 
+                      std::vector<std::unique_ptr<ASTVarDeclaration>> declarations)
+    : INHERITED(position, kInterfaceBlock_Kind)
+    , fModifiers(modifiers)
+    , fInterfaceName(std::move(interfaceName))
+    , fValueName(std::move(valueName))
+    , fDeclarations(std::move(declarations)) {}
+
+    std::string description() const override {
+        std::string result = fModifiers.description() + fInterfaceName + " {\n";
+        for (size_t i = 0; i < fDeclarations.size(); i++) {
+            result += fDeclarations[i]->description() + "\n";
+        }
+        result += "}";
+        if (fValueName.length()) {
+            result += " " + fValueName;
+        }
+        return result + ";";
+    }
+
+    const ASTModifiers fModifiers;
+    const std::string fInterfaceName;
+    const std::string fValueName;
+    const std::vector<std::unique_ptr<ASTVarDeclaration>> fDeclarations;
+
+    typedef ASTDeclaration INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTLayout.h b/src/sksl/ast/SkSLASTLayout.h
new file mode 100644
index 0000000..487e6e9
--- /dev/null
+++ b/src/sksl/ast/SkSLASTLayout.h
@@ -0,0 +1,68 @@
+/*
+ * 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_ASTLAYOUT
+#define SKSL_ASTLAYOUT
+
+#include "SkSLASTNode.h"
+#include "SkSLUtil.h"
+
+namespace SkSL {
+
+/**
+ * Represents a layout block appearing before a variable declaration, as in:
+ *
+ * layout (location = 0) int x;
+ */
+struct ASTLayout : public ASTNode {
+    // For all parameters, a -1 means no value
+    ASTLayout(int location, int binding, int index, int set, int builtin)
+    : fLocation(location)
+    , fBinding(binding)
+    , fIndex(index)
+    , fSet(set)
+    , fBuiltin(builtin) {}
+
+    std::string description() const {
+        std::string result;
+        std::string separator;
+        if (fLocation >= 0) {
+            result += separator + "location = " + to_string(fLocation);
+            separator = ", ";
+        }
+        if (fBinding >= 0) {
+            result += separator + "binding = " + to_string(fBinding);
+            separator = ", ";
+        }
+        if (fIndex >= 0) {
+            result += separator + "index = " + to_string(fIndex);
+            separator = ", ";
+        }
+        if (fSet >= 0) {
+            result += separator + "set = " + to_string(fSet);
+            separator = ", ";
+        }
+        if (fBuiltin >= 0) {
+            result += separator + "builtin = " + to_string(fBuiltin);
+            separator = ", ";
+        }
+        if (result.length() > 0) {
+            result = "layout (" + result + ")";
+        }
+        return result;
+    }
+
+    const int fLocation;
+    const int fBinding;
+    const int fIndex;
+    const int fSet;
+    const int fBuiltin;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTModifiers.h b/src/sksl/ast/SkSLASTModifiers.h
new file mode 100644
index 0000000..6ef29aa
--- /dev/null
+++ b/src/sksl/ast/SkSLASTModifiers.h
@@ -0,0 +1,70 @@
+/*
+ * 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_ASTMODIFIERS
+#define SKSL_ASTMODIFIERS
+
+#include "SkSLASTLayout.h"
+#include "SkSLASTNode.h"
+
+namespace SkSL {
+
+/**
+ * A set of modifier keywords (in, out, uniform, etc.) appearing before a declaration. 
+ */
+struct ASTModifiers : public ASTNode {
+    enum Flag {
+        kNo_Flag      =  0,
+        kConst_Flag   =  1,
+        kIn_Flag      =  2,
+        kOut_Flag     =  4,
+        kLowp_Flag    =  8,
+        kMediump_Flag = 16,
+        kHighp_Flag   = 32,
+        kUniform_Flag = 64
+    };
+
+    ASTModifiers(ASTLayout layout, int flags)
+    : fLayout(layout)
+    , fFlags(flags) {}
+
+    std::string description() const override {
+        std::string result = fLayout.description();
+        if (fFlags & kUniform_Flag) {
+            result += "uniform ";
+        }
+        if (fFlags & kConst_Flag) {
+            result += "const ";
+        }
+        if (fFlags & kLowp_Flag) {
+            result += "lowp ";
+        }
+        if (fFlags & kMediump_Flag) {
+            result += "mediump ";
+        }
+        if (fFlags & kHighp_Flag) {
+            result += "highp ";
+        }
+
+        if ((fFlags & kIn_Flag) && (fFlags & kOut_Flag)) {
+            result += "inout ";
+        } else if (fFlags & kIn_Flag) {
+            result += "in ";
+        } else if (fFlags & kOut_Flag) {
+            result += "out ";
+        }
+
+        return result;
+    }
+
+    const ASTLayout fLayout;
+    const int fFlags;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTNode.h b/src/sksl/ast/SkSLASTNode.h
new file mode 100644
index 0000000..26be769
--- /dev/null
+++ b/src/sksl/ast/SkSLASTNode.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <memory>
+#include <string>
+
+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 std::string description() const = 0;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTParameter.h b/src/sksl/ast/SkSLASTParameter.h
new file mode 100644
index 0000000..8f1b453
--- /dev/null
+++ b/src/sksl/ast/SkSLASTParameter.h
@@ -0,0 +1,48 @@
+/*
+ * 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 "SkSLASTModifiers.h"
+#include "SkSLASTType.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(Position position, ASTModifiers modifiers, std::unique_ptr<ASTType> type, 
+                 std::string name, std::vector<int> sizes)
+    : INHERITED(position)
+    , fModifiers(modifiers)
+    , fType(std::move(type))
+    , fName(std::move(name))
+    , fSizes(std::move(sizes)) {}
+
+    std::string description() const override {
+        std::string result = fModifiers.description() + fType->description() + " " + fName;
+        for (int size : fSizes) {
+            result += "[" + to_string(size) + "]";
+        }
+        return result;
+    }
+
+    const ASTModifiers fModifiers;
+    const std::unique_ptr<ASTType> fType;
+    const std::string 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
new file mode 100644
index 0000000..226b4ae
--- /dev/null
+++ b/src/sksl/ast/SkSLASTPositionNode.h
@@ -0,0 +1,28 @@
+/*
+ * 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 "SkSLASTNode.h"
+#include "../SkSLPosition.h"
+
+namespace SkSL {
+
+/**
+ * An AST node with an associated position in the source.
+ */
+struct ASTPositionNode : public ASTNode {
+    ASTPositionNode(Position position)
+    : fPosition(position) {}
+
+    const Position fPosition;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTPrefixExpression.h b/src/sksl/ast/SkSLASTPrefixExpression.h
new file mode 100644
index 0000000..0d326e2
--- /dev/null
+++ b/src/sksl/ast/SkSLASTPrefixExpression.h
@@ -0,0 +1,37 @@
+/*
+ * 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 "SkSLASTExpression.h"
+#include "../SkSLToken.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.fPosition, kPrefix_Kind)
+    , fOperator(op.fKind)
+    , fOperand(std::move(operand)) {}
+
+    std::string description() const override {
+        return Token::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
new file mode 100644
index 0000000..3aac783
--- /dev/null
+++ b/src/sksl/ast/SkSLASTReturnStatement.h
@@ -0,0 +1,39 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'return' statement.
+ */
+struct ASTReturnStatement : public ASTStatement {
+    // expression may be null
+    ASTReturnStatement(Position position, std::unique_ptr<ASTExpression> expression)
+    : INHERITED(position, kReturn_Kind)
+    , fExpression(std::move(expression)) {}
+
+    std::string description() const override {
+        std::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/SkSLASTStatement.h b/src/sksl/ast/SkSLASTStatement.h
new file mode 100644
index 0000000..9ddde06
--- /dev/null
+++ b/src/sksl/ast/SkSLASTStatement.h
@@ -0,0 +1,46 @@
+/*
+ * 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 "SkSLASTPositionNode.h"
+#include "SkSLASTExpression.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,
+        kReturn_Kind,
+        kBreak_Kind,
+        kContinue_Kind,
+        kDiscard_Kind
+    };
+
+    ASTStatement(Position position, Kind kind)
+    : INHERITED(position)
+    , fKind(kind) {}
+
+    Kind fKind;
+
+    typedef ASTPositionNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTSuffix.h b/src/sksl/ast/SkSLASTSuffix.h
new file mode 100644
index 0000000..18f79f0
--- /dev/null
+++ b/src/sksl/ast/SkSLASTSuffix.h
@@ -0,0 +1,51 @@
+/*
+ * 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 "SkSLASTPositionNode.h"
+#include "SkSLASTExpression.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(Position position, Kind kind)
+    : INHERITED(position)
+    , fKind(kind) {}
+
+    std::string description() const override {
+        switch (fKind) {
+            case kPostIncrement_Kind:
+                return "++";
+            case kPostDecrement_Kind:
+                return "--";
+            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
new file mode 100644
index 0000000..c0fda29
--- /dev/null
+++ b/src/sksl/ast/SkSLASTSuffixExpression.h
@@ -0,0 +1,37 @@
+/*
+ * 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 "SkSLASTSuffix.h"
+#include "SkSLASTExpression.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->fPosition, kSuffix_Kind)
+    , fBase(std::move(base))
+    , fSuffix(std::move(suffix)) {}
+
+    std::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/SkSLASTTernaryExpression.h b/src/sksl/ast/SkSLASTTernaryExpression.h
new file mode 100644
index 0000000..20b827a
--- /dev/null
+++ b/src/sksl/ast/SkSLASTTernaryExpression.h
@@ -0,0 +1,41 @@
+/*
+ * 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 "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->fPosition, kTernary_Kind)
+    , fTest(std::move(test))
+    , fIfTrue(std::move(ifTrue))
+    , fIfFalse(std::move(ifFalse)) {}
+
+    std::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
new file mode 100644
index 0000000..b8fdedb
--- /dev/null
+++ b/src/sksl/ast/SkSLASTType.h
@@ -0,0 +1,40 @@
+/*
+ * 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
+
+namespace SkSL {
+
+/**
+ * A type, such as 'int' or 'struct foo'.
+ */
+struct ASTType : public ASTPositionNode {
+    enum Kind {
+        kIdentifier_Kind,
+        kStruct_Kind
+    };
+
+    ASTType(Position position, std::string name, Kind kind)
+    : INHERITED(position)
+    , fName(std::move(name))
+    , fKind(kind) {}
+
+    std::string description() const override {
+        return fName;
+    }
+
+    const std::string fName;
+
+    const Kind fKind;
+
+    typedef ASTPositionNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTVarDeclaration.h b/src/sksl/ast/SkSLASTVarDeclaration.h
new file mode 100644
index 0000000..613867e
--- /dev/null
+++ b/src/sksl/ast/SkSLASTVarDeclaration.h
@@ -0,0 +1,71 @@
+/*
+ * 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_ASTVARDECLARATION
+#define SKSL_ASTVARDECLARATION
+
+#include "SkSLASTDeclaration.h"
+#include "SkSLASTModifiers.h"
+#include "SkSLASTStatement.h"
+#include "SkSLASTType.h"
+#include "../SkSLUtil.h"
+
+namespace SkSL {
+
+/**
+ * A variable declaration, which may consist of multiple individual variables. For instance
+ * 'int x, y = 1, z[4][2]' is a single ASTVarDeclaration. This declaration would have a type of 
+ * 'int', names ['x', 'y', 'z'], sizes of [[], [], [4, 2]], and values of [null, 1, null].
+ */
+struct ASTVarDeclaration : public ASTDeclaration {
+    ASTVarDeclaration(ASTModifiers modifiers, 
+                      std::unique_ptr<ASTType> type, 
+                      std::vector<std::string> names, 
+                      std::vector<std::vector<std::unique_ptr<ASTExpression>>> sizes,
+                      std::vector<std::unique_ptr<ASTExpression>> values)
+    : INHERITED(type->fPosition, kVar_Kind)
+    , fModifiers(modifiers)
+    , fType(std::move(type))
+    , fNames(std::move(names))
+    , fSizes(std::move(sizes))
+    , fValues(std::move(values)) {
+        ASSERT(fNames.size() == fValues.size());
+    }
+
+    std::string description() const override {
+        std::string result = fModifiers.description() + fType->description() + " ";
+        std::string separator = "";
+        for (size_t i = 0; i < fNames.size(); i++) {
+            result += separator;
+            separator = ", ";
+            result += fNames[i];
+            for (size_t j = 0; j < fSizes[i].size(); j++) {
+                if (fSizes[i][j]) {
+                    result += "[" + fSizes[i][j]->description() + "]";
+                } else {
+                    result += "[]";
+                }
+            }
+            if (fValues[i]) {
+                result += " = " + fValues[i]->description();
+            }
+        }
+        return result;        
+    }
+
+    const ASTModifiers fModifiers;
+    const std::unique_ptr<ASTType> fType;
+    const std::vector<std::string> fNames;
+    const std::vector<std::vector<std::unique_ptr<ASTExpression>>> fSizes;
+    const std::vector<std::unique_ptr<ASTExpression>> fValues;
+
+    typedef ASTDeclaration INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTVarDeclarationStatement.h b/src/sksl/ast/SkSLASTVarDeclarationStatement.h
new file mode 100644
index 0000000..b647b6e
--- /dev/null
+++ b/src/sksl/ast/SkSLASTVarDeclarationStatement.h
@@ -0,0 +1,35 @@
+/*
+ * 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 "SkSLASTStatement.h"
+#include "SkSLASTVarDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * A variable declaration appearing as a statement within a function.
+ */
+struct ASTVarDeclarationStatement : public ASTStatement {
+    ASTVarDeclarationStatement(std::unique_ptr<ASTVarDeclaration> decl)
+    : INHERITED(decl->fPosition, kVarDeclaration_Kind)
+    , fDeclaration(std::move(decl)) {}
+
+    std::string description() const override {
+        return fDeclaration->description() + ";";
+    }
+
+    std::unique_ptr<ASTVarDeclaration> fDeclaration;
+
+    typedef ASTStatement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ast/SkSLASTWhileStatement.h b/src/sksl/ast/SkSLASTWhileStatement.h
new file mode 100644
index 0000000..e29aa23
--- /dev/null
+++ b/src/sksl/ast/SkSLASTWhileStatement.h
@@ -0,0 +1,37 @@
+/*
+ * 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 "SkSLASTStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'while' statement.
+ */
+struct ASTWhileStatement : public ASTStatement {
+    ASTWhileStatement(Position position, std::unique_ptr<ASTExpression> test, 
+                      std::unique_ptr<ASTStatement> statement)
+    : INHERITED(position, kWhile_Kind)
+    , fTest(std::move(test))
+    , fStatement(std::move(statement)) {}
+
+    std::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
new file mode 100644
index 0000000..bd89d6c
--- /dev/null
+++ b/src/sksl/ir/SkSLBinaryExpression.h
@@ -0,0 +1,41 @@
+/*
+ * 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_BINARYEXPRESSION
+#define SKSL_BINARYEXPRESSION
+
+#include "SkSLExpression.h"
+#include "../SkSLToken.h"
+
+namespace SkSL {
+
+/**
+ * A binary operation. 
+ */
+struct BinaryExpression : public Expression {
+    BinaryExpression(Position position, std::unique_ptr<Expression> left, Token::Kind op,
+                     std::unique_ptr<Expression> right, std::shared_ptr<Type> type)
+    : INHERITED(position, kBinary_Kind, type)
+    , fLeft(std::move(left))
+    , fOperator(op)
+    , fRight(std::move(right)) {}
+
+    virtual std::string description() const override {
+        return "(" + fLeft->description() + " " + Token::OperatorName(fOperator) + " " +
+               fRight->description() + ")";
+    }
+
+    const std::unique_ptr<Expression> fLeft;
+    const Token::Kind fOperator;
+    const std::unique_ptr<Expression> fRight;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLBlock.h b/src/sksl/ir/SkSLBlock.h
new file mode 100644
index 0000000..56ed77a
--- /dev/null
+++ b/src/sksl/ir/SkSLBlock.h
@@ -0,0 +1,40 @@
+/*
+ * 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_BLOCK
+#define SKSL_BLOCK
+
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A block of multiple statements functioning as a single statement.
+ */
+struct Block : public Statement {
+    Block(Position position, std::vector<std::unique_ptr<Statement>> statements)
+    : INHERITED(position, kBlock_Kind)
+    , fStatements(std::move(statements)) {}
+
+    std::string description() const override {
+        std::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<Statement>> fStatements;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLBoolLiteral.h b/src/sksl/ir/SkSLBoolLiteral.h
new file mode 100644
index 0000000..3c40e59
--- /dev/null
+++ b/src/sksl/ir/SkSLBoolLiteral.h
@@ -0,0 +1,38 @@
+/*
+ * 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_BOOLLITERAL
+#define SKSL_BOOLLITERAL
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * Represents 'true' or 'false'.
+ */
+struct BoolLiteral : public Expression {
+    BoolLiteral(Position position, bool value)
+    : INHERITED(position, kBoolLiteral_Kind, kBool_Type)
+    , fValue(value) {}
+
+    std::string description() const override {
+        return fValue ? "true" : "false";
+    }
+
+    bool isConstant() const override {
+    	return true;
+    }
+
+    const bool fValue;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLBreakStatement.h b/src/sksl/ir/SkSLBreakStatement.h
new file mode 100644
index 0000000..8aa17b0
--- /dev/null
+++ b/src/sksl/ir/SkSLBreakStatement.h
@@ -0,0 +1,32 @@
+/*
+ * 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_BREAKSTATEMENT
+#define SKSL_BREAKSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'break' statement. 
+ */
+struct BreakStatement : public Statement {
+    BreakStatement(Position position)
+    : INHERITED(position, kBreak_Kind) {}
+
+    std::string description() const override {
+        return "break;";
+    }
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLConstructor.h b/src/sksl/ir/SkSLConstructor.h
new file mode 100644
index 0000000..c58da7e
--- /dev/null
+++ b/src/sksl/ir/SkSLConstructor.h
@@ -0,0 +1,52 @@
+/*
+ * 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_CONSTRUCTOR
+#define SKSL_CONSTRUCTOR
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * Represents the construction of a compound type, such as "vec2(x, y)".
+ */
+struct Constructor : public Expression {
+    Constructor(Position position, std::shared_ptr<Type> type, 
+                std::vector<std::unique_ptr<Expression>> arguments)
+    : INHERITED(position, kConstructor_Kind, std::move(type))
+    , fArguments(std::move(arguments)) {}
+
+    std::string description() const override {
+        std::string result = fType->description() + "(";
+        std::string separator = "";
+        for (size_t i = 0; i < fArguments.size(); i++) {
+            result += separator;
+            result += fArguments[i]->description();
+            separator = ", ";
+        }
+        result += ")";
+        return result;
+    }
+
+    bool isConstant() const override {
+        for (size_t i = 0; i < fArguments.size(); i++) {
+            if (!fArguments[i]->isConstant()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    const std::vector<std::unique_ptr<Expression>> fArguments;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLContinueStatement.h b/src/sksl/ir/SkSLContinueStatement.h
new file mode 100644
index 0000000..1951bd9
--- /dev/null
+++ b/src/sksl/ir/SkSLContinueStatement.h
@@ -0,0 +1,32 @@
+/*
+ * 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_CONTINUESTATEMENT
+#define SKSL_CONTINUESTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'continue' statement. 
+ */
+struct ContinueStatement : public Statement {
+    ContinueStatement(Position position)
+    : INHERITED(position, kContinue_Kind) {}
+
+    std::string description() const override {
+        return "continue;";
+    }
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLDiscardStatement.h b/src/sksl/ir/SkSLDiscardStatement.h
new file mode 100644
index 0000000..b39712e
--- /dev/null
+++ b/src/sksl/ir/SkSLDiscardStatement.h
@@ -0,0 +1,32 @@
+/*
+ * 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_DISCARDSTATEMENT
+#define SKSL_DISCARDSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'discard' statement. 
+ */
+struct DiscardStatement : public Statement {
+    DiscardStatement(Position position)
+    : INHERITED(position, kDiscard_Kind) {}
+
+    std::string description() const override {
+        return "discard;";
+    }
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLDoStatement.h b/src/sksl/ir/SkSLDoStatement.h
new file mode 100644
index 0000000..6012453
--- /dev/null
+++ b/src/sksl/ir/SkSLDoStatement.h
@@ -0,0 +1,38 @@
+/*
+ * 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_DOSTATEMENT
+#define SKSL_DOSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'do' statement.
+ */
+struct DoStatement : public Statement {
+    DoStatement(Position position, std::unique_ptr<Statement> statement,
+                std::unique_ptr<Expression> test)
+    : INHERITED(position, kDo_Kind)
+    , fStatement(std::move(statement))
+    , fTest(std::move(test)) {}
+
+    std::string description() const override {
+        return "do " + fStatement->description() + " while (" + fTest->description() + ");";
+    }
+
+    const std::unique_ptr<Statement> fStatement;
+    const std::unique_ptr<Expression> fTest;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLExpression.h b/src/sksl/ir/SkSLExpression.h
new file mode 100644
index 0000000..1e42c7a
--- /dev/null
+++ b/src/sksl/ir/SkSLExpression.h
@@ -0,0 +1,55 @@
+/*
+ * 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_EXPRESSION
+#define SKSL_EXPRESSION
+
+#include "SkSLIRNode.h"
+#include "SkSLType.h"
+
+namespace SkSL {
+
+/**
+ * Abstract supertype of all expressions. 
+ */
+struct Expression : public IRNode {
+    enum Kind {
+        kBinary_Kind,
+        kBoolLiteral_Kind,
+        kConstructor_Kind,
+        kIntLiteral_Kind,
+        kFieldAccess_Kind,
+        kFloatLiteral_Kind,
+        kFunctionReference_Kind,
+        kFunctionCall_Kind,
+        kIndex_Kind,
+        kPrefix_Kind,
+        kPostfix_Kind,
+        kSwizzle_Kind,
+        kVariableReference_Kind,
+        kTernary_Kind,
+        kTypeReference_Kind,
+    };
+
+    Expression(Position position, Kind kind, std::shared_ptr<Type> type)
+    : INHERITED(position)
+    , fKind(kind)
+    , fType(std::move(type)) {}
+
+    virtual bool isConstant() const {
+        return false;
+    }
+
+    const Kind fKind;
+    const std::shared_ptr<Type> fType;
+
+    typedef IRNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLExpressionStatement.h b/src/sksl/ir/SkSLExpressionStatement.h
new file mode 100644
index 0000000..e975ccf
--- /dev/null
+++ b/src/sksl/ir/SkSLExpressionStatement.h
@@ -0,0 +1,35 @@
+/*
+ * 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_EXPRESSIONSTATEMENT
+#define SKSL_EXPRESSIONSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A lone expression being used as a statement. 
+ */
+struct ExpressionStatement : public Statement {
+    ExpressionStatement(std::unique_ptr<Expression> expression)
+    : INHERITED(expression->fPosition, kExpression_Kind)
+    , fExpression(std::move(expression)) {}
+
+    std::string description() const override {
+        return fExpression->description() + ";";
+    }
+
+    const std::unique_ptr<Expression> fExpression;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLExtension.h b/src/sksl/ir/SkSLExtension.h
new file mode 100644
index 0000000..d7f83fa
--- /dev/null
+++ b/src/sksl/ir/SkSLExtension.h
@@ -0,0 +1,34 @@
+/*
+ * 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_EXTENSION
+#define SKSL_EXTENSION
+
+#include "SkSLProgramElement.h"
+
+namespace SkSL {
+
+/** 
+ * An extension declaration. 
+ */
+struct Extension : public ProgramElement {
+    Extension(Position position, std::string name)
+    : INHERITED(position, kExtension_Kind) 
+    , fName(std::move(name)) {}
+
+    std::string description() const override {
+        return "#extension " + fName + " : enable";
+    }
+
+    const std::string fName;
+
+    typedef ProgramElement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLField.h b/src/sksl/ir/SkSLField.h
new file mode 100644
index 0000000..f2b68bc
--- /dev/null
+++ b/src/sksl/ir/SkSLField.h
@@ -0,0 +1,41 @@
+/*
+ * 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_FIELD
+#define SKSL_FIELD
+
+#include "SkSLModifiers.h"
+#include "SkSLPosition.h"
+#include "SkSLSymbol.h"
+#include "SkSLType.h"
+
+namespace SkSL {
+
+/** 
+ * A symbol which should be interpreted as a field access. Fields are added to the symboltable 
+ * whenever a bare reference to an identifier should refer to a struct field; in GLSL, this is the 
+ * result of declaring anonymous interface blocks.
+ */
+struct Field : public Symbol {
+    Field(Position position, std::shared_ptr<Variable> owner, int fieldIndex)
+    : INHERITED(position, kField_Kind, owner->fType->fields()[fieldIndex].fName)
+    , fOwner(owner)
+    , fFieldIndex(fieldIndex) {}
+
+    virtual std::string description() const override {
+        return fOwner->description() + "." + fOwner->fType->fields()[fFieldIndex].fName;
+    }
+
+    const std::shared_ptr<Variable> fOwner;
+    const int fFieldIndex;
+
+    typedef Symbol INHERITED;
+};
+
+} // namespace SkSL
+
+#endif
diff --git a/src/sksl/ir/SkSLFieldAccess.h b/src/sksl/ir/SkSLFieldAccess.h
new file mode 100644
index 0000000..053498e
--- /dev/null
+++ b/src/sksl/ir/SkSLFieldAccess.h
@@ -0,0 +1,37 @@
+/*
+ * 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_FIELDACCESS
+#define SKSL_FIELDACCESS
+
+#include "SkSLExpression.h"
+#include "SkSLUtil.h"
+
+namespace SkSL {
+
+/**
+ * An expression which extracts a field from a struct, as in 'foo.bar'.
+ */
+struct FieldAccess : public Expression {
+    FieldAccess(std::unique_ptr<Expression> base, int fieldIndex)
+    : INHERITED(base->fPosition, kFieldAccess_Kind, base->fType->fields()[fieldIndex].fType)
+    , fBase(std::move(base))
+    , fFieldIndex(fieldIndex) {}
+
+    virtual std::string description() const override {
+        return fBase->description() + "." + fBase->fType->fields()[fFieldIndex].fName;
+    }
+
+    const std::unique_ptr<Expression> fBase;
+    const int fFieldIndex;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLFloatLiteral.h b/src/sksl/ir/SkSLFloatLiteral.h
new file mode 100644
index 0000000..deb5b27
--- /dev/null
+++ b/src/sksl/ir/SkSLFloatLiteral.h
@@ -0,0 +1,38 @@
+/*
+ * 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_FLOATLITERAL
+#define SKSL_FLOATLITERAL
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * A literal floating point number.
+ */
+struct FloatLiteral : public Expression {
+    FloatLiteral(Position position, double value)
+    : INHERITED(position, kFloatLiteral_Kind, kFloat_Type)
+    , fValue(value) {}
+
+    virtual std::string description() const override {
+        return to_string(fValue);
+    }
+
+    bool isConstant() const override {
+    	return true;
+    }
+
+    const double fValue;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLForStatement.h b/src/sksl/ir/SkSLForStatement.h
new file mode 100644
index 0000000..70bb401
--- /dev/null
+++ b/src/sksl/ir/SkSLForStatement.h
@@ -0,0 +1,56 @@
+/*
+ * 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_FORSTATEMENT
+#define SKSL_FORSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'for' statement.
+ */
+struct ForStatement : public Statement {
+    ForStatement(Position position, std::unique_ptr<Statement> initializer, 
+                 std::unique_ptr<Expression> test, std::unique_ptr<Expression> next, 
+                 std::unique_ptr<Statement> statement)
+    : INHERITED(position, kFor_Kind)
+    , fInitializer(std::move(initializer))
+    , fTest(std::move(test))
+    , fNext(std::move(next))
+    , fStatement(std::move(statement)) {}
+
+    std::string description() const override {
+        std::string result = "for (";
+        if (fInitializer) {
+            result += fInitializer->description();
+        } 
+        result += " ";
+        if (fTest) {
+            result += fTest->description();
+        } 
+        result += "; ";
+        if (fNext) {
+            result += fNext->description();
+        }
+        result += ") " + fStatement->description();
+        return result;
+    }
+
+    const std::unique_ptr<Statement> fInitializer;
+    const std::unique_ptr<Expression> fTest;
+    const std::unique_ptr<Expression> fNext;
+    const std::unique_ptr<Statement> fStatement;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLFunctionCall.h b/src/sksl/ir/SkSLFunctionCall.h
new file mode 100644
index 0000000..78d2566
--- /dev/null
+++ b/src/sksl/ir/SkSLFunctionCall.h
@@ -0,0 +1,46 @@
+/*
+ * 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_FUNCTIONCALL
+#define SKSL_FUNCTIONCALL
+
+#include "SkSLExpression.h"
+#include "SkSLFunctionDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * A function invocation.
+ */
+struct FunctionCall : public Expression {
+    FunctionCall(Position position, std::shared_ptr<FunctionDeclaration> function,
+                 std::vector<std::unique_ptr<Expression>> arguments)
+    : INHERITED(position, kFunctionCall_Kind, function->fReturnType)
+    , fFunction(std::move(function))
+    , fArguments(std::move(arguments)) {}
+
+    std::string description() const override {
+        std::string result = fFunction->fName + "(";
+        std::string separator = "";
+        for (size_t i = 0; i < fArguments.size(); i++) {
+            result += separator;
+            result += fArguments[i]->description();
+            separator = ", ";
+        }
+        result += ")";
+        return result;
+    }
+
+    const std::shared_ptr<FunctionDeclaration> fFunction;
+    const std::vector<std::unique_ptr<Expression>> fArguments;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.h b/src/sksl/ir/SkSLFunctionDeclaration.h
new file mode 100644
index 0000000..32c23f5
--- /dev/null
+++ b/src/sksl/ir/SkSLFunctionDeclaration.h
@@ -0,0 +1,55 @@
+/*
+ * 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_FUNCTIONDECLARATION
+#define SKSL_FUNCTIONDECLARATION
+
+#include "SkSLModifiers.h"
+#include "SkSLSymbol.h"
+#include "SkSLType.h"
+#include "SkSLVariable.h"
+
+namespace SkSL {
+
+/**
+ * A function declaration (not a definition -- does not contain a body).
+ */
+struct FunctionDeclaration : public Symbol {
+    FunctionDeclaration(Position position, std::string name, 
+                        std::vector<std::shared_ptr<Variable>> parameters, 
+                        std::shared_ptr<Type> returnType)
+    : INHERITED(position, kFunctionDeclaration_Kind, std::move(name))
+    , fDefined(false)
+    , fParameters(parameters)
+    , fReturnType(returnType) {}
+
+    std::string description() const override {
+        std::string result = fReturnType->description() + " " + fName + "(";
+        std::string separator = "";
+        for (auto p : fParameters) {
+            result += separator;
+            separator = ", ";
+            result += p->description();
+        }
+        result += ")";
+        return result;
+    }
+
+    bool matches(FunctionDeclaration& f) {
+        return fName == f.fName && fParameters == f.fParameters;
+    }
+
+    mutable bool fDefined;
+    const std::vector<std::shared_ptr<Variable>> fParameters;
+    const std::shared_ptr<Type> fReturnType;
+
+    typedef Symbol INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLFunctionDefinition.h b/src/sksl/ir/SkSLFunctionDefinition.h
new file mode 100644
index 0000000..fceb547
--- /dev/null
+++ b/src/sksl/ir/SkSLFunctionDefinition.h
@@ -0,0 +1,39 @@
+/*
+ * 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_FUNCTIONDEFINITION
+#define SKSL_FUNCTIONDEFINITION
+
+#include "SkSLBlock.h"
+#include "SkSLFunctionDeclaration.h"
+#include "SkSLProgramElement.h"
+
+namespace SkSL {
+
+/**
+ * A function definition (a declaration plus an associated block of code).
+ */
+struct FunctionDefinition : public ProgramElement {
+    FunctionDefinition(Position position, std::shared_ptr<FunctionDeclaration> declaration,
+                       std::unique_ptr<Block> body)
+    : INHERITED(position, kFunction_Kind)
+    , fDeclaration(std::move(declaration))
+    , fBody(std::move(body)) {}
+
+    std::string description() const override {
+        return fDeclaration->description() + " " + fBody->description();
+    }
+
+    const std::shared_ptr<FunctionDeclaration> fDeclaration;
+    const std::unique_ptr<Block> fBody;
+
+    typedef ProgramElement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLFunctionReference.h b/src/sksl/ir/SkSLFunctionReference.h
new file mode 100644
index 0000000..d5cc444
--- /dev/null
+++ b/src/sksl/ir/SkSLFunctionReference.h
@@ -0,0 +1,36 @@
+/*
+ * 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_FUNCTIONREFERENCE
+#define SKSL_FUNCTIONREFERENCE
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * An identifier referring to a function name. This is an intermediate value: FunctionReferences are 
+ * always eventually replaced by FunctionCalls in valid programs.
+ */
+struct FunctionReference : public Expression {
+    FunctionReference(Position position, std::vector<std::shared_ptr<FunctionDeclaration>> function)
+    : INHERITED(position, kFunctionReference_Kind, kInvalid_Type)
+    , fFunctions(function) {}
+
+    virtual std::string description() const override {
+    	ASSERT(false);
+    	return "<function>";
+    }
+
+    const std::vector<std::shared_ptr<FunctionDeclaration>> fFunctions;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLIRNode.h b/src/sksl/ir/SkSLIRNode.h
new file mode 100644
index 0000000..8c433cf
--- /dev/null
+++ b/src/sksl/ir/SkSLIRNode.h
@@ -0,0 +1,32 @@
+/*
+ * 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_IRNODE
+#define SKSL_IRNODE
+
+#include "../SkSLPosition.h"
+
+namespace SkSL {
+
+/**
+ * Represents a node in the intermediate representation (IR) tree. The IR is a fully-resolved 
+ * version of the program (all types determined, everything validated), ready for code generation.
+ */
+struct IRNode {
+    IRNode(Position position)
+    : fPosition(position) {}
+
+    virtual ~IRNode() {}
+
+    virtual std::string description() const = 0;
+
+    const Position fPosition;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLIfStatement.h b/src/sksl/ir/SkSLIfStatement.h
new file mode 100644
index 0000000..8ab5c00
--- /dev/null
+++ b/src/sksl/ir/SkSLIfStatement.h
@@ -0,0 +1,44 @@
+/*
+ * 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_IFSTATEMENT
+#define SKSL_IFSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * An 'if' statement.
+ */
+struct IfStatement : public Statement {
+    IfStatement(Position position, std::unique_ptr<Expression> test, 
+                std::unique_ptr<Statement> ifTrue, std::unique_ptr<Statement> ifFalse)
+    : INHERITED(position, kIf_Kind)
+    , fTest(std::move(test))
+    , fIfTrue(std::move(ifTrue))
+    , fIfFalse(std::move(ifFalse)) {}
+
+    std::string description() const override {
+        std::string result = "if (" + fTest->description() + ") " + fIfTrue->description();
+        if (fIfFalse) {
+            result += " else " + fIfFalse->description();
+        }
+        return result;
+    }
+
+    const std::unique_ptr<Expression> fTest;
+    const std::unique_ptr<Statement> fIfTrue;
+    const std::unique_ptr<Statement> fIfFalse;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLIndexExpression.h b/src/sksl/ir/SkSLIndexExpression.h
new file mode 100644
index 0000000..538c656
--- /dev/null
+++ b/src/sksl/ir/SkSLIndexExpression.h
@@ -0,0 +1,64 @@
+/*
+ * 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_INDEX
+#define SKSL_INDEX
+
+#include "SkSLExpression.h"
+#include "SkSLUtil.h"
+
+namespace SkSL {
+
+/**
+ * Given a type, returns the type that will result from extracting an array value from it.
+ */
+static std::shared_ptr<Type> index_type(const Type& type) {
+    if (type.kind() == Type::kMatrix_Kind) {
+        if (type.componentType() == kFloat_Type) {
+            switch (type.columns()) {
+                case 2: return kVec2_Type;
+                case 3: return kVec3_Type;
+                case 4: return kVec4_Type;
+                default: ASSERT(false);
+            }
+        } else {
+            ASSERT(type.componentType() == kDouble_Type);
+            switch (type.columns()) {
+                case 2: return kDVec2_Type;
+                case 3: return kDVec3_Type;
+                case 4: return kDVec4_Type;
+                default: ASSERT(false);
+            }
+        }
+    }
+    return type.componentType();
+}
+
+/**
+ * An expression which extracts a value from an array or matrix, as in 'm[2]'.
+ */
+struct IndexExpression : public Expression {
+    IndexExpression(std::unique_ptr<Expression> base, std::unique_ptr<Expression> index)
+    : INHERITED(base->fPosition, kIndex_Kind, index_type(*base->fType))
+    , fBase(std::move(base))
+    , fIndex(std::move(index)) {
+        ASSERT(fIndex->fType == kInt_Type);
+    }
+
+    std::string description() const override {
+        return fBase->description() + "[" + fIndex->description() + "]";
+    }
+
+    const std::unique_ptr<Expression> fBase;
+    const std::unique_ptr<Expression> fIndex;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLIntLiteral.h b/src/sksl/ir/SkSLIntLiteral.h
new file mode 100644
index 0000000..80b30d7
--- /dev/null
+++ b/src/sksl/ir/SkSLIntLiteral.h
@@ -0,0 +1,40 @@
+/*
+ * 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_INTLITERAL
+#define SKSL_INTLITERAL
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * A literal integer.
+ */
+struct IntLiteral : public Expression {
+    // FIXME: we will need to revisit this if/when we add full support for both signed and unsigned
+    // 64-bit integers, but for right now an int64_t will hold every value we care about
+    IntLiteral(Position position, int64_t value)
+    : INHERITED(position, kIntLiteral_Kind, kInt_Type)
+    , fValue(value) {}
+
+    virtual std::string description() const override {
+        return to_string(fValue);
+    }
+
+   bool isConstant() const override {
+    	return true;
+    }
+
+    const int64_t fValue;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLInterfaceBlock.h b/src/sksl/ir/SkSLInterfaceBlock.h
new file mode 100644
index 0000000..baedb58
--- /dev/null
+++ b/src/sksl/ir/SkSLInterfaceBlock.h
@@ -0,0 +1,49 @@
+/*
+ * 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_INTERFACEBLOCK
+#define SKSL_INTERFACEBLOCK
+
+#include "SkSLProgramElement.h"
+#include "SkSLVarDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * An interface block, as in:
+ *
+ * out gl_PerVertex {
+ *   layout(builtin=0) vec4 gl_Position;
+ *   layout(builtin=1) float gl_PointSize;
+ * };
+ *
+ * At the IR level, this is represented by a single variable of struct type.
+ */
+struct InterfaceBlock : public ProgramElement {
+    InterfaceBlock(Position position, std::shared_ptr<Variable> var)
+    : INHERITED(position, kInterfaceBlock_Kind) 
+    , fVariable(std::move(var)) {
+        ASSERT(fVariable->fType->kind() == Type::kStruct_Kind);
+    }
+
+    std::string description() const override {
+        std::string result = fVariable->fModifiers.description() + fVariable->fName + " {\n";
+        for (size_t i = 0; i < fVariable->fType->fields().size(); i++) {
+            result += fVariable->fType->fields()[i].description() + "\n";
+        }
+        result += "};";
+        return result;
+    }
+
+    const std::shared_ptr<Variable> fVariable;
+
+    typedef ProgramElement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLLayout.h b/src/sksl/ir/SkSLLayout.h
new file mode 100644
index 0000000..bab2f0e
--- /dev/null
+++ b/src/sksl/ir/SkSLLayout.h
@@ -0,0 +1,83 @@
+/*
+ * 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_LAYOUT
+#define SKSL_LAYOUT
+
+namespace SkSL {
+
+/**
+ * Represents a layout block appearing before a variable declaration, as in:
+ *
+ * layout (location = 0) int x;
+ */
+struct Layout {
+    Layout(const ASTLayout& layout)
+    : fLocation(layout.fLocation)
+    , fBinding(layout.fBinding)
+    , fIndex(layout.fIndex)
+    , fSet(layout.fSet)
+    , fBuiltin(layout.fBuiltin) {}
+
+    Layout(int location, int binding, int index, int set, int builtin)
+    : fLocation(location)
+    , fBinding(binding)
+    , fIndex(index)
+    , fSet(set)
+    , fBuiltin(builtin) {}
+
+    std::string description() const {
+        std::string result;
+        std::string separator;
+        if (fLocation >= 0) {
+            result += separator + "location = " + to_string(fLocation);
+            separator = ", ";
+        }
+        if (fBinding >= 0) {
+            result += separator + "binding = " + to_string(fBinding);
+            separator = ", ";
+        }
+        if (fIndex >= 0) {
+            result += separator + "index = " + to_string(fIndex);
+            separator = ", ";
+        }
+        if (fSet >= 0) {
+            result += separator + "set = " + to_string(fSet);
+            separator = ", ";
+        }
+        if (fBuiltin >= 0) {
+            result += separator + "builtin = " + to_string(fBuiltin);
+            separator = ", ";
+        }
+        if (result.length() > 0) {
+            result = "layout (" + result + ")";
+        }
+        return result;
+    }
+
+    bool operator==(const Layout& other) const {
+        return fLocation == other.fLocation &&
+               fBinding  == other.fBinding &&
+               fIndex    == other.fIndex &&
+               fSet      == other.fSet &&
+               fBuiltin  == other.fBuiltin;
+    }
+
+    bool operator!=(const Layout& other) const {
+        return !(*this == other);
+    }
+
+    const int fLocation;
+    const int fBinding;
+    const int fIndex;
+    const int fSet;
+    const int fBuiltin;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLModifiers.h b/src/sksl/ir/SkSLModifiers.h
new file mode 100644
index 0000000..d3b9c40
--- /dev/null
+++ b/src/sksl/ir/SkSLModifiers.h
@@ -0,0 +1,82 @@
+/*
+ * 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_MODIFIERS
+#define SKSL_MODIFIERS
+
+#include "../ast/SkSLASTModifiers.h"
+#include "SkSLLayout.h"
+
+namespace SkSL {
+
+/**
+ * A set of modifier keywords (in, out, uniform, etc.) appearing before a declaration. 
+ */
+struct Modifiers {
+    enum Flag {
+        kNo_Flag      = ASTModifiers::kNo_Flag,
+        kConst_Flag   = ASTModifiers::kConst_Flag,
+        kIn_Flag      = ASTModifiers::kIn_Flag,
+        kOut_Flag     = ASTModifiers::kOut_Flag,
+        kLowp_Flag    = ASTModifiers::kLowp_Flag,
+        kMediump_Flag = ASTModifiers::kMediump_Flag,
+        kHighp_Flag   = ASTModifiers::kHighp_Flag,
+        kUniform_Flag = ASTModifiers::kUniform_Flag
+    };
+
+    Modifiers(const ASTModifiers& modifiers)
+    : fLayout(modifiers.fLayout)
+    , fFlags(modifiers.fFlags) {}
+
+    Modifiers(Layout& layout, int flags)
+    : fLayout(layout)
+    , fFlags(flags) {}
+
+    std::string description() const {
+        std::string result = fLayout.description();
+        if (fFlags & kUniform_Flag) {
+            result += "uniform ";
+        }
+        if (fFlags & kConst_Flag) {
+            result += "const ";
+        }
+        if (fFlags & kLowp_Flag) {
+            result += "lowp ";
+        }
+        if (fFlags & kMediump_Flag) {
+            result += "mediump ";
+        }
+        if (fFlags & kHighp_Flag) {
+            result += "highp ";
+        }
+
+        if ((fFlags & kIn_Flag) && (fFlags & kOut_Flag)) {
+            result += "inout ";
+        } else if (fFlags & kIn_Flag) {
+            result += "in ";
+        } else if (fFlags & kOut_Flag) {
+            result += "out ";
+        }
+
+        return result;
+    }
+
+    bool operator==(const Modifiers& other) const {
+        return fLayout == other.fLayout && fFlags == other.fFlags;
+    }
+
+    bool operator!=(const Modifiers& other) const {
+        return !(*this == other);
+    }
+
+    const Layout fLayout;
+    const int fFlags;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLPostfixExpression.h b/src/sksl/ir/SkSLPostfixExpression.h
new file mode 100644
index 0000000..de146ac
--- /dev/null
+++ b/src/sksl/ir/SkSLPostfixExpression.h
@@ -0,0 +1,36 @@
+/*
+ * 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_POSTFIXEXPRESSION
+#define SKSL_POSTFIXEXPRESSION
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * An expression modified by a unary operator appearing after it, such as 'i++'.
+ */
+struct PostfixExpression : public Expression {
+    PostfixExpression(std::unique_ptr<Expression> operand, Token::Kind op)
+    : INHERITED(operand->fPosition, kPostfix_Kind, operand->fType)
+    , fOperand(std::move(operand))
+    , fOperator(op) {}
+
+    virtual std::string description() const override {
+        return fOperand->description() + Token::OperatorName(fOperator);
+    }
+
+    const std::unique_ptr<Expression> fOperand;
+    const Token::Kind fOperator;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLPrefixExpression.h b/src/sksl/ir/SkSLPrefixExpression.h
new file mode 100644
index 0000000..53c3849
--- /dev/null
+++ b/src/sksl/ir/SkSLPrefixExpression.h
@@ -0,0 +1,36 @@
+/*
+ * 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_PREFIXEXPRESSION
+#define SKSL_PREFIXEXPRESSION
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * An expression modified by a unary operator appearing before it, such as '!flag'.
+ */
+struct PrefixExpression : public Expression {
+    PrefixExpression(Token::Kind op, std::unique_ptr<Expression> operand)
+    : INHERITED(operand->fPosition, kPrefix_Kind, operand->fType)
+    , fOperand(std::move(operand))
+    , fOperator(op) {}
+
+    virtual std::string description() const override {
+        return Token::OperatorName(fOperator) + fOperand->description();
+    }
+
+    const std::unique_ptr<Expression> fOperand;
+    const Token::Kind fOperator;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLProgram.h b/src/sksl/ir/SkSLProgram.h
new file mode 100644
index 0000000..5edcfde
--- /dev/null
+++ b/src/sksl/ir/SkSLProgram.h
@@ -0,0 +1,38 @@
+/*
+ * 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_PROGRAM
+#define SKSL_PROGRAM
+
+#include <vector>
+#include <memory>
+
+#include "SkSLProgramElement.h"
+
+namespace SkSL {
+
+/**
+ * Represents a fully-digested program, ready for code generation.
+ */
+struct Program {
+    enum Kind {
+        kFragment_Kind,
+        kVertex_Kind
+    };
+
+    Program(Kind kind, std::vector<std::unique_ptr<ProgramElement>> elements)
+    : fKind(kind) 
+    , fElements(std::move(elements)) {}
+
+    Kind fKind;
+
+    std::vector<std::unique_ptr<ProgramElement>> fElements;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLProgramElement.h b/src/sksl/ir/SkSLProgramElement.h
new file mode 100644
index 0000000..44fc340
--- /dev/null
+++ b/src/sksl/ir/SkSLProgramElement.h
@@ -0,0 +1,37 @@
+/*
+ * 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_PROGRAMELEMENT
+#define SKSL_PROGRAMELEMENT
+
+#include "SkSLIRNode.h"
+
+namespace SkSL {
+
+/**
+ * Represents a top-level element (e.g. function or global variable) in a program.
+ */
+struct ProgramElement : public IRNode {
+    enum Kind {
+        kVar_Kind,
+        kFunction_Kind,
+        kInterfaceBlock_Kind,
+        kExtension_Kind
+    };
+
+    ProgramElement(Position position, Kind kind)
+    : INHERITED(position)
+    , fKind(kind) {}
+
+    Kind fKind;
+
+    typedef IRNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLReturnStatement.h b/src/sksl/ir/SkSLReturnStatement.h
new file mode 100644
index 0000000..ec2226c
--- /dev/null
+++ b/src/sksl/ir/SkSLReturnStatement.h
@@ -0,0 +1,42 @@
+/*
+ * 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_RETURNSTATEMENT
+#define SKSL_RETURNSTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'return' statement.
+ */
+struct ReturnStatement : public Statement {
+    ReturnStatement(Position position)
+    : INHERITED(position, kReturn_Kind) {}
+
+    ReturnStatement(std::unique_ptr<Expression> expression)
+    : INHERITED(expression->fPosition, kReturn_Kind) 
+    , fExpression(std::move(expression)) {}
+
+    std::string description() const override {
+        if (fExpression) {
+            return "return " + fExpression->description() + ";";
+        } else {
+            return "return;";
+        }
+    }
+
+    const std::unique_ptr<Expression> fExpression;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLStatement.h b/src/sksl/ir/SkSLStatement.h
new file mode 100644
index 0000000..64b7bdf
--- /dev/null
+++ b/src/sksl/ir/SkSLStatement.h
@@ -0,0 +1,45 @@
+/*
+ * 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_STATEMENT
+#define SKSL_STATEMENT
+
+#include "SkSLIRNode.h"
+#include "SkSLType.h"
+
+namespace SkSL {
+
+/**
+ * Abstract supertype of all statements.
+ */
+struct Statement : public IRNode {
+    enum Kind {
+        kBlock_Kind,
+        kBreak_Kind,
+        kContinue_Kind,
+        kDiscard_Kind,
+        kDo_Kind,
+        kExpression_Kind,
+        kFor_Kind,
+        kIf_Kind,
+        kReturn_Kind,
+        kVarDeclaration_Kind,
+        kWhile_Kind
+    };
+
+    Statement(Position position, Kind kind)
+    : INHERITED(position)
+    , fKind(kind) {}
+
+    const Kind fKind;
+
+    typedef IRNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLSwizzle.h b/src/sksl/ir/SkSLSwizzle.h
new file mode 100644
index 0000000..ce360d1
--- /dev/null
+++ b/src/sksl/ir/SkSLSwizzle.h
@@ -0,0 +1,88 @@
+/*
+ * 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_SWIZZLE
+#define SKSL_SWIZZLE
+
+#include "SkSLExpression.h"
+#include "SkSLUtil.h"
+
+namespace SkSL {
+
+/**
+ * Given a type and a swizzle component count, returns the type that will result from swizzling. For 
+ * instance, swizzling a vec3 with two components will result in a vec2. It is possible to swizzle
+ * with more components than the source vector, as in 'vec2(1).xxxx'.
+ */
+static std::shared_ptr<Type> get_type(Expression& value, 
+                                      size_t count) {
+    std::shared_ptr<Type> base = value.fType->componentType();
+    if (count == 1) {
+        return base;
+    }
+    if (base == kFloat_Type) {
+        switch (count) {
+            case 2: return kVec2_Type;
+            case 3: return kVec3_Type;
+            case 4: return kVec4_Type;
+        }
+    } else if (base == kDouble_Type) {
+        switch (count) {
+            case 2: return kDVec2_Type;
+            case 3: return kDVec3_Type;
+            case 4: return kDVec4_Type;
+        }
+    } else if (base == kInt_Type) {
+        switch (count) {
+            case 2: return kIVec2_Type;
+            case 3: return kIVec3_Type;
+            case 4: return kIVec4_Type;
+        }
+    } else if (base == kUInt_Type) {
+        switch (count) {
+            case 2: return kUVec2_Type;
+            case 3: return kUVec3_Type;
+            case 4: return kUVec4_Type;
+        }
+    } else if (base == kBool_Type) {
+        switch (count) {
+            case 2: return kBVec2_Type;
+            case 3: return kBVec3_Type;
+            case 4: return kBVec4_Type;
+        }
+    }
+    ABORT("cannot swizzle %s\n", value.description().c_str());
+}
+
+/**
+ * Represents a vector swizzle operation such as 'vec2(1, 2, 3).zyx'.
+ */
+struct Swizzle : public Expression {
+    Swizzle(std::unique_ptr<Expression> base, std::vector<int> components)
+    : INHERITED(base->fPosition, kSwizzle_Kind, get_type(*base, components.size()))
+    , fBase(std::move(base))
+    , fComponents(std::move(components)) {
+        ASSERT(fComponents.size() >= 1 && fComponents.size() <= 4);
+    }
+
+    std::string description() const override {
+        std::string result = fBase->description() + ".";
+        for (int x : fComponents) {
+            result += "xyzw"[x];
+        }
+        return result;
+    }
+
+    const std::unique_ptr<Expression> fBase;
+    const std::vector<int> fComponents;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLSymbol.h b/src/sksl/ir/SkSLSymbol.h
new file mode 100644
index 0000000..d736516
--- /dev/null
+++ b/src/sksl/ir/SkSLSymbol.h
@@ -0,0 +1,40 @@
+/*
+ * 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_SYMBOL
+#define SKSL_SYMBOL
+
+#include "SkSLIRNode.h"
+
+namespace SkSL {
+
+/**
+ * Represents a symboltable entry.
+ */
+struct Symbol : public IRNode {
+    enum Kind {
+        kFunctionDeclaration_Kind,
+        kUnresolvedFunction_Kind,
+        kType_Kind,
+        kVariable_Kind,
+        kField_Kind
+    };
+
+    Symbol(Position position, Kind kind, std::string name)
+    : INHERITED(position)
+    , fKind(kind)
+    , fName(std::move(name)) {}
+
+    const Kind fKind;
+    const std::string fName;
+
+    typedef IRNode INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLSymbolTable.cpp b/src/sksl/ir/SkSLSymbolTable.cpp
new file mode 100644
index 0000000..af83f7a
--- /dev/null
+++ b/src/sksl/ir/SkSLSymbolTable.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+ #include "SkSLSymbolTable.h"
+
+namespace SkSL {
+
+std::vector<std::shared_ptr<FunctionDeclaration>> SymbolTable::GetFunctions(
+                                                                 const std::shared_ptr<Symbol>& s) {
+    switch (s->fKind) {
+        case Symbol::kFunctionDeclaration_Kind:
+            return { std::static_pointer_cast<FunctionDeclaration>(s) };
+        case Symbol::kUnresolvedFunction_Kind:
+            return ((UnresolvedFunction&) *s).fFunctions;
+        default:
+            return { };
+    }
+}
+
+std::shared_ptr<Symbol> SymbolTable::operator[](const std::string& name) {
+    const auto& entry = fSymbols.find(name);
+    if (entry == fSymbols.end()) {
+        if (fParent) {
+            return (*fParent)[name];
+        }
+        return nullptr;
+    }
+    if (fParent) {
+        auto functions = GetFunctions(entry->second);
+        if (functions.size() > 0) {
+            bool modified = false;
+            std::shared_ptr<Symbol> previous = (*fParent)[name];
+            if (previous) {
+                auto previousFunctions = GetFunctions(previous);
+                for (const std::shared_ptr<FunctionDeclaration>& prev : previousFunctions) {
+                    bool found = false;
+                    for (const std::shared_ptr<FunctionDeclaration>& current : functions) {
+                        if (current->matches(*prev)) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        functions.push_back(prev);
+                        modified = true;
+                    }
+                }
+                if (modified) {
+                    ASSERT(functions.size() > 1);
+                    return std::shared_ptr<Symbol>(new UnresolvedFunction(functions));
+                }
+            }
+        }
+    }
+    return entry->second;
+}
+
+void SymbolTable::add(const std::string& name, std::shared_ptr<Symbol> symbol) {
+        const auto& existing = fSymbols.find(name);
+        if (existing == fSymbols.end()) {
+            fSymbols[name] = symbol;
+        } else if (symbol->fKind == Symbol::kFunctionDeclaration_Kind) {
+            const std::shared_ptr<Symbol>& oldSymbol = existing->second;
+            if (oldSymbol->fKind == Symbol::kFunctionDeclaration_Kind) {
+                std::vector<std::shared_ptr<FunctionDeclaration>> functions;
+                functions.push_back(std::static_pointer_cast<FunctionDeclaration>(oldSymbol));
+                functions.push_back(std::static_pointer_cast<FunctionDeclaration>(symbol));
+                fSymbols[name].reset(new UnresolvedFunction(std::move(functions)));
+            } else if (oldSymbol->fKind == Symbol::kUnresolvedFunction_Kind) {
+                std::vector<std::shared_ptr<FunctionDeclaration>> functions;
+                for (const auto& f : ((UnresolvedFunction&) *oldSymbol).fFunctions) {
+                    functions.push_back(f);
+                }
+                functions.push_back(std::static_pointer_cast<FunctionDeclaration>(symbol));
+                fSymbols[name].reset(new UnresolvedFunction(std::move(functions)));
+            }
+        } else {
+            fErrorReporter.error(symbol->fPosition, "symbol '" + name + "' was already defined");
+        }
+    }
+} // namespace
diff --git a/src/sksl/ir/SkSLSymbolTable.h b/src/sksl/ir/SkSLSymbolTable.h
new file mode 100644
index 0000000..151475d
--- /dev/null
+++ b/src/sksl/ir/SkSLSymbolTable.h
@@ -0,0 +1,49 @@
+/*
+ * 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_SYMBOLTABLE
+#define SKSL_SYMBOLTABLE
+
+#include <memory>
+#include <unordered_map>
+#include "SkSLErrorReporter.h"
+#include "SkSLSymbol.h"
+#include "SkSLUnresolvedFunction.h"
+
+namespace SkSL {
+
+/**
+ * Maps identifiers to symbols. Functions, in particular, are mapped to either FunctionDeclaration
+ * or UnresolvedFunction depending on whether they are overloaded or not.
+ */
+class SymbolTable {
+public:
+    SymbolTable(ErrorReporter& errorReporter)
+    : fErrorReporter(errorReporter) {}
+
+    SymbolTable(std::shared_ptr<SymbolTable> parent, ErrorReporter& errorReporter)
+    : fParent(parent)
+    , fErrorReporter(errorReporter) {}
+
+    std::shared_ptr<Symbol> operator[](const std::string& name);
+
+    void add(const std::string& name, std::shared_ptr<Symbol> symbol);
+
+    const std::shared_ptr<SymbolTable> fParent;
+
+private:
+    static std::vector<std::shared_ptr<FunctionDeclaration>> GetFunctions(
+                                                                  const std::shared_ptr<Symbol>& s);
+
+    std::unordered_map<std::string, std::shared_ptr<Symbol>> fSymbols;
+
+    ErrorReporter& fErrorReporter;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLTernaryExpression.h b/src/sksl/ir/SkSLTernaryExpression.h
new file mode 100644
index 0000000..bfaf304
--- /dev/null
+++ b/src/sksl/ir/SkSLTernaryExpression.h
@@ -0,0 +1,43 @@
+/*
+ * 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_TERNARYEXPRESSION
+#define SKSL_TERNARYEXPRESSION
+
+#include "SkSLExpression.h"
+#include "../SkSLPosition.h"
+
+namespace SkSL {
+
+/**
+ * A ternary expression (test ? ifTrue : ifFalse).
+ */
+struct TernaryExpression : public Expression {
+    TernaryExpression(Position position, std::unique_ptr<Expression> test,
+                      std::unique_ptr<Expression> ifTrue, std::unique_ptr<Expression> ifFalse)
+    : INHERITED(position, kTernary_Kind, ifTrue->fType)
+    , fTest(std::move(test))
+    , fIfTrue(std::move(ifTrue))
+    , fIfFalse(std::move(ifFalse)) {
+        ASSERT(fIfTrue->fType == fIfFalse->fType);
+    }
+
+    std::string description() const override {
+        return "(" + fTest->description() + " ? " + fIfTrue->description() + " : " + 
+               fIfFalse->description() + ")";
+    }
+
+    const std::unique_ptr<Expression> fTest;
+    const std::unique_ptr<Expression> fIfTrue;
+    const std::unique_ptr<Expression> fIfFalse;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLType.cpp b/src/sksl/ir/SkSLType.cpp
new file mode 100644
index 0000000..27cbd39
--- /dev/null
+++ b/src/sksl/ir/SkSLType.cpp
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+ 
+#include "SkSLType.h"
+
+namespace SkSL {
+
+bool Type::determineCoercionCost(std::shared_ptr<Type> other, int* outCost) const {
+    if (this == other.get()) {
+        *outCost = 0;
+        return true;
+    }
+    if (this->kind() == kVector_Kind && other->kind() == kVector_Kind) {
+        if (this->columns() == other->columns()) {
+            return this->componentType()->determineCoercionCost(other->componentType(), outCost);
+        }
+        return false;
+    }
+    if (this->kind() == kMatrix_Kind) {
+        if (this->columns() == other->columns() && 
+            this->rows() == other->rows()) {
+            return this->componentType()->determineCoercionCost(other->componentType(), outCost);
+        }
+        return false;
+    }
+    for (size_t i = 0; i < fCoercibleTypes.size(); i++) {
+        if (fCoercibleTypes[i] == other) {
+            *outCost = (int) i + 1;
+            return true;
+        }
+    }
+    return false;
+}
+
+std::shared_ptr<Type> Type::toCompound(int columns, int rows) {
+    ASSERT(this->kind() == Type::kScalar_Kind);
+    if (columns == 1 && rows == 1) {
+        return std::shared_ptr<Type>(this);
+    }
+    if (*this == *kFloat_Type) {
+        switch (rows) {
+            case 1:
+                switch (columns) {
+                    case 2: return kVec2_Type;
+                    case 3: return kVec3_Type;
+                    case 4: return kVec4_Type;
+                    default: ABORT("unsupported vector column count (%d)", columns);
+                }
+            case 2:
+                switch (columns) {
+                    case 2: return kMat2x2_Type;
+                    case 3: return kMat3x2_Type;
+                    case 4: return kMat4x2_Type;
+                    default: ABORT("unsupported matrix column count (%d)", columns);
+                }
+            case 3:
+                switch (columns) {
+                    case 2: return kMat2x3_Type;
+                    case 3: return kMat3x3_Type;
+                    case 4: return kMat4x3_Type;
+                    default: ABORT("unsupported matrix column count (%d)", columns);
+                }
+            case 4:
+                switch (columns) {
+                    case 2: return kMat2x4_Type;
+                    case 3: return kMat3x4_Type;
+                    case 4: return kMat4x4_Type;
+                    default: ABORT("unsupported matrix column count (%d)", columns);
+                }
+            default: ABORT("unsupported row count (%d)", rows);
+        }
+    } else if (*this == *kDouble_Type) {
+        switch (rows) {
+            case 1:
+                switch (columns) {
+                    case 2: return kDVec2_Type;
+                    case 3: return kDVec3_Type;
+                    case 4: return kDVec4_Type;
+                    default: ABORT("unsupported vector column count (%d)", columns);
+                }
+            case 2:
+                switch (columns) {
+                    case 2: return kDMat2x2_Type;
+                    case 3: return kDMat3x2_Type;
+                    case 4: return kDMat4x2_Type;
+                    default: ABORT("unsupported matrix column count (%d)", columns);
+                }
+            case 3:
+                switch (columns) {
+                    case 2: return kDMat2x3_Type;
+                    case 3: return kDMat3x3_Type;
+                    case 4: return kDMat4x3_Type;
+                    default: ABORT("unsupported matrix column count (%d)", columns);
+                }
+            case 4:
+                switch (columns) {
+                    case 2: return kDMat2x4_Type;
+                    case 3: return kDMat3x4_Type;
+                    case 4: return kDMat4x4_Type;
+                    default: ABORT("unsupported matrix column count (%d)", columns);
+                }
+            default: ABORT("unsupported row count (%d)", rows);
+        }
+    } else if (*this == *kInt_Type) {
+        switch (rows) {
+            case 1:
+                switch (columns) {
+                    case 2: return kIVec2_Type;
+                    case 3: return kIVec3_Type;
+                    case 4: return kIVec4_Type;
+                    default: ABORT("unsupported vector column count (%d)", columns);
+                }
+            default: ABORT("unsupported row count (%d)", rows);
+        }
+    } else if (*this == *kUInt_Type) {
+        switch (rows) {
+            case 1:
+                switch (columns) {
+                    case 2: return kUVec2_Type;
+                    case 3: return kUVec3_Type;
+                    case 4: return kUVec4_Type;
+                    default: ABORT("unsupported vector column count (%d)", columns);
+                }
+            default: ABORT("unsupported row count (%d)", rows);
+        }
+    }
+    ABORT("unsupported scalar_to_compound type %s", this->description().c_str());
+}
+
+const std::shared_ptr<Type> kVoid_Type(new Type("void"));
+
+const std::shared_ptr<Type> kDouble_Type(new Type("double", true));
+const std::shared_ptr<Type> kDVec2_Type(new Type("dvec2", kDouble_Type, 2));
+const std::shared_ptr<Type> kDVec3_Type(new Type("dvec3", kDouble_Type, 3));
+const std::shared_ptr<Type> kDVec4_Type(new Type("dvec4", kDouble_Type, 4));
+
+const std::shared_ptr<Type> kFloat_Type(new Type("float", true, { kDouble_Type }));
+const std::shared_ptr<Type> kVec2_Type(new Type("vec2", kFloat_Type, 2));
+const std::shared_ptr<Type> kVec3_Type(new Type("vec3", kFloat_Type, 3));
+const std::shared_ptr<Type> kVec4_Type(new Type("vec4", kFloat_Type, 4));
+
+const std::shared_ptr<Type> kUInt_Type(new Type("uint", true, { kFloat_Type, kDouble_Type }));
+const std::shared_ptr<Type> kUVec2_Type(new Type("uvec2", kUInt_Type, 2));
+const std::shared_ptr<Type> kUVec3_Type(new Type("uvec3", kUInt_Type, 3));
+const std::shared_ptr<Type> kUVec4_Type(new Type("uvec4", kUInt_Type, 4));
+
+const std::shared_ptr<Type> kInt_Type(new Type("int", true, { kUInt_Type, kFloat_Type, 
+                                                              kDouble_Type }));
+const std::shared_ptr<Type> kIVec2_Type(new Type("ivec2", kInt_Type, 2));
+const std::shared_ptr<Type> kIVec3_Type(new Type("ivec3", kInt_Type, 3));
+const std::shared_ptr<Type> kIVec4_Type(new Type("ivec4", kInt_Type, 4));
+
+const std::shared_ptr<Type> kBool_Type(new Type("bool", false));
+const std::shared_ptr<Type> kBVec2_Type(new Type("bvec2", kBool_Type, 2));
+const std::shared_ptr<Type> kBVec3_Type(new Type("bvec3", kBool_Type, 3));
+const std::shared_ptr<Type> kBVec4_Type(new Type("bvec4", kBool_Type, 4));
+
+const std::shared_ptr<Type> kMat2x2_Type(new Type("mat2",   kFloat_Type, 2, 2));
+const std::shared_ptr<Type> kMat2x3_Type(new Type("mat2x3", kFloat_Type, 2, 3));
+const std::shared_ptr<Type> kMat2x4_Type(new Type("mat2x4", kFloat_Type, 2, 4));
+const std::shared_ptr<Type> kMat3x2_Type(new Type("mat3x2", kFloat_Type, 3, 2));
+const std::shared_ptr<Type> kMat3x3_Type(new Type("mat3",   kFloat_Type, 3, 3));
+const std::shared_ptr<Type> kMat3x4_Type(new Type("mat3x4", kFloat_Type, 3, 4));
+const std::shared_ptr<Type> kMat4x2_Type(new Type("mat4x2", kFloat_Type, 4, 2));
+const std::shared_ptr<Type> kMat4x3_Type(new Type("mat4x3", kFloat_Type, 4, 3));
+const std::shared_ptr<Type> kMat4x4_Type(new Type("mat4",   kFloat_Type, 4, 4));
+
+const std::shared_ptr<Type> kDMat2x2_Type(new Type("dmat2",   kFloat_Type, 2, 2));
+const std::shared_ptr<Type> kDMat2x3_Type(new Type("dmat2x3", kFloat_Type, 2, 3));
+const std::shared_ptr<Type> kDMat2x4_Type(new Type("dmat2x4", kFloat_Type, 2, 4));
+const std::shared_ptr<Type> kDMat3x2_Type(new Type("dmat3x2", kFloat_Type, 3, 2));
+const std::shared_ptr<Type> kDMat3x3_Type(new Type("dmat3",   kFloat_Type, 3, 3));
+const std::shared_ptr<Type> kDMat3x4_Type(new Type("dmat3x4", kFloat_Type, 3, 4));
+const std::shared_ptr<Type> kDMat4x2_Type(new Type("dmat4x2", kFloat_Type, 4, 2));
+const std::shared_ptr<Type> kDMat4x3_Type(new Type("dmat4x3", kFloat_Type, 4, 3));
+const std::shared_ptr<Type> kDMat4x4_Type(new Type("dmat4",   kFloat_Type, 4, 4));
+
+const std::shared_ptr<Type> kSampler1D_Type(new Type("sampler1D", SpvDim1D, false, false, false, true));
+const std::shared_ptr<Type> kSampler2D_Type(new Type("sampler2D", SpvDim2D, false, false, false, true));
+const std::shared_ptr<Type> kSampler3D_Type(new Type("sampler3D", SpvDim3D, false, false, false, true));
+const std::shared_ptr<Type> kSamplerCube_Type(new Type("samplerCube"));
+const std::shared_ptr<Type> kSampler2DRect_Type(new Type("sampler2DRect"));
+const std::shared_ptr<Type> kSampler1DArray_Type(new Type("sampler1DArray"));
+const std::shared_ptr<Type> kSampler2DArray_Type(new Type("sampler2DArray"));
+const std::shared_ptr<Type> kSamplerCubeArray_Type(new Type("samplerCubeArray"));
+const std::shared_ptr<Type> kSamplerBuffer_Type(new Type("samplerBuffer"));
+const std::shared_ptr<Type> kSampler2DMS_Type(new Type("sampler2DMS"));
+const std::shared_ptr<Type> kSampler2DMSArray_Type(new Type("sampler2DMSArray"));
+const std::shared_ptr<Type> kSampler1DShadow_Type(new Type("sampler1DShadow"));
+const std::shared_ptr<Type> kSampler2DShadow_Type(new Type("sampler2DShadow"));
+const std::shared_ptr<Type> kSamplerCubeShadow_Type(new Type("samplerCubeShadow"));
+const std::shared_ptr<Type> kSampler2DRectShadow_Type(new Type("sampler2DRectShadow"));
+const std::shared_ptr<Type> kSampler1DArrayShadow_Type(new Type("sampler1DArrayShadow"));
+const std::shared_ptr<Type> kSampler2DArrayShadow_Type(new Type("sampler2DArrayShadow"));
+const std::shared_ptr<Type> kSamplerCubeArrayShadow_Type(new Type("samplerCubeArrayShadow"));
+
+static std::vector<std::shared_ptr<Type>> type(std::shared_ptr<Type> t) {
+    return { t, t, t, t };   
+}
+
+// FIXME figure out what we're supposed to do with the gsampler et al. types
+const std::shared_ptr<Type> kGSampler1D_Type(new Type("$gsampler1D", type(kSampler1D_Type)));
+const std::shared_ptr<Type> kGSampler2D_Type(new Type("$gsampler2D", type(kSampler2D_Type)));
+const std::shared_ptr<Type> kGSampler3D_Type(new Type("$gsampler3D", type(kSampler3D_Type)));
+const std::shared_ptr<Type> kGSamplerCube_Type(new Type("$gsamplerCube", type(kSamplerCube_Type)));
+const std::shared_ptr<Type> kGSampler2DRect_Type(new Type("$gsampler2DRect", 
+                                                 type(kSampler2DRect_Type)));
+const std::shared_ptr<Type> kGSampler1DArray_Type(new Type("$gsampler1DArray", 
+                                                  type(kSampler1DArray_Type)));
+const std::shared_ptr<Type> kGSampler2DArray_Type(new Type("$gsampler2DArray", 
+                                                  type(kSampler2DArray_Type)));
+const std::shared_ptr<Type> kGSamplerCubeArray_Type(new Type("$gsamplerCubeArray", 
+                                                    type(kSamplerCubeArray_Type)));
+const std::shared_ptr<Type> kGSamplerBuffer_Type(new Type("$gsamplerBuffer", 
+                                                 type(kSamplerBuffer_Type)));
+const std::shared_ptr<Type> kGSampler2DMS_Type(new Type("$gsampler2DMS", 
+                                               type(kSampler2DMS_Type)));
+const std::shared_ptr<Type> kGSampler2DMSArray_Type(new Type("$gsampler2DMSArray", 
+                                                    type(kSampler2DMSArray_Type)));
+const std::shared_ptr<Type> kGSampler2DArrayShadow_Type(new Type("$gsampler2DArrayShadow", 
+                                                        type(kSampler2DArrayShadow_Type)));
+const std::shared_ptr<Type> kGSamplerCubeArrayShadow_Type(new Type("$gsamplerCubeArrayShadow", 
+                                                          type(kSamplerCubeArrayShadow_Type)));
+
+const std::shared_ptr<Type> kGenType_Type(new Type("$genType", { kFloat_Type, kVec2_Type,
+                                                                 kVec3_Type, kVec4_Type }));
+const std::shared_ptr<Type> kGenDType_Type(new Type("$genDType", { kDouble_Type, kDVec2_Type,
+                                                                   kDVec3_Type, kDVec4_Type }));
+const std::shared_ptr<Type> kGenIType_Type(new Type("$genIType", { kInt_Type, kIVec2_Type,
+                                                                   kIVec3_Type, kIVec4_Type }));
+const std::shared_ptr<Type> kGenUType_Type(new Type("$genUType", { kUInt_Type, kUVec2_Type,
+                                                                   kUVec3_Type, kUVec4_Type }));
+const std::shared_ptr<Type> kGenBType_Type(new Type("$genBType", { kBool_Type, kBVec2_Type,
+                                                                   kBVec3_Type, kBVec4_Type }));
+
+const std::shared_ptr<Type> kMat_Type(new Type("$mat"));
+
+const std::shared_ptr<Type> kVec_Type(new Type("$vec", { kVec2_Type, kVec2_Type, kVec3_Type, 
+                                                         kVec4_Type }));
+
+const std::shared_ptr<Type> kGVec_Type(new Type("$gvec"));
+const std::shared_ptr<Type> kGVec2_Type(new Type("$gvec2"));
+const std::shared_ptr<Type> kGVec3_Type(new Type("$gvec3"));
+const std::shared_ptr<Type> kGVec4_Type(new Type("$gvec4", type(kVec4_Type)));
+const std::shared_ptr<Type> kDVec_Type(new Type("$dvec"));
+const std::shared_ptr<Type> kIVec_Type(new Type("$ivec"));
+const std::shared_ptr<Type> kUVec_Type(new Type("$uvec"));
+
+const std::shared_ptr<Type> kBVec_Type(new Type("$bvec", { kBVec2_Type, kBVec2_Type,
+                                                           kBVec3_Type, kBVec4_Type }));
+
+const std::shared_ptr<Type> kInvalid_Type(new Type("<INVALID>"));
+
+} // namespace
diff --git a/src/sksl/ir/SkSLType.h b/src/sksl/ir/SkSLType.h
new file mode 100644
index 0000000..e17bae6
--- /dev/null
+++ b/src/sksl/ir/SkSLType.h
@@ -0,0 +1,438 @@
+/*
+ * 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 SKIASL_TYPE
+#define SKIASL_TYPE
+
+#include "SkSLModifiers.h"
+#include "SkSLSymbol.h"
+#include "../SkSLPosition.h"
+#include "../SkSLUtil.h"
+#include "../spirv.h"
+#include <vector>
+#include <memory>
+
+namespace SkSL {
+
+/**
+ * Represents a type, such as int or vec4.
+ */
+class Type : public Symbol {
+public:
+    struct Field {
+        Field(Modifiers modifiers, std::string name, std::shared_ptr<Type> type)
+        : fModifiers(modifiers)
+        , fName(std::move(name))
+        , fType(std::move(type)) {}
+
+        const std::string description() {
+            return fType->description() + " " + fName + ";";
+        }
+
+        const Modifiers fModifiers;
+        const std::string fName;
+        const std::shared_ptr<Type> fType;
+    };
+
+    enum Kind {
+        kScalar_Kind,
+        kVector_Kind,
+        kMatrix_Kind,
+        kArray_Kind,
+        kStruct_Kind,
+        kGeneric_Kind,
+        kSampler_Kind,
+        kOther_Kind
+    };
+
+    // Create an "other" (special) type with the given name. These types cannot be directly 
+    // referenced from user code.
+    Type(std::string name)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kOther_Kind) {}
+
+    // Create a generic type which maps to the listed types.
+    Type(std::string name, std::vector<std::shared_ptr<Type>> types)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kGeneric_Kind)
+    , fCoercibleTypes(std::move(types)) {
+        ASSERT(fCoercibleTypes.size() == 4);
+    }
+
+    // Create a struct type with the given fields.
+    Type(std::string name, std::vector<Field> fields)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kStruct_Kind)
+    , fFields(std::move(fields)) {}
+
+    // Create a scalar type.
+    Type(std::string name, bool isNumber)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kScalar_Kind)
+    , fIsNumber(isNumber)
+    , fColumns(1)
+    , fRows(1) {}
+
+    // Create a scalar type which can be coerced to the listed types.
+    Type(std::string name, bool isNumber, std::vector<std::shared_ptr<Type>> coercibleTypes)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kScalar_Kind)
+    , fIsNumber(isNumber)
+    , fCoercibleTypes(std::move(coercibleTypes))
+    , fColumns(1)
+    , fRows(1) {}
+
+    // Create a vector type.
+    Type(std::string name, std::shared_ptr<Type> componentType, int columns)
+    : Type(name, kVector_Kind, componentType, columns) {}
+
+    // Create a vector or array type.
+    Type(std::string name, Kind kind, std::shared_ptr<Type> componentType, int columns)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kind)
+    , fComponentType(std::move(componentType))
+    , fColumns(columns)
+    , fRows(1)    
+    , fDimensions(SpvDim1D) {}
+
+    // Create a matrix type.
+    Type(std::string name, std::shared_ptr<Type> componentType, int columns, int rows)
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kMatrix_Kind)
+    , fComponentType(std::move(componentType))
+    , fColumns(columns)
+    , fRows(rows)    
+    , fDimensions(SpvDim1D) {}
+
+    // Create a sampler type.
+    Type(std::string name, SpvDim_ dimensions, bool isDepth, bool isArrayed, bool isMultisampled, 
+         bool isSampled) 
+    : INHERITED(Position(), kType_Kind, std::move(name))
+    , fTypeKind(kSampler_Kind)
+    , fDimensions(dimensions)
+    , fIsDepth(isDepth)
+    , fIsArrayed(isArrayed)
+    , fIsMultisampled(isMultisampled)
+    , fIsSampled(isSampled) {}
+
+    std::string name() const {
+        return fName;
+    }
+
+    std::string description() const override {
+        return fName;
+    }
+
+    bool operator==(const Type& other) const {
+        return fName == other.fName;
+    }
+
+    bool operator!=(const Type& other) const {
+        return fName != other.fName;
+    }
+
+    /**
+     * Returns the category (scalar, vector, matrix, etc.) of this type.
+     */
+    Kind kind() const {
+        return fTypeKind;
+    }
+
+    /**
+     * Returns true if this is a numeric scalar type.
+     */
+    bool isNumber() const {
+        return fIsNumber;
+    }
+
+    /**
+     * Returns true if an instance of this type can be freely coerced (implicitly converted) to 
+     * another type.
+     */
+    bool canCoerceTo(std::shared_ptr<Type> other) const {
+        int cost;
+        return determineCoercionCost(other, &cost);
+    }
+
+    /**
+     * Determines the "cost" of coercing (implicitly converting) this type to another type. The cost
+     * is a number with no particular meaning other than that lower costs are preferable to higher 
+     * costs. Returns true if a conversion is possible, false otherwise. The value of the out 
+     * parameter is undefined if false is returned.
+     */
+    bool determineCoercionCost(std::shared_ptr<Type> other, int* outCost) const;
+
+    /**
+     * For matrices and vectors, returns the type of individual cells (e.g. mat2 has a component
+     * type of kFloat_Type). For all other types, causes an assertion failure.
+     */
+    std::shared_ptr<Type> componentType() const {
+        ASSERT(fComponentType);
+        return fComponentType;
+    }
+
+    /**
+     * For matrices and vectors, returns the number of columns (e.g. both mat3 and vec3 return 3).
+     * For scalars, returns 1. For arrays, returns either the size of the array (if known) or -1. 
+     * For all other types, causes an assertion failure.
+     */
+    int columns() const {
+        ASSERT(fTypeKind == kScalar_Kind || fTypeKind == kVector_Kind || 
+               fTypeKind == kMatrix_Kind || fTypeKind == kArray_Kind);
+        return fColumns;
+    }
+
+    /**
+     * For matrices, returns the number of rows (e.g. mat2x4 returns 4). For vectors and scalars,
+     * returns 1. For all other types, causes an assertion failure.
+     */
+    int rows() const {
+        ASSERT(fRows > 0);
+        return fRows;
+    }
+
+    std::vector<Field> fields() const {
+        ASSERT(fTypeKind == kStruct_Kind);
+        return fFields;
+    }
+
+    /**
+     * For generic types, returns the types that this generic type can substitute for. For other
+     * types, returns a list of other types that this type can be coerced into.
+     */
+    std::vector<std::shared_ptr<Type>> coercibleTypes() const {
+        ASSERT(fCoercibleTypes.size() > 0);
+        return fCoercibleTypes;
+    }
+
+    int dimensions() const {
+        ASSERT(fTypeKind == kSampler_Kind);
+        return fDimensions;
+    }
+
+    bool isDepth() const {
+        ASSERT(fTypeKind == kSampler_Kind);
+        return fIsDepth;
+    }
+
+    bool isArrayed() const {
+        ASSERT(fTypeKind == kSampler_Kind);
+        return fIsArrayed;
+    }
+
+    bool isMultisampled() const {
+        ASSERT(fTypeKind == kSampler_Kind);
+        return fIsMultisampled;
+    }
+
+    bool isSampled() const {
+        ASSERT(fTypeKind == kSampler_Kind);
+        return fIsSampled;
+    }
+
+    static size_t vector_alignment(size_t componentSize, int columns) {
+        return componentSize * (columns + columns % 2);
+    }
+
+    /**
+     * Returns the type's required alignment (when putting this type into a struct, the offset must
+     * be a multiple of the alignment).
+     */
+    size_t alignment() const {
+        // See OpenGL Spec 7.6.2.2 Standard Uniform Block Layout
+        switch (fTypeKind) {
+            case kScalar_Kind:
+                return this->size();
+            case kVector_Kind:
+                return vector_alignment(fComponentType->size(), fColumns);
+            case kMatrix_Kind:
+                return (vector_alignment(fComponentType->size(), fRows) + 15) & ~15;
+            case kArray_Kind:
+                // round up to next multiple of 16
+                return (fComponentType->alignment() + 15) & ~15;
+            case kStruct_Kind: {
+                size_t result = 16;
+                for (size_t i = 0; i < fFields.size(); i++) {
+                    size_t alignment = fFields[i].fType->alignment();
+                    if (alignment > result) {
+                        result = alignment;
+                    }
+                }
+            }
+            default:
+                ABORT(("cannot determine size of type " + fName).c_str());
+        }
+    }
+
+    /**
+     * For matrices and arrays, returns the number of bytes from the start of one entry (row, in
+     * the case of matrices) to the start of the next.
+     */
+    size_t stride() const {
+        switch (fTypeKind) {
+            case kMatrix_Kind: // fall through
+            case kArray_Kind:
+                return this->alignment();
+            default:
+                ABORT("type does not have a stride");
+        }
+    }
+
+    /**
+     * Returns the size of this type in bytes.
+     */
+    size_t size() const {
+        switch (fTypeKind) {
+            case kScalar_Kind:
+                // FIXME need to take precision into account, once we figure out how we want to
+                // handle it...
+                return 4;
+            case kVector_Kind:
+                return fColumns * fComponentType->size();
+            case kMatrix_Kind:
+                return vector_alignment(fComponentType->size(), fRows) * fColumns;
+            case kArray_Kind:
+                return fColumns * this->stride();
+            case kStruct_Kind: {
+                size_t total = 0;
+                for (size_t i = 0; i < fFields.size(); i++) {
+                    size_t alignment = fFields[i].fType->alignment();
+                    if (total % alignment != 0) {
+                        total += alignment - total % alignment;
+                    }
+                    ASSERT(false);
+                    ASSERT(total % alignment == 0);
+                    total += fFields[i].fType->size();
+                }
+                return total;
+            }
+            default:
+                ABORT(("cannot determine size of type " + fName).c_str());
+        }
+    }
+
+    /**
+     * Returns the corresponding vector or matrix type with the specified number of columns and 
+     * rows.
+     */
+    std::shared_ptr<Type> toCompound(int columns, int rows);
+
+private:
+    typedef Symbol INHERITED;
+
+    const Kind fTypeKind;
+    const bool fIsNumber = false;
+    const std::shared_ptr<Type> fComponentType = nullptr;
+    const std::vector<std::shared_ptr<Type>> fCoercibleTypes = { };
+    const int fColumns = -1;
+    const int fRows = -1;
+    const std::vector<Field> fFields = { };
+    const SpvDim_ fDimensions = SpvDim1D;
+    const bool fIsDepth = false;
+    const bool fIsArrayed = false;
+    const bool fIsMultisampled = false;
+    const bool fIsSampled = false;
+};
+
+extern const std::shared_ptr<Type> kVoid_Type;
+
+extern const std::shared_ptr<Type> kFloat_Type;
+extern const std::shared_ptr<Type> kVec2_Type;
+extern const std::shared_ptr<Type> kVec3_Type;
+extern const std::shared_ptr<Type> kVec4_Type;
+extern const std::shared_ptr<Type> kDouble_Type;
+extern const std::shared_ptr<Type> kDVec2_Type;
+extern const std::shared_ptr<Type> kDVec3_Type;
+extern const std::shared_ptr<Type> kDVec4_Type;
+extern const std::shared_ptr<Type> kInt_Type;
+extern const std::shared_ptr<Type> kIVec2_Type;
+extern const std::shared_ptr<Type> kIVec3_Type;
+extern const std::shared_ptr<Type> kIVec4_Type;
+extern const std::shared_ptr<Type> kUInt_Type;
+extern const std::shared_ptr<Type> kUVec2_Type;
+extern const std::shared_ptr<Type> kUVec3_Type;
+extern const std::shared_ptr<Type> kUVec4_Type;
+extern const std::shared_ptr<Type> kBool_Type;
+extern const std::shared_ptr<Type> kBVec2_Type;
+extern const std::shared_ptr<Type> kBVec3_Type;
+extern const std::shared_ptr<Type> kBVec4_Type;
+
+extern const std::shared_ptr<Type> kMat2x2_Type;
+extern const std::shared_ptr<Type> kMat2x3_Type;
+extern const std::shared_ptr<Type> kMat2x4_Type;
+extern const std::shared_ptr<Type> kMat3x2_Type;
+extern const std::shared_ptr<Type> kMat3x3_Type;
+extern const std::shared_ptr<Type> kMat3x4_Type;
+extern const std::shared_ptr<Type> kMat4x2_Type;
+extern const std::shared_ptr<Type> kMat4x3_Type;
+extern const std::shared_ptr<Type> kMat4x4_Type;
+
+extern const std::shared_ptr<Type> kDMat2x2_Type;
+extern const std::shared_ptr<Type> kDMat2x3_Type;
+extern const std::shared_ptr<Type> kDMat2x4_Type;
+extern const std::shared_ptr<Type> kDMat3x2_Type;
+extern const std::shared_ptr<Type> kDMat3x3_Type;
+extern const std::shared_ptr<Type> kDMat3x4_Type;
+extern const std::shared_ptr<Type> kDMat4x2_Type;
+extern const std::shared_ptr<Type> kDMat4x3_Type;
+extern const std::shared_ptr<Type> kDMat4x4_Type;
+
+extern const std::shared_ptr<Type> kSampler1D_Type;
+extern const std::shared_ptr<Type> kSampler2D_Type;
+extern const std::shared_ptr<Type> kSampler3D_Type;
+extern const std::shared_ptr<Type> kSamplerCube_Type;
+extern const std::shared_ptr<Type> kSampler2DRect_Type;
+extern const std::shared_ptr<Type> kSampler1DArray_Type;
+extern const std::shared_ptr<Type> kSampler2DArray_Type;
+extern const std::shared_ptr<Type> kSamplerCubeArray_Type;
+extern const std::shared_ptr<Type> kSamplerBuffer_Type;
+extern const std::shared_ptr<Type> kSampler2DMS_Type;
+extern const std::shared_ptr<Type> kSampler2DMSArray_Type;
+
+extern const std::shared_ptr<Type> kGSampler1D_Type;
+extern const std::shared_ptr<Type> kGSampler2D_Type;
+extern const std::shared_ptr<Type> kGSampler3D_Type;
+extern const std::shared_ptr<Type> kGSamplerCube_Type;
+extern const std::shared_ptr<Type> kGSampler2DRect_Type;
+extern const std::shared_ptr<Type> kGSampler1DArray_Type;
+extern const std::shared_ptr<Type> kGSampler2DArray_Type;
+extern const std::shared_ptr<Type> kGSamplerCubeArray_Type;
+extern const std::shared_ptr<Type> kGSamplerBuffer_Type;
+extern const std::shared_ptr<Type> kGSampler2DMS_Type;
+extern const std::shared_ptr<Type> kGSampler2DMSArray_Type;
+
+extern const std::shared_ptr<Type> kSampler1DShadow_Type;
+extern const std::shared_ptr<Type> kSampler2DShadow_Type;
+extern const std::shared_ptr<Type> kSamplerCubeShadow_Type;
+extern const std::shared_ptr<Type> kSampler2DRectShadow_Type;
+extern const std::shared_ptr<Type> kSampler1DArrayShadow_Type;
+extern const std::shared_ptr<Type> kSampler2DArrayShadow_Type;
+extern const std::shared_ptr<Type> kSamplerCubeArrayShadow_Type;
+extern const std::shared_ptr<Type> kGSampler2DArrayShadow_Type;
+extern const std::shared_ptr<Type> kGSamplerCubeArrayShadow_Type;
+
+extern const std::shared_ptr<Type> kGenType_Type;
+extern const std::shared_ptr<Type> kGenDType_Type;
+extern const std::shared_ptr<Type> kGenIType_Type;
+extern const std::shared_ptr<Type> kGenUType_Type;
+extern const std::shared_ptr<Type> kGenBType_Type;
+extern const std::shared_ptr<Type> kMat_Type;
+extern const std::shared_ptr<Type> kVec_Type;
+extern const std::shared_ptr<Type> kGVec_Type;
+extern const std::shared_ptr<Type> kGVec2_Type;
+extern const std::shared_ptr<Type> kGVec3_Type;
+extern const std::shared_ptr<Type> kGVec4_Type;
+extern const std::shared_ptr<Type> kDVec_Type;
+extern const std::shared_ptr<Type> kIVec_Type;
+extern const std::shared_ptr<Type> kUVec_Type;
+extern const std::shared_ptr<Type> kBVec_Type;
+
+extern const std::shared_ptr<Type> kInvalid_Type;
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLTypeReference.h b/src/sksl/ir/SkSLTypeReference.h
new file mode 100644
index 0000000..5f4990f
--- /dev/null
+++ b/src/sksl/ir/SkSLTypeReference.h
@@ -0,0 +1,36 @@
+/*
+ * 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_TYPEREFERENCE
+#define SKSL_TYPEREFERENCE
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * Represents an identifier referring to a type. This is an intermediate value: TypeReferences are 
+ * always eventually replaced by Constructors in valid programs.
+ */
+struct TypeReference : public Expression {
+    TypeReference(Position position, std::shared_ptr<Type> type)
+    : INHERITED(position, kTypeReference_Kind, kInvalid_Type)
+    , fValue(std::move(type)) {}
+
+    std::string description() const override {
+    	ASSERT(false);
+    	return "<type>";
+    }
+
+    const std::shared_ptr<Type> fValue;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLUnresolvedFunction.h b/src/sksl/ir/SkSLUnresolvedFunction.h
new file mode 100644
index 0000000..a6cee0d
--- /dev/null
+++ b/src/sksl/ir/SkSLUnresolvedFunction.h
@@ -0,0 +1,38 @@
+/*
+ * 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_UNRESOLVEDFUNCTION
+#define SKSL_UNRESOLVEDFUNCTION
+
+#include "SkSLFunctionDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * A symbol representing multiple functions with the same name.
+ */
+struct UnresolvedFunction : public Symbol {
+    UnresolvedFunction(std::vector<std::shared_ptr<FunctionDeclaration>> funcs)
+    : INHERITED(Position(), kUnresolvedFunction_Kind, funcs[0]->fName)
+    , fFunctions(std::move(funcs)) {
+    	for (auto func : funcs) {
+    		ASSERT(func->fName == fName);
+    	}
+    }
+
+    virtual std::string description() const override {
+        return fName;
+    }
+
+    const std::vector<std::shared_ptr<FunctionDeclaration>> fFunctions;
+
+    typedef Symbol INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLVarDeclaration.h b/src/sksl/ir/SkSLVarDeclaration.h
new file mode 100644
index 0000000..400f430
--- /dev/null
+++ b/src/sksl/ir/SkSLVarDeclaration.h
@@ -0,0 +1,67 @@
+/*
+ * 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_VARDECLARATION
+#define SKSL_VARDECLARATION
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+#include "SkSLVariable.h"
+
+namespace SkSL {
+
+/**
+ * A variable declaration, which may consist of multiple individual variables. For instance
+ * 'int x, y = 1, z[4][2];' is a single VarDeclaration. This declaration would have a type of 'int', 
+ * names ['x', 'y', 'z'], sizes of [[], [], [4, 2]], and values of [null, 1, null].
+ */
+struct VarDeclaration : public ProgramElement {
+    VarDeclaration(Position position, std::vector<std::shared_ptr<Variable>> vars,
+                   std::vector<std::vector<std::unique_ptr<Expression>>> sizes,
+                   std::vector<std::unique_ptr<Expression>> values)
+    : INHERITED(position, kVar_Kind) 
+    , fVars(std::move(vars))
+    , fSizes(std::move(sizes))
+    , fValues(std::move(values)) {}
+
+    std::string description() const override {
+        std::string result = fVars[0]->fModifiers.description();
+        std::shared_ptr<Type> baseType = fVars[0]->fType;
+        while (baseType->kind() == Type::kArray_Kind) {
+            baseType = baseType->componentType();
+        }
+        result += baseType->description();
+        std::string separator = " ";
+        for (size_t i = 0; i < fVars.size(); i++) {
+            result += separator;
+            separator = ", ";
+            result += fVars[i]->fName;
+            for (size_t j = 0; j < fSizes[i].size(); j++) {
+                if (fSizes[i][j]) {
+                    result += "[" + fSizes[i][j]->description() + "]";
+                } else {
+                    result += "[]";
+                }
+            }
+            if (fValues[i]) {
+                result += " = " + fValues[i]->description();
+            }
+        }
+        result += ";";
+        return result;
+    }
+
+    const std::vector<std::shared_ptr<Variable>> fVars;
+    const std::vector<std::vector<std::unique_ptr<Expression>>> fSizes;
+    const std::vector<std::unique_ptr<Expression>> fValues;
+
+    typedef ProgramElement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLVarDeclarationStatement.h b/src/sksl/ir/SkSLVarDeclarationStatement.h
new file mode 100644
index 0000000..e81c0ac
--- /dev/null
+++ b/src/sksl/ir/SkSLVarDeclarationStatement.h
@@ -0,0 +1,35 @@
+/*
+ * 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_VARDECLARATIONSTATEMENT
+#define SKSL_VARDECLARATIONSTATEMENT
+
+#include "SkSLStatement.h"
+#include "SkSLVarDeclaration.h"
+
+namespace SkSL {
+
+/**
+ * A variable declaration appearing as a statement within a function.
+ */
+struct VarDeclarationStatement : public Statement {
+    VarDeclarationStatement(std::unique_ptr<VarDeclaration> decl)
+    : INHERITED(decl->fPosition, kVarDeclaration_Kind) 
+    , fDeclaration(std::move(decl)) {}
+
+    std::string description() const override {
+        return fDeclaration->description();
+    }
+
+    const std::shared_ptr<VarDeclaration> fDeclaration;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLVariable.h b/src/sksl/ir/SkSLVariable.h
new file mode 100644
index 0000000..d4ea2c4
--- /dev/null
+++ b/src/sksl/ir/SkSLVariable.h
@@ -0,0 +1,66 @@
+/*
+ * 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_VARIABLE
+#define SKSL_VARIABLE
+
+#include "SkSLModifiers.h"
+#include "SkSLPosition.h"
+#include "SkSLSymbol.h"
+#include "SkSLType.h"
+
+namespace SkSL {
+
+/**
+ * Represents a variable, whether local, global, or a function parameter. This represents the
+ * variable itself (the storage location), which is shared between all VariableReferences which
+ * read or write that storage location.
+ */
+struct Variable : public Symbol {
+    enum Storage {
+        kGlobal_Storage,
+        kLocal_Storage,
+        kParameter_Storage
+    };
+
+    Variable(Position position, Modifiers modifiers, std::string name, std::shared_ptr<Type> type,
+             Storage storage)
+    : INHERITED(position, kVariable_Kind, std::move(name))
+    , fModifiers(modifiers)
+    , fType(type)
+    , fStorage(storage)
+    , fIsReadFrom(false)
+    , fIsWrittenTo(false) {}
+
+    virtual std::string description() const override {
+        return fModifiers.description() + fType->fName + " " + fName;
+    }
+
+    const Modifiers fModifiers;
+    const std::string fValue;
+    const std::shared_ptr<Type> fType;
+    const Storage fStorage;
+
+    mutable bool fIsReadFrom;
+    mutable bool fIsWrittenTo;
+
+    typedef Symbol INHERITED;
+};
+
+} // namespace SkSL
+
+namespace std {
+    template <>
+        struct hash<SkSL::Variable> {
+        public :
+        size_t operator()(const SkSL::Variable &var) const{
+            return hash<std::string>()(var.fName) ^ hash<std::string>()(var.fType->description());
+        }
+    };
+} // namespace std
+
+#endif
diff --git a/src/sksl/ir/SkSLVariableReference.h b/src/sksl/ir/SkSLVariableReference.h
new file mode 100644
index 0000000..8499511
--- /dev/null
+++ b/src/sksl/ir/SkSLVariableReference.h
@@ -0,0 +1,38 @@
+/*
+ * 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_VARIABLEREFERENCE
+#define SKSL_VARIABLEREFERENCE
+
+#include "SkSLExpression.h"
+
+namespace SkSL {
+
+/**
+ * A reference to a variable, through which it can be read or written. In the statement:
+ *
+ * x = x + 1;
+ *
+ * there is only one Variable 'x', but two VariableReferences to it.
+ */
+struct VariableReference : public Expression {
+    VariableReference(Position position, std::shared_ptr<Variable> variable)
+    : INHERITED(position, kVariableReference_Kind, variable->fType)
+    , fVariable(std::move(variable)) {}
+
+    std::string description() const override {
+        return fVariable->fName;
+    }
+
+    const std::shared_ptr<Variable> fVariable;
+
+    typedef Expression INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/ir/SkSLWhileStatement.h b/src/sksl/ir/SkSLWhileStatement.h
new file mode 100644
index 0000000..1acb572
--- /dev/null
+++ b/src/sksl/ir/SkSLWhileStatement.h
@@ -0,0 +1,38 @@
+/*
+ * 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_WHILESTATEMENT
+#define SKSL_WHILESTATEMENT
+
+#include "SkSLExpression.h"
+#include "SkSLStatement.h"
+
+namespace SkSL {
+
+/**
+ * A 'while' loop.
+ */
+struct WhileStatement : public Statement {
+    WhileStatement(Position position, std::unique_ptr<Expression> test, 
+                   std::unique_ptr<Statement> statement)
+    : INHERITED(position, kWhile_Kind)
+    , fTest(std::move(test))
+    , fStatement(std::move(statement)) {}
+
+    std::string description() const override {
+        return "while (" + fTest->description() + ") " + fStatement->description();
+    }
+
+    const std::unique_ptr<Expression> fTest;
+    const std::unique_ptr<Statement> fStatement;
+
+    typedef Statement INHERITED;
+};
+
+} // namespace
+
+#endif
diff --git a/src/sksl/lex.sksl.c b/src/sksl/lex.sksl.c
new file mode 100644
index 0000000..7afbd94
--- /dev/null
+++ b/src/sksl/lex.sksl.c
@@ -0,0 +1,2473 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+ 
+#line 3 "lex.sksl.c"
+
+#define  YY_INT_ALIGNED short int
+
+/* A lexical scanner generated by flex */
+
+#define FLEX_SCANNER
+#define YY_FLEX_MAJOR_VERSION 2
+#define YY_FLEX_MINOR_VERSION 5
+#define YY_FLEX_SUBMINOR_VERSION 35
+#if YY_FLEX_SUBMINOR_VERSION > 0
+#define FLEX_BETA
+#endif
+
+/* First, we deal with  platform-specific or compiler-specific issues. */
+
+/* begin standard C headers. */
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+/* end standard C headers. */
+
+/* flex integer type definitions */
+
+#ifndef FLEXINT_H
+#define FLEXINT_H
+
+/* C99 systems have <inttypes.h>. Non-C99 systems may or may not. */
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+
+/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h,
+ * if you want the limit (max/min) macros for int types. 
+ */
+#ifndef __STDC_LIMIT_MACROS
+#define __STDC_LIMIT_MACROS 1
+#endif
+
+#include <inttypes.h>
+typedef int8_t flex_int8_t;
+typedef uint8_t flex_uint8_t;
+typedef int16_t flex_int16_t;
+typedef uint16_t flex_uint16_t;
+typedef int32_t flex_int32_t;
+typedef uint32_t flex_uint32_t;
+typedef uint64_t flex_uint64_t;
+#else
+typedef signed char flex_int8_t;
+typedef short int flex_int16_t;
+typedef int flex_int32_t;
+typedef unsigned char flex_uint8_t; 
+typedef unsigned short int flex_uint16_t;
+typedef unsigned int flex_uint32_t;
+#endif /* ! C99 */
+
+/* Limits of integral types. */
+#ifndef INT8_MIN
+#define INT8_MIN               (-128)
+#endif
+#ifndef INT16_MIN
+#define INT16_MIN              (-32767-1)
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN              (-2147483647-1)
+#endif
+#ifndef INT8_MAX
+#define INT8_MAX               (127)
+#endif
+#ifndef INT16_MAX
+#define INT16_MAX              (32767)
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX              (2147483647)
+#endif
+#ifndef UINT8_MAX
+#define UINT8_MAX              (255U)
+#endif
+#ifndef UINT16_MAX
+#define UINT16_MAX             (65535U)
+#endif
+#ifndef UINT32_MAX
+#define UINT32_MAX             (4294967295U)
+#endif
+
+#endif /* ! FLEXINT_H */
+
+#ifdef __cplusplus
+
+/* The "const" storage-class-modifier is valid. */
+#define YY_USE_CONST
+
+#else	/* ! __cplusplus */
+
+/* C99 requires __STDC__ to be defined as 1. */
+#if defined (__STDC__)
+
+#define YY_USE_CONST
+
+#endif	/* defined (__STDC__) */
+#endif	/* ! __cplusplus */
+
+#ifdef YY_USE_CONST
+#define yyconst const
+#else
+#define yyconst
+#endif
+
+/* Returned upon end-of-file. */
+#define YY_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* An opaque pointer. */
+#ifndef YY_TYPEDEF_YY_SCANNER_T
+#define YY_TYPEDEF_YY_SCANNER_T
+typedef void* yyscan_t;
+#endif
+
+/* For convenience, these vars (plus the bison vars far below)
+   are macros in the reentrant scanner. */
+#define yyin yyg->yyin_r
+#define yyout yyg->yyout_r
+#define yyextra yyg->yyextra_r
+#define yyleng yyg->yyleng_r
+#define yytext yyg->yytext_r
+#define yylineno (YY_CURRENT_BUFFER_LVALUE->yy_bs_lineno)
+#define yycolumn (YY_CURRENT_BUFFER_LVALUE->yy_bs_column)
+#define yy_flex_debug yyg->yy_flex_debug_r
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN yyg->yy_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The YYSTATE alias is for lex
+ * compatibility.
+ */
+#define YY_START ((yyg->yy_start - 1) / 2)
+#define YYSTATE YY_START
+
+/* Action number for EOF rule of a given start state. */
+#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define YY_NEW_FILE skslrestart(yyin ,yyscanner )
+
+#define YY_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#ifndef YY_BUF_SIZE
+#define YY_BUF_SIZE 16384
+#endif
+
+/* The state buf must be large enough to hold one state per character in the main buffer.
+ */
+#define YY_STATE_BUF_SIZE   ((YY_BUF_SIZE + 2) * sizeof(yy_state_type))
+
+#ifndef YY_TYPEDEF_YY_BUFFER_STATE
+#define YY_TYPEDEF_YY_BUFFER_STATE
+typedef struct yy_buffer_state *YY_BUFFER_STATE;
+#endif
+
+#ifndef YY_TYPEDEF_YY_SIZE_T
+#define YY_TYPEDEF_YY_SIZE_T
+typedef size_t yy_size_t;
+#endif
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+    /* Note: We specifically omit the test for yy_rule_can_match_eol because it requires
+     *       access to the local variable yy_act. Since yyless() is a macro, it would break
+     *       existing scanners that call yyless() from OUTSIDE sksllex. 
+     *       One obvious solution it to make yy_act a global. I tried that, and saw
+     *       a 5% performance hit in a non-yylineno scanner, because yy_act is
+     *       normally declared as a register variable-- so it is not worth it.
+     */
+    #define  YY_LESS_LINENO(n) \
+            do { \
+                yy_size_t yyl;\
+                for ( yyl = n; yyl < yyleng; ++yyl )\
+                    if ( yytext[yyl] == '\n' )\
+                        --yylineno;\
+            }while(0)
+    
+/* Return all but the first "n" matched characters back to the input stream. */
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		*yy_cp = yyg->yy_hold_char; \
+		YY_RESTORE_YY_MORE_OFFSET \
+		yyg->yy_c_buf_p = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \
+		YY_DO_BEFORE_ACTION; /* set up yytext again */ \
+		} \
+	while ( 0 )
+
+#define unput(c) yyunput( c, yyg->yytext_ptr , yyscanner )
+
+#ifndef YY_STRUCT_YY_BUFFER_STATE
+#define YY_STRUCT_YY_BUFFER_STATE
+struct yy_buffer_state
+	{
+	FILE *yy_input_file;
+
+	char *yy_ch_buf;		/* input buffer */
+	char *yy_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	yy_size_t yy_buf_size;
+
+	/* Number of characters read into yy_ch_buf, not including EOB
+	 * characters.
+	 */
+	yy_size_t yy_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int yy_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int yy_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int yy_at_bol;
+
+    int yy_bs_lineno; /**< The line count. */
+    int yy_bs_column; /**< The column count. */
+    
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int yy_fill_buffer;
+
+	int yy_buffer_status;
+
+#define YY_BUFFER_NEW 0
+#define YY_BUFFER_NORMAL 1
+	/* When an EOF's been seen but there's still some text to process
+	 * then we mark the buffer as YY_EOF_PENDING, to indicate that we
+	 * shouldn't try reading from the input source any more.  We might
+	 * still have a bunch of tokens to match, though, because of
+	 * possible backing-up.
+	 *
+	 * When we actually see the EOF, we change the status to "new"
+	 * (via skslrestart()), so that the user can continue scanning by
+	 * just pointing yyin at a new input file.
+	 */
+#define YY_BUFFER_EOF_PENDING 2
+
+	};
+#endif /* !YY_STRUCT_YY_BUFFER_STATE */
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ *
+ * Returns the top of the stack, or NULL.
+ */
+#define YY_CURRENT_BUFFER ( yyg->yy_buffer_stack \
+                          ? yyg->yy_buffer_stack[yyg->yy_buffer_stack_top] \
+                          : NULL)
+
+/* Same as previous macro, but useful when we know that the buffer stack is not
+ * NULL or when we need an lvalue. For internal use only.
+ */
+#define YY_CURRENT_BUFFER_LVALUE yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]
+
+void skslrestart (FILE *input_file ,yyscan_t yyscanner );
+void sksl_switch_to_buffer (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+YY_BUFFER_STATE sksl_create_buffer (FILE *file,int size ,yyscan_t yyscanner );
+void sksl_delete_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void sksl_flush_buffer (YY_BUFFER_STATE b ,yyscan_t yyscanner );
+void skslpush_buffer_state (YY_BUFFER_STATE new_buffer ,yyscan_t yyscanner );
+void skslpop_buffer_state (yyscan_t yyscanner );
+
+static void skslensure_buffer_stack (yyscan_t yyscanner );
+static void sksl_load_buffer_state (yyscan_t yyscanner );
+static void sksl_init_buffer (YY_BUFFER_STATE b,FILE *file ,yyscan_t yyscanner );
+
+#define YY_FLUSH_BUFFER sksl_flush_buffer(YY_CURRENT_BUFFER ,yyscanner)
+
+YY_BUFFER_STATE sksl_scan_buffer (char *base,yy_size_t size ,yyscan_t yyscanner );
+YY_BUFFER_STATE sksl_scan_string (yyconst char *yy_str ,yyscan_t yyscanner );
+YY_BUFFER_STATE sksl_scan_bytes (yyconst char *bytes,yy_size_t len ,yyscan_t yyscanner );
+
+void *skslalloc (yy_size_t ,yyscan_t yyscanner );
+void *skslrealloc (void *,yy_size_t ,yyscan_t yyscanner );
+void skslfree (void * ,yyscan_t yyscanner );
+
+#define yy_new_buffer sksl_create_buffer
+
+#define yy_set_interactive(is_interactive) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){ \
+        skslensure_buffer_stack (yyscanner); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            sksl_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \
+	}
+
+#define yy_set_bol(at_bol) \
+	{ \
+	if ( ! YY_CURRENT_BUFFER ){\
+        skslensure_buffer_stack (yyscanner); \
+		YY_CURRENT_BUFFER_LVALUE =    \
+            sksl_create_buffer(yyin,YY_BUF_SIZE ,yyscanner); \
+	} \
+	YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \
+	}
+
+#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol)
+
+/* Begin user sect3 */
+
+typedef unsigned char YY_CHAR;
+
+typedef int yy_state_type;
+
+#define yytext_ptr yytext_r
+
+static yy_state_type yy_get_previous_state (yyscan_t yyscanner );
+static yy_state_type yy_try_NUL_trans (yy_state_type current_state  ,yyscan_t yyscanner);
+static int yy_get_next_buffer (yyscan_t yyscanner );
+static void yy_fatal_error (yyconst char msg[] ,yyscan_t yyscanner );
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up yytext.
+ */
+#define YY_DO_BEFORE_ACTION \
+	yyg->yytext_ptr = yy_bp; \
+	yyleng = (yy_size_t) (yy_cp - yy_bp); \
+	yyg->yy_hold_char = *yy_cp; \
+	*yy_cp = '\0'; \
+	yyg->yy_c_buf_p = yy_cp;
+
+#define YY_NUM_RULES 80
+#define YY_END_OF_BUFFER 81
+/* This struct is not used in this scanner,
+   but its presence is necessary. */
+struct yy_trans_info
+	{
+	flex_int32_t yy_verify;
+	flex_int32_t yy_nxt;
+	};
+static yyconst flex_int16_t yy_accept[185] =
+    {   0,
+        0,    0,   81,   79,   78,   78,   52,   79,   27,   43,
+       48,   29,   30,   41,   39,   36,   40,   35,   42,    4,
+       54,   75,   59,   55,   58,   53,   33,   34,   47,   27,
+       27,   27,   27,   27,   27,   27,   27,   27,   27,   27,
+       27,   27,   27,   27,   27,   31,   46,   32,   78,   57,
+       28,   27,   66,   51,   71,   64,   37,   62,   38,   63,
+        1,    0,   76,   65,    2,    4,    0,   44,   61,   56,
+       60,   45,   70,   50,   27,   27,   27,   11,   27,   27,
+       27,   27,    7,   16,   27,   27,   27,   27,   27,   27,
+       27,   27,   27,   27,   69,   49,   28,   74,    0,    0,
+
+        0,   76,    1,    0,    0,    3,   67,   68,   73,   27,
+       27,   27,   27,   27,    9,   27,   27,   27,   27,   27,
+       17,   27,   27,   27,   27,   27,   27,   72,    0,    1,
+       77,    0,    0,    2,   27,   27,   27,   27,    8,   27,
+       27,   27,   27,   21,   27,   27,   27,   27,    5,   27,
+       27,    0,    1,   12,   20,   27,   27,    6,   23,   18,
+       27,   27,   27,   27,   27,   27,   10,   27,   27,   25,
+       27,   27,   15,   24,   27,   27,   14,   22,   27,   19,
+       13,   27,   26,    0
+    } ;
+
+static yyconst flex_int32_t yy_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
+        1,    1,    2,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    2,    4,    1,    5,    6,    7,    8,    1,    9,
+       10,   11,   12,   13,   14,   15,   16,   17,   17,   17,
+       17,   17,   17,   17,   17,   17,   17,   18,   19,   20,
+       21,   22,   23,    1,    6,    6,    6,    6,   24,    6,
+        6,    6,    6,    6,    6,    6,    6,    6,    6,    6,
+        6,    6,    6,    6,    6,    6,    6,    6,    6,    6,
+       25,    1,   26,   27,    6,    1,   28,   29,   30,   31,
+
+       32,   33,   34,   35,   36,    6,   37,   38,   39,   40,
+       41,   42,    6,   43,   44,   45,   46,    6,   47,    6,
+       48,    6,   49,   50,   51,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static yyconst flex_int32_t yy_meta[52] =
+    {   0,
+        1,    1,    2,    1,    1,    3,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    4,    1,    1,    1,
+        1,    1,    1,    3,    1,    1,    1,    3,    3,    3,
+        3,    3,    3,    3,    3,    3,    3,    3,    3,    3,
+        3,    3,    3,    3,    3,    3,    3,    3,    1,    1,
+        1
+    } ;
+
+static yyconst flex_int16_t yy_base[190] =
+    {   0,
+        0,    0,  222,  223,   50,   52,  200,    0,    0,  199,
+       48,  223,  223,  198,   45,  223,   44,  201,   51,   44,
+      223,  223,   43,  196,   49,  223,  223,  223,   52,  173,
+      174,   39,  176,   46,  177,   44,   50,  180,  165,  167,
+      177,  163,  164,  166,  170,  223,   39,  223,   79,  223,
+        0,    0,  223,  183,  223,  223,  223,  223,  223,  223,
+       66,  192,    0,  223,   68,   71,   82,  181,  223,  223,
+      223,  180,  223,  179,  167,  158,  153,    0,  152,  157,
+      151,  159,    0,  151,  143,  143,  158,  143,  155,  141,
+      142,  138,  147,  146,  223,  160,    0,  223,   90,  169,
+
+      163,    0,   84,   97,  161,  160,  223,  223,  223,  148,
+       61,  145,  142,  129,    0,  137,  125,  129,  127,  132,
+        0,  137,  120,  119,  132,  130,  124,  223,  144,  143,
+      223,   98,  142,  141,  120,  111,  119,  126,    0,  121,
+      110,  106,  104,    0,  103,  112,  104,  116,    0,  104,
+      112,  126,  125,    0,    0,  101,   97,    0,    0,    0,
+       94,   99,   93,   96,   90,   91,    0,   87,  101,    0,
+       89,   94,    0,    0,   90,   94,    0,    0,   72,    0,
+        0,   57,    0,  223,   90,  114,  116,  120,  124
+    } ;
+
+static yyconst flex_int16_t yy_def[190] =
+    {   0,
+      184,    1,  184,  184,  184,  184,  184,  185,  186,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  186,
+      186,  186,  186,  186,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  186,  184,  184,  184,  184,  184,
+      187,  186,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  188,  189,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  184,  184,  187,  184,  184,  188,
+
+      188,  189,  184,  184,  184,  184,  184,  184,  184,  186,
+      186,  186,  186,  186,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  186,  186,  186,  184,  184,  184,
+      184,  184,  184,  184,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  186,  186,  186,  186,  186,  186,
+      186,  184,  184,  186,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,  186,  186,  186,  186,  186,  186,  186,
+      186,  186,  186,    0,  184,  184,  184,  184,  184
+    } ;
+
+static yyconst flex_int16_t yy_nxt[275] =
+    {   0,
+        4,    5,    6,    7,    8,    9,   10,   11,   12,   13,
+       14,   15,   16,   17,   18,   19,   20,   21,   22,   23,
+       24,   25,   26,    9,   27,   28,   29,    9,   30,   31,
+       32,   33,   34,    9,   35,   36,    9,   37,   38,    9,
+       39,   40,   41,   42,   43,   44,   45,    9,   46,   47,
+       48,   49,   49,   49,   49,   54,   57,   59,   65,   95,
+       66,   62,   68,   69,   60,   58,   63,   67,   55,   71,
+       72,   64,   73,   80,   77,   67,   83,   85,   74,   78,
+       49,   49,   61,   84,  103,   65,   81,   66,   96,   99,
+       86,  104,   51,  105,   67,  105,  183,   99,  106,  104,
+
+      103,  129,   67,  129,  136,  137,  130,  132,  133,  152,
+      133,  152,  182,  134,  153,  132,   52,   52,   97,   97,
+      100,  100,  100,  100,  102,  181,  102,  102,  180,  179,
+      178,  177,  176,  175,  174,  173,  172,  171,  170,  169,
+      168,  153,  153,  167,  166,  165,  164,  163,  162,  161,
+      160,  159,  158,  157,  156,  155,  154,  134,  134,  130,
+      130,  151,  150,  149,  148,  147,  146,  145,  144,  143,
+      142,  141,  140,  139,  138,  135,  106,  106,  131,  101,
+      128,  127,  126,  125,  124,  123,  122,  121,  120,  119,
+      118,  117,  116,  115,  114,  113,  112,  111,  110,  109,
+
+      108,  107,  101,   98,   94,   93,   92,   91,   90,   89,
+       88,   87,   82,   79,   76,   75,   70,   61,   56,   53,
+       50,  184,    3,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184
+    } ;
+
+static yyconst flex_int16_t yy_chk[275] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    5,    5,    6,    6,   11,   15,   17,   20,   47,
+       20,   19,   23,   23,   17,   15,   19,   20,   11,   25,
+       25,   19,   29,   34,   32,   20,   36,   37,   29,   32,
+       49,   49,   61,   36,   65,   66,   34,   66,   47,   61,
+       37,   65,  185,   67,   66,   67,  182,   61,   67,   65,
+
+      103,   99,   66,   99,  111,  111,   99,  103,  104,  132,
+      104,  132,  179,  104,  132,  103,  186,  186,  187,  187,
+      188,  188,  188,  188,  189,  176,  189,  189,  175,  172,
+      171,  169,  168,  166,  165,  164,  163,  162,  161,  157,
+      156,  153,  152,  151,  150,  148,  147,  146,  145,  143,
+      142,  141,  140,  138,  137,  136,  135,  134,  133,  130,
+      129,  127,  126,  125,  124,  123,  122,  120,  119,  118,
+      117,  116,  114,  113,  112,  110,  106,  105,  101,  100,
+       96,   94,   93,   92,   91,   90,   89,   88,   87,   86,
+       85,   84,   82,   81,   80,   79,   77,   76,   75,   74,
+
+       72,   68,   62,   54,   45,   44,   43,   42,   41,   40,
+       39,   38,   35,   33,   31,   30,   24,   18,   14,   10,
+        7,    3,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184,  184,  184,  184,  184,  184,  184,
+      184,  184,  184,  184
+    } ;
+
+/* Table of booleans, true if rule could match eol. */
+static yyconst flex_int32_t yy_rule_can_match_eol[81] =
+    {   0,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
+    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 
+    0,     };
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define yymore() yymore_used_but_not_detected
+#define YY_MORE_ADJ 0
+#define YY_RESTORE_YY_MORE_OFFSET
+#line 1 "sksl.flex"
+/*
+
+	This file is IGNORED during the build process!
+
+	As this file is updated so infrequently and flex is not universally present on build machines,
+	the lex.sksl.c file must be manually regenerated if you make any changes to this file. Just run:
+
+		flex sksl.flex
+
+*/
+#define YY_NO_UNISTD_H 1
+#line 582 "lex.sksl.c"
+
+#define INITIAL 0
+
+#ifndef YY_NO_UNISTD_H
+/* Special case for "unistd.h", since it is non-ANSI. We include it way
+ * down here because we want the user's section 1 to have been scanned first.
+ * The user has a chance to override it with an option.
+ */
+#include <unistd.h>
+#endif
+
+#ifndef YY_EXTRA_TYPE
+#define YY_EXTRA_TYPE void *
+#endif
+
+/* Holds the entire state of the reentrant scanner. */
+struct yyguts_t
+    {
+
+    /* User-defined. Not touched by flex. */
+    YY_EXTRA_TYPE yyextra_r;
+
+    /* The rest are the same as the globals declared in the non-reentrant scanner. */
+    FILE *yyin_r, *yyout_r;
+    size_t yy_buffer_stack_top; /**< index of top of stack. */
+    size_t yy_buffer_stack_max; /**< capacity of stack. */
+    YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */
+    char yy_hold_char;
+    yy_size_t yy_n_chars;
+    yy_size_t yyleng_r;
+    char *yy_c_buf_p;
+    int yy_init;
+    int yy_start;
+    int yy_did_buffer_switch_on_eof;
+    int yy_start_stack_ptr;
+    int yy_start_stack_depth;
+    int *yy_start_stack;
+    yy_state_type yy_last_accepting_state;
+    char* yy_last_accepting_cpos;
+
+    int yylineno_r;
+    int yy_flex_debug_r;
+
+    char *yytext_r;
+    int yy_more_flag;
+    int yy_more_len;
+
+    }; /* end struct yyguts_t */
+
+static int yy_init_globals (yyscan_t yyscanner );
+
+int sksllex_init (yyscan_t* scanner);
+
+int sksllex_init_extra (YY_EXTRA_TYPE user_defined,yyscan_t* scanner);
+
+/* Accessor methods to globals.
+   These are made visible to non-reentrant scanners for convenience. */
+
+int sksllex_destroy (yyscan_t yyscanner );
+
+int skslget_debug (yyscan_t yyscanner );
+
+void skslset_debug (int debug_flag ,yyscan_t yyscanner );
+
+YY_EXTRA_TYPE skslget_extra (yyscan_t yyscanner );
+
+void skslset_extra (YY_EXTRA_TYPE user_defined ,yyscan_t yyscanner );
+
+FILE *skslget_in (yyscan_t yyscanner );
+
+void skslset_in  (FILE * in_str ,yyscan_t yyscanner );
+
+FILE *skslget_out (yyscan_t yyscanner );
+
+void skslset_out  (FILE * out_str ,yyscan_t yyscanner );
+
+yy_size_t skslget_leng (yyscan_t yyscanner );
+
+char *skslget_text (yyscan_t yyscanner );
+
+int skslget_lineno (yyscan_t yyscanner );
+
+void skslset_lineno (int line_number ,yyscan_t yyscanner );
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef YY_SKIP_YYWRAP
+#ifdef __cplusplus
+extern "C" int skslwrap (yyscan_t yyscanner );
+#else
+extern int skslwrap (yyscan_t yyscanner );
+#endif
+#endif
+
+    static void yyunput (int c,char *buf_ptr  ,yyscan_t yyscanner);
+    
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char *,yyconst char *,int ,yyscan_t yyscanner);
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * ,yyscan_t yyscanner);
+#endif
+
+#ifndef YY_NO_INPUT
+
+#ifdef __cplusplus
+static int yyinput (yyscan_t yyscanner );
+#else
+static int input (yyscan_t yyscanner );
+#endif
+
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef YY_READ_BUF_SIZE
+#define YY_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO fwrite( yytext, yyleng, 1, yyout )
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or YY_NULL,
+ * is returned in "result".
+ */
+#ifndef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \
+		{ \
+		int c = '*'; \
+		yy_size_t n; \
+		for ( n = 0; n < max_size && \
+			     (c = getc( yyin )) != EOF && c != '\n'; ++n ) \
+			buf[n] = (char) c; \
+		if ( c == '\n' ) \
+			buf[n++] = (char) c; \
+		if ( c == EOF && ferror( yyin ) ) \
+			YY_FATAL_ERROR( "input in flex scanner failed" ); \
+		result = n; \
+		} \
+	else \
+		{ \
+		errno=0; \
+		while ( (result = fread(buf, 1, max_size, yyin))==0 && ferror(yyin)) \
+			{ \
+			if( errno != EINTR) \
+				{ \
+				YY_FATAL_ERROR( "input in flex scanner failed" ); \
+				break; \
+				} \
+			errno=0; \
+			clearerr(yyin); \
+			} \
+		}\
+\
+
+#endif
+
+/* No semi-colon after return; correct usage is to write "yyterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef yyterminate
+#define yyterminate() return YY_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef YY_START_STACK_INCR
+#define YY_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef YY_FATAL_ERROR
+#define YY_FATAL_ERROR(msg) yy_fatal_error( msg , yyscanner)
+#endif
+
+/* end tables serialization structures and prototypes */
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef YY_DECL
+#define YY_DECL_IS_OURS 1
+
+extern int sksllex (yyscan_t yyscanner);
+
+#define YY_DECL int sksllex (yyscan_t yyscanner)
+#endif /* !YY_DECL */
+
+/* Code executed at the beginning of each rule, after yytext and yyleng
+ * have been set up.
+ */
+#ifndef YY_USER_ACTION
+#define YY_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef YY_BREAK
+#define YY_BREAK break;
+#endif
+
+#define YY_RULE_SETUP \
+	YY_USER_ACTION
+
+/** The main scanner function which does all the work.
+ */
+YY_DECL
+{
+	register yy_state_type yy_current_state;
+	register char *yy_cp, *yy_bp;
+	register int yy_act;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+#line 21 "sksl.flex"
+
+
+#line 806 "lex.sksl.c"
+
+	if ( !yyg->yy_init )
+		{
+		yyg->yy_init = 1;
+
+#ifdef YY_USER_INIT
+		YY_USER_INIT;
+#endif
+
+		if ( ! yyg->yy_start )
+			yyg->yy_start = 1;	/* first start state */
+
+		if ( ! yyin )
+			yyin = stdin;
+
+		if ( ! yyout )
+			yyout = stdout;
+
+		if ( ! YY_CURRENT_BUFFER ) {
+			skslensure_buffer_stack (yyscanner);
+			YY_CURRENT_BUFFER_LVALUE =
+				sksl_create_buffer(yyin,YY_BUF_SIZE ,yyscanner);
+		}
+
+		sksl_load_buffer_state(yyscanner );
+		}
+
+	while ( 1 )		/* loops until end-of-file is reached */
+		{
+		yy_cp = yyg->yy_c_buf_p;
+
+		/* Support of yytext. */
+		*yy_cp = yyg->yy_hold_char;
+
+		/* yy_bp points to the position in yy_ch_buf of the start of
+		 * the current run.
+		 */
+		yy_bp = yy_cp;
+
+		yy_current_state = yyg->yy_start;
+yy_match:
+		do
+			{
+			register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+			if ( yy_accept[yy_current_state] )
+				{
+				yyg->yy_last_accepting_state = yy_current_state;
+				yyg->yy_last_accepting_cpos = yy_cp;
+				}
+			while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+				{
+				yy_current_state = (int) yy_def[yy_current_state];
+				if ( yy_current_state >= 185 )
+					yy_c = yy_meta[(unsigned int) yy_c];
+				}
+			yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+			++yy_cp;
+			}
+		while ( yy_current_state != 184 );
+		yy_cp = yyg->yy_last_accepting_cpos;
+		yy_current_state = yyg->yy_last_accepting_state;
+
+yy_find_action:
+		yy_act = yy_accept[yy_current_state];
+
+		YY_DO_BEFORE_ACTION;
+
+		if ( yy_act != YY_END_OF_BUFFER && yy_rule_can_match_eol[yy_act] )
+			{
+			yy_size_t yyl;
+			for ( yyl = 0; yyl < yyleng; ++yyl )
+				if ( yytext[yyl] == '\n' )
+					   
+    do{ yylineno++;
+        yycolumn=0;
+    }while(0)
+;
+			}
+
+do_action:	/* This label is used only to access EOF actions. */
+
+		switch ( yy_act )
+	{ /* beginning of action switch */
+			case 0: /* must back up */
+			/* undo the effects of YY_DO_BEFORE_ACTION */
+			*yy_cp = yyg->yy_hold_char;
+			yy_cp = yyg->yy_last_accepting_cpos;
+			yy_current_state = yyg->yy_last_accepting_state;
+			goto yy_find_action;
+
+case 1:
+YY_RULE_SETUP
+#line 23 "sksl.flex"
+{ return SkSL::Token::FLOAT_LITERAL; }
+	YY_BREAK
+case 2:
+YY_RULE_SETUP
+#line 25 "sksl.flex"
+{ return SkSL::Token::FLOAT_LITERAL; }
+	YY_BREAK
+case 3:
+YY_RULE_SETUP
+#line 27 "sksl.flex"
+{ return SkSL::Token::FLOAT_LITERAL; }
+	YY_BREAK
+case 4:
+YY_RULE_SETUP
+#line 29 "sksl.flex"
+{ return SkSL::Token::INT_LITERAL; }
+	YY_BREAK
+case 5:
+YY_RULE_SETUP
+#line 31 "sksl.flex"
+{ return SkSL::Token::TRUE_LITERAL; }
+	YY_BREAK
+case 6:
+YY_RULE_SETUP
+#line 33 "sksl.flex"
+{ return SkSL::Token::FALSE_LITERAL; }
+	YY_BREAK
+case 7:
+YY_RULE_SETUP
+#line 35 "sksl.flex"
+{ return SkSL::Token::IF; }
+	YY_BREAK
+case 8:
+YY_RULE_SETUP
+#line 37 "sksl.flex"
+{ return SkSL::Token::ELSE; }
+	YY_BREAK
+case 9:
+YY_RULE_SETUP
+#line 39 "sksl.flex"
+{ return SkSL::Token::FOR; }
+	YY_BREAK
+case 10:
+YY_RULE_SETUP
+#line 41 "sksl.flex"
+{ return SkSL::Token::WHILE; }
+	YY_BREAK
+case 11:
+YY_RULE_SETUP
+#line 43 "sksl.flex"
+{ return SkSL::Token::DO; }
+	YY_BREAK
+case 12:
+YY_RULE_SETUP
+#line 45 "sksl.flex"
+{ return SkSL::Token::BREAK; }
+	YY_BREAK
+case 13:
+YY_RULE_SETUP
+#line 47 "sksl.flex"
+{ return SkSL::Token::CONTINUE; }
+	YY_BREAK
+case 14:
+YY_RULE_SETUP
+#line 49 "sksl.flex"
+{ return SkSL::Token::DISCARD; }
+	YY_BREAK
+case 15:
+YY_RULE_SETUP
+#line 51 "sksl.flex"
+{ return SkSL::Token::RETURN; }
+	YY_BREAK
+case 16:
+YY_RULE_SETUP
+#line 53 "sksl.flex"
+{ return SkSL::Token::IN; }
+	YY_BREAK
+case 17:
+YY_RULE_SETUP
+#line 55 "sksl.flex"
+{ return SkSL::Token::OUT; }
+	YY_BREAK
+case 18:
+YY_RULE_SETUP
+#line 57 "sksl.flex"
+{ return SkSL::Token::INOUT; }
+	YY_BREAK
+case 19:
+YY_RULE_SETUP
+#line 59 "sksl.flex"
+{ return SkSL::Token::UNIFORM; }
+	YY_BREAK
+case 20:
+YY_RULE_SETUP
+#line 61 "sksl.flex"
+{ return SkSL::Token::CONST; }
+	YY_BREAK
+case 21:
+YY_RULE_SETUP
+#line 63 "sksl.flex"
+{ return SkSL::Token::LOWP; }
+	YY_BREAK
+case 22:
+YY_RULE_SETUP
+#line 65 "sksl.flex"
+{ return SkSL::Token::MEDIUMP; }
+	YY_BREAK
+case 23:
+YY_RULE_SETUP
+#line 67 "sksl.flex"
+{ return SkSL::Token::HIGHP; }
+	YY_BREAK
+case 24:
+YY_RULE_SETUP
+#line 69 "sksl.flex"
+{ return SkSL::Token::STRUCT; }
+	YY_BREAK
+case 25:
+YY_RULE_SETUP
+#line 71 "sksl.flex"
+{ return SkSL::Token::LAYOUT; }
+	YY_BREAK
+case 26:
+YY_RULE_SETUP
+#line 73 "sksl.flex"
+{ return SkSL::Token::PRECISION; }
+	YY_BREAK
+case 27:
+YY_RULE_SETUP
+#line 75 "sksl.flex"
+{ return SkSL::Token::IDENTIFIER; }
+	YY_BREAK
+case 28:
+YY_RULE_SETUP
+#line 77 "sksl.flex"
+{ return SkSL::Token::DIRECTIVE; }
+	YY_BREAK
+case 29:
+YY_RULE_SETUP
+#line 79 "sksl.flex"
+{ return SkSL::Token::LPAREN; }
+	YY_BREAK
+case 30:
+YY_RULE_SETUP
+#line 81 "sksl.flex"
+{ return SkSL::Token::RPAREN; }
+	YY_BREAK
+case 31:
+YY_RULE_SETUP
+#line 83 "sksl.flex"
+{ return SkSL::Token::LBRACE; }
+	YY_BREAK
+case 32:
+YY_RULE_SETUP
+#line 85 "sksl.flex"
+{ return SkSL::Token::RBRACE; }
+	YY_BREAK
+case 33:
+YY_RULE_SETUP
+#line 87 "sksl.flex"
+{ return SkSL::Token::LBRACKET; }
+	YY_BREAK
+case 34:
+YY_RULE_SETUP
+#line 89 "sksl.flex"
+{ return SkSL::Token::RBRACKET; }
+	YY_BREAK
+case 35:
+YY_RULE_SETUP
+#line 91 "sksl.flex"
+{ return SkSL::Token::DOT; }
+	YY_BREAK
+case 36:
+YY_RULE_SETUP
+#line 93 "sksl.flex"
+{ return SkSL::Token::COMMA; }
+	YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 95 "sksl.flex"
+{ return SkSL::Token::PLUSPLUS; }
+	YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 97 "sksl.flex"
+{ return SkSL::Token::MINUSMINUS; }
+	YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 99 "sksl.flex"
+{ return SkSL::Token::PLUS; }
+	YY_BREAK
+case 40:
+YY_RULE_SETUP
+#line 101 "sksl.flex"
+{ return SkSL::Token::MINUS; }
+	YY_BREAK
+case 41:
+YY_RULE_SETUP
+#line 103 "sksl.flex"
+{ return SkSL::Token::STAR; }
+	YY_BREAK
+case 42:
+YY_RULE_SETUP
+#line 105 "sksl.flex"
+{ return SkSL::Token::SLASH; }
+	YY_BREAK
+case 43:
+YY_RULE_SETUP
+#line 107 "sksl.flex"
+{ return SkSL::Token::PERCENT; }
+	YY_BREAK
+case 44:
+YY_RULE_SETUP
+#line 109 "sksl.flex"
+{ return SkSL::Token::SHL; }
+	YY_BREAK
+case 45:
+YY_RULE_SETUP
+#line 111 "sksl.flex"
+{ return SkSL::Token::SHR; }
+	YY_BREAK
+case 46:
+YY_RULE_SETUP
+#line 113 "sksl.flex"
+{ return SkSL::Token::BITWISEOR; }
+	YY_BREAK
+case 47:
+YY_RULE_SETUP
+#line 115 "sksl.flex"
+{ return SkSL::Token::BITWISEXOR; }
+	YY_BREAK
+case 48:
+YY_RULE_SETUP
+#line 117 "sksl.flex"
+{ return SkSL::Token::BITWISEAND; }
+	YY_BREAK
+case 49:
+YY_RULE_SETUP
+#line 119 "sksl.flex"
+{ return SkSL::Token::LOGICALOR; }
+	YY_BREAK
+case 50:
+YY_RULE_SETUP
+#line 121 "sksl.flex"
+{ return SkSL::Token::LOGICALXOR; }
+	YY_BREAK
+case 51:
+YY_RULE_SETUP
+#line 123 "sksl.flex"
+{ return SkSL::Token::LOGICALAND; }
+	YY_BREAK
+case 52:
+YY_RULE_SETUP
+#line 125 "sksl.flex"
+{ return SkSL::Token::NOT; }
+	YY_BREAK
+case 53:
+YY_RULE_SETUP
+#line 127 "sksl.flex"
+{ return SkSL::Token::QUESTION; }
+	YY_BREAK
+case 54:
+YY_RULE_SETUP
+#line 129 "sksl.flex"
+{ return SkSL::Token::COLON; }
+	YY_BREAK
+case 55:
+YY_RULE_SETUP
+#line 131 "sksl.flex"
+{ return SkSL::Token::EQ; }
+	YY_BREAK
+case 56:
+YY_RULE_SETUP
+#line 133 "sksl.flex"
+{ return SkSL::Token::EQEQ; }
+	YY_BREAK
+case 57:
+YY_RULE_SETUP
+#line 135 "sksl.flex"
+{ return SkSL::Token::NEQ; }
+	YY_BREAK
+case 58:
+YY_RULE_SETUP
+#line 137 "sksl.flex"
+{ return SkSL::Token::GT; }
+	YY_BREAK
+case 59:
+YY_RULE_SETUP
+#line 139 "sksl.flex"
+{ return SkSL::Token::LT; }
+	YY_BREAK
+case 60:
+YY_RULE_SETUP
+#line 141 "sksl.flex"
+{ return SkSL::Token::GTEQ; }
+	YY_BREAK
+case 61:
+YY_RULE_SETUP
+#line 143 "sksl.flex"
+{ return SkSL::Token::LTEQ; }
+	YY_BREAK
+case 62:
+YY_RULE_SETUP
+#line 145 "sksl.flex"
+{ return SkSL::Token::PLUSEQ; }
+	YY_BREAK
+case 63:
+YY_RULE_SETUP
+#line 147 "sksl.flex"
+{ return SkSL::Token::MINUSEQ; }
+	YY_BREAK
+case 64:
+YY_RULE_SETUP
+#line 149 "sksl.flex"
+{ return SkSL::Token::STAREQ; }
+	YY_BREAK
+case 65:
+YY_RULE_SETUP
+#line 151 "sksl.flex"
+{ return SkSL::Token::SLASHEQ; }
+	YY_BREAK
+case 66:
+YY_RULE_SETUP
+#line 153 "sksl.flex"
+{ return SkSL::Token::PERCENTEQ; }
+	YY_BREAK
+case 67:
+YY_RULE_SETUP
+#line 155 "sksl.flex"
+{ return SkSL::Token::SHLEQ; }
+	YY_BREAK
+case 68:
+YY_RULE_SETUP
+#line 157 "sksl.flex"
+{ return SkSL::Token::SHREQ; }
+	YY_BREAK
+case 69:
+YY_RULE_SETUP
+#line 159 "sksl.flex"
+{ return SkSL::Token::BITWISEOREQ; }
+	YY_BREAK
+case 70:
+YY_RULE_SETUP
+#line 161 "sksl.flex"
+{ return SkSL::Token::BITWISEXOREQ; }
+	YY_BREAK
+case 71:
+YY_RULE_SETUP
+#line 163 "sksl.flex"
+{ return SkSL::Token::BITWISEANDEQ; }
+	YY_BREAK
+case 72:
+YY_RULE_SETUP
+#line 165 "sksl.flex"
+{ return SkSL::Token::LOGICALOREQ; }
+	YY_BREAK
+case 73:
+YY_RULE_SETUP
+#line 167 "sksl.flex"
+{ return SkSL::Token::LOGICALXOREQ; }
+	YY_BREAK
+case 74:
+YY_RULE_SETUP
+#line 169 "sksl.flex"
+{ return SkSL::Token::LOGICALANDEQ; }
+	YY_BREAK
+case 75:
+YY_RULE_SETUP
+#line 171 "sksl.flex"
+{ return SkSL::Token::SEMICOLON; }
+	YY_BREAK
+case 76:
+YY_RULE_SETUP
+#line 173 "sksl.flex"
+/* line comment */
+	YY_BREAK
+case 77:
+/* rule 77 can match eol */
+YY_RULE_SETUP
+#line 175 "sksl.flex"
+/* block comment */
+	YY_BREAK
+case 78:
+/* rule 78 can match eol */
+YY_RULE_SETUP
+#line 177 "sksl.flex"
+/* whitespace */
+	YY_BREAK
+case 79:
+YY_RULE_SETUP
+#line 179 "sksl.flex"
+{ return SkSL::Token::INVALID_TOKEN; }
+	YY_BREAK
+case 80:
+YY_RULE_SETUP
+#line 181 "sksl.flex"
+ECHO;
+	YY_BREAK
+#line 1299 "lex.sksl.c"
+case YY_STATE_EOF(INITIAL):
+	yyterminate();
+
+	case YY_END_OF_BUFFER:
+		{
+		/* Amount of text matched not including the EOB char. */
+		int yy_amount_of_matched_text = (int) (yy_cp - yyg->yytext_ptr) - 1;
+
+		/* Undo the effects of YY_DO_BEFORE_ACTION. */
+		*yy_cp = yyg->yy_hold_char;
+		YY_RESTORE_YY_MORE_OFFSET
+
+		if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW )
+			{
+			/* We're scanning a new file or input source.  It's
+			 * possible that this happened because the user
+			 * just pointed yyin at a new source and called
+			 * sksllex().  If so, then we have to assure
+			 * consistency between YY_CURRENT_BUFFER and our
+			 * globals.  Here is the right place to do so, because
+			 * this is the first action (other than possibly a
+			 * back-up) that will match for the new input source.
+			 */
+			yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+			YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL;
+			}
+
+		/* Note that here we test for yy_c_buf_p "<=" to the position
+		 * of the first EOB in the buffer, since yy_c_buf_p will
+		 * already have been incremented past the NUL character
+		 * (since all states make transitions on EOB to the
+		 * end-of-buffer state).  Contrast this with the test
+		 * in input().
+		 */
+		if ( yyg->yy_c_buf_p <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+			{ /* This was really a NUL. */
+			yy_state_type yy_next_state;
+
+			yyg->yy_c_buf_p = yyg->yytext_ptr + yy_amount_of_matched_text;
+
+			yy_current_state = yy_get_previous_state( yyscanner );
+
+			/* Okay, we're now positioned to make the NUL
+			 * transition.  We couldn't have
+			 * yy_get_previous_state() go ahead and do it
+			 * for us because it doesn't know how to deal
+			 * with the possibility of jamming (and we don't
+			 * want to build jamming into it because then it
+			 * will run more slowly).
+			 */
+
+			yy_next_state = yy_try_NUL_trans( yy_current_state , yyscanner);
+
+			yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+
+			if ( yy_next_state )
+				{
+				/* Consume the NUL. */
+				yy_cp = ++yyg->yy_c_buf_p;
+				yy_current_state = yy_next_state;
+				goto yy_match;
+				}
+
+			else
+				{
+				yy_cp = yyg->yy_last_accepting_cpos;
+				yy_current_state = yyg->yy_last_accepting_state;
+				goto yy_find_action;
+				}
+			}
+
+		else switch ( yy_get_next_buffer( yyscanner ) )
+			{
+			case EOB_ACT_END_OF_FILE:
+				{
+				yyg->yy_did_buffer_switch_on_eof = 0;
+
+				if ( skslwrap(yyscanner ) )
+					{
+					/* Note: because we've taken care in
+					 * yy_get_next_buffer() to have set up
+					 * yytext, we can now set up
+					 * yy_c_buf_p so that if some total
+					 * hoser (like flex itself) wants to
+					 * call the scanner after we return the
+					 * YY_NULL, it'll still work - another
+					 * YY_NULL will get returned.
+					 */
+					yyg->yy_c_buf_p = yyg->yytext_ptr + YY_MORE_ADJ;
+
+					yy_act = YY_STATE_EOF(YY_START);
+					goto do_action;
+					}
+
+				else
+					{
+					if ( ! yyg->yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+					}
+				break;
+				}
+
+			case EOB_ACT_CONTINUE_SCAN:
+				yyg->yy_c_buf_p =
+					yyg->yytext_ptr + yy_amount_of_matched_text;
+
+				yy_current_state = yy_get_previous_state( yyscanner );
+
+				yy_cp = yyg->yy_c_buf_p;
+				yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+				goto yy_match;
+
+			case EOB_ACT_LAST_MATCH:
+				yyg->yy_c_buf_p =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars];
+
+				yy_current_state = yy_get_previous_state( yyscanner );
+
+				yy_cp = yyg->yy_c_buf_p;
+				yy_bp = yyg->yytext_ptr + YY_MORE_ADJ;
+				goto yy_find_action;
+			}
+		break;
+		}
+
+	default:
+		YY_FATAL_ERROR(
+			"fatal flex scanner internal error--no action found" );
+	} /* end of action switch */
+		} /* end of scanning one token */
+} /* end of sksllex */
+
+/* yy_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *	EOB_ACT_LAST_MATCH -
+ *	EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *	EOB_ACT_END_OF_FILE - end of file
+ */
+static int yy_get_next_buffer (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+	register char *source = yyg->yytext_ptr;
+	register int number_to_move, i;
+	int ret_val;
+
+	if ( yyg->yy_c_buf_p > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] )
+		YY_FATAL_ERROR(
+		"fatal flex scanner internal error--end of buffer missed" );
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 )
+		{ /* Don't try to fill the buffer, so this is an EOF. */
+		if ( yyg->yy_c_buf_p - yyg->yytext_ptr - YY_MORE_ADJ == 1 )
+			{
+			/* We matched a single character, the EOB, so
+			 * treat this as a final EOF.
+			 */
+			return EOB_ACT_END_OF_FILE;
+			}
+
+		else
+			{
+			/* We matched some text prior to the EOB, first
+			 * process it.
+			 */
+			return EOB_ACT_LAST_MATCH;
+			}
+		}
+
+	/* Try to read more data. */
+
+	/* First move last chars to start of buffer. */
+	number_to_move = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr) - 1;
+
+	for ( i = 0; i < number_to_move; ++i )
+		*(dest++) = *(source++);
+
+	if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING )
+		/* don't do the read, it's not guaranteed to return an EOF,
+		 * just force an EOF
+		 */
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars = 0;
+
+	else
+		{
+			yy_size_t num_to_read =
+			YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1;
+
+		while ( num_to_read <= 0 )
+			{ /* Not enough room in the buffer - grow it. */
+
+			/* just a shorter name for the current buffer */
+			YY_BUFFER_STATE b = YY_CURRENT_BUFFER;
+
+			int yy_c_buf_p_offset =
+				(int) (yyg->yy_c_buf_p - b->yy_ch_buf);
+
+			if ( b->yy_is_our_buffer )
+				{
+				yy_size_t new_size = b->yy_buf_size * 2;
+
+				if ( new_size <= 0 )
+					b->yy_buf_size += b->yy_buf_size / 8;
+				else
+					b->yy_buf_size *= 2;
+
+				b->yy_ch_buf = (char *)
+					/* Include room in for 2 EOB chars. */
+					skslrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ,yyscanner );
+				}
+			else
+				/* Can't grow it, we don't own it. */
+				b->yy_ch_buf = 0;
+
+			if ( ! b->yy_ch_buf )
+				YY_FATAL_ERROR(
+				"fatal error - scanner input buffer overflow" );
+
+			yyg->yy_c_buf_p = &b->yy_ch_buf[yy_c_buf_p_offset];
+
+			num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size -
+						number_to_move - 1;
+
+			}
+
+		if ( num_to_read > YY_READ_BUF_SIZE )
+			num_to_read = YY_READ_BUF_SIZE;
+
+		/* Read in more data. */
+		YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]),
+			yyg->yy_n_chars, num_to_read );
+
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	if ( yyg->yy_n_chars == 0 )
+		{
+		if ( number_to_move == YY_MORE_ADJ )
+			{
+			ret_val = EOB_ACT_END_OF_FILE;
+			skslrestart(yyin  ,yyscanner);
+			}
+
+		else
+			{
+			ret_val = EOB_ACT_LAST_MATCH;
+			YY_CURRENT_BUFFER_LVALUE->yy_buffer_status =
+				YY_BUFFER_EOF_PENDING;
+			}
+		}
+
+	else
+		ret_val = EOB_ACT_CONTINUE_SCAN;
+
+	if ((yy_size_t) (yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) {
+		/* Extend the array by 50%, plus the number we really need. */
+		yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1);
+		YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) skslrealloc((void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf,new_size ,yyscanner );
+		if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+			YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" );
+	}
+
+	yyg->yy_n_chars += number_to_move;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] = YY_END_OF_BUFFER_CHAR;
+	YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars + 1] = YY_END_OF_BUFFER_CHAR;
+
+	yyg->yytext_ptr = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0];
+
+	return ret_val;
+}
+
+/* yy_get_previous_state - get the state just before the EOB char was reached */
+
+    static yy_state_type yy_get_previous_state (yyscan_t yyscanner)
+{
+	register yy_state_type yy_current_state;
+	register char *yy_cp;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	yy_current_state = yyg->yy_start;
+
+	for ( yy_cp = yyg->yytext_ptr + YY_MORE_ADJ; yy_cp < yyg->yy_c_buf_p; ++yy_cp )
+		{
+		register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+		if ( yy_accept[yy_current_state] )
+			{
+			yyg->yy_last_accepting_state = yy_current_state;
+			yyg->yy_last_accepting_cpos = yy_cp;
+			}
+		while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+			{
+			yy_current_state = (int) yy_def[yy_current_state];
+			if ( yy_current_state >= 185 )
+				yy_c = yy_meta[(unsigned int) yy_c];
+			}
+		yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+		}
+
+	return yy_current_state;
+}
+
+/* yy_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *	next_state = yy_try_NUL_trans( current_state );
+ */
+    static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state , yyscan_t yyscanner)
+{
+	register int yy_is_jam;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; /* This var may be unused depending upon options. */
+	register char *yy_cp = yyg->yy_c_buf_p;
+
+	register YY_CHAR yy_c = 1;
+	if ( yy_accept[yy_current_state] )
+		{
+		yyg->yy_last_accepting_state = yy_current_state;
+		yyg->yy_last_accepting_cpos = yy_cp;
+		}
+	while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
+		{
+		yy_current_state = (int) yy_def[yy_current_state];
+		if ( yy_current_state >= 185 )
+			yy_c = yy_meta[(unsigned int) yy_c];
+		}
+	yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
+	yy_is_jam = (yy_current_state == 184);
+
+	return yy_is_jam ? 0 : yy_current_state;
+}
+
+    static void yyunput (int c, register char * yy_bp , yyscan_t yyscanner)
+{
+	register char *yy_cp;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+    yy_cp = yyg->yy_c_buf_p;
+
+	/* undo effects of setting up yytext */
+	*yy_cp = yyg->yy_hold_char;
+
+	if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+		{ /* need to shift things up to make room */
+		/* +2 for EOB chars. */
+		register yy_size_t number_to_move = yyg->yy_n_chars + 2;
+		register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
+					YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2];
+		register char *source =
+				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move];
+
+		while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
+			*--dest = *--source;
+
+		yy_cp += (int) (dest - source);
+		yy_bp += (int) (dest - source);
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars =
+			yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_buf_size;
+
+		if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
+			YY_FATAL_ERROR( "flex scanner push-back overflow" );
+		}
+
+	*--yy_cp = (char) c;
+
+    if ( c == '\n' ){
+        --yylineno;
+    }
+
+	yyg->yytext_ptr = yy_bp;
+	yyg->yy_hold_char = *yy_cp;
+	yyg->yy_c_buf_p = yy_cp;
+}
+
+#ifndef YY_NO_INPUT
+#ifdef __cplusplus
+    static int yyinput (yyscan_t yyscanner)
+#else
+    static int input  (yyscan_t yyscanner)
+#endif
+
+{
+	int c;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	*yyg->yy_c_buf_p = yyg->yy_hold_char;
+
+	if ( *yyg->yy_c_buf_p == YY_END_OF_BUFFER_CHAR )
+		{
+		/* yy_c_buf_p now points to the character we want to return.
+		 * If this occurs *before* the EOB characters, then it's a
+		 * valid NUL; if not, then we've hit the end of the buffer.
+		 */
+		if ( yyg->yy_c_buf_p < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[yyg->yy_n_chars] )
+			/* This was really a NUL. */
+			*yyg->yy_c_buf_p = '\0';
+
+		else
+			{ /* need more input */
+			yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr;
+			++yyg->yy_c_buf_p;
+
+			switch ( yy_get_next_buffer( yyscanner ) )
+				{
+				case EOB_ACT_LAST_MATCH:
+					/* This happens because yy_g_n_b()
+					 * sees that we've accumulated a
+					 * token and flags that we need to
+					 * try matching the token before
+					 * proceeding.  But for input(),
+					 * there's no matching to consider.
+					 * So convert the EOB_ACT_LAST_MATCH
+					 * to EOB_ACT_END_OF_FILE.
+					 */
+
+					/* Reset buffer status. */
+					skslrestart(yyin ,yyscanner);
+
+					/*FALLTHROUGH*/
+
+				case EOB_ACT_END_OF_FILE:
+					{
+					if ( skslwrap(yyscanner ) )
+						return 0;
+
+					if ( ! yyg->yy_did_buffer_switch_on_eof )
+						YY_NEW_FILE;
+#ifdef __cplusplus
+					return yyinput(yyscanner);
+#else
+					return input(yyscanner);
+#endif
+					}
+
+				case EOB_ACT_CONTINUE_SCAN:
+					yyg->yy_c_buf_p = yyg->yytext_ptr + offset;
+					break;
+				}
+			}
+		}
+
+	c = *(unsigned char *) yyg->yy_c_buf_p;	/* cast for 8-bit char's */
+	*yyg->yy_c_buf_p = '\0';	/* preserve yytext */
+	yyg->yy_hold_char = *++yyg->yy_c_buf_p;
+
+	if ( c == '\n' )
+		   
+    do{ yylineno++;
+        yycolumn=0;
+    }while(0)
+;
+
+	return c;
+}
+#endif	/* ifndef YY_NO_INPUT */
+
+/** Immediately switch to a different input stream.
+ * @param input_file A readable stream.
+ * @param yyscanner The scanner object.
+ * @note This function does not reset the start condition to @c INITIAL .
+ */
+    void skslrestart  (FILE * input_file , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( ! YY_CURRENT_BUFFER ){
+        skslensure_buffer_stack (yyscanner);
+		YY_CURRENT_BUFFER_LVALUE =
+            sksl_create_buffer(yyin,YY_BUF_SIZE ,yyscanner);
+	}
+
+	sksl_init_buffer(YY_CURRENT_BUFFER,input_file ,yyscanner);
+	sksl_load_buffer_state(yyscanner );
+}
+
+/** Switch to a different input buffer.
+ * @param new_buffer The new input buffer.
+ * @param yyscanner The scanner object.
+ */
+    void sksl_switch_to_buffer  (YY_BUFFER_STATE  new_buffer , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	/* TODO. We should be able to replace this entire function body
+	 * with
+	 *		skslpop_buffer_state();
+	 *		skslpush_buffer_state(new_buffer);
+     */
+	skslensure_buffer_stack (yyscanner);
+	if ( YY_CURRENT_BUFFER == new_buffer )
+		return;
+
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*yyg->yy_c_buf_p = yyg->yy_hold_char;
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+	sksl_load_buffer_state(yyscanner );
+
+	/* We don't actually know whether we did this switch during
+	 * EOF (skslwrap()) processing, but the only time this flag
+	 * is looked at is after skslwrap() is called, so it's safe
+	 * to go ahead and always set it.
+	 */
+	yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+static void sksl_load_buffer_state  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	yyg->yy_n_chars = YY_CURRENT_BUFFER_LVALUE->yy_n_chars;
+	yyg->yytext_ptr = yyg->yy_c_buf_p = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos;
+	yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file;
+	yyg->yy_hold_char = *yyg->yy_c_buf_p;
+}
+
+/** Allocate and initialize an input buffer state.
+ * @param file A readable stream.
+ * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE.
+ * @param yyscanner The scanner object.
+ * @return the allocated buffer state.
+ */
+    YY_BUFFER_STATE sksl_create_buffer  (FILE * file, int  size , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+    
+	b = (YY_BUFFER_STATE) skslalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in sksl_create_buffer()" );
+
+	b->yy_buf_size = size;
+
+	/* yy_ch_buf has to be 2 characters longer than the size given because
+	 * we need to put in 2 end-of-buffer characters.
+	 */
+	b->yy_ch_buf = (char *) skslalloc(b->yy_buf_size + 2 ,yyscanner );
+	if ( ! b->yy_ch_buf )
+		YY_FATAL_ERROR( "out of dynamic memory in sksl_create_buffer()" );
+
+	b->yy_is_our_buffer = 1;
+
+	sksl_init_buffer(b,file ,yyscanner);
+
+	return b;
+}
+
+/** Destroy the buffer.
+ * @param b a buffer created with sksl_create_buffer()
+ * @param yyscanner The scanner object.
+ */
+    void sksl_delete_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if ( ! b )
+		return;
+
+	if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */
+		YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0;
+
+	if ( b->yy_is_our_buffer )
+		skslfree((void *) b->yy_ch_buf ,yyscanner );
+
+	skslfree((void *) b ,yyscanner );
+}
+
+/* Initializes or reinitializes a buffer.
+ * This function is sometimes called more than once on the same buffer,
+ * such as during a skslrestart() or at EOF.
+ */
+    static void sksl_init_buffer  (YY_BUFFER_STATE  b, FILE * file , yyscan_t yyscanner)
+
+{
+	int oerrno = errno;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	sksl_flush_buffer(b ,yyscanner);
+
+	b->yy_input_file = file;
+	b->yy_fill_buffer = 1;
+
+    /* If b is the current buffer, then sksl_init_buffer was _probably_
+     * called from skslrestart() or through yy_get_next_buffer.
+     * In that case, we don't want to reset the lineno or column.
+     */
+    if (b != YY_CURRENT_BUFFER){
+        b->yy_bs_lineno = 1;
+        b->yy_bs_column = 0;
+    }
+
+        b->yy_is_interactive = 0;
+    
+	errno = oerrno;
+}
+
+/** Discard all buffered characters. On the next scan, YY_INPUT will be called.
+ * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER.
+ * @param yyscanner The scanner object.
+ */
+    void sksl_flush_buffer (YY_BUFFER_STATE  b , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if ( ! b )
+		return;
+
+	b->yy_n_chars = 0;
+
+	/* We always need two end-of-buffer characters.  The first causes
+	 * a transition to the end-of-buffer state.  The second causes
+	 * a jam in that state.
+	 */
+	b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR;
+	b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR;
+
+	b->yy_buf_pos = &b->yy_ch_buf[0];
+
+	b->yy_at_bol = 1;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	if ( b == YY_CURRENT_BUFFER )
+		sksl_load_buffer_state(yyscanner );
+}
+
+/** Pushes the new state onto the stack. The new state becomes
+ *  the current state. This function will allocate the stack
+ *  if necessary.
+ *  @param new_buffer The new state.
+ *  @param yyscanner The scanner object.
+ */
+void skslpush_buffer_state (YY_BUFFER_STATE new_buffer , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if (new_buffer == NULL)
+		return;
+
+	skslensure_buffer_stack(yyscanner);
+
+	/* This block is copied from sksl_switch_to_buffer. */
+	if ( YY_CURRENT_BUFFER )
+		{
+		/* Flush out information for old buffer. */
+		*yyg->yy_c_buf_p = yyg->yy_hold_char;
+		YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
+		YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
+		}
+
+	/* Only push if top exists. Otherwise, replace top. */
+	if (YY_CURRENT_BUFFER)
+		yyg->yy_buffer_stack_top++;
+	YY_CURRENT_BUFFER_LVALUE = new_buffer;
+
+	/* copied from sksl_switch_to_buffer. */
+	sksl_load_buffer_state(yyscanner );
+	yyg->yy_did_buffer_switch_on_eof = 1;
+}
+
+/** Removes and deletes the top of the stack, if present.
+ *  The next element becomes the new top.
+ *  @param yyscanner The scanner object.
+ */
+void skslpop_buffer_state (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+	if (!YY_CURRENT_BUFFER)
+		return;
+
+	sksl_delete_buffer(YY_CURRENT_BUFFER ,yyscanner);
+	YY_CURRENT_BUFFER_LVALUE = NULL;
+	if (yyg->yy_buffer_stack_top > 0)
+		--yyg->yy_buffer_stack_top;
+
+	if (YY_CURRENT_BUFFER) {
+		sksl_load_buffer_state(yyscanner );
+		yyg->yy_did_buffer_switch_on_eof = 1;
+	}
+}
+
+/* Allocates the stack if it does not exist.
+ *  Guarantees space for at least one push.
+ */
+static void skslensure_buffer_stack (yyscan_t yyscanner)
+{
+	yy_size_t num_to_alloc;
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+	if (!yyg->yy_buffer_stack) {
+
+		/* First allocation is just for 2 elements, since we don't know if this
+		 * scanner will even need a stack. We use 2 instead of 1 to avoid an
+		 * immediate realloc on the next call.
+         */
+		num_to_alloc = 1;
+		yyg->yy_buffer_stack = (struct yy_buffer_state**)skslalloc
+								(num_to_alloc * sizeof(struct yy_buffer_state*)
+								, yyscanner);
+		if ( ! yyg->yy_buffer_stack )
+			YY_FATAL_ERROR( "out of dynamic memory in skslensure_buffer_stack()" );
+								  
+		memset(yyg->yy_buffer_stack, 0, num_to_alloc * sizeof(struct yy_buffer_state*));
+				
+		yyg->yy_buffer_stack_max = num_to_alloc;
+		yyg->yy_buffer_stack_top = 0;
+		return;
+	}
+
+	if (yyg->yy_buffer_stack_top >= (yyg->yy_buffer_stack_max) - 1){
+
+		/* Increase the buffer to prepare for a possible push. */
+		int grow_size = 8 /* arbitrary grow size */;
+
+		num_to_alloc = yyg->yy_buffer_stack_max + grow_size;
+		yyg->yy_buffer_stack = (struct yy_buffer_state**)skslrealloc
+								(yyg->yy_buffer_stack,
+								num_to_alloc * sizeof(struct yy_buffer_state*)
+								, yyscanner);
+		if ( ! yyg->yy_buffer_stack )
+			YY_FATAL_ERROR( "out of dynamic memory in skslensure_buffer_stack()" );
+
+		/* zero only the new slots.*/
+		memset(yyg->yy_buffer_stack + yyg->yy_buffer_stack_max, 0, grow_size * sizeof(struct yy_buffer_state*));
+		yyg->yy_buffer_stack_max = num_to_alloc;
+	}
+}
+
+/** Setup the input buffer state to scan directly from a user-specified character buffer.
+ * @param base the character buffer
+ * @param size the size in bytes of the character buffer
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object. 
+ */
+YY_BUFFER_STATE sksl_scan_buffer  (char * base, yy_size_t  size , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+    
+	if ( size < 2 ||
+	     base[size-2] != YY_END_OF_BUFFER_CHAR ||
+	     base[size-1] != YY_END_OF_BUFFER_CHAR )
+		/* They forgot to leave room for the EOB's. */
+		return 0;
+
+	b = (YY_BUFFER_STATE) skslalloc(sizeof( struct yy_buffer_state ) ,yyscanner );
+	if ( ! b )
+		YY_FATAL_ERROR( "out of dynamic memory in sksl_scan_buffer()" );
+
+	b->yy_buf_size = size - 2;	/* "- 2" to take care of EOB's */
+	b->yy_buf_pos = b->yy_ch_buf = base;
+	b->yy_is_our_buffer = 0;
+	b->yy_input_file = 0;
+	b->yy_n_chars = b->yy_buf_size;
+	b->yy_is_interactive = 0;
+	b->yy_at_bol = 1;
+	b->yy_fill_buffer = 0;
+	b->yy_buffer_status = YY_BUFFER_NEW;
+
+	sksl_switch_to_buffer(b ,yyscanner );
+
+	return b;
+}
+
+/** Setup the input buffer state to scan a string. The next call to sksllex() will
+ * scan from a @e copy of @a str.
+ * @param yystr a NUL-terminated string to scan
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ * @note If you want to scan bytes that may contain NUL values, then use
+ *       sksl_scan_bytes() instead.
+ */
+YY_BUFFER_STATE sksl_scan_string (yyconst char * yystr , yyscan_t yyscanner)
+{
+    
+	return sksl_scan_bytes(yystr,strlen(yystr) ,yyscanner);
+}
+
+/** Setup the input buffer state to scan the given bytes. The next call to sksllex() will
+ * scan from a @e copy of @a bytes.
+ * @param bytes the byte buffer to scan
+ * @param len the number of bytes in the buffer pointed to by @a bytes.
+ * @param yyscanner The scanner object.
+ * @return the newly allocated buffer state object.
+ */
+YY_BUFFER_STATE sksl_scan_bytes  (yyconst char * yybytes, yy_size_t  _yybytes_len , yyscan_t yyscanner)
+{
+	YY_BUFFER_STATE b;
+	char *buf;
+	yy_size_t n, i;
+    
+	/* Get memory for full buffer, including space for trailing EOB's. */
+	n = _yybytes_len + 2;
+	buf = (char *) skslalloc(n ,yyscanner );
+	if ( ! buf )
+		YY_FATAL_ERROR( "out of dynamic memory in sksl_scan_bytes()" );
+
+	for ( i = 0; i < _yybytes_len; ++i )
+		buf[i] = yybytes[i];
+
+	buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR;
+
+	b = sksl_scan_buffer(buf,n ,yyscanner);
+	if ( ! b )
+		YY_FATAL_ERROR( "bad buffer in sksl_scan_bytes()" );
+
+	/* It's okay to grow etc. this buffer, and we should throw it
+	 * away when we're done.
+	 */
+	b->yy_is_our_buffer = 1;
+
+	return b;
+}
+
+#ifndef YY_EXIT_FAILURE
+#define YY_EXIT_FAILURE 2
+#endif
+
+static void yy_fatal_error (yyconst char* msg , yyscan_t yyscanner)
+{
+    	(void) fprintf( stderr, "%s\n", msg );
+	exit( YY_EXIT_FAILURE );
+}
+
+/* Redefine yyless() so it works in section 3 code. */
+
+#undef yyless
+#define yyless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up yytext. */ \
+        int yyless_macro_arg = (n); \
+        YY_LESS_LINENO(yyless_macro_arg);\
+		yytext[yyleng] = yyg->yy_hold_char; \
+		yyg->yy_c_buf_p = yytext + yyless_macro_arg; \
+		yyg->yy_hold_char = *yyg->yy_c_buf_p; \
+		*yyg->yy_c_buf_p = '\0'; \
+		yyleng = yyless_macro_arg; \
+		} \
+	while ( 0 )
+
+/* Accessor  methods (get/set functions) to struct members. */
+
+/** Get the user-defined data for this scanner.
+ * @param yyscanner The scanner object.
+ */
+YY_EXTRA_TYPE skslget_extra  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyextra;
+}
+
+/** Get the current line number.
+ * @param yyscanner The scanner object.
+ */
+int skslget_lineno  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    
+        if (! YY_CURRENT_BUFFER)
+            return 0;
+    
+    return yylineno;
+}
+
+/** Get the current column number.
+ * @param yyscanner The scanner object.
+ */
+int skslget_column  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    
+        if (! YY_CURRENT_BUFFER)
+            return 0;
+    
+    return yycolumn;
+}
+
+/** Get the input stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *skslget_in  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyin;
+}
+
+/** Get the output stream.
+ * @param yyscanner The scanner object.
+ */
+FILE *skslget_out  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyout;
+}
+
+/** Get the length of the current token.
+ * @param yyscanner The scanner object.
+ */
+yy_size_t skslget_leng  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yyleng;
+}
+
+/** Get the current token.
+ * @param yyscanner The scanner object.
+ */
+
+char *skslget_text  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yytext;
+}
+
+/** Set the user-defined data. This data is never touched by the scanner.
+ * @param user_defined The data to be associated with this scanner.
+ * @param yyscanner The scanner object.
+ */
+void skslset_extra (YY_EXTRA_TYPE  user_defined , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyextra = user_defined ;
+}
+
+/** Set the current line number.
+ * @param line_number
+ * @param yyscanner The scanner object.
+ */
+void skslset_lineno (int  line_number , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        /* lineno is only valid if an input buffer exists. */
+        if (! YY_CURRENT_BUFFER )
+           yy_fatal_error( "skslset_lineno called with no buffer" , yyscanner); 
+    
+    yylineno = line_number;
+}
+
+/** Set the current column.
+ * @param line_number
+ * @param yyscanner The scanner object.
+ */
+void skslset_column (int  column_no , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+        /* column is only valid if an input buffer exists. */
+        if (! YY_CURRENT_BUFFER )
+           yy_fatal_error( "skslset_column called with no buffer" , yyscanner); 
+    
+    yycolumn = column_no;
+}
+
+/** Set the input stream. This does not discard the current
+ * input buffer.
+ * @param in_str A readable stream.
+ * @param yyscanner The scanner object.
+ * @see sksl_switch_to_buffer
+ */
+void skslset_in (FILE *  in_str , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyin = in_str ;
+}
+
+void skslset_out (FILE *  out_str , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yyout = out_str ;
+}
+
+int skslget_debug  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    return yy_flex_debug;
+}
+
+void skslset_debug (int  bdebug , yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    yy_flex_debug = bdebug ;
+}
+
+/* Accessor methods for yylval and yylloc */
+
+/* User-visible API */
+
+/* sksllex_init is special because it creates the scanner itself, so it is
+ * the ONLY reentrant function that doesn't take the scanner as the last argument.
+ * That's why we explicitly handle the declaration, instead of using our macros.
+ */
+
+int sksllex_init(yyscan_t* ptr_yy_globals)
+
+{
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+
+    *ptr_yy_globals = (yyscan_t) skslalloc ( sizeof( struct yyguts_t ), NULL );
+
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+
+    /* By setting to 0xAA, we expose bugs in yy_init_globals. Leave at 0x00 for releases. */
+    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
+/* sksllex_init_extra has the same functionality as sksllex_init, but follows the
+ * convention of taking the scanner as the last argument. Note however, that
+ * this is a *pointer* to a scanner, as it will be allocated by this call (and
+ * is the reason, too, why this function also must handle its own declaration).
+ * The user defined value in the first argument will be available to skslalloc in
+ * the yyextra field.
+ */
+
+int sksllex_init_extra(YY_EXTRA_TYPE yy_user_defined,yyscan_t* ptr_yy_globals )
+
+{
+    struct yyguts_t dummy_yyguts;
+
+    skslset_extra (yy_user_defined, &dummy_yyguts);
+
+    if (ptr_yy_globals == NULL){
+        errno = EINVAL;
+        return 1;
+    }
+	
+    *ptr_yy_globals = (yyscan_t) skslalloc ( sizeof( struct yyguts_t ), &dummy_yyguts );
+	
+    if (*ptr_yy_globals == NULL){
+        errno = ENOMEM;
+        return 1;
+    }
+    
+    /* By setting to 0xAA, we expose bugs in
+    yy_init_globals. Leave at 0x00 for releases. */
+    memset(*ptr_yy_globals,0x00,sizeof(struct yyguts_t));
+    
+    skslset_extra (yy_user_defined, *ptr_yy_globals);
+    
+    return yy_init_globals ( *ptr_yy_globals );
+}
+
+static int yy_init_globals (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+    /* Initialization is the same as for the non-reentrant scanner.
+     * This function is called from sksllex_destroy(), so don't allocate here.
+     */
+
+    yyg->yy_buffer_stack = 0;
+    yyg->yy_buffer_stack_top = 0;
+    yyg->yy_buffer_stack_max = 0;
+    yyg->yy_c_buf_p = (char *) 0;
+    yyg->yy_init = 0;
+    yyg->yy_start = 0;
+
+    yyg->yy_start_stack_ptr = 0;
+    yyg->yy_start_stack_depth = 0;
+    yyg->yy_start_stack =  NULL;
+
+/* Defined in main.c */
+#ifdef YY_STDINIT
+    yyin = stdin;
+    yyout = stdout;
+#else
+    yyin = (FILE *) 0;
+    yyout = (FILE *) 0;
+#endif
+
+    /* For future reference: Set errno on error, since we are called by
+     * sksllex_init()
+     */
+    return 0;
+}
+
+/* sksllex_destroy is for both reentrant and non-reentrant scanners. */
+int sksllex_destroy  (yyscan_t yyscanner)
+{
+    struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
+
+    /* Pop the buffer stack, destroying each element. */
+	while(YY_CURRENT_BUFFER){
+		sksl_delete_buffer(YY_CURRENT_BUFFER ,yyscanner );
+		YY_CURRENT_BUFFER_LVALUE = NULL;
+		skslpop_buffer_state(yyscanner);
+	}
+
+	/* Destroy the stack itself. */
+	skslfree(yyg->yy_buffer_stack ,yyscanner);
+	yyg->yy_buffer_stack = NULL;
+
+    /* Destroy the start condition stack. */
+        skslfree(yyg->yy_start_stack ,yyscanner );
+        yyg->yy_start_stack = NULL;
+
+    /* Reset the globals. This is important in a non-reentrant scanner so the next time
+     * sksllex() is called, initialization will occur. */
+    yy_init_globals( yyscanner);
+
+    /* Destroy the main struct (reentrant only). */
+    skslfree ( yyscanner , yyscanner );
+    yyscanner = NULL;
+    return 0;
+}
+
+/*
+ * Internal utility routines.
+ */
+
+#ifndef yytext_ptr
+static void yy_flex_strncpy (char* s1, yyconst char * s2, int n , yyscan_t yyscanner)
+{
+	register int i;
+	for ( i = 0; i < n; ++i )
+		s1[i] = s2[i];
+}
+#endif
+
+#ifdef YY_NEED_STRLEN
+static int yy_flex_strlen (yyconst char * s , yyscan_t yyscanner)
+{
+	register int n;
+	for ( n = 0; s[n]; ++n )
+		;
+
+	return n;
+}
+#endif
+
+void *skslalloc (yy_size_t  size , yyscan_t yyscanner)
+{
+	return (void *) malloc( size );
+}
+
+void *skslrealloc  (void * ptr, yy_size_t  size , yyscan_t yyscanner)
+{
+	/* The cast to (char *) in the following accommodates both
+	 * implementations that use char* generic pointers, and those
+	 * that use void* generic pointers.  It works with the latter
+	 * because both ANSI C and C++ allow castless assignment from
+	 * any pointer type to void*, and deal with argument conversions
+	 * as though doing an assignment.
+	 */
+	return (void *) realloc( (char *) ptr, size );
+}
+
+void skslfree (void * ptr , yyscan_t yyscanner)
+{
+	free( (char *) ptr );	/* see skslrealloc() for (char *) cast */
+}
+
+#define YYTABLES_NAME "yytables"
+
+#line 181 "sksl.flex"
+
+
+
+int skslwrap(yyscan_t scanner) {
+    return 1; // terminate
+}
+
diff --git a/src/sksl/sksl.flex b/src/sksl/sksl.flex
new file mode 100644
index 0000000..cbc938a
--- /dev/null
+++ b/src/sksl/sksl.flex
@@ -0,0 +1,187 @@
+/*
+
+	This file is IGNORED during the build process!
+
+	As this file is updated so infrequently and flex is not universally present on build machines,
+	the lex.sksl.c file must be manually regenerated if you make any changes to this file. Just run:
+
+		flex sksl.flex
+
+    You will have to manually add a copyright notice to the top of lex.sksl.c.
+    
+*/
+
+%option prefix="sksl"
+%option reentrant
+%option yylineno
+%option never-interactive
+%option nounistd
+
+DIGIT  [0-9]
+LETTER [a-zA-Z_$]
+
+%%
+
+{DIGIT}*"."{DIGIT}+([eE][+-]?{DIGIT}+)? { return SkSL::Token::FLOAT_LITERAL; }
+
+{DIGIT}+"."{DIGIT}*([eE][+-]?{DIGIT}+)? { return SkSL::Token::FLOAT_LITERAL; }
+
+{DIGIT}+([eE][+-]?{DIGIT}+) { return SkSL::Token::FLOAT_LITERAL; }
+
+{DIGIT}+ { return SkSL::Token::INT_LITERAL; }
+
+true { return SkSL::Token::TRUE_LITERAL; }
+
+false { return SkSL::Token::FALSE_LITERAL; }
+
+if { return SkSL::Token::IF; }
+
+else { return SkSL::Token::ELSE; }
+
+for { return SkSL::Token::FOR; }
+
+while { return SkSL::Token::WHILE; }
+
+do { return SkSL::Token::DO; }
+
+break { return SkSL::Token::BREAK; }
+
+continue { return SkSL::Token::CONTINUE; }
+
+discard { return SkSL::Token::DISCARD; }
+
+return { return SkSL::Token::RETURN; }
+
+in { return SkSL::Token::IN; }
+
+out { return SkSL::Token::OUT; }
+
+inout { return SkSL::Token::INOUT; }
+
+uniform { return SkSL::Token::UNIFORM; }
+
+const { return SkSL::Token::CONST; }
+
+lowp { return SkSL::Token::LOWP; }
+
+mediump { return SkSL::Token::MEDIUMP; }
+
+highp { return SkSL::Token::HIGHP; }
+
+struct { return SkSL::Token::STRUCT; }
+
+layout { return SkSL::Token::LAYOUT; }
+
+precision { return SkSL::Token::PRECISION; }
+
+{LETTER}({DIGIT}|{LETTER})* { return SkSL::Token::IDENTIFIER; }
+
+"#"{LETTER}({DIGIT}|{LETTER})* { return SkSL::Token::DIRECTIVE; }
+
+"(" { return SkSL::Token::LPAREN; }
+
+")" { return SkSL::Token::RPAREN; }
+
+"{" { return SkSL::Token::LBRACE; }
+
+"}" { return SkSL::Token::RBRACE; }
+
+"[" { return SkSL::Token::LBRACKET; }
+
+"]" { return SkSL::Token::RBRACKET; }
+
+"." { return SkSL::Token::DOT; }
+
+"," { return SkSL::Token::COMMA; }
+
+"++" { return SkSL::Token::PLUSPLUS; }
+
+"--" { return SkSL::Token::MINUSMINUS; }
+
+"+" { return SkSL::Token::PLUS; }
+
+"-" { return SkSL::Token::MINUS; }
+
+"*" { return SkSL::Token::STAR; }
+
+"/" { return SkSL::Token::SLASH; }
+
+"%" { return SkSL::Token::PERCENT; }
+
+"<<" { return SkSL::Token::SHL; }
+
+">>" { return SkSL::Token::SHR; }
+
+"|" { return SkSL::Token::BITWISEOR; }
+
+"^" { return SkSL::Token::BITWISEXOR; }
+
+"&" { return SkSL::Token::BITWISEAND; }
+
+"||" { return SkSL::Token::LOGICALOR; }
+
+"^^" { return SkSL::Token::LOGICALXOR; }
+
+"&&" { return SkSL::Token::LOGICALAND; }
+
+"!" { return SkSL::Token::NOT; }
+
+"?" { return SkSL::Token::QUESTION; }
+
+":" { return SkSL::Token::COLON; }
+
+"=" { return SkSL::Token::EQ; }
+
+"==" { return SkSL::Token::EQEQ; }
+
+"!=" { return SkSL::Token::NEQ; }
+
+">" { return SkSL::Token::GT; }
+
+"<" { return SkSL::Token::LT; }
+
+">=" { return SkSL::Token::GTEQ; }
+
+"<=" { return SkSL::Token::LTEQ; }
+
+"+=" { return SkSL::Token::PLUSEQ; }
+
+"-=" { return SkSL::Token::MINUSEQ; }
+
+"*=" { return SkSL::Token::STAREQ; }
+
+"/=" { return SkSL::Token::SLASHEQ; }
+
+"%=" { return SkSL::Token::PERCENTEQ; }
+
+"<<=" { return SkSL::Token::SHLEQ; }
+
+">>=" { return SkSL::Token::SHREQ; }
+
+"|=" { return SkSL::Token::BITWISEOREQ; }
+
+"^=" { return SkSL::Token::BITWISEXOREQ; }
+
+"&=" { return SkSL::Token::BITWISEANDEQ; }
+
+"||=" { return SkSL::Token::LOGICALOREQ; }
+
+"^^=" { return SkSL::Token::LOGICALXOREQ; }
+
+"&&=" { return SkSL::Token::LOGICALANDEQ; }
+
+";" { return SkSL::Token::SEMICOLON; }
+
+"//".* /* line comment */
+
+"/*"([^*]|"*"[^/])*"*/" /* block comment */
+
+[ \t\r\n]+  /* whitespace */
+
+.    { return SkSL::Token::INVALID_TOKEN; }
+
+%%
+
+int skslwrap(yyscan_t scanner) {
+    return 1; // terminate
+}
diff --git a/src/sksl/sksl.include b/src/sksl/sksl.include
new file mode 100644
index 0000000..bf35f22
--- /dev/null
+++ b/src/sksl/sksl.include
@@ -0,0 +1,543 @@
+STRINGIFY(
+
+// defines built-in functions supported by SkiaSL
+
+$genType radians($genType degrees);
+$genType sin($genType angle);
+$genType cos($genType angle);
+$genType tan($genType angle);
+$genType asin($genType x);
+$genType acos($genType x);
+$genType atan($genType y, $genType x);
+$genType atan($genType y_over_x);
+$genType sinh($genType x);
+$genType cosh($genType x);
+$genType tanh($genType x);
+$genType asinh($genType x);
+$genType acosh($genType x);
+$genType atanh($genType x);
+$genType pow($genType x, $genType y);
+$genType exp($genType x);
+$genType log($genType x);
+$genType exp2($genType x);
+$genType log2($genType x);
+$genType sqrt($genType x);
+$genDType sqrt($genDType x);
+$genType inversesqrt($genType x);
+$genDType inversesqrt($genDType x);
+$genType abs($genType x);
+$genIType abs($genIType x);
+$genDType abs($genDType x);
+$genType sign($genType x);
+$genIType sign($genIType x);
+$genDType sign($genDType x);
+$genType floor($genType x);
+$genDType floor($genDType x);
+$genType trunc($genType x);
+$genDType trunc($genDType x);
+$genType round($genType x);
+$genDType round($genDType x);
+$genType roundEven($genType x);
+$genDType roundEven($genDType x);
+$genType ceil($genType x);
+$genDType ceil($genDType x);
+$genType fract($genType x);
+$genDType fract($genDType x);
+$genType mod($genType x, float y);
+$genType mod($genType x, $genType y);
+$genDType mod($genDType x, double y);
+$genDType mod($genDType x, $genDType y);
+$genType modf($genType x, out $genType i);
+$genDType modf($genDType x, out $genDType i);
+$genType min($genType x, $genType y);
+$genType min($genType x, float y);
+$genDType min($genDType x, $genDType y);
+$genDType min($genDType x, double y);
+$genIType min($genIType x, $genIType y);
+$genIType min($genIType x, int y);
+$genUType min($genUType x, $genUType y);
+$genUType min($genUType x, uint y);
+$genType max($genType x, $genType y);
+$genType max($genType x, float y);
+$genDType max($genDType x, $genDType y);
+$genDType max($genDType x, double y);
+$genIType max($genIType x, $genIType y);
+$genIType max($genIType x, int y);
+$genUType max($genUType x, $genUType y);
+$genUType max($genUType x, uint y);
+$genType clamp($genType x, $genType minVal, $genType maxVal);
+$genType clamp($genType x, float minVal, float maxVal);
+$genDType clamp($genDType x, $genDType minVal, $genDType maxVal);
+$genDType clamp($genDType x, double minVal, double maxVal);
+$genIType clamp($genIType x, $genIType minVal, $genIType maxVal);
+$genIType clamp($genIType x, int minVal, int maxVal);
+$genUType clamp($genUType x, $genUType minVal, $genUType maxVal);
+$genUType clamp($genUType x, uint minVal, uint maxVal);
+$genType mix($genType x, $genType y, $genType a);
+$genType mix($genType x, $genType y, float a);
+$genDType mix($genDType x, $genDType y, $genDType a);
+$genDType mix($genDType x, $genDType y, double a);
+$genType mix($genType x, $genType y, $genBType a);
+$genDType mix($genDType x, $genDType y, $genBType a);
+$genIType mix($genIType x, $genIType y, $genBType a);
+$genUType mix($genUType x, $genUType y, $genBType a);
+$genBType mix($genBType x, $genBType y, $genBType a);
+$genType step($genType edge, $genType x);
+$genType step(float edge, $genType x);
+$genDType step($genDType edge, $genDType x);
+$genDType step(double edge, $genDType x);
+$genType smoothstep($genType edge0, $genType edge1, $genType x);
+$genType smoothstep(float edge0, float edge1, $genType x);
+$genDType smoothstep($genDType edge0, $genDType edge1, $genDType x);
+$genDType smoothstep(double edge0, double edge1, $genDType x);
+$genBType isnan($genType x);
+$genBType isnan($genDType x);
+$genBType isinf($genType x);
+$genBType isinf($genDType x);
+$genIType floatBitsToInt($genType value);
+$genUType floatBitsToUint($genType value);
+$genType intBitsToFloat($genIType value);
+$genType uintBitsToFloat($genUType value);
+$genType fma($genType a, $genType b, $genType c);
+$genDType fma($genDType a, $genDType b, $genDType c);
+$genType frexp($genType x, out $genIType exp);
+$genDType frexp($genDType x, out $genIType exp);
+$genType ldexp($genType x, in $genIType exp);
+$genDType ldexp($genDType x, in $genIType exp);
+uint packUnorm2x16(vec2 v);
+uint packSnorm2x16(vec2 v);
+uint packUnorm4x8(vec4 v);
+uint packSnorm4x8(vec4 v);
+vec2 unpackUnorm2x16(uint p);
+vec2 unpackSnorm2x16(uint p);
+vec4 unpackUnorm4x8(uint p);
+vec4 unpackSnorm4x8(uint p);
+double packDouble2x32(uvec2 v);
+uvec2 unpackDouble2x32(double v);
+uint packHalf2x16(vec2 v);
+vec2 unpackHalf2x16(uint v);
+float length($genType x);
+double length($genDType x);
+float distance($genType p0, $genType p1);
+double distance($genDType p0, $genDType p1);
+float dot($genType x, $genType y);
+double dot($genDType x, $genDType y);
+vec3 cross(vec3 x, vec3 y);
+dvec3 cross(dvec3 x, dvec3 y);
+$genType normalize($genType x);
+$genDType normalize($genDType x);
+vec4 ftransform();
+$genType faceforward($genType N, $genType I, $genType Nref);
+$genDType faceforward($genDType N, $genDType I, $genDType Nref);
+$genType reflect($genType I, $genType N);
+$genDType reflect($genDType I, $genDType N);
+$genType refract($genType I, $genType N, float eta);
+$genDType refract($genDType I, $genDType N, float eta);
+$mat matrixCompMult($mat x, $mat y);
+mat2 outerProduct(vec2 c, vec2 r);
+mat3 outerProduct(vec3 c, vec3 r);
+mat4 outerProduct(vec4 c, vec4 r);
+mat2x3 outerProduct(vec3 c, vec2 r);
+mat3x2 outerProduct(vec2 c, vec3 r);
+mat2x4 outerProduct(vec4 c, vec2 r);
+mat4x2 outerProduct(vec2 c, vec4 r);
+mat3x4 outerProduct(vec4 c, vec3 r);
+mat4x3 outerProduct(vec3 c, vec4 r);
+mat2 transpose(mat2 m);
+mat3 transpose(mat3 m);
+mat4 transpose(mat4 m);
+mat2x3 transpose(mat3x2 m);
+mat3x2 transpose(mat2x3 m);
+mat2x4 transpose(mat4x2 m);
+mat4x2 transpose(mat2x4 m);
+mat3x4 transpose(mat4x3 m);
+mat4x3 transpose(mat3x4 m);
+float determinant(mat2 m);
+float determinant(mat3 m);
+float determinant(mat4 m);
+mat2 inverse(mat2 m);
+mat3 inverse(mat3 m);
+mat4 inverse(mat4 m);
+$bvec lessThan($vec x, $vec y);
+$bvec lessThan($ivec x, $ivec y);
+$bvec lessThan($uvec x, $uvec y);
+$bvec lessThanEqual($vec x, $vec y);
+$bvec lessThanEqual($ivec x, $ivec y);
+$bvec lessThanEqual($uvec x, $uvec y);
+$bvec greaterThan($vec x, $vec y);
+$bvec greaterThan($ivec x, $ivec y);
+$bvec greaterThan($uvec x, $uvec y);
+$bvec greaterThanEqual($vec x, $vec y);
+$bvec greaterThanEqual($ivec x, $ivec y);
+$bvec greaterThanEqual($uvec x, $uvec y);
+$bvec equal($vec x, $vec y);
+$bvec equal($ivec x, $ivec y);
+$bvec equal($uvec x, $uvec y);
+$bvec equal($bvec x, $bvec y);
+$bvec notEqual($vec x, $vec y);
+$bvec notEqual($ivec x, $ivec y);
+$bvec notEqual($uvec x, $uvec y);
+$bvec notEqual($bvec x, $bvec y);
+bool any($bvec x);
+bool all($bvec x);
+$bvec not($bvec x);
+$genUType uaddCarry($genUType x, $genUType y, out $genUType carry);
+$genUType usubBorrow($genUType x, $genUType y, out $genUType borrow);
+void umulExtended($genUType x, $genUType y, out $genUType msb, out $genUType lsb);
+void imulExtended($genIType x, $genIType y, out $genIType msb, out $genIType lsb);
+$genIType bitfieldExtract($genIType value, int offset, int bits);
+$genUType bitfieldExtract($genUType value, int offset, int bits);
+$genIType bitfieldInsert($genIType base, $genIType insert, int offset, int bits);
+$genUType bitfieldInsert($genUType base, $genUType insert, int offset, int bits);
+$genIType bitfieldReverse($genIType value);
+$genUType bitfieldReverse($genUType value);
+$genIType bitCount($genIType value);
+$genIType bitCount($genUType value);
+$genIType findLSB($genIType value);
+$genIType findLSB($genUType value);
+$genIType findMSB($genIType value);
+$genIType findMSB($genUType value);
+int textureSize($gsampler1D sampler, int lod);
+ivec2 textureSize($gsampler2D sampler, int lod);
+ivec3 textureSize($gsampler3D sampler, int lod);
+ivec2 textureSize($gsamplerCube sampler, int lod);
+int textureSize(sampler1DShadow sampler, int lod);
+ivec2 textureSize(sampler2DShadow sampler, int lod);
+ivec2 textureSize(samplerCubeShadow sampler, int lod);
+ivec3 textureSize($gsamplerCubeArray sampler, int lod);
+ivec3 textureSize(samplerCubeArrayShadow sampler, int lod);
+ivec2 textureSize($gsampler2DRect sampler);
+ivec2 textureSize(sampler2DRectShadow sampler);
+ivec2 textureSize($gsampler1DArray sampler, int lod);
+ivec3 textureSize($gsampler2DArray sampler, int lod);
+ivec2 textureSize(sampler1DArrayShadow sampler, int lod);
+ivec3 textureSize(sampler2DArrayShadow sampler, int lod);
+int textureSize($gsamplerBuffer sampler);
+ivec2 textureSize($gsampler2DMS sampler);
+ivec3 textureSize($gsampler2DMSArray sampler);
+vec2 textureQueryLod($gsampler1D sampler, float P);
+vec2 textureQueryLod($gsampler2D sampler, vec2 P);
+vec2 textureQueryLod($gsampler3D sampler, vec3 P);
+vec2 textureQueryLod($gsamplerCube sampler, vec3 P);
+vec2 textureQueryLod($gsampler1DArray sampler, float P);
+vec2 textureQueryLod($gsampler2DArray sampler, vec2 P);
+vec2 textureQueryLod($gsamplerCubeArray sampler, vec3 P);
+vec2 textureQueryLod(sampler1DShadow sampler, float P);
+vec2 textureQueryLod(sampler2DShadow sampler, vec2 P);
+vec2 textureQueryLod(samplerCubeShadow sampler, vec3 P);
+vec2 textureQueryLod(sampler1DArrayShadow sampler, float P);
+vec2 textureQueryLod(sampler2DArrayShadow sampler, vec2 P);
+vec2 textureQueryLod(samplerCubeArrayShadow sampler, vec3 P);
+int textureQueryLevels($gsampler1D sampler);
+int textureQueryLevels($gsampler2D sampler);
+int textureQueryLevels($gsampler3D sampler);
+int textureQueryLevels($gsamplerCube sampler);
+int textureQueryLevels($gsampler1DArray sampler);
+int textureQueryLevels($gsampler2DArray sampler);
+int textureQueryLevels($gsamplerCubeArray sampler);
+int textureQueryLevels(sampler1DShadow sampler);
+int textureQueryLevels(sampler2DShadow sampler);
+int textureQueryLevels(samplerCubeShadow sampler);
+int textureQueryLevels(sampler1DArrayShadow sampler);
+int textureQueryLevels(sampler2DArrayShadow sampler);
+int textureQueryLevels(samplerCubeArrayShadow sampler);
+$gvec4 texture($gsampler1D sampler, float P);
+$gvec4 texture($gsampler1D sampler, float P, float bias);
+$gvec4 texture($gsampler2D sampler, vec2 P);
+$gvec4 texture($gsampler2D sampler, vec2 P, float bias);
+$gvec4 texture($gsampler3D sampler, vec3 P);
+$gvec4 texture($gsampler3D sampler, vec3 P, float bias);
+$gvec4 texture($gsamplerCube sampler, vec3 P);
+$gvec4 texture($gsamplerCube sampler, vec3 P, float bias);
+float texture(sampler1DShadow sampler, vec3 P);
+float texture(sampler1DShadow sampler, vec3 P, float bias);
+float texture(sampler2DShadow sampler, vec3 P);
+float texture(sampler2DShadow sampler, vec3 P, float bias);
+float texture(samplerCubeShadow sampler, vec4 P);
+float texture(samplerCubeShadow sampler, vec4 P, float bias);
+$gvec4 texture($gsampler1DArray sampler, vec2 P);
+$gvec4 texture($gsampler1DArray sampler, vec2 P, float bias);
+$gvec4 texture($gsampler2DArray sampler, vec3 P);
+$gvec4 texture($gsampler2DArray sampler, vec3 P, float bias);
+$gvec4 texture($gsamplerCubeArray sampler, vec4 P);
+$gvec4 texture($gsamplerCubeArray sampler, vec4 P, float bias);
+float texture(sampler1DArrayShadow sampler, vec3 P);
+float texture(sampler1DArrayShadow sampler, vec3 P, float bias);
+float texture(sampler2DArrayShadow sampler, vec4 P);
+$gvec4 texture($gsampler2DRect sampler, vec2 P);
+float texture(sampler2DRectShadow sampler, vec3 P);
+float texture($gsamplerCubeArrayShadow sampler, vec4 P, float compare);
+
+)
+
+// split into multiple chunks, as MSVC++ complains if a single string is too long
+
+STRINGIFY(
+
+$gvec4 textureProj($gsampler1D sampler, vec2 P);
+$gvec4 textureProj($gsampler1D sampler, vec2 P, float bias);
+$gvec4 textureProj($gsampler1D sampler, vec4 P);
+$gvec4 textureProj($gsampler1D sampler, vec4 P, float bias);
+$gvec4 textureProj($gsampler2D sampler, vec3 P);
+$gvec4 textureProj($gsampler2D sampler, vec3 P, float bias);
+$gvec4 textureProj($gsampler2D sampler, vec4 P);
+$gvec4 textureProj($gsampler2D sampler, vec4 P, float bias);
+$gvec4 textureProj($gsampler3D sampler, vec4 P);
+$gvec4 textureProj($gsampler3D sampler, vec4 P, float bias);
+float textureProj(sampler1DShadow sampler, vec4 P);
+float textureProj(sampler1DShadow sampler, vec4 P, float bias);
+float textureProj(sampler2DShadow sampler, vec4 P);
+float textureProj(sampler2DShadow sampler, vec4 P, float bias);
+$gvec4 textureProj($gsampler2DRect sampler, vec3 P);
+$gvec4 textureProj($gsampler2DRect sampler, vec4 P);
+float textureProj(sampler2DRectShadow sampler, vec4 P);
+$gvec4 textureLod($gsampler1D sampler, float P, float lod);
+$gvec4 textureLod($gsampler2D sampler, vec2 P, float lod);
+$gvec4 textureLod($gsampler3D sampler, vec3 P, float lod);
+$gvec4 textureLod($gsamplerCube sampler, vec3 P, float lod);
+float textureLod(sampler1DShadow sampler, vec3 P, float lod);
+float textureLod(sampler2DShadow sampler, vec3 P, float lod);
+$gvec4 textureLod($gsampler1DArray sampler, vec2 P, float lod);
+$gvec4 textureLod($gsampler2DArray sampler, vec3 P, float lod);
+float textureLod(sampler1DArrayShadow sampler, vec3 P, float lod);
+$gvec4 textureLod($gsamplerCubeArray sampler, vec4 P, float lod);
+$gvec4 textureOffset($gsampler1D sampler, float P, int offset);
+$gvec4 textureOffset($gsampler1D sampler, float P, int offset, float bias);
+$gvec4 textureOffset($gsampler2D sampler, vec2 P, ivec2 offset);
+$gvec4 textureOffset($gsampler2D sampler, vec2 P, ivec2 offset, float bias);
+$gvec4 textureOffset($gsampler3D sampler, vec3 P, ivec3 offset);
+$gvec4 textureOffset($gsampler3D sampler, vec3 P, ivec3 offset, float bias);
+$gvec4 textureOffset($gsampler2DRect sampler, vec2 P, ivec2 offset);
+float textureOffset(sampler2DRectShadow sampler, vec3 P, ivec2 offset);
+float textureOffset(sampler1DShadow sampler, vec3 P, int offset);
+float textureOffset(sampler1DShadow sampler, vec3 P, int offset, float bias);
+float textureOffset(sampler2DShadow sampler, vec3 P, ivec2 offset);
+float textureOffset(sampler2DShadow sampler, vec3 P, ivec2 offset, float bias);
+$gvec4 textureOffset($gsampler1DArray sampler, vec2 P, int offset);
+$gvec4 textureOffset($gsampler1DArray sampler, vec2 P, int offset, float bias);
+$gvec4 textureOffset($gsampler2DArray sampler, vec3 P, ivec2 offset);
+$gvec4 textureOffset($gsampler2DArray sampler, vec3 P, ivec2 offset, float bias);
+float textureOffset(sampler1DArrayShadow sampler, vec3 P, int offset);
+float textureOffset(sampler1DArrayShadow sampler, vec3 P, int offset, float bias);
+float textureOffset(sampler2DArrayShadow sampler, vec4 P, ivec2 offset);
+$gvec4 texelFetch($gsampler1D sampler, int P, int lod);
+$gvec4 texelFetch($gsampler2D sampler, ivec2 P, int lod);
+$gvec4 texelFetch($gsampler3D sampler, ivec3 P, int lod);
+$gvec4 texelFetch($gsampler2DRect sampler, ivec2 P);
+$gvec4 texelFetch($gsampler1DArray sampler, ivec2 P, int lod);
+$gvec4 texelFetch($gsampler2DArray sampler, ivec3 P, int lod);
+$gvec4 texelFetch($gsamplerBuffer sampler, int P);
+$gvec4 texelFetch($gsampler2DMS sampler, ivec2 P, int sample);
+$gvec4 texelFetch($gsampler2DMSArray sampler, ivec3 P, int sample);
+$gvec4 texelFetchOffset($gsampler1D sampler, int P, int lod, int offset);
+$gvec4 texelFetchOffset($gsampler2D sampler, ivec2 P, int lod, ivec2 offset);
+$gvec4 texelFetchOffset($gsampler3D sampler, ivec3 P, int lod, ivec3 offset);
+$gvec4 texelFetchOffset($gsampler2DRect sampler, ivec2 P, ivec2 offset);
+$gvec4 texelFetchOffset($gsampler1DArray sampler, ivec2 P, int lod, int offset);
+$gvec4 texelFetchOffset($gsampler2DArray sampler, ivec3 P, int lod, ivec2 offset);
+$gvec4 textureProjOffset($gsampler1D sampler, vec2 P, int offset);
+$gvec4 textureProjOffset($gsampler1D sampler, vec2 P, int offset, float bias);
+$gvec4 textureProjOffset($gsampler1D sampler, vec4 P, int offset);
+$gvec4 textureProjOffset($gsampler1D sampler, vec4 P, int offset, float bias);
+$gvec4 textureProjOffset($gsampler2D sampler, vec3 P, ivec2 offset);
+$gvec4 textureProjOffset($gsampler2D sampler, vec3 P, ivec2 offset, float bias);
+$gvec4 textureProjOffset($gsampler2D sampler, vec4 P, ivec2 offset);
+$gvec4 textureProjOffset($gsampler2D sampler, vec4 P, ivec2 offset, float bias);
+$gvec4 textureProjOffset($gsampler3D sampler, vec4 P, ivec3 offset);
+$gvec4 textureProjOffset($gsampler3D sampler, vec4 P, ivec3 offset, float bias);
+$gvec4 textureProjOffset($gsampler2DRect sampler, vec3 P, ivec2 offset);
+$gvec4 textureProjOffset($gsampler2DRect sampler, vec4 P, ivec2 offset);
+float textureProjOffset(sampler2DRectShadow sampler, vec4 P, ivec2 offset);
+float textureProjOffset(sampler1DShadow sampler, vec4 P, int offset);
+float textureProjOffset(sampler1DShadow sampler, vec4 P, int offset, float bias);
+float textureProjOffset(sampler2DShadow sampler, vec4 P, ivec2 offset);
+float textureProjOffset(sampler2DShadow sampler, vec4 P, ivec2 offset, float bias);
+$gvec4 textureLodOffset($gsampler1D sampler, float P, float lod, int offset);
+$gvec4 textureLodOffset($gsampler2D sampler, vec2 P, float lod, ivec2 offset);
+$gvec4 textureLodOffset($gsampler3D sampler, vec3 P, float lod, ivec3 offset);
+float textureLodOffset(sampler1DShadow sampler, vec3 P, float lod, int offset);
+float textureLodOffset(sampler2DShadow sampler, vec3 P, float lod, ivec2 offset);
+$gvec4 textureLodOffset($gsampler1DArray sampler, vec2 P, float lod, int offset);
+$gvec4 textureLodOffset($gsampler2DArray sampler, vec3 P, float lod, ivec2 offset);
+float textureLodOffset(sampler1DArrayShadow sampler, vec3 P, float lod, int offset);
+$gvec4 textureProjLod($gsampler1D sampler, vec2 P, float lod);
+$gvec4 textureProjLod($gsampler1D sampler, vec4 P, float lod);
+$gvec4 textureProjLod($gsampler2D sampler, vec3 P, float lod);
+$gvec4 textureProjLod($gsampler2D sampler, vec4 P, float lod);
+$gvec4 textureProjLod($gsampler3D sampler, vec4 P, float lod);
+float textureProjLod(sampler1DShadow sampler, vec4 P, float lod);
+float textureProjLod(sampler2DShadow sampler, vec4 P, float lod);
+$gvec4 textureProjLodOffset($gsampler1D sampler, vec2 P, float lod, int offset);
+$gvec4 textureProjLodOffset($gsampler1D sampler, vec4 P, float lod, int offset);
+$gvec4 textureProjLodOffset($gsampler2D sampler, vec3 P, float lod, ivec2 offset);
+$gvec4 textureProjLodOffset($gsampler2D sampler, vec4 P, float lod, ivec2 offset);
+$gvec4 textureProjLodOffset($gsampler3D sampler, vec4 P, float lod, ivec3 offset);
+float textureProjLodOffset(sampler1DShadow sampler, vec4 P, float lod, int offset);
+float textureProjLodOffset(sampler2DShadow sampler, vec4 P, float lod, ivec2 offset);
+$gvec4 textureGrad($gsampler1D sampler, float P, float dPdx, float dPdy);
+$gvec4 textureGrad($gsampler2D sampler, vec2 P, vec2 dPdx, vec2 dPdy);
+$gvec4 textureGrad($gsampler3D sampler, vec3 P, vec3 dPdx, vec3 dPdy);
+$gvec4 textureGrad($gsamplerCube sampler, vec3 P, vec3 dPdx, vec3 dPdy);
+$gvec4 textureGrad($gsampler2DRect sampler, vec2 P, vec2 dPdx, vec2 dPdy);
+float textureGrad(sampler2DRectShadow sampler, vec3 P, vec2 dPdx, vec2 dPdy);
+float textureGrad(sampler1DShadow sampler, vec3 P, float dPdx, float dPdy);
+float textureGrad(sampler2DShadow sampler, vec3 P, vec2 dPdx, vec2 dPdy);
+float textureGrad(samplerCubeShadow sampler, vec4 P, vec3 dPdx, vec3 dPdy);
+$gvec4 textureGrad($gsampler1DArray sampler, vec2 P, float dPdx, float dPdy);
+$gvec4 textureGrad($gsampler2DArray sampler, vec3 P, vec2 dPdx, vec2 dPdy);
+float textureGrad(sampler1DArrayShadow sampler, vec3 P, float dPdx, float dPdy);
+float textureGrad(sampler2DArrayShadow sampler, vec4 P, vec2 dPdx, vec2 dPdy);
+$gvec4 textureGrad($gsamplerCubeArray sampler, vec4 P, vec3 dPdx, vec3 dPdy);
+$gvec4 textureGradOffset($gsampler1D sampler, float P, float dPdx, float dPdy, int offset);
+$gvec4 textureGradOffset($gsampler2D sampler, vec2 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureGradOffset($gsampler3D sampler, vec3 P, vec3 dPdx, vec3 dPdy, ivec3 offset);
+$gvec4 textureGradOffset($gsampler2DRect sampler, vec2 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+float textureGradOffset(sampler2DRectShadow sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+float textureGradOffset(sampler1DShadow sampler, vec3 P, float dPdx, float dPdy, int offset );
+float textureGradOffset(sampler2DShadow sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureGradOffset($gsampler1DArray sampler, vec2 P, float dPdx, float dPdy, int offset);
+$gvec4 textureGradOffset($gsampler2DArray sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+float textureGradOffset(sampler1DArrayShadow sampler, vec3 P, float dPdx, float dPdy, int offset);
+float textureGradOffset(sampler2DArrayShadow sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureProjGrad($gsampler1D sampler, vec2 P, float dPdx, float dPdy);
+$gvec4 textureProjGrad($gsampler1D sampler, vec4 P, float dPdx, float dPdy);
+$gvec4 textureProjGrad($gsampler2D sampler, vec3 P, vec2 dPdx, vec2 dPdy);
+$gvec4 textureProjGrad($gsampler2D sampler, vec4 P, vec2 dPdx, vec2 dPdy);
+$gvec4 textureProjGrad($gsampler3D sampler, vec4 P, vec3 dPdx, vec3 dPdy);
+$gvec4 textureProjGrad($gsampler2DRect sampler, vec3 P, vec2 dPdx, vec2 dPdy);
+$gvec4 textureProjGrad($gsampler2DRect sampler, vec4 P, vec2 dPdx, vec2 dPdy);
+float textureProjGrad(sampler2DRectShadow sampler, vec4 P, vec2 dPdx, vec2 dPdy);
+float textureProjGrad(sampler1DShadow sampler, vec4 P, float dPdx, float dPdy);
+float textureProjGrad(sampler2DShadow sampler, vec4 P, vec2 dPdx, vec2 dPdy);
+$gvec4 textureProjGradOffset($gsampler1D sampler, vec2 P, float dPdx, float dPdy, int offset);
+$gvec4 textureProjGradOffset($gsampler1D sampler, vec4 P, float dPdx, float dPdy, int offset);
+$gvec4 textureProjGradOffset($gsampler2D sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureProjGradOffset($gsampler2D sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureProjGradOffset($gsampler2DRect sampler, vec3 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureProjGradOffset($gsampler2DRect sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+float textureProjGradOffset(sampler2DRectShadow sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureProjGradOffset($gsampler3D sampler, vec4 P, vec3 dPdx, vec3 dPdy, ivec3 offset);
+float textureProjGradOffset(sampler1DShadow sampler, vec4 P, float dPdx, float dPdy, int offset);
+float textureProjGradOffset(sampler2DShadow sampler, vec4 P, vec2 dPdx, vec2 dPdy, ivec2 offset);
+$gvec4 textureGather($gsampler2D sampler, vec2 P);
+$gvec4 textureGather($gsampler2D sampler, vec2 P, int comp);
+$gvec4 textureGather($gsampler2DArray sampler, vec3 P);
+$gvec4 textureGather($gsampler2DArray sampler, vec3 P, int comp);
+$gvec4 textureGather($gsamplerCube sampler, vec3 P);
+$gvec4 textureGather($gsamplerCube sampler, vec3 P, int comp);
+$gvec4 textureGather($gsamplerCubeArray sampler, vec4 P);
+$gvec4 textureGather($gsamplerCubeArray sampler, vec4 P, int comp);
+$gvec4 textureGather($gsampler2DRect sampler, vec2 P);
+$gvec4 textureGather($gsampler2DRect sampler, vec2 P, int comp);
+vec4 textureGather(sampler2DShadow sampler, vec2 P, float refZ);
+vec4 textureGather(sampler2DArrayShadow sampler, vec3 P, float refZ);
+vec4 textureGather(samplerCubeShadow sampler, vec3 P, float refZ);
+vec4 textureGather(samplerCubeArrayShadow sampler, vec4 P, float refZ);
+vec4 textureGather(sampler2DRectShadow sampler, vec2 P, float refZ);
+$gvec4 textureGatherOffset($gsampler2D sampler, vec2 P, ivec2 offset);
+$gvec4 textureGatherOffset($gsampler2D sampler, vec2 P, ivec2 offset, int comp);
+$gvec4 textureGatherOffset($gsampler2DArray sampler, vec3 P, ivec2 offset);
+$gvec4 textureGatherOffset($gsampler2DArray sampler, vec3 P, ivec2 offset, int comp);
+$gvec4 textureGatherOffset($gsampler2DRect sampler, vec2 P, ivec2 offset);
+$gvec4 textureGatherOffset($gsampler2DRect sampler, vec2 P, ivec2 offset, int comp);
+vec4 textureGatherOffset(sampler2DShadow sampler, vec2 P, float refZ, ivec2 offset);
+vec4 textureGatherOffset(sampler2DArrayShadow sampler, vec3 P, float refZ, ivec2 offset);
+vec4 textureGatherOffset(sampler2DRectShadow sampler, vec2 P, float refZ, ivec2 offset);
+/*
+$gvec4 textureGatherOffsets($gsampler2D sampler, vec2 P, ivec2 offsets[4]);
+$gvec4 textureGatherOffsets($gsampler2D sampler, vec2 P, ivec2 offsets[4], int comp);
+$gvec4 textureGatherOffsets($gsampler2DArray sampler, vec3 P, ivec2 offsets[4]);
+$gvec4 textureGatherOffsets($gsampler2DArray sampler, vec3 P, ivec2 offsets[4], int comp);
+$gvec4 textureGatherOffsets($gsampler2DRect sampler, vec2 P, ivec2 offsets[4]);
+$gvec4 textureGatherOffsets($gsampler2DRect sampler, vec2 P, ivec2 offsets[4], int comp);
+vec4 textureGatherOffsets(sampler2DShadow sampler, vec2 P, float refZ, ivec2 offsets[4]);
+vec4 textureGatherOffsets(sampler2DArrayShadow sampler, vec3 P, float refZ, ivec2 offsets[4]);
+vec4 textureGatherOffsets(sampler2DRectShadow sampler, vec2 P, float refZ, ivec2 offsets[4]);
+*/
+vec4 texture1D(sampler1D sampler, float coord);
+vec4 texture1D(sampler1D sampler, float coord, float bias);
+vec4 texture1DProj(sampler1D sampler, vec2 coord);
+vec4 texture1DProj(sampler1D sampler, vec2 coord, float bias);
+vec4 texture1DProj(sampler1D sampler, vec4 coord);
+vec4 texture1DProj(sampler1D sampler, vec4 coord, float bias);
+vec4 texture1DLod(sampler1D sampler, float coord, float lod);
+vec4 texture1DProjLod(sampler1D sampler, vec2 coord, float lod);
+vec4 texture1DProjLod(sampler1D sampler, vec4 coord, float lod);
+vec4 texture2D(sampler2D sampler, vec2 coord);
+vec4 texture2D(sampler2D sampler, vec2 coord, float bias);
+vec4 texture2DProj(sampler2D sampler, vec3 coord);
+vec4 texture2DProj(sampler2D sampler, vec3 coord, float bias);
+vec4 texture2DProj(sampler2D sampler, vec4 coord);
+vec4 texture2DProj(sampler2D sampler, vec4 coord, float bias);
+vec4 texture2DLod(sampler2D sampler, vec2 coord, float lod);
+vec4 texture2DProjLod(sampler2D sampler, vec3 coord, float lod);
+vec4 texture2DProjLod(sampler2D sampler, vec4 coord, float lod);
+vec4 texture3D(sampler3D sampler, vec3 coord);
+vec4 texture3D(sampler3D sampler, vec3 coord, float bias);
+vec4 texture3DProj(sampler3D sampler, vec4 coord);
+vec4 texture3DProj(sampler3D sampler, vec4 coord, float bias);
+vec4 texture3DLod(sampler3D sampler, vec3 coord, float lod);
+vec4 texture3DProjLod(sampler3D sampler, vec4 coord, float lod);
+vec4 textureCube(samplerCube sampler, vec3 coord);
+vec4 textureCube(samplerCube sampler, vec3 coord, float bias);
+vec4 textureCubeLod(samplerCube sampler, vec3 coord, float lod);
+vec4 shadow1D(sampler1DShadow sampler, vec3 coord);
+vec4 shadow1D(sampler1DShadow sampler, vec3 coord, float bias);
+vec4 shadow2D(sampler2DShadow sampler, vec3 coord);
+vec4 shadow2D(sampler2DShadow sampler, vec3 coord, float bias);
+vec4 shadow1DProj(sampler1DShadow sampler, vec4 coord);
+vec4 shadow1DProj(sampler1DShadow sampler, vec4 coord, float bias);
+vec4 shadow2DProj(sampler2DShadow sampler, vec4 coord);
+vec4 shadow2DProj(sampler2DShadow sampler, vec4 coord, float bias);
+vec4 shadow1DLod(sampler1DShadow sampler, vec3 coord, float lod);
+vec4 shadow2DLod(sampler2DShadow sampler, vec3 coord, float lod);
+vec4 shadow1DProjLod(sampler1DShadow sampler, vec4 coord, float lod);
+vec4 shadow2DProjLod(sampler2DShadow sampler, vec4 coord, float lod);
+/*
+uint atomicCounterIncrement(atomic_uint c);
+uint atomicCounter(atomic_uint c);
+uint atomicAdd(inout uint mem, uint data);
+int atomicAdd(inout int mem, int data);
+uint atomicMin(inout uint mem, uint data);
+int atomicMin(inout int mem, int data);
+uint atomicMax(inout uint mem, uint data);
+int atomicMax(inout int mem, int data);
+uint atomicAnd(inout uint mem, uint data);
+int atomicAnd(inout int mem, int data);
+uint atomicOr(inout uint mem, uint data);
+int atomicOr(inout int mem, int data);
+uint atomicXor(inout uint mem, uint data);
+int atomicXor(inout int mem, int data);
+uint atomicExchange(inout uint mem, uint data);
+int atomicExchange(inout int mem, int data);
+uint atomicCompSwap(inout uint mem, uint compare, uint data);
+int atomicCompSwap(inout int mem, int compare, int data);
+*/
+// section 8.12 Image Functions will go here if and when we add support for them
+
+$genType dFdx($genType p);
+$genType dFdy($genType p);
+$genType fwidth($genType p);
+$genType fwidthCoarse($genType p);
+$genType fwidthFine($genType p);
+float interpolateAtSample(float interpolant, int sample);
+vec2 interpolateAtSample(vec2 interpolant, int sample);
+vec3 interpolateAtSample(vec3 interpolant, int sample);
+vec4 interpolateAtSample(vec4 interpolant, int sample);
+float interpolateAtOffset(float interpolant, vec2 offset);
+vec2 interpolateAtOffset(vec2 interpolant, vec2 offset);
+vec3 interpolateAtOffset(vec3 interpolant, vec2 offset);
+vec4 interpolateAtOffset(vec4 interpolant, vec2 offset);
+void EmitStreamVertex(int stream);
+void EndStreamPrimitive(int stream);
+void EmitVertex();
+void EndPrimitive();
+void barrier();
+void memoryBarrier();
+void memoryBarrierAtomicCounter();
+void memoryBarrierBuffer();
+void memoryBarrierShared();
+void memoryBarrierImage();
+void groupMemoryBarrier();
+
+)
\ No newline at end of file
diff --git a/src/sksl/sksl_frag.include b/src/sksl/sksl_frag.include
new file mode 100644
index 0000000..b4047fe
--- /dev/null
+++ b/src/sksl/sksl_frag.include
@@ -0,0 +1,7 @@
+STRINGIFY(
+
+// defines built-in interfaces supported by SkiaSL fragment shaders
+
+layout(builtin=15) in vec4 gl_FragCoord;
+
+)
\ No newline at end of file
diff --git a/src/sksl/sksl_vert.include b/src/sksl/sksl_vert.include
new file mode 100644
index 0000000..e0b01ef
--- /dev/null
+++ b/src/sksl/sksl_vert.include
@@ -0,0 +1,10 @@
+STRINGIFY(
+
+// defines built-in interfaces supported by SkiaSL vertex shaders
+
+out gl_PerVertex {
+  	layout(builtin=0) vec4 gl_Position;
+  	layout(builtin=1) float gl_PointSize;
+};
+
+)
\ No newline at end of file
diff --git a/src/sksl/spirv.h b/src/sksl/spirv.h
new file mode 100644
index 0000000..e4f5b5b
--- /dev/null
+++ b/src/sksl/spirv.h
@@ -0,0 +1,870 @@
+/*
+** Copyright (c) 2014-2016 The Khronos Group Inc.
+** 
+** Permission is hereby granted, free of charge, to any person obtaining a copy
+** of this software and/or associated documentation files (the "Materials"),
+** to deal in the Materials without restriction, including without limitation
+** the rights to use, copy, modify, merge, publish, distribute, sublicense,
+** and/or sell copies of the Materials, and to permit persons to whom the
+** Materials are furnished to do so, subject to the following conditions:
+** 
+** The above copyright notice and this permission notice shall be included in
+** all copies or substantial portions of the Materials.
+** 
+** MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
+** STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
+** HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ 
+** 
+** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+** OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+** THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+** FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
+** IN THE MATERIALS.
+*/
+
+/*
+** This header is automatically generated by the same tool that creates
+** the Binary Section of the SPIR-V specification.
+*/
+
+/*
+** Enumeration tokens for SPIR-V, in various styles:
+**   C, C++, C++11, JSON, Lua, Python
+** 
+** - C will have tokens with a "Spv" prefix, e.g.: SpvSourceLanguageGLSL
+** - C++ will have tokens in the "spv" name space, e.g.: spv::SourceLanguageGLSL
+** - C++11 will use enum classes in the spv namespace, e.g.: spv::SourceLanguage::GLSL
+** - Lua will use tables, e.g.: spv.SourceLanguage.GLSL
+** - Python will use dictionaries, e.g.: spv['SourceLanguage']['GLSL']
+** 
+** Some tokens act like mask values, which can be OR'd together,
+** while others are mutually exclusive.  The mask-like ones have
+** "Mask" in their name, and a parallel enum that has the shift
+** amount (1 << x) for each corresponding enumerant.
+*/
+
+#ifndef spirv_H
+#define spirv_H
+
+typedef unsigned int SpvId;
+
+#define SPV_VERSION 0x10000
+#define SPV_REVISION 4
+
+static const unsigned int SpvMagicNumber = 0x07230203;
+static const unsigned int SpvVersion = 0x00010000;
+static const unsigned int SpvRevision = 4;
+static const unsigned int SpvOpCodeMask = 0xffff;
+static const unsigned int SpvWordCountShift = 16;
+
+typedef enum SpvSourceLanguage_ {
+    SpvSourceLanguageUnknown = 0,
+    SpvSourceLanguageESSL = 1,
+    SpvSourceLanguageGLSL = 2,
+    SpvSourceLanguageOpenCL_C = 3,
+    SpvSourceLanguageOpenCL_CPP = 4,
+} SpvSourceLanguage;
+
+typedef enum SpvExecutionModel_ {
+    SpvExecutionModelVertex = 0,
+    SpvExecutionModelTessellationControl = 1,
+    SpvExecutionModelTessellationEvaluation = 2,
+    SpvExecutionModelGeometry = 3,
+    SpvExecutionModelFragment = 4,
+    SpvExecutionModelGLCompute = 5,
+    SpvExecutionModelKernel = 6,
+} SpvExecutionModel;
+
+typedef enum SpvAddressingModel_ {
+    SpvAddressingModelLogical = 0,
+    SpvAddressingModelPhysical32 = 1,
+    SpvAddressingModelPhysical64 = 2,
+} SpvAddressingModel;
+
+typedef enum SpvMemoryModel_ {
+    SpvMemoryModelSimple = 0,
+    SpvMemoryModelGLSL450 = 1,
+    SpvMemoryModelOpenCL = 2,
+} SpvMemoryModel;
+
+typedef enum SpvExecutionMode_ {
+    SpvExecutionModeInvocations = 0,
+    SpvExecutionModeSpacingEqual = 1,
+    SpvExecutionModeSpacingFractionalEven = 2,
+    SpvExecutionModeSpacingFractionalOdd = 3,
+    SpvExecutionModeVertexOrderCw = 4,
+    SpvExecutionModeVertexOrderCcw = 5,
+    SpvExecutionModePixelCenterInteger = 6,
+    SpvExecutionModeOriginUpperLeft = 7,
+    SpvExecutionModeOriginLowerLeft = 8,
+    SpvExecutionModeEarlyFragmentTests = 9,
+    SpvExecutionModePointMode = 10,
+    SpvExecutionModeXfb = 11,
+    SpvExecutionModeDepthReplacing = 12,
+    SpvExecutionModeDepthGreater = 14,
+    SpvExecutionModeDepthLess = 15,
+    SpvExecutionModeDepthUnchanged = 16,
+    SpvExecutionModeLocalSize = 17,
+    SpvExecutionModeLocalSizeHint = 18,
+    SpvExecutionModeInputPoints = 19,
+    SpvExecutionModeInputLines = 20,
+    SpvExecutionModeInputLinesAdjacency = 21,
+    SpvExecutionModeTriangles = 22,
+    SpvExecutionModeInputTrianglesAdjacency = 23,
+    SpvExecutionModeQuads = 24,
+    SpvExecutionModeIsolines = 25,
+    SpvExecutionModeOutputVertices = 26,
+    SpvExecutionModeOutputPoints = 27,
+    SpvExecutionModeOutputLineStrip = 28,
+    SpvExecutionModeOutputTriangleStrip = 29,
+    SpvExecutionModeVecTypeHint = 30,
+    SpvExecutionModeContractionOff = 31,
+} SpvExecutionMode;
+
+typedef enum SpvStorageClass_ {
+    SpvStorageClassUniformConstant = 0,
+    SpvStorageClassInput = 1,
+    SpvStorageClassUniform = 2,
+    SpvStorageClassOutput = 3,
+    SpvStorageClassWorkgroup = 4,
+    SpvStorageClassCrossWorkgroup = 5,
+    SpvStorageClassPrivate = 6,
+    SpvStorageClassFunction = 7,
+    SpvStorageClassGeneric = 8,
+    SpvStorageClassPushConstant = 9,
+    SpvStorageClassAtomicCounter = 10,
+    SpvStorageClassImage = 11,
+} SpvStorageClass;
+
+typedef enum SpvDim_ {
+    SpvDim1D = 0,
+    SpvDim2D = 1,
+    SpvDim3D = 2,
+    SpvDimCube = 3,
+    SpvDimRect = 4,
+    SpvDimBuffer = 5,
+    SpvDimSubpassData = 6,
+} SpvDim;
+
+typedef enum SpvSamplerAddressingMode_ {
+    SpvSamplerAddressingModeNone = 0,
+    SpvSamplerAddressingModeClampToEdge = 1,
+    SpvSamplerAddressingModeClamp = 2,
+    SpvSamplerAddressingModeRepeat = 3,
+    SpvSamplerAddressingModeRepeatMirrored = 4,
+} SpvSamplerAddressingMode;
+
+typedef enum SpvSamplerFilterMode_ {
+    SpvSamplerFilterModeNearest = 0,
+    SpvSamplerFilterModeLinear = 1,
+} SpvSamplerFilterMode;
+
+typedef enum SpvImageFormat_ {
+    SpvImageFormatUnknown = 0,
+    SpvImageFormatRgba32f = 1,
+    SpvImageFormatRgba16f = 2,
+    SpvImageFormatR32f = 3,
+    SpvImageFormatRgba8 = 4,
+    SpvImageFormatRgba8Snorm = 5,
+    SpvImageFormatRg32f = 6,
+    SpvImageFormatRg16f = 7,
+    SpvImageFormatR11fG11fB10f = 8,
+    SpvImageFormatR16f = 9,
+    SpvImageFormatRgba16 = 10,
+    SpvImageFormatRgb10A2 = 11,
+    SpvImageFormatRg16 = 12,
+    SpvImageFormatRg8 = 13,
+    SpvImageFormatR16 = 14,
+    SpvImageFormatR8 = 15,
+    SpvImageFormatRgba16Snorm = 16,
+    SpvImageFormatRg16Snorm = 17,
+    SpvImageFormatRg8Snorm = 18,
+    SpvImageFormatR16Snorm = 19,
+    SpvImageFormatR8Snorm = 20,
+    SpvImageFormatRgba32i = 21,
+    SpvImageFormatRgba16i = 22,
+    SpvImageFormatRgba8i = 23,
+    SpvImageFormatR32i = 24,
+    SpvImageFormatRg32i = 25,
+    SpvImageFormatRg16i = 26,
+    SpvImageFormatRg8i = 27,
+    SpvImageFormatR16i = 28,
+    SpvImageFormatR8i = 29,
+    SpvImageFormatRgba32ui = 30,
+    SpvImageFormatRgba16ui = 31,
+    SpvImageFormatRgba8ui = 32,
+    SpvImageFormatR32ui = 33,
+    SpvImageFormatRgb10a2ui = 34,
+    SpvImageFormatRg32ui = 35,
+    SpvImageFormatRg16ui = 36,
+    SpvImageFormatRg8ui = 37,
+    SpvImageFormatR16ui = 38,
+    SpvImageFormatR8ui = 39,
+} SpvImageFormat;
+
+typedef enum SpvImageChannelOrder_ {
+    SpvImageChannelOrderR = 0,
+    SpvImageChannelOrderA = 1,
+    SpvImageChannelOrderRG = 2,
+    SpvImageChannelOrderRA = 3,
+    SpvImageChannelOrderRGB = 4,
+    SpvImageChannelOrderRGBA = 5,
+    SpvImageChannelOrderBGRA = 6,
+    SpvImageChannelOrderARGB = 7,
+    SpvImageChannelOrderIntensity = 8,
+    SpvImageChannelOrderLuminance = 9,
+    SpvImageChannelOrderRx = 10,
+    SpvImageChannelOrderRGx = 11,
+    SpvImageChannelOrderRGBx = 12,
+    SpvImageChannelOrderDepth = 13,
+    SpvImageChannelOrderDepthStencil = 14,
+    SpvImageChannelOrdersRGB = 15,
+    SpvImageChannelOrdersRGBx = 16,
+    SpvImageChannelOrdersRGBA = 17,
+    SpvImageChannelOrdersBGRA = 18,
+} SpvImageChannelOrder;
+
+typedef enum SpvImageChannelDataType_ {
+    SpvImageChannelDataTypeSnormInt8 = 0,
+    SpvImageChannelDataTypeSnormInt16 = 1,
+    SpvImageChannelDataTypeUnormInt8 = 2,
+    SpvImageChannelDataTypeUnormInt16 = 3,
+    SpvImageChannelDataTypeUnormShort565 = 4,
+    SpvImageChannelDataTypeUnormShort555 = 5,
+    SpvImageChannelDataTypeUnormInt101010 = 6,
+    SpvImageChannelDataTypeSignedInt8 = 7,
+    SpvImageChannelDataTypeSignedInt16 = 8,
+    SpvImageChannelDataTypeSignedInt32 = 9,
+    SpvImageChannelDataTypeUnsignedInt8 = 10,
+    SpvImageChannelDataTypeUnsignedInt16 = 11,
+    SpvImageChannelDataTypeUnsignedInt32 = 12,
+    SpvImageChannelDataTypeHalfFloat = 13,
+    SpvImageChannelDataTypeFloat = 14,
+    SpvImageChannelDataTypeUnormInt24 = 15,
+    SpvImageChannelDataTypeUnormInt101010_2 = 16,
+} SpvImageChannelDataType;
+
+typedef enum SpvImageOperandsShift_ {
+    SpvImageOperandsBiasShift = 0,
+    SpvImageOperandsLodShift = 1,
+    SpvImageOperandsGradShift = 2,
+    SpvImageOperandsConstOffsetShift = 3,
+    SpvImageOperandsOffsetShift = 4,
+    SpvImageOperandsConstOffsetsShift = 5,
+    SpvImageOperandsSampleShift = 6,
+    SpvImageOperandsMinLodShift = 7,
+} SpvImageOperandsShift;
+
+typedef enum SpvImageOperandsMask_ {
+    SpvImageOperandsMaskNone = 0,
+    SpvImageOperandsBiasMask = 0x00000001,
+    SpvImageOperandsLodMask = 0x00000002,
+    SpvImageOperandsGradMask = 0x00000004,
+    SpvImageOperandsConstOffsetMask = 0x00000008,
+    SpvImageOperandsOffsetMask = 0x00000010,
+    SpvImageOperandsConstOffsetsMask = 0x00000020,
+    SpvImageOperandsSampleMask = 0x00000040,
+    SpvImageOperandsMinLodMask = 0x00000080,
+} SpvImageOperandsMask;
+
+typedef enum SpvFPFastMathModeShift_ {
+    SpvFPFastMathModeNotNaNShift = 0,
+    SpvFPFastMathModeNotInfShift = 1,
+    SpvFPFastMathModeNSZShift = 2,
+    SpvFPFastMathModeAllowRecipShift = 3,
+    SpvFPFastMathModeFastShift = 4,
+} SpvFPFastMathModeShift;
+
+typedef enum SpvFPFastMathModeMask_ {
+    SpvFPFastMathModeMaskNone = 0,
+    SpvFPFastMathModeNotNaNMask = 0x00000001,
+    SpvFPFastMathModeNotInfMask = 0x00000002,
+    SpvFPFastMathModeNSZMask = 0x00000004,
+    SpvFPFastMathModeAllowRecipMask = 0x00000008,
+    SpvFPFastMathModeFastMask = 0x00000010,
+} SpvFPFastMathModeMask;
+
+typedef enum SpvFPRoundingMode_ {
+    SpvFPRoundingModeRTE = 0,
+    SpvFPRoundingModeRTZ = 1,
+    SpvFPRoundingModeRTP = 2,
+    SpvFPRoundingModeRTN = 3,
+} SpvFPRoundingMode;
+
+typedef enum SpvLinkageType_ {
+    SpvLinkageTypeExport = 0,
+    SpvLinkageTypeImport = 1,
+} SpvLinkageType;
+
+typedef enum SpvAccessQualifier_ {
+    SpvAccessQualifierReadOnly = 0,
+    SpvAccessQualifierWriteOnly = 1,
+    SpvAccessQualifierReadWrite = 2,
+} SpvAccessQualifier;
+
+typedef enum SpvFunctionParameterAttribute_ {
+    SpvFunctionParameterAttributeZext = 0,
+    SpvFunctionParameterAttributeSext = 1,
+    SpvFunctionParameterAttributeByVal = 2,
+    SpvFunctionParameterAttributeSret = 3,
+    SpvFunctionParameterAttributeNoAlias = 4,
+    SpvFunctionParameterAttributeNoCapture = 5,
+    SpvFunctionParameterAttributeNoWrite = 6,
+    SpvFunctionParameterAttributeNoReadWrite = 7,
+} SpvFunctionParameterAttribute;
+
+typedef enum SpvDecoration_ {
+    SpvDecorationRelaxedPrecision = 0,
+    SpvDecorationSpecId = 1,
+    SpvDecorationBlock = 2,
+    SpvDecorationBufferBlock = 3,
+    SpvDecorationRowMajor = 4,
+    SpvDecorationColMajor = 5,
+    SpvDecorationArrayStride = 6,
+    SpvDecorationMatrixStride = 7,
+    SpvDecorationGLSLShared = 8,
+    SpvDecorationGLSLPacked = 9,
+    SpvDecorationCPacked = 10,
+    SpvDecorationBuiltIn = 11,
+    SpvDecorationNoPerspective = 13,
+    SpvDecorationFlat = 14,
+    SpvDecorationPatch = 15,
+    SpvDecorationCentroid = 16,
+    SpvDecorationSample = 17,
+    SpvDecorationInvariant = 18,
+    SpvDecorationRestrict = 19,
+    SpvDecorationAliased = 20,
+    SpvDecorationVolatile = 21,
+    SpvDecorationConstant = 22,
+    SpvDecorationCoherent = 23,
+    SpvDecorationNonWritable = 24,
+    SpvDecorationNonReadable = 25,
+    SpvDecorationUniform = 26,
+    SpvDecorationSaturatedConversion = 28,
+    SpvDecorationStream = 29,
+    SpvDecorationLocation = 30,
+    SpvDecorationComponent = 31,
+    SpvDecorationIndex = 32,
+    SpvDecorationBinding = 33,
+    SpvDecorationDescriptorSet = 34,
+    SpvDecorationOffset = 35,
+    SpvDecorationXfbBuffer = 36,
+    SpvDecorationXfbStride = 37,
+    SpvDecorationFuncParamAttr = 38,
+    SpvDecorationFPRoundingMode = 39,
+    SpvDecorationFPFastMathMode = 40,
+    SpvDecorationLinkageAttributes = 41,
+    SpvDecorationNoContraction = 42,
+    SpvDecorationInputAttachmentIndex = 43,
+    SpvDecorationAlignment = 44,
+} SpvDecoration;
+
+typedef enum SpvBuiltIn_ {
+    SpvBuiltInPosition = 0,
+    SpvBuiltInPointSize = 1,
+    SpvBuiltInClipDistance = 3,
+    SpvBuiltInCullDistance = 4,
+    SpvBuiltInVertexId = 5,
+    SpvBuiltInInstanceId = 6,
+    SpvBuiltInPrimitiveId = 7,
+    SpvBuiltInInvocationId = 8,
+    SpvBuiltInLayer = 9,
+    SpvBuiltInViewportIndex = 10,
+    SpvBuiltInTessLevelOuter = 11,
+    SpvBuiltInTessLevelInner = 12,
+    SpvBuiltInTessCoord = 13,
+    SpvBuiltInPatchVertices = 14,
+    SpvBuiltInFragCoord = 15,
+    SpvBuiltInPointCoord = 16,
+    SpvBuiltInFrontFacing = 17,
+    SpvBuiltInSampleId = 18,
+    SpvBuiltInSamplePosition = 19,
+    SpvBuiltInSampleMask = 20,
+    SpvBuiltInFragDepth = 22,
+    SpvBuiltInHelperInvocation = 23,
+    SpvBuiltInNumWorkgroups = 24,
+    SpvBuiltInWorkgroupSize = 25,
+    SpvBuiltInWorkgroupId = 26,
+    SpvBuiltInLocalInvocationId = 27,
+    SpvBuiltInGlobalInvocationId = 28,
+    SpvBuiltInLocalInvocationIndex = 29,
+    SpvBuiltInWorkDim = 30,
+    SpvBuiltInGlobalSize = 31,
+    SpvBuiltInEnqueuedWorkgroupSize = 32,
+    SpvBuiltInGlobalOffset = 33,
+    SpvBuiltInGlobalLinearId = 34,
+    SpvBuiltInSubgroupSize = 36,
+    SpvBuiltInSubgroupMaxSize = 37,
+    SpvBuiltInNumSubgroups = 38,
+    SpvBuiltInNumEnqueuedSubgroups = 39,
+    SpvBuiltInSubgroupId = 40,
+    SpvBuiltInSubgroupLocalInvocationId = 41,
+    SpvBuiltInVertexIndex = 42,
+    SpvBuiltInInstanceIndex = 43,
+} SpvBuiltIn;
+
+typedef enum SpvSelectionControlShift_ {
+    SpvSelectionControlFlattenShift = 0,
+    SpvSelectionControlDontFlattenShift = 1,
+} SpvSelectionControlShift;
+
+typedef enum SpvSelectionControlMask_ {
+    SpvSelectionControlMaskNone = 0,
+    SpvSelectionControlFlattenMask = 0x00000001,
+    SpvSelectionControlDontFlattenMask = 0x00000002,
+} SpvSelectionControlMask;
+
+typedef enum SpvLoopControlShift_ {
+    SpvLoopControlUnrollShift = 0,
+    SpvLoopControlDontUnrollShift = 1,
+} SpvLoopControlShift;
+
+typedef enum SpvLoopControlMask_ {
+    SpvLoopControlMaskNone = 0,
+    SpvLoopControlUnrollMask = 0x00000001,
+    SpvLoopControlDontUnrollMask = 0x00000002,
+} SpvLoopControlMask;
+
+typedef enum SpvFunctionControlShift_ {
+    SpvFunctionControlInlineShift = 0,
+    SpvFunctionControlDontInlineShift = 1,
+    SpvFunctionControlPureShift = 2,
+    SpvFunctionControlConstShift = 3,
+} SpvFunctionControlShift;
+
+typedef enum SpvFunctionControlMask_ {
+    SpvFunctionControlMaskNone = 0,
+    SpvFunctionControlInlineMask = 0x00000001,
+    SpvFunctionControlDontInlineMask = 0x00000002,
+    SpvFunctionControlPureMask = 0x00000004,
+    SpvFunctionControlConstMask = 0x00000008,
+} SpvFunctionControlMask;
+
+typedef enum SpvMemorySemanticsShift_ {
+    SpvMemorySemanticsAcquireShift = 1,
+    SpvMemorySemanticsReleaseShift = 2,
+    SpvMemorySemanticsAcquireReleaseShift = 3,
+    SpvMemorySemanticsSequentiallyConsistentShift = 4,
+    SpvMemorySemanticsUniformMemoryShift = 6,
+    SpvMemorySemanticsSubgroupMemoryShift = 7,
+    SpvMemorySemanticsWorkgroupMemoryShift = 8,
+    SpvMemorySemanticsCrossWorkgroupMemoryShift = 9,
+    SpvMemorySemanticsAtomicCounterMemoryShift = 10,
+    SpvMemorySemanticsImageMemoryShift = 11,
+} SpvMemorySemanticsShift;
+
+typedef enum SpvMemorySemanticsMask_ {
+    SpvMemorySemanticsMaskNone = 0,
+    SpvMemorySemanticsAcquireMask = 0x00000002,
+    SpvMemorySemanticsReleaseMask = 0x00000004,
+    SpvMemorySemanticsAcquireReleaseMask = 0x00000008,
+    SpvMemorySemanticsSequentiallyConsistentMask = 0x00000010,
+    SpvMemorySemanticsUniformMemoryMask = 0x00000040,
+    SpvMemorySemanticsSubgroupMemoryMask = 0x00000080,
+    SpvMemorySemanticsWorkgroupMemoryMask = 0x00000100,
+    SpvMemorySemanticsCrossWorkgroupMemoryMask = 0x00000200,
+    SpvMemorySemanticsAtomicCounterMemoryMask = 0x00000400,
+    SpvMemorySemanticsImageMemoryMask = 0x00000800,
+} SpvMemorySemanticsMask;
+
+typedef enum SpvMemoryAccessShift_ {
+    SpvMemoryAccessVolatileShift = 0,
+    SpvMemoryAccessAlignedShift = 1,
+    SpvMemoryAccessNontemporalShift = 2,
+} SpvMemoryAccessShift;
+
+typedef enum SpvMemoryAccessMask_ {
+    SpvMemoryAccessMaskNone = 0,
+    SpvMemoryAccessVolatileMask = 0x00000001,
+    SpvMemoryAccessAlignedMask = 0x00000002,
+    SpvMemoryAccessNontemporalMask = 0x00000004,
+} SpvMemoryAccessMask;
+
+typedef enum SpvScope_ {
+    SpvScopeCrossDevice = 0,
+    SpvScopeDevice = 1,
+    SpvScopeWorkgroup = 2,
+    SpvScopeSubgroup = 3,
+    SpvScopeInvocation = 4,
+} SpvScope;
+
+typedef enum SpvGroupOperation_ {
+    SpvGroupOperationReduce = 0,
+    SpvGroupOperationInclusiveScan = 1,
+    SpvGroupOperationExclusiveScan = 2,
+} SpvGroupOperation;
+
+typedef enum SpvKernelEnqueueFlags_ {
+    SpvKernelEnqueueFlagsNoWait = 0,
+    SpvKernelEnqueueFlagsWaitKernel = 1,
+    SpvKernelEnqueueFlagsWaitWorkGroup = 2,
+} SpvKernelEnqueueFlags;
+
+typedef enum SpvKernelProfilingInfoShift_ {
+    SpvKernelProfilingInfoCmdExecTimeShift = 0,
+} SpvKernelProfilingInfoShift;
+
+typedef enum SpvKernelProfilingInfoMask_ {
+    SpvKernelProfilingInfoMaskNone = 0,
+    SpvKernelProfilingInfoCmdExecTimeMask = 0x00000001,
+} SpvKernelProfilingInfoMask;
+
+typedef enum SpvCapability_ {
+    SpvCapabilityMatrix = 0,
+    SpvCapabilityShader = 1,
+    SpvCapabilityGeometry = 2,
+    SpvCapabilityTessellation = 3,
+    SpvCapabilityAddresses = 4,
+    SpvCapabilityLinkage = 5,
+    SpvCapabilityKernel = 6,
+    SpvCapabilityVector16 = 7,
+    SpvCapabilityFloat16Buffer = 8,
+    SpvCapabilityFloat16 = 9,
+    SpvCapabilityFloat64 = 10,
+    SpvCapabilityInt64 = 11,
+    SpvCapabilityInt64Atomics = 12,
+    SpvCapabilityImageBasic = 13,
+    SpvCapabilityImageReadWrite = 14,
+    SpvCapabilityImageMipmap = 15,
+    SpvCapabilityPipes = 17,
+    SpvCapabilityGroups = 18,
+    SpvCapabilityDeviceEnqueue = 19,
+    SpvCapabilityLiteralSampler = 20,
+    SpvCapabilityAtomicStorage = 21,
+    SpvCapabilityInt16 = 22,
+    SpvCapabilityTessellationPointSize = 23,
+    SpvCapabilityGeometryPointSize = 24,
+    SpvCapabilityImageGatherExtended = 25,
+    SpvCapabilityStorageImageMultisample = 27,
+    SpvCapabilityUniformBufferArrayDynamicIndexing = 28,
+    SpvCapabilitySampledImageArrayDynamicIndexing = 29,
+    SpvCapabilityStorageBufferArrayDynamicIndexing = 30,
+    SpvCapabilityStorageImageArrayDynamicIndexing = 31,
+    SpvCapabilityClipDistance = 32,
+    SpvCapabilityCullDistance = 33,
+    SpvCapabilityImageCubeArray = 34,
+    SpvCapabilitySampleRateShading = 35,
+    SpvCapabilityImageRect = 36,
+    SpvCapabilitySampledRect = 37,
+    SpvCapabilityGenericPointer = 38,
+    SpvCapabilityInt8 = 39,
+    SpvCapabilityInputAttachment = 40,
+    SpvCapabilitySparseResidency = 41,
+    SpvCapabilityMinLod = 42,
+    SpvCapabilitySampled1D = 43,
+    SpvCapabilityImage1D = 44,
+    SpvCapabilitySampledCubeArray = 45,
+    SpvCapabilitySampledBuffer = 46,
+    SpvCapabilityImageBuffer = 47,
+    SpvCapabilityImageMSArray = 48,
+    SpvCapabilityStorageImageExtendedFormats = 49,
+    SpvCapabilityImageQuery = 50,
+    SpvCapabilityDerivativeControl = 51,
+    SpvCapabilityInterpolationFunction = 52,
+    SpvCapabilityTransformFeedback = 53,
+    SpvCapabilityGeometryStreams = 54,
+    SpvCapabilityStorageImageReadWithoutFormat = 55,
+    SpvCapabilityStorageImageWriteWithoutFormat = 56,
+    SpvCapabilityMultiViewport = 57,
+} SpvCapability;
+
+typedef enum SpvOp_ {
+    SpvOpNop = 0,
+    SpvOpUndef = 1,
+    SpvOpSourceContinued = 2,
+    SpvOpSource = 3,
+    SpvOpSourceExtension = 4,
+    SpvOpName = 5,
+    SpvOpMemberName = 6,
+    SpvOpString = 7,
+    SpvOpLine = 8,
+    SpvOpExtension = 10,
+    SpvOpExtInstImport = 11,
+    SpvOpExtInst = 12,
+    SpvOpMemoryModel = 14,
+    SpvOpEntryPoint = 15,
+    SpvOpExecutionMode = 16,
+    SpvOpCapability = 17,
+    SpvOpTypeVoid = 19,
+    SpvOpTypeBool = 20,
+    SpvOpTypeInt = 21,
+    SpvOpTypeFloat = 22,
+    SpvOpTypeVector = 23,
+    SpvOpTypeMatrix = 24,
+    SpvOpTypeImage = 25,
+    SpvOpTypeSampler = 26,
+    SpvOpTypeSampledImage = 27,
+    SpvOpTypeArray = 28,
+    SpvOpTypeRuntimeArray = 29,
+    SpvOpTypeStruct = 30,
+    SpvOpTypeOpaque = 31,
+    SpvOpTypePointer = 32,
+    SpvOpTypeFunction = 33,
+    SpvOpTypeEvent = 34,
+    SpvOpTypeDeviceEvent = 35,
+    SpvOpTypeReserveId = 36,
+    SpvOpTypeQueue = 37,
+    SpvOpTypePipe = 38,
+    SpvOpTypeForwardPointer = 39,
+    SpvOpConstantTrue = 41,
+    SpvOpConstantFalse = 42,
+    SpvOpConstant = 43,
+    SpvOpConstantComposite = 44,
+    SpvOpConstantSampler = 45,
+    SpvOpConstantNull = 46,
+    SpvOpSpecConstantTrue = 48,
+    SpvOpSpecConstantFalse = 49,
+    SpvOpSpecConstant = 50,
+    SpvOpSpecConstantComposite = 51,
+    SpvOpSpecConstantOp = 52,
+    SpvOpFunction = 54,
+    SpvOpFunctionParameter = 55,
+    SpvOpFunctionEnd = 56,
+    SpvOpFunctionCall = 57,
+    SpvOpVariable = 59,
+    SpvOpImageTexelPointer = 60,
+    SpvOpLoad = 61,
+    SpvOpStore = 62,
+    SpvOpCopyMemory = 63,
+    SpvOpCopyMemorySized = 64,
+    SpvOpAccessChain = 65,
+    SpvOpInBoundsAccessChain = 66,
+    SpvOpPtrAccessChain = 67,
+    SpvOpArrayLength = 68,
+    SpvOpGenericPtrMemSemantics = 69,
+    SpvOpInBoundsPtrAccessChain = 70,
+    SpvOpDecorate = 71,
+    SpvOpMemberDecorate = 72,
+    SpvOpDecorationGroup = 73,
+    SpvOpGroupDecorate = 74,
+    SpvOpGroupMemberDecorate = 75,
+    SpvOpVectorExtractDynamic = 77,
+    SpvOpVectorInsertDynamic = 78,
+    SpvOpVectorShuffle = 79,
+    SpvOpCompositeConstruct = 80,
+    SpvOpCompositeExtract = 81,
+    SpvOpCompositeInsert = 82,
+    SpvOpCopyObject = 83,
+    SpvOpTranspose = 84,
+    SpvOpSampledImage = 86,
+    SpvOpImageSampleImplicitLod = 87,
+    SpvOpImageSampleExplicitLod = 88,
+    SpvOpImageSampleDrefImplicitLod = 89,
+    SpvOpImageSampleDrefExplicitLod = 90,
+    SpvOpImageSampleProjImplicitLod = 91,
+    SpvOpImageSampleProjExplicitLod = 92,
+    SpvOpImageSampleProjDrefImplicitLod = 93,
+    SpvOpImageSampleProjDrefExplicitLod = 94,
+    SpvOpImageFetch = 95,
+    SpvOpImageGather = 96,
+    SpvOpImageDrefGather = 97,
+    SpvOpImageRead = 98,
+    SpvOpImageWrite = 99,
+    SpvOpImage = 100,
+    SpvOpImageQueryFormat = 101,
+    SpvOpImageQueryOrder = 102,
+    SpvOpImageQuerySizeLod = 103,
+    SpvOpImageQuerySize = 104,
+    SpvOpImageQueryLod = 105,
+    SpvOpImageQueryLevels = 106,
+    SpvOpImageQuerySamples = 107,
+    SpvOpConvertFToU = 109,
+    SpvOpConvertFToS = 110,
+    SpvOpConvertSToF = 111,
+    SpvOpConvertUToF = 112,
+    SpvOpUConvert = 113,
+    SpvOpSConvert = 114,
+    SpvOpFConvert = 115,
+    SpvOpQuantizeToF16 = 116,
+    SpvOpConvertPtrToU = 117,
+    SpvOpSatConvertSToU = 118,
+    SpvOpSatConvertUToS = 119,
+    SpvOpConvertUToPtr = 120,
+    SpvOpPtrCastToGeneric = 121,
+    SpvOpGenericCastToPtr = 122,
+    SpvOpGenericCastToPtrExplicit = 123,
+    SpvOpBitcast = 124,
+    SpvOpSNegate = 126,
+    SpvOpFNegate = 127,
+    SpvOpIAdd = 128,
+    SpvOpFAdd = 129,
+    SpvOpISub = 130,
+    SpvOpFSub = 131,
+    SpvOpIMul = 132,
+    SpvOpFMul = 133,
+    SpvOpUDiv = 134,
+    SpvOpSDiv = 135,
+    SpvOpFDiv = 136,
+    SpvOpUMod = 137,
+    SpvOpSRem = 138,
+    SpvOpSMod = 139,
+    SpvOpFRem = 140,
+    SpvOpFMod = 141,
+    SpvOpVectorTimesScalar = 142,
+    SpvOpMatrixTimesScalar = 143,
+    SpvOpVectorTimesMatrix = 144,
+    SpvOpMatrixTimesVector = 145,
+    SpvOpMatrixTimesMatrix = 146,
+    SpvOpOuterProduct = 147,
+    SpvOpDot = 148,
+    SpvOpIAddCarry = 149,
+    SpvOpISubBorrow = 150,
+    SpvOpUMulExtended = 151,
+    SpvOpSMulExtended = 152,
+    SpvOpAny = 154,
+    SpvOpAll = 155,
+    SpvOpIsNan = 156,
+    SpvOpIsInf = 157,
+    SpvOpIsFinite = 158,
+    SpvOpIsNormal = 159,
+    SpvOpSignBitSet = 160,
+    SpvOpLessOrGreater = 161,
+    SpvOpOrdered = 162,
+    SpvOpUnordered = 163,
+    SpvOpLogicalEqual = 164,
+    SpvOpLogicalNotEqual = 165,
+    SpvOpLogicalOr = 166,
+    SpvOpLogicalAnd = 167,
+    SpvOpLogicalNot = 168,
+    SpvOpSelect = 169,
+    SpvOpIEqual = 170,
+    SpvOpINotEqual = 171,
+    SpvOpUGreaterThan = 172,
+    SpvOpSGreaterThan = 173,
+    SpvOpUGreaterThanEqual = 174,
+    SpvOpSGreaterThanEqual = 175,
+    SpvOpULessThan = 176,
+    SpvOpSLessThan = 177,
+    SpvOpULessThanEqual = 178,
+    SpvOpSLessThanEqual = 179,
+    SpvOpFOrdEqual = 180,
+    SpvOpFUnordEqual = 181,
+    SpvOpFOrdNotEqual = 182,
+    SpvOpFUnordNotEqual = 183,
+    SpvOpFOrdLessThan = 184,
+    SpvOpFUnordLessThan = 185,
+    SpvOpFOrdGreaterThan = 186,
+    SpvOpFUnordGreaterThan = 187,
+    SpvOpFOrdLessThanEqual = 188,
+    SpvOpFUnordLessThanEqual = 189,
+    SpvOpFOrdGreaterThanEqual = 190,
+    SpvOpFUnordGreaterThanEqual = 191,
+    SpvOpShiftRightLogical = 194,
+    SpvOpShiftRightArithmetic = 195,
+    SpvOpShiftLeftLogical = 196,
+    SpvOpBitwiseOr = 197,
+    SpvOpBitwiseXor = 198,
+    SpvOpBitwiseAnd = 199,
+    SpvOpNot = 200,
+    SpvOpBitFieldInsert = 201,
+    SpvOpBitFieldSExtract = 202,
+    SpvOpBitFieldUExtract = 203,
+    SpvOpBitReverse = 204,
+    SpvOpBitCount = 205,
+    SpvOpDPdx = 207,
+    SpvOpDPdy = 208,
+    SpvOpFwidth = 209,
+    SpvOpDPdxFine = 210,
+    SpvOpDPdyFine = 211,
+    SpvOpFwidthFine = 212,
+    SpvOpDPdxCoarse = 213,
+    SpvOpDPdyCoarse = 214,
+    SpvOpFwidthCoarse = 215,
+    SpvOpEmitVertex = 218,
+    SpvOpEndPrimitive = 219,
+    SpvOpEmitStreamVertex = 220,
+    SpvOpEndStreamPrimitive = 221,
+    SpvOpControlBarrier = 224,
+    SpvOpMemoryBarrier = 225,
+    SpvOpAtomicLoad = 227,
+    SpvOpAtomicStore = 228,
+    SpvOpAtomicExchange = 229,
+    SpvOpAtomicCompareExchange = 230,
+    SpvOpAtomicCompareExchangeWeak = 231,
+    SpvOpAtomicIIncrement = 232,
+    SpvOpAtomicIDecrement = 233,
+    SpvOpAtomicIAdd = 234,
+    SpvOpAtomicISub = 235,
+    SpvOpAtomicSMin = 236,
+    SpvOpAtomicUMin = 237,
+    SpvOpAtomicSMax = 238,
+    SpvOpAtomicUMax = 239,
+    SpvOpAtomicAnd = 240,
+    SpvOpAtomicOr = 241,
+    SpvOpAtomicXor = 242,
+    SpvOpPhi = 245,
+    SpvOpLoopMerge = 246,
+    SpvOpSelectionMerge = 247,
+    SpvOpLabel = 248,
+    SpvOpBranch = 249,
+    SpvOpBranchConditional = 250,
+    SpvOpSwitch = 251,
+    SpvOpKill = 252,
+    SpvOpReturn = 253,
+    SpvOpReturnValue = 254,
+    SpvOpUnreachable = 255,
+    SpvOpLifetimeStart = 256,
+    SpvOpLifetimeStop = 257,
+    SpvOpGroupAsyncCopy = 259,
+    SpvOpGroupWaitEvents = 260,
+    SpvOpGroupAll = 261,
+    SpvOpGroupAny = 262,
+    SpvOpGroupBroadcast = 263,
+    SpvOpGroupIAdd = 264,
+    SpvOpGroupFAdd = 265,
+    SpvOpGroupFMin = 266,
+    SpvOpGroupUMin = 267,
+    SpvOpGroupSMin = 268,
+    SpvOpGroupFMax = 269,
+    SpvOpGroupUMax = 270,
+    SpvOpGroupSMax = 271,
+    SpvOpReadPipe = 274,
+    SpvOpWritePipe = 275,
+    SpvOpReservedReadPipe = 276,
+    SpvOpReservedWritePipe = 277,
+    SpvOpReserveReadPipePackets = 278,
+    SpvOpReserveWritePipePackets = 279,
+    SpvOpCommitReadPipe = 280,
+    SpvOpCommitWritePipe = 281,
+    SpvOpIsValidReserveId = 282,
+    SpvOpGetNumPipePackets = 283,
+    SpvOpGetMaxPipePackets = 284,
+    SpvOpGroupReserveReadPipePackets = 285,
+    SpvOpGroupReserveWritePipePackets = 286,
+    SpvOpGroupCommitReadPipe = 287,
+    SpvOpGroupCommitWritePipe = 288,
+    SpvOpEnqueueMarker = 291,
+    SpvOpEnqueueKernel = 292,
+    SpvOpGetKernelNDrangeSubGroupCount = 293,
+    SpvOpGetKernelNDrangeMaxSubGroupSize = 294,
+    SpvOpGetKernelWorkGroupSize = 295,
+    SpvOpGetKernelPreferredWorkGroupSizeMultiple = 296,
+    SpvOpRetainEvent = 297,
+    SpvOpReleaseEvent = 298,
+    SpvOpCreateUserEvent = 299,
+    SpvOpIsValidEvent = 300,
+    SpvOpSetUserEventStatus = 301,
+    SpvOpCaptureEventProfilingInfo = 302,
+    SpvOpGetDefaultQueue = 303,
+    SpvOpBuildNDRange = 304,
+    SpvOpImageSparseSampleImplicitLod = 305,
+    SpvOpImageSparseSampleExplicitLod = 306,
+    SpvOpImageSparseSampleDrefImplicitLod = 307,
+    SpvOpImageSparseSampleDrefExplicitLod = 308,
+    SpvOpImageSparseSampleProjImplicitLod = 309,
+    SpvOpImageSparseSampleProjExplicitLod = 310,
+    SpvOpImageSparseSampleProjDrefImplicitLod = 311,
+    SpvOpImageSparseSampleProjDrefExplicitLod = 312,
+    SpvOpImageSparseFetch = 313,
+    SpvOpImageSparseGather = 314,
+    SpvOpImageSparseDrefGather = 315,
+    SpvOpImageSparseTexelsResident = 316,
+    SpvOpNoLine = 317,
+    SpvOpAtomicFlagTestAndSet = 318,
+    SpvOpAtomicFlagClear = 319,
+    SpvOpImageSparseRead = 320,
+} SpvOp;
+
+#endif  // #ifndef spirv_H