lang: add read-only type decorators
diff --git a/doc/changelog.md b/doc/changelog.md
index c2f5fe4..0efed9b 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -6,6 +6,7 @@
 The dot points below probably aren't of interest unless you're _writing_ Wuffs
 code (instead of writing C/C++ code that _uses_ Wuffs' standard library).
 
+- Added read-only type decorators: `roarray`, `roslice` and `rotable`.
 - Wuffs struct private data now needs a "+" between the "()" pairs.
 - `wuffsfmt` double-indents hanging lines and each indent is now 4 spaces (not
   a tab).
diff --git a/internal/cgen/builtin.go b/internal/cgen/builtin.go
index f8981c6..12d4f0e 100644
--- a/internal/cgen/builtin.go
+++ b/internal/cgen/builtin.go
@@ -80,9 +80,9 @@
 		b.writeb(')')
 		return nil
 
-	case t.IDSlice:
+	case t.IDRoslice, t.IDSlice:
 		return g.writeBuiltinSlice(b, recv, method.Ident(), n.Args(), sideEffectsOnly, depth)
-	case t.IDTable:
+	case t.IDRotable, t.IDTable:
 		return g.writeBuiltinTable(b, recv, method.Ident(), n.Args(), sideEffectsOnly, depth)
 	default:
 		return errNoSuchBuiltin
@@ -759,7 +759,7 @@
 		if err := g.writeExpr(b, arrayOrSlice, sideEffectsOnly, depth); err != nil {
 			return err
 		}
-		if arrayOrSlice.MType().IsSliceType() {
+		if arrayOrSlice.MType().IsEitherSliceType() {
 			b.writes(".ptr")
 		}
 		if lo != nil {
@@ -774,7 +774,7 @@
 	if err := g.writeExpr(b, n, sideEffectsOnly, depth); err != nil {
 		return err
 	}
-	if n.MType().IsSliceType() {
+	if n.MType().IsEitherSliceType() {
 		b.writes(".ptr")
 	}
 	return nil
@@ -966,7 +966,7 @@
 	if err := g.writeExpr(b, foo, false, depth); err != nil {
 		return err
 	}
-	if foo.MType().IsSliceType() {
+	if foo.MType().IsEitherSliceType() {
 		b.writes(".ptr")
 	}
 	if fIndex != nil {
@@ -979,7 +979,7 @@
 	if err := g.writeExpr(b, bar, false, depth); err != nil {
 		return err
 	}
-	if bar.MType().IsSliceType() {
+	if bar.MType().IsEitherSliceType() {
 		b.writes(".ptr")
 	}
 	if bIndex != nil {
diff --git a/internal/cgen/expr.go b/internal/cgen/expr.go
index afb60f2..c4a439f 100644
--- a/internal/cgen/expr.go
+++ b/internal/cgen/expr.go
@@ -139,7 +139,7 @@
 		if err := g.writeExpr(b, n.LHS().AsExpr(), false, depth); err != nil {
 			return err
 		}
-		if lTyp := n.LHS().AsExpr().MType(); lTyp.IsSliceType() {
+		if lTyp := n.LHS().AsExpr().MType(); lTyp.IsEitherSliceType() {
 			// TODO: don't assume that the slice is a slice of base.u8.
 			b.writes(".ptr")
 		}
@@ -164,7 +164,7 @@
 			comma = ",\n"
 		}
 
-		if lhs.MType().IsArrayType() {
+		if lhs.MType().IsEitherArrayType() {
 			if mhs == nil {
 				b.writes("wuffs_base__make_slice_u8(")
 			} else {
@@ -449,7 +449,7 @@
 
 	// TODO: fix this, allow slices of all types, not just of base.u8's. Also
 	// allow arrays of slices, slices of pointers, etc.
-	if n.IsSliceType() {
+	if n.IsEitherSliceType() {
 		o := n.Inner()
 		if o.Decorator() == 0 && o.QID() == (t.QID{t.IDBase, t.IDU8}) && !o.IsRefined() {
 			b.writes("wuffs_base__slice_u8")
@@ -462,7 +462,7 @@
 		}
 		return fmt.Errorf("cannot convert Wuffs type %q to C", n.Str(g.tm))
 	}
-	if n.IsTableType() {
+	if n.IsEitherTableType() {
 		o := n.Inner()
 		if o.Decorator() == 0 && o.QID() == (t.QID{t.IDBase, t.IDU8}) && !o.IsRefined() {
 			b.writes("wuffs_base__table_u8")
@@ -480,7 +480,7 @@
 	const maxNumPointers = 16
 
 	x := n
-	for ; x != nil && x.IsArrayType(); x = x.Inner() {
+	for ; x != nil && x.IsEitherArrayType(); x = x.Inner() {
 	}
 
 	numPointers, innermost := 0, x
@@ -521,7 +521,7 @@
 	}
 
 	x = n
-	for ; x != nil && x.IsArrayType(); x = x.Inner() {
+	for ; x != nil && x.IsEitherArrayType(); x = x.Inner() {
 		b.writeb('[')
 		b.writes(x.ArrayLength().ConstValue().String())
 		b.writeb(']')
diff --git a/internal/cgen/func.go b/internal/cgen/func.go
index a28637a..8c5dc33 100644
--- a/internal/cgen/func.go
+++ b/internal/cgen/func.go
@@ -342,7 +342,7 @@
 	} else if typ.IsNumType() {
 		b.writes("0")
 		return nil
-	} else if typ.IsSliceType() {
+	} else if typ.IsEitherSliceType() {
 		if inner := typ.Inner(); (inner.Decorator() == 0) && (inner.QID() == t.QID{t.IDBase, t.IDU8}) {
 			b.writes("wuffs_base__make_slice_u8(NULL, 0)")
 			return nil
diff --git a/internal/cgen/statement.go b/internal/cgen/statement.go
index 151b814..52d8d9a 100644
--- a/internal/cgen/statement.go
+++ b/internal/cgen/statement.go
@@ -136,7 +136,7 @@
 			return err
 		}
 
-		if lTyp := lhs.MType(); lTyp.IsArrayType() {
+		if lTyp := lhs.MType(); lTyp.IsEitherArrayType() {
 			b.writes("memcpy(")
 			opName, closer = ",", fmt.Sprintf(", sizeof(%s))", lhsBuf)
 
diff --git a/lang/ast/ast.go b/lang/ast/ast.go
index 8adbfb4..84ce414 100644
--- a/lang/ast/ast.go
+++ b/lang/ast/ast.go
@@ -784,7 +784,8 @@
 
 // TypeExpr is a type expression, such as "base.u32", "base.u32[..= 8]", "foo",
 // "pkg.bar", "ptr T", "array[8] T", "slice T" or "table T":
-//   - ID0:   <0|IDArray|IDFunc|IDNptr|IDPtr|IDSlice|IDTable>
+//   - ID0:   <0|IDArray|IDFunc|IDNptr|IDPtr|IDRoarray|IDRoslice|IDRotable|
+//     ....      IDSlice|IDTable>
 //   - ID1:   <0|pkg>
 //   - ID2:   <0|type name>
 //   - LHS:   <nil|Expr>
@@ -889,8 +890,16 @@
 	return n.id0 == 0 && n.id1 == t.IDBase && n.id2 == t.IDStatus
 }
 
-func (n *TypeExpr) IsArrayType() bool {
-	return n.id0 == t.IDArray
+func (n *TypeExpr) IsEitherArrayType() bool {
+	return (n.id0 == t.IDArray) || (n.id0 == t.IDRoarray)
+}
+
+func (n *TypeExpr) IsEitherSliceType() bool {
+	return (n.id0 == t.IDSlice) || (n.id0 == t.IDRoslice)
+}
+
+func (n *TypeExpr) IsEitherTableType() bool {
+	return (n.id0 == t.IDTable) || (n.id0 == t.IDRotable)
 }
 
 func (n *TypeExpr) IsFuncType() bool {
@@ -901,12 +910,8 @@
 	return n.id0 == t.IDNptr || n.id0 == t.IDPtr
 }
 
-func (n *TypeExpr) IsSliceType() bool {
-	return n.id0 == t.IDSlice
-}
-
-func (n *TypeExpr) IsTableType() bool {
-	return n.id0 == t.IDTable
+func (n *TypeExpr) IsReadOnly() bool {
+	return (n.id0 == t.IDRoarray) || (n.id0 == t.IDRoslice) || (n.id0 == t.IDRotable)
 }
 
 func (n *TypeExpr) IsUnsignedInteger() bool {
diff --git a/lang/ast/eq.go b/lang/ast/eq.go
index 5725f8e..03e8c45 100644
--- a/lang/ast/eq.go
+++ b/lang/ast/eq.go
@@ -84,16 +84,23 @@
 
 // Eq returns whether n and o are equal.
 func (n *TypeExpr) Eq(o *TypeExpr) bool {
-	return n.eq(o, false)
+	return n.eq(o, false, false)
 }
 
 // EqIgnoringRefinements returns whether n and o are equal, ignoring the
 // "[i:j]" in "base.u32[i:j]".
 func (n *TypeExpr) EqIgnoringRefinements(o *TypeExpr) bool {
-	return n.eq(o, true)
+	return n.eq(o, true, false)
 }
 
-func (n *TypeExpr) eq(o *TypeExpr, ignoreRefinements bool) bool {
+// EqIgnoringRefinementsLHSReadOnly returns whether n and o are equal, ignoring
+// the "[i:j]" in "base.u32[i:j]" and allowing n (the Left Hand Side of an
+// assignment) to be read-only when o (the Right Hand Side) is read-write.
+func (n *TypeExpr) EqIgnoringRefinementsLHSReadOnly(o *TypeExpr) bool {
+	return n.eq(o, true, true)
+}
+
+func (n *TypeExpr) eq(o *TypeExpr, ignoreRefinements bool, lhsReadOnly bool) bool {
 	for {
 		if n == o {
 			return true
@@ -101,10 +108,22 @@
 		if n == nil || o == nil {
 			return false
 		}
-		if n.id0 != o.id0 || n.id1 != o.id1 || n.id2 != o.id2 {
+		if n.id0 != o.id0 {
+			if !lhsReadOnly {
+				return false
+			}
+			switch {
+			default:
+				return false
+			case (n.id0 == t.IDRoarray) && (o.id0 == t.IDArray):
+			case (n.id0 == t.IDRoslice) && (o.id0 == t.IDSlice):
+			case (n.id0 == t.IDRotable) && (o.id0 == t.IDTable):
+			}
+		}
+		if n.id1 != o.id1 || n.id2 != o.id2 {
 			return false
 		}
-		if n.IsArrayType() || !ignoreRefinements {
+		if !ignoreRefinements || !n.IsNumType() {
 			if !n.lhs.AsExpr().Eq(o.lhs.AsExpr()) || !n.mhs.AsExpr().Eq(o.mhs.AsExpr()) {
 				return false
 			}
diff --git a/lang/ast/string.go b/lang/ast/string.go
index 57806e4..ee3ea4a 100644
--- a/lang/ast/string.go
+++ b/lang/ast/string.go
@@ -208,7 +208,7 @@
 		return append(buf, "!invalid_type!"...)
 	}
 
-	switch n.Decorator() {
+	switch dec := n.Decorator(); dec {
 	case 0:
 		buf = append(buf, n.QID().Str(tm)...)
 	case t.IDNptr:
@@ -217,15 +217,24 @@
 	case t.IDPtr:
 		buf = append(buf, "ptr "...)
 		return n.Inner().appendStr(buf, tm, depth)
-	case t.IDArray:
+	case t.IDArray, t.IDRoarray:
+		if dec == t.IDRoarray {
+			buf = append(buf, "ro"...)
+		}
 		buf = append(buf, "array["...)
 		buf = n.ArrayLength().appendStr(buf, tm, false, 0)
 		buf = append(buf, "] "...)
 		return n.Inner().appendStr(buf, tm, depth)
-	case t.IDSlice:
+	case t.IDRoslice, t.IDSlice:
+		if dec == t.IDRoslice {
+			buf = append(buf, "ro"...)
+		}
 		buf = append(buf, "slice "...)
 		return n.Inner().appendStr(buf, tm, depth)
-	case t.IDTable:
+	case t.IDRotable, t.IDTable:
+		if dec == t.IDRotable {
+			buf = append(buf, "ro"...)
+		}
 		buf = append(buf, "table "...)
 		return n.Inner().appendStr(buf, tm, depth)
 	case t.IDFunc:
diff --git a/lang/check/bounds.go b/lang/check/bounds.go
index 10e037f..f0c2feb 100644
--- a/lang/check/bounds.go
+++ b/lang/check/bounds.go
@@ -977,7 +977,7 @@
 		}
 
 		lengthExpr := (*a.Expr)(nil)
-		if lTyp := lhs.MType(); lTyp.IsArrayType() {
+		if lTyp := lhs.MType(); lTyp.IsEitherArrayType() {
 			lengthExpr = lTyp.ArrayLength()
 		} else {
 			lengthExpr = makeSliceLength(lhs)
@@ -1013,7 +1013,7 @@
 		}
 
 		lengthExpr := (*a.Expr)(nil)
-		if lTyp := lhs.MType(); lTyp.IsArrayType() {
+		if lTyp := lhs.MType(); lTyp.IsEitherArrayType() {
 			lengthExpr = lTyp.ArrayLength()
 		} else {
 			lengthExpr = makeSliceLength(lhs)
@@ -1770,7 +1770,7 @@
 	switch typ.Decorator() {
 	case 0:
 		// No-op.
-	case t.IDArray:
+	case t.IDArray, t.IDRoarray:
 		if _, err := q.bcheckExpr(typ.ArrayLength(), 0); err != nil {
 			return bounds{}, err
 		}
@@ -1784,7 +1784,7 @@
 		return bounds{zero, one}, nil
 	case t.IDPtr:
 		return bounds{one, one}, nil
-	case t.IDSlice, t.IDTable:
+	case t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
 		return bounds{zero, zero}, nil
 	default:
 		return bounds{}, fmt.Errorf("check: internal error: unrecognized decorator")
diff --git a/lang/check/check.go b/lang/check/check.go
index b0a1bc1..a4c2b5f 100644
--- a/lang/check/check.go
+++ b/lang/check/check.go
@@ -375,7 +375,7 @@
 
 	nLists := 0
 	for elemTyp := typ; ; {
-		if elemTyp.IsArrayType() {
+		if elemTyp.IsEitherArrayType() {
 			if nLists == a.MaxTypeExprDepth {
 				return fmt.Errorf("check: type expression recursion depth too large")
 			}
@@ -399,7 +399,7 @@
 func (c *Checker) checkConstElement(typ *a.TypeExpr, n *a.Expr, nb bounds, nLists int) error {
 	if nLists > 0 {
 		nLists--
-		if !typ.IsArrayType() {
+		if !typ.IsEitherArrayType() {
 			return fmt.Errorf("internal error: inconsistent element type %q", typ.Str(c.tm))
 		}
 		cv := typ.ArrayLength().ConstValue()
diff --git a/lang/check/resolve.go b/lang/check/resolve.go
index 8b5ec7e..44492d1 100644
--- a/lang/check/resolve.go
+++ b/lang/check/resolve.go
@@ -182,23 +182,29 @@
 	lQID := lTyp.QID()
 	qqid := t.QQID{lQID[0], lQID[1], typ.FuncName()}
 
-	if lTyp.IsSliceType() {
+	if lTyp.IsEitherSliceType() {
 		qqid[0] = t.IDBase
 		qqid[1] = t.IDDagger1
 		if f := c.builtInSliceFuncs[qqid]; f != nil {
-			return f, nil
-		}
-		if lTyp.Eq(typeExprSliceU8) {
-			if f := c.builtInSliceU8Funcs[qqid]; f != nil {
+			if (lTyp.Decorator() == t.IDSlice) || f.Effect().Pure() {
 				return f, nil
 			}
 		}
+		if lTyp.Eq(typeExprSliceU8) {
+			if f := c.builtInSliceU8Funcs[qqid]; f != nil {
+				if (lTyp.Decorator() == t.IDSlice) || f.Effect().Pure() {
+					return f, nil
+				}
+			}
+		}
 
-	} else if lTyp.IsTableType() {
+	} else if lTyp.IsEitherTableType() {
 		qqid[0] = t.IDBase
 		qqid[1] = t.IDDagger2
 		if f := c.builtInTableFuncs[qqid]; f != nil {
-			return f, nil
+			if (lTyp.Decorator() == t.IDTable) || f.Effect().Pure() {
+				return f, nil
+			}
 		}
 
 	} else if f := c.funcs[qqid]; f != nil {
diff --git a/lang/check/type.go b/lang/check/type.go
index 5b3f213..aa5391a 100644
--- a/lang/check/type.go
+++ b/lang/check/type.go
@@ -171,7 +171,7 @@
 		if err := q.tcheckExpr(n.Arg1(), 0); err != nil {
 			return err
 		}
-		if typ := n.Arg1().MType(); !typ.EqIgnoringRefinements(arg1Typ) {
+		if typ := n.Arg1().MType(); !typ.EqIgnoringRefinementsLHSReadOnly(arg1Typ) {
 			return fmt.Errorf("check: %s expression %q, of type %q, does not have type %q",
 				n.Keyword().Str(q.tm), n.Arg1().Str(q.tm), typ.Str(q.tm), arg1Typ.Str(q.tm))
 		}
@@ -204,7 +204,7 @@
 					return err
 				}
 				o := o.AsAssign()
-				if typ := o.LHS().MType(); !typ.IsSliceType() {
+				if typ := o.LHS().MType(); !typ.IsEitherSliceType() {
 					return fmt.Errorf("check: iterate assignment to %q, of type %q, does not have slice type",
 						o.LHS().Str(q.tm), typ.Str(q.tm))
 				}
@@ -236,7 +236,7 @@
 			return err
 		}
 		rTyp := value.MType()
-		if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinements(rTyp) {
+		if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinementsLHSReadOnly(rTyp) {
 			return fmt.Errorf("check: cannot return %q (of type %q) as type %q",
 				value.Str(q.tm), rTyp.Str(q.tm), lTyp.Str(q.tm))
 		}
@@ -305,7 +305,7 @@
 
 func (q *checker) tcheckEq(lID t.ID, lhs *a.Expr, lTyp *a.TypeExpr, rhs *a.Expr, rTyp *a.TypeExpr) error {
 	if (rTyp.IsIdeal() && lTyp.IsNumType()) ||
-		(rTyp.EqIgnoringRefinements(lTyp)) ||
+		(lTyp.EqIgnoringRefinementsLHSReadOnly(rTyp)) ||
 		(rTyp.IsNullptr() && lTyp.Decorator() == t.IDNptr) {
 		return nil
 	}
@@ -331,6 +331,12 @@
 	if err := q.tcheckExpr(lhs, 0); err != nil {
 		return err
 	}
+	for l := lhs; l != nil; l = l.LHS().AsExpr() {
+		if l.MType().IsReadOnly() {
+			return fmt.Errorf("check: assignment %q: assignee fragment %q, of type %q, has read-only type",
+				n.Operator().Str(q.tm), l.Str(q.tm), l.MType().Str(q.tm))
+		}
+	}
 	lTyp := lhs.MType()
 	rTyp := rhs.MType()
 
@@ -363,7 +369,7 @@
 		}
 	}
 
-	if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinements(rTyp) {
+	if !(rTyp.IsIdeal() && lTyp.IsNumType()) && !lTyp.EqIgnoringRefinementsLHSReadOnly(rTyp) {
 		return fmt.Errorf("check: assignment %q: %q and %q, of types %q and %q, do not have compatible types",
 			n.Operator().Str(q.tm),
 			lhs.Str(q.tm), rhs.Str(q.tm),
@@ -508,7 +514,7 @@
 			return err
 		}
 		lTyp := lhs.MType()
-		if key := lTyp.Decorator(); key != t.IDArray && key != t.IDSlice {
+		if key := lTyp.Decorator(); key != t.IDArray && key != t.IDRoarray && key != t.IDRoslice && key != t.IDSlice {
 			return fmt.Errorf("check: %s is an index expression but %s has type %s, not an array or slice type",
 				n.Str(q.tm), lhs.Str(q.tm), lTyp.Str(q.tm))
 		}
@@ -555,7 +561,9 @@
 				n.Str(q.tm), lhs.Str(q.tm), lTyp.Str(q.tm))
 		case t.IDArray:
 			n.SetMType(a.NewTypeExpr(t.IDSlice, 0, 0, nil, nil, lTyp.Inner()))
-		case t.IDSlice:
+		case t.IDRoarray:
+			n.SetMType(a.NewTypeExpr(t.IDRoslice, 0, 0, nil, nil, lTyp.Inner()))
+		case t.IDRoslice, t.IDSlice:
 			n.SetMType(lTyp)
 		}
 		return nil
@@ -621,11 +629,16 @@
 		case t.IDDagger1:
 			genericType1 = lhs.MType().Receiver()
 		case t.IDDagger2:
+			decorator := t.ID(0)
 			genericType2 = lhs.MType().Receiver()
-			if genericType2.Decorator() != t.IDTable {
+			if genericType2.Decorator() == t.IDRotable {
+				decorator = t.IDRoslice
+			} else if genericType2.Decorator() == t.IDTable {
+				decorator = t.IDSlice
+			} else {
 				return fmt.Errorf("check: internal error: %q is not a generic table", genericType2.Str(q.tm))
 			}
-			genericType1 = a.NewTypeExpr(t.IDSlice, 0, 0, nil, nil, genericType2.Inner())
+			genericType1 = a.NewTypeExpr(decorator, 0, 0, nil, nil, genericType2.Inner())
 		}
 	}
 
@@ -687,7 +700,7 @@
 	lQID := lTyp.QID()
 	qqid := t.QQID{lQID[0], lQID[1], n.Ident()}
 
-	if lTyp.IsSliceType() {
+	if lTyp.IsEitherSliceType() {
 		qqid[0] = t.IDBase
 		qqid[1] = t.IDDagger1
 		if (q.c.builtInSliceFuncs[qqid] != nil) ||
@@ -697,7 +710,7 @@
 		}
 		return fmt.Errorf("check: no slice method %q", n.Ident().Str(q.tm))
 
-	} else if lTyp.IsTableType() {
+	} else if lTyp.IsEitherTableType() {
 		qqid[0] = t.IDBase
 		qqid[1] = t.IDDagger2
 		if q.c.builtInTableFuncs[qqid] != nil {
@@ -1132,7 +1145,7 @@
 		}
 		return fmt.Errorf("check: %q is not a type", typ.Str(q.tm))
 
-	case t.IDArray:
+	case t.IDArray, t.IDRoarray:
 		aLen := typ.ArrayLength()
 		if err := q.tcheckExpr(aLen, 0); err != nil {
 			return err
@@ -1142,7 +1155,7 @@
 		}
 		fallthrough
 
-	case t.IDNptr, t.IDPtr, t.IDSlice, t.IDTable:
+	case t.IDNptr, t.IDPtr, t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
 		if err := q.tcheckTypeExpr(typ.Inner(), depth); err != nil {
 			return err
 		}
diff --git a/lang/parse/parse.go b/lang/parse/parse.go
index 003791e..c666705 100644
--- a/lang/parse/parse.go
+++ b/lang/parse/parse.go
@@ -510,9 +510,9 @@
 	}
 
 	decorator, arrayLength := t.ID(0), (*a.Expr)(nil)
-	switch p.peek1() {
-	case t.IDArray:
-		decorator = t.IDArray
+	switch peek1 := p.peek1(); peek1 {
+	case t.IDArray, t.IDRoarray:
+		decorator = peek1
 		p.src = p.src[1:]
 
 		if x := p.peek1(); x != t.IDOpenBracket {
@@ -533,12 +533,8 @@
 		}
 		p.src = p.src[1:]
 
-	case t.IDSlice:
-		decorator = t.IDSlice
-		p.src = p.src[1:]
-
-	case t.IDTable:
-		decorator = t.IDTable
+	case t.IDRoslice, t.IDRotable, t.IDSlice, t.IDTable:
+		decorator = peek1
 		p.src = p.src[1:]
 	}
 
diff --git a/lang/token/list.go b/lang/token/list.go
index a4a83f7..2ace176 100644
--- a/lang/token/list.go
+++ b/lang/token/list.go
@@ -398,11 +398,14 @@
 	minTypeModifier = 0xD0
 	maxTypeModifier = 0xDF
 
-	IDArray = ID(0xD0)
-	IDNptr  = ID(0xD1)
-	IDPtr   = ID(0xD2)
-	IDSlice = ID(0xD3)
-	IDTable = ID(0xD4)
+	IDArray   = ID(0xD0)
+	IDNptr    = ID(0xD1)
+	IDPtr     = ID(0xD2)
+	IDRoarray = ID(0xD3)
+	IDRoslice = ID(0xD4)
+	IDRotable = ID(0xD5)
+	IDSlice   = ID(0xD6)
+	IDTable   = ID(0xD7)
 )
 
 const (
@@ -820,11 +823,14 @@
 	IDWhile:      "while",
 	IDYield:      "yield",
 
-	IDArray: "array",
-	IDNptr:  "nptr",
-	IDPtr:   "ptr",
-	IDSlice: "slice",
-	IDTable: "table",
+	IDArray:   "array",
+	IDNptr:    "nptr",
+	IDPtr:     "ptr",
+	IDRoarray: "roarray",
+	IDRoslice: "roslice",
+	IDRotable: "rotable",
+	IDSlice:   "slice",
+	IDTable:   "table",
 
 	IDFalse:   "false",
 	IDTrue:    "true",