Add ast.ExprOperatorEtc constants
diff --git a/internal/cgen/builtin.go b/internal/cgen/builtin.go
index 78f316a..c1c4300 100644
--- a/internal/cgen/builtin.go
+++ b/internal/cgen/builtin.go
@@ -30,7 +30,7 @@
 )
 
 func (g *gen) writeBuiltinCall(b *buffer, n *a.Expr, sideEffectsOnly bool, depth uint32) error {
-	if n.Operator() != t.IDOpenParen {
+	if n.Operator() != a.ExprOperatorCall {
 		return errNoSuchBuiltin
 	}
 	method := n.LHS().AsExpr()
@@ -1043,7 +1043,7 @@
 
 func (g *gen) writeBuiltinQuestionCall(b *buffer, n *a.Expr, depth uint32) error {
 	// TODO: also handle (or reject??) being on the RHS of an =? operator.
-	if (n.Operator() != t.IDOpenParen) || (!n.Effect().Coroutine()) {
+	if (n.Operator() != a.ExprOperatorCall) || (!n.Effect().Coroutine()) {
 		return errNoSuchBuiltin
 	}
 	method := n.LHS().AsExpr()
diff --git a/internal/cgen/expr.go b/internal/cgen/expr.go
index 1ed28cf..f90936c 100644
--- a/internal/cgen/expr.go
+++ b/internal/cgen/expr.go
@@ -363,7 +363,7 @@
 func (g *gen) writeExprRepr(b *buffer, n *a.Expr, depth uint32) error {
 	isStatus := n.MType().IsStatus()
 	if isStatus {
-		if op := n.Operator(); ((op == 0) || (op == t.IDDot)) && n.Ident().IsDQStrLiteral(g.tm) {
+		if op := n.Operator(); ((op == 0) || (op == a.ExprOperatorSelector)) && n.Ident().IsDQStrLiteral(g.tm) {
 			qid := t.QID{0, n.Ident()}
 			if op == t.IDDot {
 				qid[0] = n.LHS().AsExpr().Ident()
diff --git a/internal/cgen/statement.go b/internal/cgen/statement.go
index 23ee914..b30ed39 100644
--- a/internal/cgen/statement.go
+++ b/internal/cgen/statement.go
@@ -86,7 +86,7 @@
 
 	needWriteLoadExprDerivedVars := false
 	if (len(g.currFunk.derivedVars) > 0) &&
-		(rhs.Operator() == t.IDOpenParen) {
+		(rhs.Operator() == a.ExprOperatorCall) {
 		method := rhs.LHS().AsExpr()
 		recvTyp := method.LHS().MType().Pointee()
 		if (recvTyp.Decorator() == 0) && (recvTyp.QID()[0] != t.IDBase) {
diff --git a/internal/cgen/var.go b/internal/cgen/var.go
index 9e59443..59000b8 100644
--- a/internal/cgen/var.go
+++ b/internal/cgen/var.go
@@ -248,7 +248,7 @@
 }
 
 func (g *gen) writeLoadExprDerivedVars(b *buffer, n *a.Expr) error {
-	if (g.currFunk.derivedVars != nil) && (n.Operator() == t.IDOpenParen) {
+	if (g.currFunk.derivedVars != nil) && (n.Operator() == a.ExprOperatorCall) {
 		for _, o := range n.Args() {
 			if v := o.AsArg().Value(); g.couldHaveDerivedVar(v) {
 				if err := g.writeLoadDerivedVar(b, v); err != nil {
@@ -261,7 +261,7 @@
 }
 
 func (g *gen) writeSaveExprDerivedVars(b *buffer, n *a.Expr) error {
-	if (g.currFunk.derivedVars != nil) && (n.Operator() == t.IDOpenParen) {
+	if (g.currFunk.derivedVars != nil) && (n.Operator() == a.ExprOperatorCall) {
 		for _, o := range n.Args() {
 			if v := o.AsArg().Value(); g.couldHaveDerivedVar(v) {
 				if err := g.writeSaveDerivedVar(b, v); err != nil {
diff --git a/lang/ast/ast.go b/lang/ast/ast.go
index c1d4613..509fa97 100644
--- a/lang/ast/ast.go
+++ b/lang/ast/ast.go
@@ -358,6 +358,14 @@
 // For lists, like "[0, 1, 2]", ID0 is IDComma.
 type Expr Node
 
+const (
+	ExprOperatorCall     = t.IDOpenParen
+	ExprOperatorIndex    = t.IDOpenBracket
+	ExprOperatorList     = t.IDComma
+	ExprOperatorSelector = t.IDDot
+	ExprOperatorSlice    = t.IDDotDot
+)
+
 func (n *Expr) AsNode() *Node              { return (*Node)(n) }
 func (n *Expr) Effect() Effect             { return Effect(n.flags) }
 func (n *Expr) GlobalIdent() bool          { return n.flags&FlagsGlobalIdent != 0 }
@@ -387,21 +395,21 @@
 }
 
 func (n *Expr) IsIndex() (arrayOrSlice *Expr, index *Expr, ok bool) {
-	if n.id0 == t.IDOpenBracket {
+	if n.id0 == ExprOperatorIndex {
 		return n.lhs.AsExpr(), n.rhs.AsExpr(), true
 	}
 	return nil, nil, false
 }
 
 func (n *Expr) IsList() (args []*Node, ok bool) {
-	if n.id0 == t.IDComma {
+	if n.id0 == ExprOperatorList {
 		return n.list0, true
 	}
 	return nil, false
 }
 
 func (n *Expr) IsMethodCall() (recv *Expr, meth t.ID, args []*Node, ok bool) {
-	if n.id0 == t.IDOpenParen {
+	if n.id0 == ExprOperatorCall {
 		if o := n.lhs; o.id0 == t.IDDot {
 			return o.lhs.AsExpr(), o.id2, n.list0, true
 		}
@@ -410,14 +418,14 @@
 }
 
 func (n *Expr) IsSelector() (lhs *Expr, field t.ID, ok bool) {
-	if n.id0 == t.IDDot {
+	if n.id0 == ExprOperatorSelector {
 		return n.lhs.AsExpr(), n.id2, true
 	}
 	return nil, 0, false
 }
 
 func (n *Expr) IsSlice() (arrayOrSlice *Expr, lo *Expr, hi *Expr, ok bool) {
-	if n.id0 == t.IDDotDot {
+	if n.id0 == ExprOperatorSlice {
 		return n.lhs.AsExpr(), n.mhs.AsExpr(), n.rhs.AsExpr(), true
 	}
 	return nil, nil, nil, false
diff --git a/lang/check/bounds.go b/lang/check/bounds.go
index 6301680..d718e4d 100644
--- a/lang/check/bounds.go
+++ b/lang/check/bounds.go
@@ -331,7 +331,7 @@
 		}
 
 		if lTyp.IsStatus() {
-			if v := n.Value(); (v.Operator() == 0) || (v.Operator() == t.IDDot) {
+			if v := n.Value(); (v.Operator() == 0) || (v.Operator() == a.ExprOperatorSelector) {
 				if id := v.Ident(); (id != t.IDOk) && (q.hasIsErrorFact(id) || isErrorStatus(id, q.tm)) {
 					n.SetRetsError()
 				}
@@ -430,7 +430,7 @@
 
 func (q *checker) bcheckAssignment(lhs *a.Expr, op t.ID, rhs *a.Expr) error {
 	oldFacts := (map[*a.Expr]struct{})(nil)
-	if (rhs.Operator() == t.IDOpenParen) && rhs.Effect().Impure() {
+	if (rhs.Operator() == a.ExprOperatorCall) && rhs.Effect().Impure() {
 		oldFacts = map[*a.Expr]struct{}{}
 		for _, x := range q.facts {
 			oldFacts[x] = struct{}{}
@@ -450,7 +450,7 @@
 		return err
 	}
 
-	if (rhs.Operator() == t.IDOpenParen) && rhs.Effect().Impure() {
+	if (rhs.Operator() == a.ExprOperatorCall) && rhs.Effect().Impure() {
 		if rhs.Effect().Coroutine() && (op != t.IDEqQuestion) {
 			if err := q.facts.update(updateFactsForSuspension); err != nil {
 				return err
@@ -505,7 +505,7 @@
 		if lhs.MType().IsNumType() && rhs.Effect().Pure() {
 			q.facts.appendBinaryOpFact(t.IDXBinaryEqEq, lhs, rhs)
 
-			if rhs.Operator() == t.IDOpenParen {
+			if rhs.Operator() == a.ExprOperatorCall {
 				if lTyp := rhs.LHS().AsExpr().MType(); lTyp.IsFuncType() && lTyp.Receiver().IsNumType() {
 					switch fn := lTyp.FuncName(); fn {
 					case t.IDMax, t.IDMin:
diff --git a/lang/check/optimize.go b/lang/check/optimize.go
index 1f9b723..3c8780f 100644
--- a/lang/check/optimize.go
+++ b/lang/check/optimize.go
@@ -33,12 +33,12 @@
 // splitReceiverMethodArgs returns the "receiver", "method" and "args" in the
 // expression "receiver.method(args)".
 func splitReceiverMethodArgs(n *a.Expr) (receiver *a.Expr, method t.ID, args []*a.Node) {
-	if n.Operator() != t.IDOpenParen {
+	if n.Operator() != a.ExprOperatorCall {
 		return nil, 0, nil
 	}
 	args = n.Args()
 	n = n.LHS().AsExpr()
-	if n.Operator() != t.IDDot {
+	if n.Operator() != a.ExprOperatorSelector {
 		return nil, 0, nil
 	}
 	return n.LHS().AsExpr(), n.Ident(), args
@@ -94,11 +94,11 @@
 
 		// Check that lhs is "receiver.length()".
 		lhs := x.LHS().AsExpr()
-		if lhs.Operator() != t.IDOpenParen || len(lhs.Args()) != 0 {
+		if (lhs.Operator() != a.ExprOperatorCall) || (len(lhs.Args()) != 0) {
 			return x, nil
 		}
 		lhs = lhs.LHS().AsExpr()
-		if lhs.Operator() != t.IDDot || lhs.Ident() != t.IDLength {
+		if (lhs.Operator() != a.ExprOperatorSelector) || (lhs.Ident() != t.IDLength) {
 			return x, nil
 		}
 		lhs = lhs.LHS().AsExpr()
@@ -155,11 +155,11 @@
 
 		// Check that lhs is "receiver.length()".
 		lhs := x.LHS().AsExpr()
-		if lhs.Operator() != t.IDOpenParen || len(lhs.Args()) != 0 {
+		if (lhs.Operator() != a.ExprOperatorCall) || (len(lhs.Args()) != 0) {
 			return x, nil
 		}
 		lhs = lhs.LHS().AsExpr()
-		if lhs.Operator() != t.IDDot || lhs.Ident() != t.IDLength {
+		if (lhs.Operator() != a.ExprOperatorSelector) || (lhs.Ident() != t.IDLength) {
 			return x, nil
 		}
 		lhs = lhs.LHS().AsExpr()
diff --git a/lang/parse/parse.go b/lang/parse/parse.go
index 2cea0f2..5e3a019 100644
--- a/lang/parse/parse.go
+++ b/lang/parse/parse.go
@@ -591,7 +591,7 @@
 
 	case x == t.IDCloseBracket && sep == t.IDDotDot:
 		p.src = p.src[1:]
-		return t.IDOpenBracket, nil, ei, nil
+		return a.ExprOperatorIndex, nil, ei, nil
 
 	default:
 		extra := ``
@@ -1000,7 +1000,7 @@
 		}
 
 		if op == t.IDEqQuestion {
-			if (rhs.Operator() != t.IDOpenParen) || (!rhs.Effect().Coroutine()) {
+			if (rhs.Operator() != a.ExprOperatorCall) || (!rhs.Effect().Coroutine()) {
 				return nil, fmt.Errorf(`parse: expected ?-function call after "=?", got %q at %s:%d`,
 					rhs.Str(p.tm), p.filename, p.line())
 			}
@@ -1405,7 +1405,7 @@
 	if err != nil {
 		return nil, err
 	}
-	return a.NewExpr(0, t.IDComma, 0, nil, nil, nil, args), nil
+	return a.NewExpr(0, a.ExprOperatorList, 0, nil, nil, nil, args), nil
 }
 
 func (p *parser) parseExpr() (*a.Expr, error) {
@@ -1521,7 +1521,7 @@
 			if err != nil {
 				return nil, err
 			}
-			lhs = a.NewExpr(flags, t.IDOpenParen, 0, lhs.AsNode(), nil, nil, args)
+			lhs = a.NewExpr(flags, a.ExprOperatorCall, 0, lhs.AsNode(), nil, nil, args)
 
 		case t.IDOpenBracket:
 			id0, mhs, rhs, err := p.parseBracket(t.IDDotDot)
@@ -1541,7 +1541,7 @@
 					return nil, err
 				}
 			}
-			lhs = a.NewExpr(0, t.IDDot, selector, lhs.AsNode(), nil, nil, nil)
+			lhs = a.NewExpr(0, a.ExprOperatorSelector, selector, lhs.AsNode(), nil, nil, nil)
 		}
 	}
 }