diff --git a/src/sksl/SkSLASTNode.cpp b/src/sksl/SkSLASTNode.cpp
index ccfe25451a..e25bdf0af5 100644
--- a/src/sksl/SkSLASTNode.cpp
+++ b/src/sksl/SkSLASTNode.cpp
@@ -17,7 +17,7 @@ String ASTNode::description() const {
         case Kind::kNull: return "";
         case Kind::kBinary:
             return "(" + this->begin()->description() + " " +
-                               Operators::OperatorName(getToken().fKind) + " " +
+                               getOperator().operatorName() + " " +
                                (this->begin() + 1)->description() + ")";
         case Kind::kBlock: {
             String result = "{\n";
@@ -162,9 +162,9 @@ String ASTNode::description() const {
             return result;
         }
         case Kind::kPostfix:
-            return this->begin()->description() + Operators::OperatorName(getToken().fKind);
+            return this->begin()->description() + getOperator().operatorName();
         case Kind::kPrefix:
-            return Operators::OperatorName(getToken().fKind) + this->begin()->description();
+            return getOperator().operatorName() + this->begin()->description();
         case Kind::kReturn:
             if (this->begin() != this->end()) {
                 return "return " + this->begin()->description() + ";";
diff --git a/src/sksl/SkSLASTNode.h b/src/sksl/SkSLASTNode.h
index a9678d7d94..e6d3015d33 100644
--- a/src/sksl/SkSLASTNode.h
+++ b/src/sksl/SkSLASTNode.h
@@ -9,6 +9,7 @@
 #define SKSL_ASTNODE
 
 #include "src/sksl/SkSLLexer.h"
+#include "src/sksl/SkSLOperators.h"
 #include "src/sksl/SkSLString.h"
 #include "src/sksl/ir/SkSLModifiers.h"
 
@@ -53,7 +54,7 @@ struct ASTNode {
     };
 
     enum class Kind {
-        // data: operator(Token), children: left, right
+        // data: operator, children: left, right
         kBinary,
         // children: statements
         kBlock,
@@ -97,9 +98,9 @@ struct ASTNode {
         kNull,
         // data: ParameterData, children: type, arraySize1, arraySize2, ..., value?
         kParameter,
-        // data: operator(Token), children: operand
+        // data: operator, children: operand
         kPostfix,
-        // data: operator(Token), children: operand
+        // data: operator, children: operand
         kPrefix,
         // children: value
         kReturn,
@@ -250,7 +251,7 @@ struct ASTNode {
     };
 
     struct NodeData {
-        char fBytes[std::max({sizeof(Token),
+        char fBytes[std::max({sizeof(Token::Kind),
                               sizeof(StringFragment),
                               sizeof(bool),
                               sizeof(SKSL_INT),
@@ -263,7 +264,7 @@ struct ASTNode {
                               sizeof(SectionData)})];
 
         enum class Kind {
-            kToken,
+            kOperator,
             kStringFragment,
             kBool,
             kInt,
@@ -278,8 +279,9 @@ struct ASTNode {
 
         NodeData() = default;
 
-        NodeData(Token data)
-            : fKind(Kind::kToken) {
+        NodeData(Operator op)
+            : fKind(Kind::kOperator) {
+            Token::Kind data = op.kind();
             memcpy(fBytes, &data, sizeof(data));
         }
 
@@ -341,12 +343,13 @@ struct ASTNode {
     ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind)
         : fNodes(nodes)
         , fOffset(offset)
-            , fKind(kind) {
+        , fKind(kind) {
+
         switch (kind) {
             case Kind::kBinary:
             case Kind::kPostfix:
             case Kind::kPrefix:
-                fData.fKind = NodeData::Kind::kToken;
+                fData.fKind = NodeData::Kind::kOperator;
                 break;
 
             case Kind::kBool:
@@ -398,9 +401,9 @@ struct ASTNode {
         }
     }
 
-    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, Token t)
+    ASTNode(std::vector<ASTNode>* nodes, int offset, Kind kind, Operator op)
         : fNodes(nodes)
-        , fData(t)
+        , fData(op)
         , fOffset(offset)
         , fKind(kind) {}
 
@@ -450,11 +453,11 @@ struct ASTNode {
         return fKind != Kind::kNull;
     }
 
-    Token getToken() const {
-        SkASSERT(fData.fKind == NodeData::Kind::kToken);
-        Token result;
-        memcpy(&result, fData.fBytes, sizeof(result));
-        return result;
+    Operator getOperator() const {
+        SkASSERT(fData.fKind == NodeData::Kind::kOperator);
+        Token::Kind tokenKind;
+        memcpy(&tokenKind, fData.fBytes, sizeof(tokenKind));
+        return Operator{tokenKind};
     }
 
     bool getBool() const {
diff --git a/src/sksl/SkSLAnalysis.cpp b/src/sksl/SkSLAnalysis.cpp
index fa19e7a923..cf9905fdf8 100644
--- a/src/sksl/SkSLAnalysis.cpp
+++ b/src/sksl/SkSLAnalysis.cpp
@@ -538,7 +538,7 @@ static const char* invalid_for_ES2(const ForStatement& loop,
         return "expected loop index on left hand side of condition";
     }
     // relational_operator is one of: > >= < <= == or !=
-    switch (cond.getOperator()) {
+    switch (cond.getOperator().kind()) {
         case Token::Kind::TK_GT:
         case Token::Kind::TK_GTEQ:
         case Token::Kind::TK_LT:
@@ -575,7 +575,7 @@ static const char* invalid_for_ES2(const ForStatement& loop,
             if (!getConstant(next.right(), &loopInfo.fDelta)) {
                 return "loop index must be modified by a constant expression";
             }
-            switch (next.getOperator()) {
+            switch (next.getOperator().kind()) {
                 case Token::Kind::TK_PLUSEQ:                                      break;
                 case Token::Kind::TK_MINUSEQ: loopInfo.fDelta = -loopInfo.fDelta; break;
                 default:
@@ -587,7 +587,7 @@ static const char* invalid_for_ES2(const ForStatement& loop,
             if (!is_loop_index(next.operand())) {
                 return "expected loop index in loop expression";
             }
-            switch (next.getOperator()) {
+            switch (next.getOperator().kind()) {
                 case Token::Kind::TK_PLUSPLUS:   loopInfo.fDelta =  1; break;
                 case Token::Kind::TK_MINUSMINUS: loopInfo.fDelta = -1; break;
                 default:
@@ -599,7 +599,7 @@ static const char* invalid_for_ES2(const ForStatement& loop,
             if (!is_loop_index(next.operand())) {
                 return "expected loop index in loop expression";
             }
-            switch (next.getOperator()) {
+            switch (next.getOperator().kind()) {
                 case Token::Kind::TK_PLUSPLUS:   loopInfo.fDelta =  1; break;
                 case Token::Kind::TK_MINUSMINUS: loopInfo.fDelta = -1; break;
                 default:
@@ -624,7 +624,7 @@ static const char* invalid_for_ES2(const ForStatement& loop,
 
     double val = loopInfo.fStart;
     auto evalCond = [&]() {
-        switch (cond.getOperator()) {
+        switch (cond.getOperator().kind()) {
             case Token::Kind::TK_GT:   return val >  loopEnd;
             case Token::Kind::TK_GTEQ: return val >= loopEnd;
             case Token::Kind::TK_LT:   return val <  loopEnd;
diff --git a/src/sksl/SkSLCFGGenerator.cpp b/src/sksl/SkSLCFGGenerator.cpp
index 45e6d7cf36..6932369214 100644
--- a/src/sksl/SkSLCFGGenerator.cpp
+++ b/src/sksl/SkSLCFGGenerator.cpp
@@ -197,7 +197,7 @@ bool BasicBlock::tryRemoveExpression(std::vector<BasicBlock::Node>::iterator* it
     switch (expr->kind()) {
         case Expression::Kind::kBinary: {
             BinaryExpression& b = expr->as<BinaryExpression>();
-            if (b.getOperator() == Token::Kind::TK_EQ) {
+            if (b.getOperator().kind() == Token::Kind::TK_EQ) {
                 if (!this->tryRemoveLValueBefore(iter, b.left().get())) {
                     return false;
                 }
@@ -349,8 +349,8 @@ void CFGGenerator::addExpression(CFG& cfg, std::unique_ptr<Expression>* e, bool
     switch ((*e)->kind()) {
         case Expression::Kind::kBinary: {
             BinaryExpression& b = e->get()->as<BinaryExpression>();
-            Token::Kind op = b.getOperator();
-            switch (op) {
+            Operator op = b.getOperator();
+            switch (op.kind()) {
                 case Token::Kind::TK_LOGICALAND: // fall through
                 case Token::Kind::TK_LOGICALOR: {
                     // this isn't as precise as it could be -- we don't bother to track that if we
@@ -374,8 +374,7 @@ void CFGGenerator::addExpression(CFG& cfg, std::unique_ptr<Expression>* e, bool
                     break;
                 }
                 default:
-                    this->addExpression(cfg, &b.left(),
-                                        !Operators::IsAssignment(b.getOperator()));
+                    this->addExpression(cfg, &b.left(), !b.getOperator().isAssignment());
                     this->addExpression(cfg, &b.right(), constantPropagate);
                     cfg.currentBlock().fNodes.push_back(
                             BasicBlock::MakeExpression(e, constantPropagate));
@@ -421,9 +420,10 @@ void CFGGenerator::addExpression(CFG& cfg, std::unique_ptr<Expression>* e, bool
         }
         case Expression::Kind::kPrefix: {
             PrefixExpression& p = e->get()->as<PrefixExpression>();
-            this->addExpression(cfg, &p.operand(), constantPropagate &&
-                                                  p.getOperator() != Token::Kind::TK_PLUSPLUS &&
-                                                  p.getOperator() != Token::Kind::TK_MINUSMINUS);
+            this->addExpression(cfg, &p.operand(),
+                                constantPropagate &&
+                                p.getOperator().kind() != Token::Kind::TK_PLUSPLUS &&
+                                p.getOperator().kind() != Token::Kind::TK_MINUSMINUS);
             cfg.currentBlock().fNodes.push_back(BasicBlock::MakeExpression(e, constantPropagate));
             break;
         }
diff --git a/src/sksl/SkSLCPPCodeGenerator.cpp b/src/sksl/SkSLCPPCodeGenerator.cpp
index 8f9fe6f17c..0c26b438f0 100644
--- a/src/sksl/SkSLCPPCodeGenerator.cpp
+++ b/src/sksl/SkSLCPPCodeGenerator.cpp
@@ -72,10 +72,10 @@ void CPPCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
                                              Precedence parentPrecedence) {
     const Expression& left = *b.left();
     const Expression& right = *b.right();
-    Token::Kind op = b.getOperator();
-    if (op == Token::Kind::TK_PERCENT) {
+    Operator op = b.getOperator();
+    if (op.kind() == Token::Kind::TK_PERCENT) {
         // need to use "%%" instead of "%" b/c the code will be inside of a printf
-        Precedence precedence = Operators::GetBinaryPrecedence(op);
+        Precedence precedence = op.getBinaryPrecedence();
         if (precedence >= parentPrecedence) {
             this->write("(");
         }
diff --git a/src/sksl/SkSLCPPCodeGenerator.h b/src/sksl/SkSLCPPCodeGenerator.h
index 287352c59b..252f5b7e7f 100644
--- a/src/sksl/SkSLCPPCodeGenerator.h
+++ b/src/sksl/SkSLCPPCodeGenerator.h
@@ -25,7 +25,7 @@ public:
     bool generateCode() override;
 
 private:
-    using Precedence = Operators::Precedence;
+    using Precedence = Operator::Precedence;
 
     void writef(const char* s, va_list va) SK_PRINTF_LIKE(2, 0);
 
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index 137084e15a..b8a01c29b1 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -431,7 +431,7 @@ static bool is_dead(const Expression& lvalue, ProgramUsage* usage) {
  * to a dead target and lack of side effects on the left hand side.
  */
 static bool dead_assignment(const BinaryExpression& b, ProgramUsage* usage) {
-    if (!Operators::IsAssignment(b.getOperator())) {
+    if (!b.getOperator().isAssignment()) {
         return false;
     }
     return is_dead(*b.left(), usage);
@@ -477,7 +477,7 @@ static bool is_matching_expression_tree(const Expression& left, const Expression
 }
 
 static bool self_assignment(const BinaryExpression& b) {
-    return b.getOperator() == Token::Kind::TK_EQ &&
+    return b.getOperator().kind() == Token::Kind::TK_EQ &&
            is_matching_expression_tree(*b.left(), *b.right());
 }
 
@@ -576,7 +576,7 @@ static void delete_left(BasicBlock* b,
     std::unique_ptr<Expression>& rightPointer = bin.right();
     SkASSERT(!left.hasSideEffects());
     bool result;
-    if (bin.getOperator() == Token::Kind::TK_EQ) {
+    if (bin.getOperator().kind() == Token::Kind::TK_EQ) {
         result = b->tryRemoveLValueBefore(iter, &left);
     } else {
         result = b->tryRemoveExpressionBefore(iter, &left);
@@ -764,7 +764,7 @@ void Compiler::simplifyExpression(DefinitionMap& definitions,
                 (!rightType.isScalar() && !rightType.isVector())) {
                 break;
             }
-            switch (bin->getOperator()) {
+            switch (bin->getOperator().kind()) {
                 case Token::Kind::TK_STAR:
                     if (is_constant(left, 1)) {
                         if (leftType.isVector() && rightType.isScalar()) {
diff --git a/src/sksl/SkSLConstantFolder.cpp b/src/sksl/SkSLConstantFolder.cpp
index 137ab64373..7d05ffda13 100644
--- a/src/sksl/SkSLConstantFolder.cpp
+++ b/src/sksl/SkSLConstantFolder.cpp
@@ -24,17 +24,17 @@
 namespace SkSL {
 
 static std::unique_ptr<Expression> eliminate_no_op_boolean(const Expression& left,
-                                                           Token::Kind op,
+                                                           Operator op,
                                                            const Expression& right) {
     SkASSERT(right.is<BoolLiteral>());
     bool rightVal = right.as<BoolLiteral>().value();
 
     // Detect no-op Boolean expressions and optimize them away.
-    if ((op == Token::Kind::TK_LOGICALAND && rightVal)  ||  // (expr && true)  -> (expr)
-        (op == Token::Kind::TK_LOGICALOR  && !rightVal) ||  // (expr || false) -> (expr)
-        (op == Token::Kind::TK_LOGICALXOR && !rightVal) ||  // (expr ^^ false) -> (expr)
-        (op == Token::Kind::TK_EQEQ       && rightVal)  ||  // (expr == true)  -> (expr)
-        (op == Token::Kind::TK_NEQ        && !rightVal)) {  // (expr != false) -> (expr)
+    if ((op.kind() == Token::Kind::TK_LOGICALAND && rightVal)  ||  // (expr && true)  -> (expr)
+        (op.kind() == Token::Kind::TK_LOGICALOR  && !rightVal) ||  // (expr || false) -> (expr)
+        (op.kind() == Token::Kind::TK_LOGICALXOR && !rightVal) ||  // (expr ^^ false) -> (expr)
+        (op.kind() == Token::Kind::TK_EQEQ       && rightVal)  ||  // (expr == true)  -> (expr)
+        (op.kind() == Token::Kind::TK_NEQ        && !rightVal)) {  // (expr != false) -> (expr)
 
         return left.clone();
     }
@@ -43,14 +43,14 @@ static std::unique_ptr<Expression> eliminate_no_op_boolean(const Expression& lef
 }
 
 static std::unique_ptr<Expression> short_circuit_boolean(const Expression& left,
-                                                         Token::Kind op,
+                                                         Operator op,
                                                          const Expression& right) {
     SkASSERT(left.is<BoolLiteral>());
     bool leftVal = left.as<BoolLiteral>().value();
 
     // When the literal is on the left, we can sometimes eliminate the other expression entirely.
-    if ((op == Token::Kind::TK_LOGICALAND && !leftVal) ||  // (false && expr) -> (false)
-        (op == Token::Kind::TK_LOGICALOR  && leftVal)) {   // (true  || expr) -> (true)
+    if ((op.kind() == Token::Kind::TK_LOGICALAND && !leftVal) ||  // (false && expr) -> (false)
+        (op.kind() == Token::Kind::TK_LOGICALOR  && leftVal)) {   // (true  || expr) -> (true)
 
         return left.clone();
     }
@@ -67,15 +67,15 @@ static std::unique_ptr<Expression> short_circuit_boolean(const Expression& left,
 template <typename T, typename U = T>
 static std::unique_ptr<Expression> simplify_vector(const Context& context,
                                                    const Expression& left,
-                                                   Token::Kind op,
+                                                   Operator op,
                                                    const Expression& right) {
     SkASSERT(left.type().isVector());
     SkASSERT(left.type() == right.type());
     const Type& type = left.type();
 
     // Handle boolean operations: == !=
-    if (op == Token::Kind::TK_EQEQ || op == Token::Kind::TK_NEQ) {
-        bool equality = (op == Token::Kind::TK_EQEQ);
+    if (op.kind() == Token::Kind::TK_EQEQ || op.kind() == Token::Kind::TK_NEQ) {
+        bool equality = (op.kind() == Token::Kind::TK_EQEQ);
 
         switch (left.compareConstant(right)) {
             case Expression::ComparisonResult::kNotEqual:
@@ -102,7 +102,7 @@ static std::unique_ptr<Expression> simplify_vector(const Context& context,
         return std::make_unique<Constructor>(left.fOffset, &type, std::move(args));
     };
 
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_PLUS:  return vectorComponentwiseFold([](U a, U b) { return a + b; });
         case Token::Kind::TK_MINUS: return vectorComponentwiseFold([](U a, U b) { return a - b; });
         case Token::Kind::TK_STAR:  return vectorComponentwiseFold([](U a, U b) { return a * b; });
@@ -167,9 +167,9 @@ static bool contains_constant_zero(const Expression& expr) {
            (ConstantFolder::GetConstantFloat(expr, &floatValue) && floatValue == 0.0f);
 }
 
-bool ConstantFolder::ErrorOnDivideByZero(const Context& context, int offset, Token::Kind op,
+bool ConstantFolder::ErrorOnDivideByZero(const Context& context, int offset, Operator op,
                                          const Expression& right) {
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_SLASH:
         case Token::Kind::TK_SLASHEQ:
         case Token::Kind::TK_PERCENT:
@@ -187,11 +187,11 @@ bool ConstantFolder::ErrorOnDivideByZero(const Context& context, int offset, Tok
 std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
                                                      int offset,
                                                      const Expression& left,
-                                                     Token::Kind op,
+                                                     Operator op,
                                                      const Expression& right) {
     // If this is the comma operator, the left side is evaluated but not otherwise used in any way.
     // So if the left side has no side effects, it can just be eliminated entirely.
-    if (op == Token::Kind::TK_COMMA && !left.hasSideEffects()) {
+    if (op.kind() == Token::Kind::TK_COMMA && !left.hasSideEffects()) {
         return right.clone();
     }
 
@@ -200,7 +200,7 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
         bool leftVal  = left.as<BoolLiteral>().value();
         bool rightVal = right.as<BoolLiteral>().value();
         bool result;
-        switch (op) {
+        switch (op.kind()) {
             case Token::Kind::TK_LOGICALAND: result = leftVal && rightVal; break;
             case Token::Kind::TK_LOGICALOR:  result = leftVal || rightVal; break;
             case Token::Kind::TK_LOGICALXOR: result = leftVal ^  rightVal; break;
@@ -249,7 +249,7 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
     if (left.is<IntLiteral>() && right.is<IntLiteral>()) {
         SKSL_INT leftVal  = left.as<IntLiteral>().value();
         SKSL_INT rightVal = right.as<IntLiteral>().value();
-        switch (op) {
+        switch (op.kind()) {
             case Token::Kind::TK_PLUS:       return URESULT(Int, +);
             case Token::Kind::TK_MINUS:      return URESULT(Int, -);
             case Token::Kind::TK_STAR:       return URESULT(Int, *);
@@ -298,7 +298,7 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
     if (left.is<FloatLiteral>() && right.is<FloatLiteral>()) {
         SKSL_FLOAT leftVal  = left.as<FloatLiteral>().value();
         SKSL_FLOAT rightVal = right.as<FloatLiteral>().value();
-        switch (op) {
+        switch (op.kind()) {
             case Token::Kind::TK_PLUS:  return RESULT(Float, +);
             case Token::Kind::TK_MINUS: return RESULT(Float, -);
             case Token::Kind::TK_STAR:  return RESULT(Float, *);
@@ -354,7 +354,7 @@ std::unique_ptr<Expression> ConstantFolder::Simplify(const Context& context,
     // Perform constant folding on pairs of matrices.
     if (leftType.isMatrix() && rightType.isMatrix()) {
         bool equality;
-        switch (op) {
+        switch (op.kind()) {
             case Token::Kind::TK_EQEQ:
                 equality = true;
                 break;
diff --git a/src/sksl/SkSLConstantFolder.h b/src/sksl/SkSLConstantFolder.h
index 20c9a0c6db..bcc5bda168 100644
--- a/src/sksl/SkSLConstantFolder.h
+++ b/src/sksl/SkSLConstantFolder.h
@@ -11,7 +11,7 @@
 #include <memory>
 
 #include "src/sksl/SkSLDefines.h"
-#include "src/sksl/SkSLLexer.h"
+#include "src/sksl/SkSLOperators.h"
 
 namespace SkSL {
 
@@ -41,14 +41,14 @@ public:
      * Reports an error and returns true if op is a division / mod operator and right is zero or
      * contains a zero element.
      */
-    static bool ErrorOnDivideByZero(const Context& context, int offset, Token::Kind op,
+    static bool ErrorOnDivideByZero(const Context& context, int offset, Operator op,
                                     const Expression& right);
 
     /** Simplifies the binary expression `left OP right`. Returns null if it can't be simplified. */
     static std::unique_ptr<Expression> Simplify(const Context& context,
                                                 int offset,
                                                 const Expression& left,
-                                                Token::Kind op,
+                                                Operator op,
                                                 const Expression& right);
 };
 
diff --git a/src/sksl/SkSLDefinitionMap.cpp b/src/sksl/SkSLDefinitionMap.cpp
index 9cecef53d7..968d7655a6 100644
--- a/src/sksl/SkSLDefinitionMap.cpp
+++ b/src/sksl/SkSLDefinitionMap.cpp
@@ -119,9 +119,9 @@ void DefinitionMap::addDefinitions(const Context& context, const BasicBlock::Nod
         switch (expr->kind()) {
             case Expression::Kind::kBinary: {
                 BinaryExpression* b = &expr->as<BinaryExpression>();
-                if (b->getOperator() == Token::Kind::TK_EQ) {
+                if (b->getOperator().kind() == Token::Kind::TK_EQ) {
                     this->addDefinition(context, b->left().get(), &b->right());
-                } else if (Operators::IsAssignment(b->getOperator())) {
+                } else if (b->getOperator().isAssignment()) {
                     this->addDefinition(
                             context, b->left().get(),
                             (std::unique_ptr<Expression>*)&context.fDefined_Expression);
@@ -142,8 +142,8 @@ void DefinitionMap::addDefinitions(const Context& context, const BasicBlock::Nod
             }
             case Expression::Kind::kPrefix: {
                 const PrefixExpression* p = &expr->as<PrefixExpression>();
-                if (p->getOperator() == Token::Kind::TK_MINUSMINUS ||
-                    p->getOperator() == Token::Kind::TK_PLUSPLUS) {
+                if (p->getOperator().kind() == Token::Kind::TK_MINUSMINUS ||
+                    p->getOperator().kind() == Token::Kind::TK_PLUSPLUS) {
                     this->addDefinition(
                             context, p->operand().get(),
                             (std::unique_ptr<Expression>*)&context.fDefined_Expression);
@@ -152,8 +152,8 @@ void DefinitionMap::addDefinitions(const Context& context, const BasicBlock::Nod
             }
             case Expression::Kind::kPostfix: {
                 const PostfixExpression* p = &expr->as<PostfixExpression>();
-                if (p->getOperator() == Token::Kind::TK_MINUSMINUS ||
-                    p->getOperator() == Token::Kind::TK_PLUSPLUS) {
+                if (p->getOperator().kind() == Token::Kind::TK_MINUSMINUS ||
+                    p->getOperator().kind() == Token::Kind::TK_PLUSPLUS) {
                     this->addDefinition(
                             context, p->operand().get(),
                             (std::unique_ptr<Expression>*)&context.fDefined_Expression);
diff --git a/src/sksl/SkSLDehydrator.cpp b/src/sksl/SkSLDehydrator.cpp
index e1e2cafbe8..d6d63ce635 100644
--- a/src/sksl/SkSLDehydrator.cpp
+++ b/src/sksl/SkSLDehydrator.cpp
@@ -262,7 +262,7 @@ void Dehydrator::write(const Expression* e) {
                 const BinaryExpression& b = e->as<BinaryExpression>();
                 this->writeCommand(Rehydrator::kBinary_Command);
                 this->write(b.left().get());
-                this->writeU8((int) b.getOperator());
+                this->writeU8((int) b.getOperator().kind());
                 this->write(b.right().get());
                 this->write(b.type());
                 break;
@@ -332,14 +332,14 @@ void Dehydrator::write(const Expression* e) {
             case Expression::Kind::kPostfix: {
                 const PostfixExpression& p = e->as<PostfixExpression>();
                 this->writeCommand(Rehydrator::kPostfix_Command);
-                this->writeU8((int) p.getOperator());
+                this->writeU8((int) p.getOperator().kind());
                 this->write(p.operand().get());
                 break;
             }
             case Expression::Kind::kPrefix: {
                 const PrefixExpression& p = e->as<PrefixExpression>();
                 this->writeCommand(Rehydrator::kPrefix_Command);
-                this->writeU8((int) p.getOperator());
+                this->writeU8((int) p.getOperator().kind());
                 this->write(p.operand().get());
                 break;
             }
diff --git a/src/sksl/SkSLGLSLCodeGenerator.cpp b/src/sksl/SkSLGLSLCodeGenerator.cpp
index 2b290c74c4..0712d4c075 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.cpp
+++ b/src/sksl/SkSLGLSLCodeGenerator.cpp
@@ -509,7 +509,7 @@ void GLSLCodeGenerator::writeFunctionCall(const FunctionCall& c) {
                     arguments.size() == 2 &&
                     arguments[1]->kind() == Expression::Kind::kPrefix) {
                     const PrefixExpression& p = (PrefixExpression&) *arguments[1];
-                    if (p.getOperator() == Token::Kind::TK_MINUS) {
+                    if (p.getOperator().kind() == Token::Kind::TK_MINUS) {
                         this->write("atan(");
                         this->writeExpression(*arguments[0], Precedence::kSequence);
                         this->write(", -1.0 * ");
@@ -862,21 +862,21 @@ void GLSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
                                               Precedence parentPrecedence) {
     const Expression& left = *b.left();
     const Expression& right = *b.right();
-    Token::Kind op = b.getOperator();
+    Operator op = b.getOperator();
     if (fProgram.fCaps->unfoldShortCircuitAsTernary() &&
-            (op == Token::Kind::TK_LOGICALAND || op == Token::Kind::TK_LOGICALOR)) {
+            (op.kind() == Token::Kind::TK_LOGICALAND || op.kind() == Token::Kind::TK_LOGICALOR)) {
         this->writeShortCircuitWorkaroundExpression(b, parentPrecedence);
         return;
     }
 
-    Precedence precedence = Operators::GetBinaryPrecedence(op);
+    Precedence precedence = op.getBinaryPrecedence();
     if (precedence >= parentPrecedence) {
         this->write("(");
     }
     bool positionWorkaround = fProgramKind == Program::Kind::kVertex_Kind &&
-                              Operators::IsAssignment(op) &&
-                              left.kind() == Expression::Kind::kFieldAccess &&
-                              is_sk_position((FieldAccess&) left) &&
+                              op.isAssignment() &&
+                              left.is<FieldAccess>() &&
+                              is_sk_position(left.as<FieldAccess>()) &&
                               !right.containsRTAdjust() &&
                               !fProgram.fCaps->canUseFragCoord();
     if (positionWorkaround) {
@@ -884,7 +884,7 @@ void GLSLCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
     }
     this->writeExpression(left, precedence);
     this->write(" ");
-    this->write(Operators::OperatorName(op));
+    this->write(op.operatorName());
     this->write(" ");
     this->writeExpression(right, precedence);
     if (positionWorkaround) {
@@ -906,14 +906,14 @@ void GLSLCodeGenerator::writeShortCircuitWorkaroundExpression(const BinaryExpres
     // a || b  =>   a ? true : b
     this->writeExpression(*b.left(), Precedence::kTernary);
     this->write(" ? ");
-    if (b.getOperator() == Token::Kind::TK_LOGICALAND) {
+    if (b.getOperator().kind() == Token::Kind::TK_LOGICALAND) {
         this->writeExpression(*b.right(), Precedence::kTernary);
     } else {
         BoolLiteral boolTrue(fContext, -1, true);
         this->writeBoolLiteral(boolTrue);
     }
     this->write(" : ");
-    if (b.getOperator() == Token::Kind::TK_LOGICALAND) {
+    if (b.getOperator().kind() == Token::Kind::TK_LOGICALAND) {
         BoolLiteral boolFalse(fContext, -1, false);
         this->writeBoolLiteral(boolFalse);
     } else {
@@ -944,7 +944,7 @@ void GLSLCodeGenerator::writePrefixExpression(const PrefixExpression& p,
     if (Precedence::kPrefix >= parentPrecedence) {
         this->write("(");
     }
-    this->write(Operators::OperatorName(p.getOperator()));
+    this->write(p.getOperator().operatorName());
     this->writeExpression(*p.operand(), Precedence::kPrefix);
     if (Precedence::kPrefix >= parentPrecedence) {
         this->write(")");
@@ -957,7 +957,7 @@ void GLSLCodeGenerator::writePostfixExpression(const PostfixExpression& p,
         this->write("(");
     }
     this->writeExpression(*p.operand(), Precedence::kPostfix);
-    this->write(Operators::OperatorName(p.getOperator()));
+    this->write(p.getOperator().operatorName());
     if (Precedence::kPostfix >= parentPrecedence) {
         this->write(")");
     }
@@ -1332,7 +1332,7 @@ void GLSLCodeGenerator::writeForStatement(const ForStatement& f) {
     if (f.test()) {
         if (fProgram.fCaps->addAndTrueToLoopCondition()) {
             std::unique_ptr<Expression> and_true(new BinaryExpression(
-                    -1, f.test()->clone(), Token::Kind::TK_LOGICALAND,
+                    /*offset=*/-1, f.test()->clone(), Token::Kind::TK_LOGICALAND,
                     std::make_unique<BoolLiteral>(fContext, -1, true),
                     fContext.fTypes.fBool.get()));
             this->writeExpression(*and_true, Precedence::kTopLevel);
diff --git a/src/sksl/SkSLGLSLCodeGenerator.h b/src/sksl/SkSLGLSLCodeGenerator.h
index b40a78c8e5..78bcabcdcf 100644
--- a/src/sksl/SkSLGLSLCodeGenerator.h
+++ b/src/sksl/SkSLGLSLCodeGenerator.h
@@ -61,7 +61,7 @@ public:
     bool generateCode() override;
 
 protected:
-    using Precedence = Operators::Precedence;
+    using Precedence = Operator::Precedence;
 
     void write(const char* s);
 
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 9800dfdd2e..82dd7aafe3 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -934,10 +934,12 @@ std::unique_ptr<Statement> IRGenerator::getNormalizeSkPositionCode() {
     //                      sk_Position.w);
     SkASSERT(skPerVertex && fRTAdjust);
     auto Ref = [](const Variable* var) -> std::unique_ptr<Expression> {
-        return std::make_unique<VariableReference>(-1, var, VariableReference::RefKind::kRead);
+        return std::make_unique<VariableReference>(/*offset=*/-1, var,
+                                                   VariableReference::RefKind::kRead);
     };
     auto WRef = [](const Variable* var) -> std::unique_ptr<Expression> {
-        return std::make_unique<VariableReference>(-1, var, VariableReference::RefKind::kWrite);
+        return std::make_unique<VariableReference>(/*offset=*/-1, var,
+                                                   VariableReference::RefKind::kWrite);
     };
     auto Field = [&](const Variable* var, int idx) -> std::unique_ptr<Expression> {
         return std::make_unique<FieldAccess>(Ref(var), idx,
@@ -957,8 +959,8 @@ std::unique_ptr<Statement> IRGenerator::getNormalizeSkPositionCode() {
     };
     auto Op = [&](std::unique_ptr<Expression> left, Token::Kind op,
                   std::unique_ptr<Expression> right) -> std::unique_ptr<Expression> {
-        return std::make_unique<BinaryExpression>(-1, std::move(left), op, std::move(right),
-                                                  fContext.fTypes.fFloat2.get());
+        return std::make_unique<BinaryExpression>(/*offset=*/-1, std::move(left), op,
+                                                  std::move(right), fContext.fTypes.fFloat2.get());
     };
 
     static const ComponentArray kXYIndices{0, 1};
@@ -1720,8 +1722,8 @@ std::unique_ptr<Expression> IRGenerator::coerce(std::unique_ptr<Expression> expr
     return this->convertConstructor(offset, type.scalarTypeForLiteral(), std::move(args));
 }
 
-static bool is_matrix_multiply(const Type& left, Token::Kind op, const Type& right) {
-    if (op != Token::Kind::TK_STAR && op != Token::Kind::TK_STAREQ) {
+static bool is_matrix_multiply(const Type& left, Operator op, const Type& right) {
+    if (op.kind() != Token::Kind::TK_STAR && op.kind() != Token::Kind::TK_STAREQ) {
         return false;
     }
     if (left.isMatrix()) {
@@ -1730,98 +1732,19 @@ static bool is_matrix_multiply(const Type& left, Token::Kind op, const Type& rig
     return left.isVector() && right.isMatrix();
 }
 
-/**
- * Defines the set of logical (comparison) operators.
- */
-static bool op_is_logical(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_LT:
-        case Token::Kind::TK_GT:
-        case Token::Kind::TK_LTEQ:
-        case Token::Kind::TK_GTEQ:
-            return true;
-        default:
-            return false;
-    }
-}
-
-/**
- * Defines the set of operators which are only valid on integral types.
- */
-static bool op_only_valid_for_integral_types(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_SHL:
-        case Token::Kind::TK_SHR:
-        case Token::Kind::TK_BITWISEAND:
-        case Token::Kind::TK_BITWISEOR:
-        case Token::Kind::TK_BITWISEXOR:
-        case Token::Kind::TK_PERCENT:
-        case Token::Kind::TK_SHLEQ:
-        case Token::Kind::TK_SHREQ:
-        case Token::Kind::TK_BITWISEANDEQ:
-        case Token::Kind::TK_BITWISEOREQ:
-        case Token::Kind::TK_BITWISEXOREQ:
-        case Token::Kind::TK_PERCENTEQ:
-            return true;
-        default:
-            return false;
-    }
-}
-
-/**
- * Defines the set of operators which perform vector/matrix math.
- */
-static bool op_valid_for_matrix_or_vector(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_PLUS:
-        case Token::Kind::TK_MINUS:
-        case Token::Kind::TK_STAR:
-        case Token::Kind::TK_SLASH:
-        case Token::Kind::TK_PERCENT:
-        case Token::Kind::TK_SHL:
-        case Token::Kind::TK_SHR:
-        case Token::Kind::TK_BITWISEAND:
-        case Token::Kind::TK_BITWISEOR:
-        case Token::Kind::TK_BITWISEXOR:
-        case Token::Kind::TK_PLUSEQ:
-        case Token::Kind::TK_MINUSEQ:
-        case Token::Kind::TK_STAREQ:
-        case Token::Kind::TK_SLASHEQ:
-        case Token::Kind::TK_PERCENTEQ:
-        case Token::Kind::TK_SHLEQ:
-        case Token::Kind::TK_SHREQ:
-        case Token::Kind::TK_BITWISEANDEQ:
-        case Token::Kind::TK_BITWISEOREQ:
-        case Token::Kind::TK_BITWISEXOREQ:
-            return true;
-        default:
-            return false;
-    }
-}
-
-/*
- * Defines the set of operators allowed by The OpenGL ES Shading Language 1.00, Section 5.1.
- * The set of illegal (reserved) operators are the ones that only make sense with integral types.
- * This is not a coincidence: It's because ES2 doesn't require 'int' to be anything but syntactic
- * sugar for floats with truncation after each operation).
- */
-static bool op_allowed_in_strict_es2_mode(Token::Kind op) {
-    return !op_only_valid_for_integral_types(op);
-}
-
 /**
  * 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(const Context& context,
                                   bool allowNarrowing,
-                                  Token::Kind op,
+                                  Operator op,
                                   const Type& left,
                                   const Type& right,
                                   const Type** outLeftType,
                                   const Type** outRightType,
                                   const Type** outResultType) {
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_EQ:  // left = right
             *outLeftType = &left;
             *outRightType = &left;
@@ -1876,7 +1799,7 @@ static bool determine_binary_type(const Context& context,
         return false;
     }
 
-    bool isAssignment = Operators::IsAssignment(op);
+    bool isAssignment = op.isAssignment();
     if (is_matrix_multiply(left, op, right)) {  // left * right
         // Determine final component type.
         if (!determine_binary_type(context, allowNarrowing, op,
@@ -1907,13 +1830,13 @@ static bool determine_binary_type(const Context& context,
     }
 
     bool leftIsVectorOrMatrix = left.isVector() || left.isMatrix();
-    bool validMatrixOrVectorOp = op_valid_for_matrix_or_vector(op);
+    bool validMatrixOrVectorOp = op.isValidForMatrixOrVector();
 
     if (leftIsVectorOrMatrix && validMatrixOrVectorOp && right.isScalar()) {
         if (determine_binary_type(context, allowNarrowing, op, left.componentType(), right,
                                   outLeftType, outRightType, outResultType)) {
             *outLeftType = &(*outLeftType)->toCompound(context, left.columns(), left.rows());
-            if (!op_is_logical(op)) {
+            if (!op.isLogical()) {
                 *outResultType = &(*outResultType)->toCompound(context, left.columns(),
                                                                left.rows());
             }
@@ -1928,7 +1851,7 @@ static bool determine_binary_type(const Context& context,
         if (determine_binary_type(context, allowNarrowing, op, left, right.componentType(),
                                   outLeftType, outRightType, outResultType)) {
             *outRightType = &(*outRightType)->toCompound(context, right.columns(), right.rows());
-            if (!op_is_logical(op)) {
+            if (!op.isLogical()) {
                 *outResultType = &(*outResultType)->toCompound(context, right.columns(),
                                                                right.rows());
             }
@@ -1942,7 +1865,7 @@ static bool determine_binary_type(const Context& context,
                                                 : left.coercionCost(right);
 
     if ((left.isScalar() && right.isScalar()) || (leftIsVectorOrMatrix && validMatrixOrVectorOp)) {
-        if (op_only_valid_for_integral_types(op)) {
+        if (op.isOnlyValidForIntegralTypes()) {
             if (!leftComponentType.isInteger() || !rightComponentType.isInteger()) {
                 return false;
             }
@@ -1960,7 +1883,7 @@ static bool determine_binary_type(const Context& context,
         } else {
             return false;
         }
-        if (op_is_logical(op)) {
+        if (op.isLogical()) {
             *outResultType = context.fTypes.fBool.get();
         }
         return true;
@@ -1975,17 +1898,17 @@ std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(const ASTNode&
     if (!left) {
         return nullptr;
     }
-    Token::Kind op = expression.getToken().fKind;
     std::unique_ptr<Expression> right = this->convertExpression(*(iter++));
     if (!right) {
         return nullptr;
     }
-    return this->convertBinaryExpression(std::move(left), op, std::move(right));
+    return this->convertBinaryExpression(std::move(left), expression.getOperator(),
+                                         std::move(right));
 }
 
 std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(
                                                                 std::unique_ptr<Expression> left,
-                                                                Token::Kind op,
+                                                                Operator op,
                                                                 std::unique_ptr<Expression> right) {
     if (!left || !right) {
         return nullptr;
@@ -2006,13 +1929,13 @@ std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(
     } else {
         rawRightType = &right->type();
     }
-    if (this->strictES2Mode() && !op_allowed_in_strict_es2_mode(op)) {
-        this->errorReporter().error(
-                offset, String("operator '") + Operators::OperatorName(op) + "' is not allowed");
+    if (this->strictES2Mode() && !op.isAllowedInStrictES2Mode()) {
+        this->errorReporter().error(offset,
+                                    String("operator '") + op.operatorName() + "' is not allowed");
         return nullptr;
     }
-    bool isAssignment = Operators::IsAssignment(op);
-    if (isAssignment && !this->setRefKind(*left, op != Token::Kind::TK_EQ
+    bool isAssignment = op.isAssignment();
+    if (isAssignment && !this->setRefKind(*left, op.kind() != Token::Kind::TK_EQ
                                                  ? VariableReference::RefKind::kReadWrite
                                                  : VariableReference::RefKind::kWrite)) {
         return nullptr;
@@ -2020,7 +1943,7 @@ std::unique_ptr<Expression> IRGenerator::convertBinaryExpression(
     if (!determine_binary_type(fContext, fSettings->fAllowNarrowingConversions, op,
                                *rawLeftType, *rawRightType, &leftType, &rightType, &resultType)) {
         this->errorReporter().error(
-                offset, String("type mismatch: '") + Operators::OperatorName(op) +
+                offset, String("type mismatch: '") + op.operatorName() +
                                 "' cannot operate on '" + left->type().displayName() + "', '" +
                                 right->type().displayName() + "'");
         return nullptr;
@@ -2441,13 +2364,13 @@ std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(const ASTNode&
     if (!base) {
         return nullptr;
     }
-    return this->convertPrefixExpression(expression.getToken().fKind, std::move(base));
+    return this->convertPrefixExpression(expression.getOperator(), std::move(base));
 }
 
-std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(Token::Kind op,
+std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(Operator op,
                                                                  std::unique_ptr<Expression> base) {
     const Type& baseType = base->type();
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_PLUS:
             if (!baseType.componentType().isNumber()) {
                 this->errorReporter().error(
@@ -2477,7 +2400,7 @@ std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(Token::Kind op,
         case Token::Kind::TK_PLUSPLUS:
             if (!baseType.isNumber()) {
                 this->errorReporter().error(base->fOffset,
-                                            String("'") + Operators::OperatorName(op) +
+                                            String("'") + op.operatorName() +
                                             "' cannot operate on '" + baseType.displayName() + "'");
                 return nullptr;
             }
@@ -2488,7 +2411,7 @@ std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(Token::Kind op,
         case Token::Kind::TK_MINUSMINUS:
             if (!baseType.isNumber()) {
                 this->errorReporter().error(base->fOffset,
-                                            String("'") + Operators::OperatorName(op) +
+                                            String("'") + op.operatorName() +
                                             "' cannot operate on '" + baseType.displayName() + "'");
                 return nullptr;
             }
@@ -2499,7 +2422,7 @@ std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(Token::Kind op,
         case Token::Kind::TK_LOGICALNOT:
             if (!baseType.isBoolean()) {
                 this->errorReporter().error(base->fOffset,
-                                            String("'") + Operators::OperatorName(op) +
+                                            String("'") + op.operatorName() +
                                             "' cannot operate on '" + baseType.displayName() + "'");
                 return nullptr;
             }
@@ -2512,14 +2435,14 @@ std::unique_ptr<Expression> IRGenerator::convertPrefixExpression(Token::Kind op,
         case Token::Kind::TK_BITWISENOT:
             if (this->strictES2Mode()) {
                 // GLSL ES 1.00, Section 5.1
-                this->errorReporter().error(base->fOffset,
-                              String("operator '") + Operators::OperatorName(op) +
-                              "' is not allowed");
+                this->errorReporter().error(
+                        base->fOffset,
+                        String("operator '") + op.operatorName() + "' is not allowed");
                 return nullptr;
             }
             if (!baseType.isInteger()) {
                 this->errorReporter().error(base->fOffset,
-                                            String("'") + Operators::OperatorName(op) +
+                                            String("'") + op.operatorName() +
                                             "' cannot operate on '" + baseType.displayName() + "'");
                 return nullptr;
             }
@@ -2918,15 +2841,15 @@ std::unique_ptr<Expression> IRGenerator::convertPostfixExpression(const ASTNode&
     if (!base) {
         return nullptr;
     }
-    return this->convertPostfixExpression(std::move(base), expression.getToken().fKind);
+    return this->convertPostfixExpression(std::move(base), expression.getOperator());
 }
 
 std::unique_ptr<Expression> IRGenerator::convertPostfixExpression(std::unique_ptr<Expression> base,
-                                                                  Token::Kind op) {
+                                                                  Operator op) {
     const Type& baseType = base->type();
     if (!baseType.isNumber()) {
         this->errorReporter().error(base->fOffset,
-                                    "'" + String(Operators::OperatorName(op)) +
+                                    "'" + String(op.operatorName()) +
                                     "' cannot operate on '" + baseType.displayName() + "'");
         return nullptr;
     }
diff --git a/src/sksl/SkSLIRGenerator.h b/src/sksl/SkSLIRGenerator.h
index 39561c25a6..1e2db9e681 100644
--- a/src/sksl/SkSLIRGenerator.h
+++ b/src/sksl/SkSLIRGenerator.h
@@ -14,6 +14,7 @@
 #include "src/sksl/SkSLASTFile.h"
 #include "src/sksl/SkSLASTNode.h"
 #include "src/sksl/SkSLErrorReporter.h"
+#include "src/sksl/SkSLOperators.h"
 #include "src/sksl/ir/SkSLBlock.h"
 #include "src/sksl/ir/SkSLExpression.h"
 #include "src/sksl/ir/SkSLExtension.h"
@@ -185,9 +186,9 @@ private:
     int convertArraySize(const Type& type, int offset, const ASTNode& s);
     int convertArraySize(const Type& type, std::unique_ptr<Expression> s);
     bool containsConstantZero(Expression& expr);
-    bool dividesByZero(Token::Kind op, Expression& right);
+    bool dividesByZero(Operator op, Expression& right);
     std::unique_ptr<Expression> convertBinaryExpression(std::unique_ptr<Expression> left,
-                                                        Token::Kind op,
+                                                        Operator op,
                                                         std::unique_ptr<Expression> right);
     std::unique_ptr<Block> convertBlock(const ASTNode& block);
     std::unique_ptr<Statement> convertBreak(const ASTNode& b);
@@ -245,9 +246,9 @@ private:
     std::unique_ptr<Expression> convertIndex(std::unique_ptr<Expression> base,
                                              std::unique_ptr<Expression> index);
     std::unique_ptr<Expression> convertPostfixExpression(std::unique_ptr<Expression> base,
-                                                         Token::Kind op);
+                                                         Operator op);
     std::unique_ptr<Expression> convertPostfixExpression(const ASTNode& expression);
-    std::unique_ptr<Expression> convertPrefixExpression(Token::Kind op,
+    std::unique_ptr<Expression> convertPrefixExpression(Operator op,
                                                         std::unique_ptr<Expression> base);
     std::unique_ptr<Expression> convertScopeExpression(const ASTNode& expression);
     std::unique_ptr<StructDefinition> convertStructDefinition(const ASTNode& expression);
diff --git a/src/sksl/SkSLInliner.cpp b/src/sksl/SkSLInliner.cpp
index 50b8268b99..0243d47161 100644
--- a/src/sksl/SkSLInliner.cpp
+++ b/src/sksl/SkSLInliner.cpp
@@ -1004,9 +1004,9 @@ public:
                 // It is illegal for side-effects from x() or y() to occur. The simplest way to
                 // enforce that rule is to avoid inlining the right side entirely. However, it is
                 // safe for other types of binary expression to inline both sides.
-                Token::Kind op = binaryExpr.getOperator();
-                bool shortCircuitable = (op == Token::Kind::TK_LOGICALAND ||
-                                         op == Token::Kind::TK_LOGICALOR);
+                Operator op = binaryExpr.getOperator();
+                bool shortCircuitable = (op.kind() == Token::Kind::TK_LOGICALAND ||
+                                         op.kind() == Token::Kind::TK_LOGICALOR);
                 if (!shortCircuitable) {
                     this->visitExpression(&binaryExpr.right());
                 }
diff --git a/src/sksl/SkSLMetalCodeGenerator.cpp b/src/sksl/SkSLMetalCodeGenerator.cpp
index 88666b3302..f7e89e0863 100644
--- a/src/sksl/SkSLMetalCodeGenerator.cpp
+++ b/src/sksl/SkSLMetalCodeGenerator.cpp
@@ -22,10 +22,10 @@
 
 namespace SkSL {
 
-const char* MetalCodeGenerator::OperatorName(Token::Kind op) {
-    switch (op) {
+const char* MetalCodeGenerator::OperatorName(Operator op) {
+    switch (op.kind()) {
         case Token::Kind::TK_LOGICALXOR:  return "!=";
-        default:                          return Operators::OperatorName(op);
+        default:                          return op.operatorName();
     }
 }
 
@@ -1257,10 +1257,10 @@ void MetalCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
     const Expression& right = *b.right();
     const Type& leftType = left.type();
     const Type& rightType = right.type();
-    Token::Kind op = b.getOperator();
-    Precedence precedence = Operators::GetBinaryPrecedence(b.getOperator());
+    Operator op = b.getOperator();
+    Precedence precedence = op.getBinaryPrecedence();
     bool needParens = precedence >= parentPrecedence;
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_EQEQ:
             if (leftType.isVector()) {
                 this->write("all");
@@ -1280,16 +1280,16 @@ void MetalCodeGenerator::writeBinaryExpression(const BinaryExpression& b,
         this->write("(");
     }
     if (leftType.isMatrix() && rightType.isMatrix()) {
-        if (op == Token::Kind::TK_STAREQ) {
+        if (op.kind() == Token::Kind::TK_STAREQ) {
             this->writeMatrixTimesEqualHelper(leftType, rightType, b.type());
-        } else if (op == Token::Kind::TK_EQEQ) {
+        } else if (op.kind() == Token::Kind::TK_EQEQ) {
             this->writeMatrixEqualityHelper(leftType, rightType);
-        } else if (op == Token::Kind::TK_NEQ) {
+        } else if (op.kind() == Token::Kind::TK_NEQ) {
             this->writeMatrixInequalityHelper(leftType, rightType);
         }
     }
     this->writeExpression(left, precedence);
-    if (op != Token::Kind::TK_EQ && Operators::IsAssignment(op) &&
+    if (op.kind() != Token::Kind::TK_EQ && op.isAssignment() &&
         left.kind() == Expression::Kind::kSwizzle && !left.hasSideEffects()) {
         // This doesn't compile in Metal:
         // float4 x = float4(1);
diff --git a/src/sksl/SkSLMetalCodeGenerator.h b/src/sksl/SkSLMetalCodeGenerator.h
index d446807f5b..02c4a7bc8f 100644
--- a/src/sksl/SkSLMetalCodeGenerator.h
+++ b/src/sksl/SkSLMetalCodeGenerator.h
@@ -68,7 +68,7 @@ public:
     bool generateCode() override;
 
 protected:
-    using Precedence = Operators::Precedence;
+    using Precedence = Operator::Precedence;
 
     typedef int Requirements;
     static constexpr Requirements kNo_Requirements       = 0;
@@ -109,7 +109,7 @@ protected:
         kTexture_IntrinsicKind,
     };
 
-    static const char* OperatorName(Token::Kind op);
+    static const char* OperatorName(Operator op);
 
     class GlobalStructVisitor;
     void visitGlobalStruct(GlobalStructVisitor* visitor);
diff --git a/src/sksl/SkSLOperators.cpp b/src/sksl/SkSLOperators.cpp
index e6890d22d2..cddd9174ac 100644
--- a/src/sksl/SkSLOperators.cpp
+++ b/src/sksl/SkSLOperators.cpp
@@ -9,123 +9,222 @@
 #include "src/sksl/SkSLOperators.h"
 
 namespace SkSL {
-namespace Operators {
 
-Precedence GetBinaryPrecedence(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_STAR:         // fall through
-        case Token::Kind::TK_SLASH:        // fall through
-        case Token::Kind::TK_PERCENT:      return Precedence::kMultiplicative;
-        case Token::Kind::TK_PLUS:         // fall through
-        case Token::Kind::TK_MINUS:        return Precedence::kAdditive;
-        case Token::Kind::TK_SHL:          // fall through
-        case Token::Kind::TK_SHR:          return Precedence::kShift;
-        case Token::Kind::TK_LT:           // fall through
-        case Token::Kind::TK_GT:           // fall through
-        case Token::Kind::TK_LTEQ:         // fall through
-        case Token::Kind::TK_GTEQ:         return Precedence::kRelational;
-        case Token::Kind::TK_EQEQ:         // fall through
-        case Token::Kind::TK_NEQ:          return Precedence::kEquality;
-        case Token::Kind::TK_BITWISEAND:   return Precedence::kBitwiseAnd;
-        case Token::Kind::TK_BITWISEXOR:   return Precedence::kBitwiseXor;
-        case Token::Kind::TK_BITWISEOR:    return Precedence::kBitwiseOr;
-        case Token::Kind::TK_LOGICALAND:   return Precedence::kLogicalAnd;
-        case Token::Kind::TK_LOGICALXOR:   return Precedence::kLogicalXor;
-        case Token::Kind::TK_LOGICALOR:    return Precedence::kLogicalOr;
-        case Token::Kind::TK_EQ:           // fall through
-        case Token::Kind::TK_PLUSEQ:       // fall through
-        case Token::Kind::TK_MINUSEQ:      // fall through
-        case Token::Kind::TK_STAREQ:       // fall through
-        case Token::Kind::TK_SLASHEQ:      // fall through
-        case Token::Kind::TK_PERCENTEQ:    // fall through
-        case Token::Kind::TK_SHLEQ:        // fall through
-        case Token::Kind::TK_SHREQ:        // fall through
-        case Token::Kind::TK_BITWISEANDEQ: // fall through
-        case Token::Kind::TK_BITWISEXOREQ: // fall through
-        case Token::Kind::TK_BITWISEOREQ:  return Precedence::kAssignment;
-        case Token::Kind::TK_COMMA:        return Precedence::kSequence;
+Operator::Precedence Operator::getBinaryPrecedence() const {
+    switch (fKind) {
+        case Kind::TK_STAR:         // fall through
+        case Kind::TK_SLASH:        // fall through
+        case Kind::TK_PERCENT:      return Precedence::kMultiplicative;
+        case Kind::TK_PLUS:         // fall through
+        case Kind::TK_MINUS:        return Precedence::kAdditive;
+        case Kind::TK_SHL:          // fall through
+        case Kind::TK_SHR:          return Precedence::kShift;
+        case Kind::TK_LT:           // fall through
+        case Kind::TK_GT:           // fall through
+        case Kind::TK_LTEQ:         // fall through
+        case Kind::TK_GTEQ:         return Precedence::kRelational;
+        case Kind::TK_EQEQ:         // fall through
+        case Kind::TK_NEQ:          return Precedence::kEquality;
+        case Kind::TK_BITWISEAND:   return Precedence::kBitwiseAnd;
+        case Kind::TK_BITWISEXOR:   return Precedence::kBitwiseXor;
+        case Kind::TK_BITWISEOR:    return Precedence::kBitwiseOr;
+        case Kind::TK_LOGICALAND:   return Precedence::kLogicalAnd;
+        case Kind::TK_LOGICALXOR:   return Precedence::kLogicalXor;
+        case Kind::TK_LOGICALOR:    return Precedence::kLogicalOr;
+        case Kind::TK_EQ:           // fall through
+        case Kind::TK_PLUSEQ:       // fall through
+        case Kind::TK_MINUSEQ:      // fall through
+        case Kind::TK_STAREQ:       // fall through
+        case Kind::TK_SLASHEQ:      // fall through
+        case Kind::TK_PERCENTEQ:    // fall through
+        case Kind::TK_SHLEQ:        // fall through
+        case Kind::TK_SHREQ:        // fall through
+        case Kind::TK_BITWISEANDEQ: // fall through
+        case Kind::TK_BITWISEXOREQ: // fall through
+        case Kind::TK_BITWISEOREQ:  return Precedence::kAssignment;
+        case Kind::TK_COMMA:        return Precedence::kSequence;
         default: SK_ABORT("unsupported binary operator");
     }
 }
 
-
-const char* OperatorName(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_PLUS:         return "+";
-        case Token::Kind::TK_MINUS:        return "-";
-        case Token::Kind::TK_STAR:         return "*";
-        case Token::Kind::TK_SLASH:        return "/";
-        case Token::Kind::TK_PERCENT:      return "%";
-        case Token::Kind::TK_SHL:          return "<<";
-        case Token::Kind::TK_SHR:          return ">>";
-        case Token::Kind::TK_LOGICALNOT:   return "!";
-        case Token::Kind::TK_LOGICALAND:   return "&&";
-        case Token::Kind::TK_LOGICALOR:    return "||";
-        case Token::Kind::TK_LOGICALXOR:   return "^^";
-        case Token::Kind::TK_BITWISENOT:   return "~";
-        case Token::Kind::TK_BITWISEAND:   return "&";
-        case Token::Kind::TK_BITWISEOR:    return "|";
-        case Token::Kind::TK_BITWISEXOR:   return "^";
-        case Token::Kind::TK_EQ:           return "=";
-        case Token::Kind::TK_EQEQ:         return "==";
-        case Token::Kind::TK_NEQ:          return "!=";
-        case Token::Kind::TK_LT:           return "<";
-        case Token::Kind::TK_GT:           return ">";
-        case Token::Kind::TK_LTEQ:         return "<=";
-        case Token::Kind::TK_GTEQ:         return ">=";
-        case Token::Kind::TK_PLUSEQ:       return "+=";
-        case Token::Kind::TK_MINUSEQ:      return "-=";
-        case Token::Kind::TK_STAREQ:       return "*=";
-        case Token::Kind::TK_SLASHEQ:      return "/=";
-        case Token::Kind::TK_PERCENTEQ:    return "%=";
-        case Token::Kind::TK_SHLEQ:        return "<<=";
-        case Token::Kind::TK_SHREQ:        return ">>=";
-        case Token::Kind::TK_BITWISEANDEQ: return "&=";
-        case Token::Kind::TK_BITWISEOREQ:  return "|=";
-        case Token::Kind::TK_BITWISEXOREQ: return "^=";
-        case Token::Kind::TK_PLUSPLUS:     return "++";
-        case Token::Kind::TK_MINUSMINUS:   return "--";
-        case Token::Kind::TK_COMMA:        return ",";
-        default:
-            SK_ABORT("unsupported operator: %d\n", (int) op);
-    }
-}
-
-
-bool IsAssignment(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_EQ:           // fall through
-        case Token::Kind::TK_PLUSEQ:       // fall through
-        case Token::Kind::TK_MINUSEQ:      // fall through
-        case Token::Kind::TK_STAREQ:       // fall through
-        case Token::Kind::TK_SLASHEQ:      // fall through
-        case Token::Kind::TK_PERCENTEQ:    // fall through
-        case Token::Kind::TK_SHLEQ:        // fall through
-        case Token::Kind::TK_SHREQ:        // fall through
-        case Token::Kind::TK_BITWISEOREQ:  // fall through
-        case Token::Kind::TK_BITWISEXOREQ: // fall through
-        case Token::Kind::TK_BITWISEANDEQ:
+bool Operator::isOperator() const {
+    switch (this->kind()) {
+        case Kind::TK_PLUS:
+        case Kind::TK_MINUS:
+        case Kind::TK_STAR:
+        case Kind::TK_SLASH:
+        case Kind::TK_PERCENT:
+        case Kind::TK_SHL:
+        case Kind::TK_SHR:
+        case Kind::TK_LOGICALNOT:
+        case Kind::TK_LOGICALAND:
+        case Kind::TK_LOGICALOR:
+        case Kind::TK_LOGICALXOR:
+        case Kind::TK_BITWISENOT:
+        case Kind::TK_BITWISEAND:
+        case Kind::TK_BITWISEOR:
+        case Kind::TK_BITWISEXOR:
+        case Kind::TK_EQ:
+        case Kind::TK_EQEQ:
+        case Kind::TK_NEQ:
+        case Kind::TK_LT:
+        case Kind::TK_GT:
+        case Kind::TK_LTEQ:
+        case Kind::TK_GTEQ:
+        case Kind::TK_PLUSEQ:
+        case Kind::TK_MINUSEQ:
+        case Kind::TK_STAREQ:
+        case Kind::TK_SLASHEQ:
+        case Kind::TK_PERCENTEQ:
+        case Kind::TK_SHLEQ:
+        case Kind::TK_SHREQ:
+        case Kind::TK_BITWISEANDEQ:
+        case Kind::TK_BITWISEOREQ:
+        case Kind::TK_BITWISEXOREQ:
+        case Kind::TK_PLUSPLUS:
+        case Kind::TK_MINUSMINUS:
+        case Kind::TK_COMMA:
             return true;
         default:
             return false;
     }
 }
 
-Token::Kind RemoveAssignment(Token::Kind op) {
-    switch (op) {
-        case Token::Kind::TK_PLUSEQ:       return Token::Kind::TK_PLUS;
-        case Token::Kind::TK_MINUSEQ:      return Token::Kind::TK_MINUS;
-        case Token::Kind::TK_STAREQ:       return Token::Kind::TK_STAR;
-        case Token::Kind::TK_SLASHEQ:      return Token::Kind::TK_SLASH;
-        case Token::Kind::TK_PERCENTEQ:    return Token::Kind::TK_PERCENT;
-        case Token::Kind::TK_SHLEQ:        return Token::Kind::TK_SHL;
-        case Token::Kind::TK_SHREQ:        return Token::Kind::TK_SHR;
-        case Token::Kind::TK_BITWISEOREQ:  return Token::Kind::TK_BITWISEOR;
-        case Token::Kind::TK_BITWISEXOREQ: return Token::Kind::TK_BITWISEXOR;
-        case Token::Kind::TK_BITWISEANDEQ: return Token::Kind::TK_BITWISEAND;
-        default: return op;
+const char* Operator::operatorName() const {
+    switch (fKind) {
+        case Kind::TK_PLUS:         return "+";
+        case Kind::TK_MINUS:        return "-";
+        case Kind::TK_STAR:         return "*";
+        case Kind::TK_SLASH:        return "/";
+        case Kind::TK_PERCENT:      return "%";
+        case Kind::TK_SHL:          return "<<";
+        case Kind::TK_SHR:          return ">>";
+        case Kind::TK_LOGICALNOT:   return "!";
+        case Kind::TK_LOGICALAND:   return "&&";
+        case Kind::TK_LOGICALOR:    return "||";
+        case Kind::TK_LOGICALXOR:   return "^^";
+        case Kind::TK_BITWISENOT:   return "~";
+        case Kind::TK_BITWISEAND:   return "&";
+        case Kind::TK_BITWISEOR:    return "|";
+        case Kind::TK_BITWISEXOR:   return "^";
+        case Kind::TK_EQ:           return "=";
+        case Kind::TK_EQEQ:         return "==";
+        case Kind::TK_NEQ:          return "!=";
+        case Kind::TK_LT:           return "<";
+        case Kind::TK_GT:           return ">";
+        case Kind::TK_LTEQ:         return "<=";
+        case Kind::TK_GTEQ:         return ">=";
+        case Kind::TK_PLUSEQ:       return "+=";
+        case Kind::TK_MINUSEQ:      return "-=";
+        case Kind::TK_STAREQ:       return "*=";
+        case Kind::TK_SLASHEQ:      return "/=";
+        case Kind::TK_PERCENTEQ:    return "%=";
+        case Kind::TK_SHLEQ:        return "<<=";
+        case Kind::TK_SHREQ:        return ">>=";
+        case Kind::TK_BITWISEANDEQ: return "&=";
+        case Kind::TK_BITWISEOREQ:  return "|=";
+        case Kind::TK_BITWISEXOREQ: return "^=";
+        case Kind::TK_PLUSPLUS:     return "++";
+        case Kind::TK_MINUSMINUS:   return "--";
+        case Kind::TK_COMMA:        return ",";
+        default:
+            SK_ABORT("unsupported operator: %d\n", (int) fKind);
+    }
+}
+
+bool Operator::isAssignment() const {
+    switch (fKind) {
+        case Kind::TK_EQ:           // fall through
+        case Kind::TK_PLUSEQ:       // fall through
+        case Kind::TK_MINUSEQ:      // fall through
+        case Kind::TK_STAREQ:       // fall through
+        case Kind::TK_SLASHEQ:      // fall through
+        case Kind::TK_PERCENTEQ:    // fall through
+        case Kind::TK_SHLEQ:        // fall through
+        case Kind::TK_SHREQ:        // fall through
+        case Kind::TK_BITWISEOREQ:  // fall through
+        case Kind::TK_BITWISEXOREQ: // fall through
+        case Kind::TK_BITWISEANDEQ:
+            return true;
+        default:
+            return false;
+    }
+}
+
+Operator Operator::removeAssignment() const {
+    switch (fKind) {
+        case Kind::TK_PLUSEQ:       return Operator{Kind::TK_PLUS};
+        case Kind::TK_MINUSEQ:      return Operator{Kind::TK_MINUS};
+        case Kind::TK_STAREQ:       return Operator{Kind::TK_STAR};
+        case Kind::TK_SLASHEQ:      return Operator{Kind::TK_SLASH};
+        case Kind::TK_PERCENTEQ:    return Operator{Kind::TK_PERCENT};
+        case Kind::TK_SHLEQ:        return Operator{Kind::TK_SHL};
+        case Kind::TK_SHREQ:        return Operator{Kind::TK_SHR};
+        case Kind::TK_BITWISEOREQ:  return Operator{Kind::TK_BITWISEOR};
+        case Kind::TK_BITWISEXOREQ: return Operator{Kind::TK_BITWISEXOR};
+        case Kind::TK_BITWISEANDEQ: return Operator{Kind::TK_BITWISEAND};
+        default: return *this;
+    }
+}
+
+bool Operator::isLogical() const {
+    switch (kind()) {
+        case Token::Kind::TK_LT:
+        case Token::Kind::TK_GT:
+        case Token::Kind::TK_LTEQ:
+        case Token::Kind::TK_GTEQ:
+            return true;
+        default:
+            return false;
+    }
+}
+
+bool Operator::isOnlyValidForIntegralTypes() const {
+    switch (kind()) {
+        case Token::Kind::TK_SHL:
+        case Token::Kind::TK_SHR:
+        case Token::Kind::TK_BITWISEAND:
+        case Token::Kind::TK_BITWISEOR:
+        case Token::Kind::TK_BITWISEXOR:
+        case Token::Kind::TK_PERCENT:
+        case Token::Kind::TK_SHLEQ:
+        case Token::Kind::TK_SHREQ:
+        case Token::Kind::TK_BITWISEANDEQ:
+        case Token::Kind::TK_BITWISEOREQ:
+        case Token::Kind::TK_BITWISEXOREQ:
+        case Token::Kind::TK_PERCENTEQ:
+            return true;
+        default:
+            return false;
+    }
+}
+
+bool Operator::isValidForMatrixOrVector() const {
+    switch (kind()) {
+        case Token::Kind::TK_PLUS:
+        case Token::Kind::TK_MINUS:
+        case Token::Kind::TK_STAR:
+        case Token::Kind::TK_SLASH:
+        case Token::Kind::TK_PERCENT:
+        case Token::Kind::TK_SHL:
+        case Token::Kind::TK_SHR:
+        case Token::Kind::TK_BITWISEAND:
+        case Token::Kind::TK_BITWISEOR:
+        case Token::Kind::TK_BITWISEXOR:
+        case Token::Kind::TK_PLUSEQ:
+        case Token::Kind::TK_MINUSEQ:
+        case Token::Kind::TK_STAREQ:
+        case Token::Kind::TK_SLASHEQ:
+        case Token::Kind::TK_PERCENTEQ:
+        case Token::Kind::TK_SHLEQ:
+        case Token::Kind::TK_SHREQ:
+        case Token::Kind::TK_BITWISEANDEQ:
+        case Token::Kind::TK_BITWISEOREQ:
+        case Token::Kind::TK_BITWISEXOREQ:
+            return true;
+        default:
+            return false;
     }
 }
 
-}  // namespace Operators
 }  // namespace SkSL
diff --git a/src/sksl/SkSLOperators.h b/src/sksl/SkSLOperators.h
index a88de88600..c151485a04 100644
--- a/src/sksl/SkSLOperators.h
+++ b/src/sksl/SkSLOperators.h
@@ -8,10 +8,19 @@
 #ifndef SKSL_OPERATORS
 #define SKSL_OPERATORS
 
+#include "src/sksl/SkSLDefines.h"
 #include "src/sksl/SkSLLexer.h"
 
 namespace SkSL {
-namespace Operators {
+
+class Operator {
+public:
+    using Kind = Token::Kind;
+
+    // Allow implicit conversion from Token::Kind, since this is just a utility wrapper on top.
+    Operator(Token::Kind t) : fKind(t) {
+        SkASSERTF(this->isOperator(), "token-kind %d is not an operator", fKind);
+    }
 
     enum class Precedence {
         kParentheses    =  1,
@@ -34,18 +43,53 @@ namespace Operators {
         kTopLevel       = kSequence
     };
 
-    Precedence GetBinaryPrecedence(Token::Kind op);
+    Token::Kind kind() const { return fKind; }
 
-    const char* OperatorName(Token::Kind op);
+    Precedence getBinaryPrecedence() const;
+
+    const char* operatorName() const;
 
     // Returns true if op is '=' or any compound assignment operator ('+=', '-=', etc.)
-    bool IsAssignment(Token::Kind op);
+    bool isAssignment() const;
 
     // Given a compound assignment operator, returns the non-assignment version of the operator
     // (e.g. '+=' becomes '+')
-    Token::Kind RemoveAssignment(Token::Kind op);
+    Operator removeAssignment() const;
+
+    /**
+     * Defines the set of logical (comparison) operators:
+     *     <  <=  >  >=
+     */
+    bool isLogical() const;
+
+    /**
+     * Defines the set of operators which are only valid on integral types:
+     *   <<  <<=  >>  >>=  &  &=  |  |=  ^  ^=  %  %=
+     */
+    bool isOnlyValidForIntegralTypes() const;
+
+    /**
+     * Defines the set of operators which perform vector/matrix math.
+     *   +  +=  -  -=  *  *=  /  /=  %  %=  <<  <<=  >>  >>=  &  &=  |  |=  ^  ^=
+     */
+    bool isValidForMatrixOrVector() const;
+
+    /*
+     * Defines the set of operators allowed by The OpenGL ES Shading Language 1.00, Section 5.1.
+     * The set of illegal (reserved) operators are the ones that only make sense with integral
+     * types. This is not a coincidence: It's because ES2 doesn't require 'int' to be anything but
+     * syntactic sugar for floats with truncation after each operation.
+     */
+    bool isAllowedInStrictES2Mode() const {
+        return !this->isOnlyValidForIntegralTypes();
+    }
+
+private:
+    bool isOperator() const;
+
+    Kind fKind;
+};
 
-}  // namespace Operators
 }  // namespace SkSL
 
 #endif
diff --git a/src/sksl/SkSLParser.cpp b/src/sksl/SkSLParser.cpp
index 3211e151dd..20fc7c10d9 100644
--- a/src/sksl/SkSLParser.cpp
+++ b/src/sksl/SkSLParser.cpp
@@ -1610,7 +1610,8 @@ ASTNode::ID Parser::expression() {
         if (!right) {
             return ASTNode::ID::Invalid();
         }
-        ASTNode::ID newResult = this->createNode(t.fOffset, ASTNode::Kind::kBinary, std::move(t));
+        ASTNode::ID newResult = this->createNode(t.fOffset, ASTNode::Kind::kBinary,
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1650,7 +1651,7 @@ ASTNode::ID Parser::assignmentExpression() {
                     return ASTNode::ID::Invalid();
                 }
                 ASTNode::ID newResult = this->createNode(getNode(result).fOffset,
-                                                         ASTNode::Kind::kBinary, std::move(t));
+                                                         ASTNode::Kind::kBinary, Operator(t.fKind));
                 getNode(newResult).addChild(result);
                 getNode(newResult).addChild(right);
                 result = newResult;
@@ -1710,7 +1711,7 @@ ASTNode::ID Parser::logicalOrExpression() {
             return ASTNode::ID::Invalid();
         }
         ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary,
-                                                 std::move(t));
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1735,7 +1736,7 @@ ASTNode::ID Parser::logicalXorExpression() {
             return ASTNode::ID::Invalid();
         }
         ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary,
-                                                 std::move(t));
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1760,7 +1761,7 @@ ASTNode::ID Parser::logicalAndExpression() {
             return ASTNode::ID::Invalid();
         }
         ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary,
-                                                 std::move(t));
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1784,8 +1785,8 @@ ASTNode::ID Parser::bitwiseOrExpression() {
         if (!right) {
             return ASTNode::ID::Invalid();
         }
-        ASTNode::ID newResult =
-                this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary,
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1809,7 +1810,8 @@ ASTNode::ID Parser::bitwiseXorExpression() {
         if (!right) {
             return ASTNode::ID::Invalid();
         }
-        ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary, std::move(t));
+        ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary,
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1834,7 +1836,7 @@ ASTNode::ID Parser::bitwiseAndExpression() {
             return ASTNode::ID::Invalid();
         }
         ASTNode::ID newResult = this->createNode(getNode(result).fOffset, ASTNode::Kind::kBinary,
-                                                 std::move(t));
+                                                 Operator(t.fKind));
         getNode(newResult).addChild(result);
         getNode(newResult).addChild(right);
         result = newResult;
@@ -1862,7 +1864,7 @@ ASTNode::ID Parser::equalityExpression() {
                     return ASTNode::ID::Invalid();
                 }
                 ASTNode::ID newResult = this->createNode(getNode(result).fOffset,
-                                                         ASTNode::Kind::kBinary, std::move(t));
+                                                         ASTNode::Kind::kBinary, Operator(t.fKind));
                 getNode(newResult).addChild(result);
                 getNode(newResult).addChild(right);
                 result = newResult;
@@ -1896,7 +1898,7 @@ ASTNode::ID Parser::relationalExpression() {
                     return ASTNode::ID::Invalid();
                 }
                 ASTNode::ID newResult = this->createNode(getNode(result).fOffset,
-                                                         ASTNode::Kind::kBinary, std::move(t));
+                                                         ASTNode::Kind::kBinary, Operator(t.fKind));
                 getNode(newResult).addChild(result);
                 getNode(newResult).addChild(right);
                 result = newResult;
@@ -1928,7 +1930,7 @@ ASTNode::ID Parser::shiftExpression() {
                     return ASTNode::ID::Invalid();
                 }
                 ASTNode::ID newResult = this->createNode(getNode(result).fOffset,
-                                                         ASTNode::Kind::kBinary, std::move(t));
+                                                         ASTNode::Kind::kBinary, Operator(t.fKind));
                 getNode(newResult).addChild(result);
                 getNode(newResult).addChild(right);
                 result = newResult;
@@ -1960,7 +1962,7 @@ ASTNode::ID Parser::additiveExpression() {
                     return ASTNode::ID::Invalid();
                 }
                 ASTNode::ID newResult = this->createNode(getNode(result).fOffset,
-                                                         ASTNode::Kind::kBinary, std::move(t));
+                                                         ASTNode::Kind::kBinary, Operator(t.fKind));
                 getNode(newResult).addChild(result);
                 getNode(newResult).addChild(right);
                 result = newResult;
@@ -1993,7 +1995,7 @@ ASTNode::ID Parser::multiplicativeExpression() {
                     return ASTNode::ID::Invalid();
                 }
                 ASTNode::ID newResult = this->createNode(getNode(result).fOffset,
-                                                         ASTNode::Kind::kBinary, std::move(t));
+                                                         ASTNode::Kind::kBinary, Operator(t.fKind));
                 getNode(newResult).addChild(result);
                 getNode(newResult).addChild(right);
                 result = newResult;
@@ -2023,7 +2025,8 @@ ASTNode::ID Parser::unaryExpression() {
             if (!expr) {
                 return ASTNode::ID::Invalid();
             }
-            ASTNode::ID result = this->createNode(t.fOffset, ASTNode::Kind::kPrefix, std::move(t));
+            ASTNode::ID result = this->createNode(t.fOffset, ASTNode::Kind::kPrefix,
+                                                  Operator(t.fKind));
             getNode(result).addChild(expr);
             return result;
         }
@@ -2160,7 +2163,8 @@ ASTNode::ID Parser::suffix(ASTNode::ID base) {
         }
         case Token::Kind::TK_PLUSPLUS: // fall through
         case Token::Kind::TK_MINUSMINUS: {
-            ASTNode::ID result = this->createNode(next.fOffset, ASTNode::Kind::kPostfix, next);
+            ASTNode::ID result = this->createNode(next.fOffset, ASTNode::Kind::kPostfix,
+                                                  Operator(next.fKind));
             getNode(result).addChild(base);
             return result;
         }
diff --git a/src/sksl/SkSLPipelineStageCodeGenerator.cpp b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
index 804ecd349c..d86b8c9baa 100644
--- a/src/sksl/SkSLPipelineStageCodeGenerator.cpp
+++ b/src/sksl/SkSLPipelineStageCodeGenerator.cpp
@@ -50,7 +50,7 @@ public:
     void generateCode();
 
 private:
-    using Precedence = Operators::Precedence;
+    using Precedence = Operator::Precedence;
 
     void write(const char* s);
     void writeLine(const char* s = nullptr);
@@ -476,15 +476,15 @@ void PipelineStageCodeGenerator::writeBinaryExpression(const BinaryExpression& b
                                                        Precedence parentPrecedence) {
     const Expression& left = *b.left();
     const Expression& right = *b.right();
-    Token::Kind op = b.getOperator();
+    Operator op = b.getOperator();
 
-    Precedence precedence = Operators::GetBinaryPrecedence(op);
+    Precedence precedence = op.getBinaryPrecedence();
     if (precedence >= parentPrecedence) {
         this->write("(");
     }
     this->writeExpression(left, precedence);
     this->write(" ");
-    this->write(Operators::OperatorName(op));
+    this->write(op.operatorName());
     this->write(" ");
     this->writeExpression(right, precedence);
     if (precedence >= parentPrecedence) {
@@ -512,7 +512,7 @@ void PipelineStageCodeGenerator::writePrefixExpression(const PrefixExpression& p
     if (Precedence::kPrefix >= parentPrecedence) {
         this->write("(");
     }
-    this->write(Operators::OperatorName(p.getOperator()));
+    this->write(p.getOperator().operatorName());
     this->writeExpression(*p.operand(), Precedence::kPrefix);
     if (Precedence::kPrefix >= parentPrecedence) {
         this->write(")");
@@ -525,7 +525,7 @@ void PipelineStageCodeGenerator::writePostfixExpression(const PostfixExpression&
         this->write("(");
     }
     this->writeExpression(*p.operand(), Precedence::kPostfix);
-    this->write(Operators::OperatorName(p.getOperator()));
+    this->write(p.getOperator().operatorName());
     if (Precedence::kPostfix >= parentPrecedence) {
         this->write(")");
     }
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.cpp b/src/sksl/SkSLSPIRVCodeGenerator.cpp
index 918fe50c59..2e774b9076 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.cpp
+++ b/src/sksl/SkSLSPIRVCodeGenerator.cpp
@@ -2233,11 +2233,11 @@ static std::unique_ptr<Expression> create_literal_1(const Context& context, cons
     }
 }
 
-SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs, Token::Kind op,
+SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
                                                 const Type& rightType, SpvId rhs,
                                                 const Type& resultType, OutputStream& out) {
     // The comma operator ignores the type of the left-hand side entirely.
-    if (op == Token::Kind::TK_COMMA) {
+    if (op.kind() == Token::Kind::TK_COMMA) {
         return rhs;
     }
     // overall type we are operating on: float2, int, uint4...
@@ -2246,14 +2246,14 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
     // handling in SPIR-V
     if (this->getActualType(leftType) != this->getActualType(rightType)) {
         if (leftType.isVector() && rightType.isNumber()) {
-            if (op == Token::Kind::TK_SLASH) {
+            if (op.kind() == Token::Kind::TK_SLASH) {
                 SpvId one = this->writeExpression(*create_literal_1(fContext, rightType), out);
                 SpvId inverse = this->nextId();
                 this->writeInstruction(SpvOpFDiv, this->getType(rightType), inverse, one, rhs, out);
                 rhs = inverse;
                 op = Token::Kind::TK_STAR;
             }
-            if (op == Token::Kind::TK_STAR) {
+            if (op.kind() == Token::Kind::TK_STAR) {
                 SpvId result = this->nextId();
                 this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType),
                                        result, lhs, rhs, out);
@@ -2271,7 +2271,7 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
             rhs = vec;
             operandType = &leftType;
         } else if (rightType.isVector() && leftType.isNumber()) {
-            if (op == Token::Kind::TK_STAR) {
+            if (op.kind() == Token::Kind::TK_STAR) {
                 SpvId result = this->nextId();
                 this->writeInstruction(SpvOpVectorTimesScalar, this->getType(resultType),
                                        result, rhs, lhs, out);
@@ -2320,7 +2320,7 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
         operandType = &this->getActualType(leftType);
         SkASSERT(*operandType == this->getActualType(rightType));
     }
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_EQEQ: {
             if (operandType->isMatrix()) {
                 return this->writeMatrixComparison(*operandType, lhs, rhs, SpvOpFOrdEqual,
@@ -2438,9 +2438,9 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const Type& leftType, SpvId lhs,
 SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, OutputStream& out) {
     const Expression& left = *b.left();
     const Expression& right = *b.right();
-    Token::Kind op = b.getOperator();
+    Operator op = b.getOperator();
     // handle cases where we don't necessarily evaluate both LHS and RHS
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_EQ: {
             SpvId rhs = this->writeExpression(right, out);
             this->getLValue(left, out)->store(rhs, out);
@@ -2456,7 +2456,7 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, Outpu
 
     std::unique_ptr<LValue> lvalue;
     SpvId lhs;
-    if (Operators::IsAssignment(op)) {
+    if (op.isAssignment()) {
         lvalue = this->getLValue(left, out);
         lhs = lvalue->load(out);
     } else {
@@ -2464,7 +2464,7 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, Outpu
         lhs = this->writeExpression(left, out);
     }
     SpvId rhs = this->writeExpression(right, out);
-    SpvId result = this->writeBinaryExpression(left.type(), lhs, Operators::RemoveAssignment(op),
+    SpvId result = this->writeBinaryExpression(left.type(), lhs, op.removeAssignment(),
                                                right.type(), rhs, b.type(), out);
     if (lvalue) {
         lvalue->store(result, out);
@@ -2473,7 +2473,7 @@ SpvId SPIRVCodeGenerator::writeBinaryExpression(const BinaryExpression& b, Outpu
 }
 
 SpvId SPIRVCodeGenerator::writeLogicalAnd(const BinaryExpression& a, OutputStream& out) {
-    SkASSERT(a.getOperator() == Token::Kind::TK_LOGICALAND);
+    SkASSERT(a.getOperator().kind() == Token::Kind::TK_LOGICALAND);
     BoolLiteral falseLiteral(fContext, -1, false);
     SpvId falseConstant = this->writeBoolLiteral(falseLiteral);
     SpvId lhs = this->writeExpression(*a.left(), out);
@@ -2494,7 +2494,7 @@ SpvId SPIRVCodeGenerator::writeLogicalAnd(const BinaryExpression& a, OutputStrea
 }
 
 SpvId SPIRVCodeGenerator::writeLogicalOr(const BinaryExpression& o, OutputStream& out) {
-    SkASSERT(o.getOperator() == Token::Kind::TK_LOGICALOR);
+    SkASSERT(o.getOperator().kind() == Token::Kind::TK_LOGICALOR);
     BoolLiteral trueLiteral(fContext, -1, true);
     SpvId trueConstant = this->writeBoolLiteral(trueLiteral);
     SpvId lhs = this->writeExpression(*o.left(), out);
@@ -2553,7 +2553,7 @@ SpvId SPIRVCodeGenerator::writeTernaryExpression(const TernaryExpression& t, Out
 
 SpvId SPIRVCodeGenerator::writePrefixExpression(const PrefixExpression& p, OutputStream& out) {
     const Type& type = p.type();
-    if (p.getOperator() == Token::Kind::TK_MINUS) {
+    if (p.getOperator().kind() == Token::Kind::TK_MINUS) {
         SpvId result = this->nextId();
         SpvId typeId = this->getType(type);
         SpvId expr = this->writeExpression(*p.operand(), out);
@@ -2567,7 +2567,7 @@ SpvId SPIRVCodeGenerator::writePrefixExpression(const PrefixExpression& p, Outpu
         this->writePrecisionModifier(type, result);
         return result;
     }
-    switch (p.getOperator()) {
+    switch (p.getOperator().kind()) {
         case Token::Kind::TK_PLUS:
             return this->writeExpression(*p.operand(), out);
         case Token::Kind::TK_PLUSPLUS: {
@@ -2611,7 +2611,7 @@ SpvId SPIRVCodeGenerator::writePostfixExpression(const PostfixExpression& p, Out
     std::unique_ptr<LValue> lv = this->getLValue(*p.operand(), out);
     SpvId result = lv->load(out);
     SpvId one = this->writeExpression(*create_literal_1(fContext, type), out);
-    switch (p.getOperator()) {
+    switch (p.getOperator().kind()) {
         case Token::Kind::TK_PLUSPLUS: {
             SpvId temp = this->writeBinaryOperation(type, type, result, one, SpvOpFAdd,
                                                     SpvOpIAdd, SpvOpIAdd, SpvOpUndef, out);
diff --git a/src/sksl/SkSLSPIRVCodeGenerator.h b/src/sksl/SkSLSPIRVCodeGenerator.h
index eae99a7eab..4808118f87 100644
--- a/src/sksl/SkSLSPIRVCodeGenerator.h
+++ b/src/sksl/SkSLSPIRVCodeGenerator.h
@@ -307,7 +307,7 @@ private:
     SpvId writeBinaryOperation(const BinaryExpression& expr, SpvOp_ ifFloat, SpvOp_ ifInt,
                                SpvOp_ ifUInt, OutputStream& out);
 
-    SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Token::Kind op,
+    SpvId writeBinaryExpression(const Type& leftType, SpvId lhs, Operator op,
                                 const Type& rightType, SpvId rhs, const Type& resultType,
                                 OutputStream& out);
 
diff --git a/src/sksl/SkSLVMGenerator.cpp b/src/sksl/SkSLVMGenerator.cpp
index 1cf45b982f..c7bb702059 100644
--- a/src/sksl/SkSLVMGenerator.cpp
+++ b/src/sksl/SkSLVMGenerator.cpp
@@ -529,8 +529,8 @@ size_t SkVMGenerator::getSlot(const Variable& v) {
 Value SkVMGenerator::writeBinaryExpression(const BinaryExpression& b) {
     const Expression& left = *b.left();
     const Expression& right = *b.right();
-    Token::Kind op = b.getOperator();
-    if (op == Token::Kind::TK_EQ) {
+    Operator op = b.getOperator();
+    if (op.kind() == Token::Kind::TK_EQ) {
         return this->writeStore(left, this->writeExpression(right));
     }
 
@@ -538,14 +538,14 @@ Value SkVMGenerator::writeBinaryExpression(const BinaryExpression& b) {
     const Type& rType = right.type();
     bool lVecOrMtx = (lType.isVector() || lType.isMatrix());
     bool rVecOrMtx = (rType.isVector() || rType.isMatrix());
-    bool isAssignment = Operators::IsAssignment(op);
+    bool isAssignment = op.isAssignment();
     if (isAssignment) {
-        op = Operators::RemoveAssignment(op);
+        op = op.removeAssignment();
     }
     Type::NumberKind nk = base_number_kind(lType);
 
     // A few ops require special treatment:
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_LOGICALAND: {
             SkASSERT(!isAssignment);
             SkASSERT(nk == Type::NumberKind::kBoolean);
@@ -576,7 +576,7 @@ Value SkVMGenerator::writeBinaryExpression(const BinaryExpression& b) {
           rVal = this->writeExpression(right);
 
     // Special case for M*V, V*M, M*M (but not V*V!)
-    if (op == Token::Kind::TK_STAR
+    if (op.kind() == Token::Kind::TK_STAR
         && lVecOrMtx && rVecOrMtx && !(lType.isVector() && rType.isVector())) {
         int rCols = rType.columns(),
             rRows = rType.rows(),
@@ -624,7 +624,7 @@ Value SkVMGenerator::writeBinaryExpression(const BinaryExpression& b) {
         return skvm::F32{};
     };
 
-    switch (op) {
+    switch (op.kind()) {
         case Token::Kind::TK_EQEQ: {
             SkASSERT(!isAssignment);
             Value cmp = binary([](skvm::F32 x, skvm::F32 y) { return x == y; },
@@ -1311,10 +1311,10 @@ Value SkVMGenerator::writeExternalFunctionCall(const ExternalFunctionCall& c) {
 Value SkVMGenerator::writePrefixExpression(const PrefixExpression& p) {
     Value val = this->writeExpression(*p.operand());
 
-    switch (p.getOperator()) {
+    switch (p.getOperator().kind()) {
         case Token::Kind::TK_PLUSPLUS:
         case Token::Kind::TK_MINUSMINUS: {
-            bool incr = p.getOperator() == Token::Kind::TK_PLUSPLUS;
+            bool incr = p.getOperator().kind() == Token::Kind::TK_PLUSPLUS;
 
             switch (base_number_kind(p.type())) {
                 case Type::NumberKind::kFloat:
@@ -1350,13 +1350,13 @@ Value SkVMGenerator::writePrefixExpression(const PrefixExpression& p) {
 }
 
 Value SkVMGenerator::writePostfixExpression(const PostfixExpression& p) {
-    switch (p.getOperator()) {
+    switch (p.getOperator().kind()) {
         case Token::Kind::TK_PLUSPLUS:
         case Token::Kind::TK_MINUSMINUS: {
             Value old = this->writeExpression(*p.operand()),
                   val = old;
             SkASSERT(val.slots() == 1);
-            bool incr = p.getOperator() == Token::Kind::TK_PLUSPLUS;
+            bool incr = p.getOperator().kind() == Token::Kind::TK_PLUSPLUS;
 
             switch (base_number_kind(p.type())) {
                 case Type::NumberKind::kFloat:
diff --git a/src/sksl/dsl/priv/DSLWriter.cpp b/src/sksl/dsl/priv/DSLWriter.cpp
index abec397226..7e8015c034 100644
--- a/src/sksl/dsl/priv/DSLWriter.cpp
+++ b/src/sksl/dsl/priv/DSLWriter.cpp
@@ -104,7 +104,7 @@ DSLExpression DSLWriter::Construct(const SkSL::Type& type, std::vector<DSLExpres
                                          std::move(args)));
 }
 
-DSLExpression DSLWriter::ConvertBinary(std::unique_ptr<Expression> left, Token::Kind op,
+DSLExpression DSLWriter::ConvertBinary(std::unique_ptr<Expression> left, Operator op,
                                        std::unique_ptr<Expression> right) {
     return IRGenerator().convertBinaryExpression(std::move(left), op, std::move(right));
 }
@@ -118,11 +118,11 @@ DSLExpression DSLWriter::ConvertIndex(std::unique_ptr<Expression> base,
     return IRGenerator().convertIndex(std::move(base), std::move(index));
 }
 
-DSLExpression DSLWriter::ConvertPostfix(std::unique_ptr<Expression> expr, Token::Kind op) {
+DSLExpression DSLWriter::ConvertPostfix(std::unique_ptr<Expression> expr, Operator op) {
     return IRGenerator().convertPostfixExpression(std::move(expr), op);
 }
 
-DSLExpression DSLWriter::ConvertPrefix(Token::Kind op, std::unique_ptr<Expression> expr) {
+DSLExpression DSLWriter::ConvertPrefix(Operator op, std::unique_ptr<Expression> expr) {
     return IRGenerator().convertPrefixExpression(op, std::move(expr));
 }
 
diff --git a/src/sksl/dsl/priv/DSLWriter.h b/src/sksl/dsl/priv/DSLWriter.h
index dd3ec727bc..769350bd9b 100644
--- a/src/sksl/dsl/priv/DSLWriter.h
+++ b/src/sksl/dsl/priv/DSLWriter.h
@@ -9,6 +9,7 @@
 #define SKSL_DSLWRITER
 
 #include "src/sksl/SkSLMangler.h"
+#include "src/sksl/SkSLOperators.h"
 #include "src/sksl/dsl/DSLExpression.h"
 #include "src/sksl/dsl/DSLStatement.h"
 #include "src/sksl/ir/SkSLExpressionStatement.h"
@@ -133,7 +134,7 @@ public:
 
     static DSLExpression Construct(const SkSL::Type& type, std::vector<DSLExpression> rawArgs);
 
-    static DSLExpression ConvertBinary(std::unique_ptr<Expression> left, Token::Kind op,
+    static DSLExpression ConvertBinary(std::unique_ptr<Expression> left, Operator op,
                                        std::unique_ptr<Expression> right);
 
     static DSLExpression ConvertField(std::unique_ptr<Expression> base, const char* name);
@@ -141,9 +142,9 @@ public:
     static DSLExpression ConvertIndex(std::unique_ptr<Expression> base,
                                       std::unique_ptr<Expression> index);
 
-    static DSLExpression ConvertPostfix(std::unique_ptr<Expression> expr, Token::Kind op);
+    static DSLExpression ConvertPostfix(std::unique_ptr<Expression> expr, Operator op);
 
-    static DSLExpression ConvertPrefix(Token::Kind op, std::unique_ptr<Expression> expr);
+    static DSLExpression ConvertPrefix(Operator op, std::unique_ptr<Expression> expr);
 
     static DSLStatement ConvertSwitch(std::unique_ptr<Expression> value,
                                       ExpressionArray caseValues,
diff --git a/src/sksl/ir/SkSLBinaryExpression.h b/src/sksl/ir/SkSLBinaryExpression.h
index 1b51708141..5e7d657c23 100644
--- a/src/sksl/ir/SkSLBinaryExpression.h
+++ b/src/sksl/ir/SkSLBinaryExpression.h
@@ -49,14 +49,14 @@ class BinaryExpression final : public Expression {
 public:
     static constexpr Kind kExpressionKind = Kind::kBinary;
 
-    BinaryExpression(int offset, std::unique_ptr<Expression> left, Token::Kind op,
+    BinaryExpression(int offset, std::unique_ptr<Expression> left, Operator op,
                      std::unique_ptr<Expression> right, const Type* type)
     : INHERITED(offset, kExpressionKind, type)
     , fLeft(std::move(left))
     , fOperator(op)
     , fRight(std::move(right)) {
         // If we are assigning to a VariableReference, ensure that it is set to Write or ReadWrite
-        SkASSERT(!Operators::IsAssignment(op) || check_ref(*this->left()));
+        SkASSERT(!op.isAssignment() || check_ref(*this->left()));
     }
 
     std::unique_ptr<Expression>& left() {
@@ -75,7 +75,7 @@ public:
         return fRight;
     }
 
-    Token::Kind getOperator() const {
+    Operator getOperator() const {
         return fOperator;
     }
 
@@ -90,7 +90,7 @@ public:
     }
 
     bool hasProperty(Property property) const override {
-        if (property == Property::kSideEffects && Operators::IsAssignment(this->getOperator())) {
+        if (property == Property::kSideEffects && this->getOperator().isAssignment()) {
             return true;
         }
         return this->left()->hasProperty(property) || this->right()->hasProperty(property);
@@ -106,13 +106,13 @@ public:
 
     String description() const override {
         return "(" + this->left()->description() + " " +
-               Operators::OperatorName(this->getOperator()) + " " + this->right()->description() +
+               this->getOperator().operatorName() + " " + this->right()->description() +
                ")";
     }
 
 private:
     std::unique_ptr<Expression> fLeft;
-    Token::Kind fOperator;
+    Operator fOperator;
     std::unique_ptr<Expression> fRight;
 
     using INHERITED = Expression;
diff --git a/src/sksl/ir/SkSLPostfixExpression.h b/src/sksl/ir/SkSLPostfixExpression.h
index 8de89e272c..92b0cafe47 100644
--- a/src/sksl/ir/SkSLPostfixExpression.h
+++ b/src/sksl/ir/SkSLPostfixExpression.h
@@ -21,12 +21,12 @@ class PostfixExpression final : public Expression {
 public:
     static constexpr Kind kExpressionKind = Kind::kPostfix;
 
-    PostfixExpression(std::unique_ptr<Expression> operand, Token::Kind op)
+    PostfixExpression(std::unique_ptr<Expression> operand, Operator op)
         : INHERITED(operand->fOffset, kExpressionKind, &operand->type())
         , fOperand(std::move(operand))
         , fOperator(op) {}
 
-    Token::Kind getOperator() const {
+    Operator getOperator() const {
         return fOperator;
     }
 
@@ -51,12 +51,12 @@ public:
     }
 
     String description() const override {
-        return this->operand()->description() + Operators::OperatorName(this->getOperator());
+        return this->operand()->description() + this->getOperator().operatorName();
     }
 
 private:
     std::unique_ptr<Expression> fOperand;
-    Token::Kind fOperator;
+    Operator fOperator;
 
     using INHERITED = Expression;
 };
diff --git a/src/sksl/ir/SkSLPrefixExpression.cpp b/src/sksl/ir/SkSLPrefixExpression.cpp
index 336d23b85f..73270a0f43 100644
--- a/src/sksl/ir/SkSLPrefixExpression.cpp
+++ b/src/sksl/ir/SkSLPrefixExpression.cpp
@@ -38,7 +38,7 @@ static std::unique_ptr<Expression> negate_operand(const Expression& operand) {
 std::unique_ptr<Expression> PrefixExpression::constantPropagate(const IRGenerator& irGenerator,
                                                                 const DefinitionMap& definitions) {
     if (this->operand()->isCompileTimeConstant()) {
-        if (this->getOperator() == Token::Kind::TK_MINUS) {
+        if (this->getOperator().kind() == Token::Kind::TK_MINUS) {
             // Constant-propagate negation onto compile-time constants.
             switch (this->operand()->kind()) {
                 case Expression::Kind::kFloatLiteral:
@@ -66,7 +66,7 @@ std::unique_ptr<Expression> PrefixExpression::constantPropagate(const IRGenerato
                 default:
                     break;
             }
-        } else if (this->getOperator() == Token::Kind::TK_LOGICALNOT) {
+        } else if (this->getOperator().kind() == Token::Kind::TK_LOGICALNOT) {
             if (this->operand()->is<BoolLiteral>()) {
                 // Convert !boolLiteral(true) to boolLiteral(false).
                 const BoolLiteral& b = this->operand()->as<BoolLiteral>();
diff --git a/src/sksl/ir/SkSLPrefixExpression.h b/src/sksl/ir/SkSLPrefixExpression.h
index 0cf046b6da..673c789dd6 100644
--- a/src/sksl/ir/SkSLPrefixExpression.h
+++ b/src/sksl/ir/SkSLPrefixExpression.h
@@ -22,12 +22,12 @@ class PrefixExpression final : public Expression {
 public:
     static constexpr Kind kExpressionKind = Kind::kPrefix;
 
-    PrefixExpression(Token::Kind op, std::unique_ptr<Expression> operand)
+    PrefixExpression(Operator op, std::unique_ptr<Expression> operand)
         : INHERITED(operand->fOffset, kExpressionKind, &operand->type())
         , fOperator(op)
         , fOperand(std::move(operand)) {}
 
-    Token::Kind getOperator() const {
+    Operator getOperator() const {
         return fOperator;
     }
 
@@ -41,8 +41,8 @@ public:
 
     bool hasProperty(Property property) const override {
         if (property == Property::kSideEffects &&
-            (this->getOperator() == Token::Kind::TK_PLUSPLUS ||
-             this->getOperator() == Token::Kind::TK_MINUSMINUS)) {
+            (this->getOperator().kind() == Token::Kind::TK_PLUSPLUS ||
+             this->getOperator().kind() == Token::Kind::TK_MINUSMINUS)) {
             return true;
         }
         return this->operand()->hasProperty(property);
@@ -57,11 +57,11 @@ public:
     }
 
     String description() const override {
-        return Operators::OperatorName(this->getOperator()) + this->operand()->description();
+        return this->getOperator().operatorName() + this->operand()->description();
     }
 
 private:
-    Token::Kind fOperator;
+    Operator fOperator;
     std::unique_ptr<Expression> fOperand;
 
     using INHERITED = Expression;