Add warnings as another status code category
diff --git a/cmd/wuffs-c/internal/cgen/base/base-public.h b/cmd/wuffs-c/internal/cgen/base/base-public.h
index fd481d5..3a70040 100644
--- a/cmd/wuffs-c/internal/cgen/base/base-public.h
+++ b/cmd/wuffs-c/internal/cgen/base/base-public.h
@@ -99,10 +99,10 @@
 
 // --------
 
-// A status is either NULL (meaning OK or no error) or string message. That
-// message is human-readable, for programmers, but it is not for end users. It
-// is not localized, and does not contain additional contextual information
-// such as a source filename.
+// A status is either NULL (meaning OK) or a string message. That message is
+// human-readable, for programmers, but it is not for end users. It is not
+// localized, and does not contain additional contextual information such as a
+// source filename.
 //
 // Status strings are statically allocated and should never be free'd. They can
 // be compared by the == operator and not just by strcmp.
@@ -112,7 +112,7 @@
 
 static inline bool  //
 wuffs_base__status__is_error(wuffs_base__status z) {
-  return z && (*z != '$');
+  return z && (*z == '?');
 }
 
 static inline bool  //
@@ -125,6 +125,11 @@
   return z && (*z == '$');
 }
 
+static inline bool  //
+wuffs_base__status__is_warning(wuffs_base__status z) {
+  return z && (*z != '$') && (*z != '?');
+}
+
 // --------
 
 // Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a
diff --git a/cmd/wuffs-c/internal/cgen/cgen.go b/cmd/wuffs-c/internal/cgen/cgen.go
index 54d8837..e2a2692 100644
--- a/cmd/wuffs-c/internal/cgen/cgen.go
+++ b/cmd/wuffs-c/internal/cgen/cgen.go
@@ -116,9 +116,11 @@
 						if z == "" {
 							continue
 						}
-						pre := "error"
+						pre := "warning"
 						if z[0] == '$' {
 							pre = "suspension"
+						} else if z[0] == '?' {
+							pre = "error"
 						}
 						b.printf("const char* wuffs_base__%s__%s = \"%sbase: %s\";\n",
 							pre, cName(z, ""), z[:1], z[1:])
@@ -185,7 +187,15 @@
 }
 
 func statusMsgIsError(msg string) bool {
-	return (len(msg) == 0) || (msg[0] != '$')
+	return (len(msg) != 0) && (msg[0] == '?')
+}
+
+func statusMsgIsSuspension(msg string) bool {
+	return (len(msg) != 0) && (msg[0] == '$')
+}
+
+func statusMsgIsWarning(msg string) bool {
+	return (len(msg) == 0) || (msg[0] != '$' && msg[0] != '?')
 }
 
 type buffer []byte
@@ -247,8 +257,10 @@
 	if err := expandBangBangInsert(buf, baseBasePublicH, map[string]func(*buffer) error{
 		"// !! INSERT wuffs_base__status names.\n": func(b *buffer) error {
 			for _, z := range builtin.StatusList {
-				pre := "error"
-				if !statusMsgIsError(z) {
+				pre := "warning"
+				if statusMsgIsError(z) {
+					pre = "error"
+				} else if statusMsgIsSuspension(z) {
 					pre = "suspension"
 				}
 				b.printf("extern const char* wuffs_base__%s__%s;\n", pre, cName(z, ""))
diff --git a/cmd/wuffs-c/internal/cgen/data.go b/cmd/wuffs-c/internal/cgen/data.go
index 13ef72d..a3a8605 100644
--- a/cmd/wuffs-c/internal/cgen/data.go
+++ b/cmd/wuffs-c/internal/cgen/data.go
@@ -22,7 +22,7 @@
 	"nt.com/nothings/stb/master/docs/stb_howto.txt\n#ifdef WUFFS_CONFIG__STATIC_FUNCTIONS\n#define WUFFS_BASE__MAYBE_STATIC static\n#else\n#define WUFFS_BASE__MAYBE_STATIC\n#endif\n\n// Clang also defines \"__GNUC__\".\n#if defined(__GNUC__)\n#define WUFFS_BASE__WARN_UNUSED_RESULT __attribute__((warn_unused_result))\n#else\n#define WUFFS_BASE__WARN_UNUSED_RESULT\n#endif\n\n// wuffs_base__empty_struct is used when a Wuffs function returns an empty\n// struct. In C, if a function f returns void, you can't say \"x = f()\", but in\n// Wuffs, if a function g returns empty, you can say \"y = g()\".\ntypedef struct {\n  // private_impl is a placeholder field. It isn't explicitly used, except that\n  // without it, the sizeof a struct with no fields can differ across C/C++\n  // compilers, and it is undefined behavior in C99. For example, gcc says that\n  // the sizeof an empty struct is 0, and g++ says that it is 1. This leads to\n  // ABI incompatibility if a Wuffs .c file is processed by one compiler and\n  // its .h file with another compiler.\n  " +
 	"//\n  // Instead, we explicitly insert an otherwise unused field, so that the\n  // sizeof this struct is always 1.\n  uint8_t private_impl;\n} wuffs_base__empty_struct;\n\n// wuffs_base__utility is a placeholder receiver type. It enables what Java\n// calls static methods, as opposed to regular methods.\ntypedef struct {\n  // private_impl is a placeholder field. It isn't explicitly used, except that\n  // without it, the sizeof a struct with no fields can differ across C/C++\n  // compilers, and it is undefined behavior in C99. For example, gcc says that\n  // the sizeof an empty struct is 0, and g++ says that it is 1. This leads to\n  // ABI incompatibility if a Wuffs .c file is processed by one compiler and\n  // its .h file with another compiler.\n  //\n  // Instead, we explicitly insert an otherwise unused field, so that the\n  // sizeof this struct is always 1.\n  uint8_t private_impl;\n} wuffs_base__utility;\n\n" +
 	"" +
-	"// --------\n\n// A status is either NULL (meaning OK or no error) or string message. That\n// message is human-readable, for programmers, but it is not for end users. It\n// is not localized, and does not contain additional contextual information\n// such as a source filename.\n//\n// Status strings are statically allocated and should never be free'd. They can\n// be compared by the == operator and not just by strcmp.\ntypedef const char* wuffs_base__status;\n\n// !! INSERT wuffs_base__status names.\n\nstatic inline bool  //\nwuffs_base__status__is_error(wuffs_base__status z) {\n  return z && (*z != '$');\n}\n\nstatic inline bool  //\nwuffs_base__status__is_ok(wuffs_base__status z) {\n  return z == NULL;\n}\n\nstatic inline bool  //\nwuffs_base__status__is_suspension(wuffs_base__status z) {\n  return z && (*z == '$');\n}\n\n" +
+	"// --------\n\n// A status is either NULL (meaning OK) or a string message. That message is\n// human-readable, for programmers, but it is not for end users. It is not\n// localized, and does not contain additional contextual information such as a\n// source filename.\n//\n// Status strings are statically allocated and should never be free'd. They can\n// be compared by the == operator and not just by strcmp.\ntypedef const char* wuffs_base__status;\n\n// !! INSERT wuffs_base__status names.\n\nstatic inline bool  //\nwuffs_base__status__is_error(wuffs_base__status z) {\n  return z && (*z == '?');\n}\n\nstatic inline bool  //\nwuffs_base__status__is_ok(wuffs_base__status z) {\n  return z == NULL;\n}\n\nstatic inline bool  //\nwuffs_base__status__is_suspension(wuffs_base__status z) {\n  return z && (*z == '$');\n}\n\nstatic inline bool  //\nwuffs_base__status__is_warning(wuffs_base__status z) {\n  return z && (*z != '$') && (*z != '?');\n}\n\n" +
 	"" +
 	"// --------\n\n// Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a\n// second. See https://github.com/OculusVR/Flicks\ntypedef int64_t wuffs_base__flicks;\n\n#define WUFFS_BASE__FLICKS_PER_SECOND ((uint64_t)705600000)\n#define WUFFS_BASE__FLICKS_PER_MILLISECOND ((uint64_t)705600)\n\n" +
 	"" +
diff --git a/cmd/wuffs-c/internal/cgen/expr.go b/cmd/wuffs-c/internal/cgen/expr.go
index 17163af..bc66e1d 100644
--- a/cmd/wuffs-c/internal/cgen/expr.go
+++ b/cmd/wuffs-c/internal/cgen/expr.go
@@ -219,8 +219,10 @@
 			b.writes("wuffs_base__")
 			if statusMsgIsError(msg) {
 				b.writes("error__")
-			} else {
+			} else if statusMsgIsSuspension(msg) {
 				b.writes("suspension__")
+			} else {
+				b.writes("warning__")
 			}
 			b.writes(cName(msg, ""))
 		}
diff --git a/cmd/wuffs-c/internal/cgen/statement.go b/cmd/wuffs-c/internal/cgen/statement.go
index 51968b1..a056ae3 100644
--- a/cmd/wuffs-c/internal/cgen/statement.go
+++ b/cmd/wuffs-c/internal/cgen/statement.go
@@ -315,6 +315,7 @@
 			if retExpr.Operator() == t.IDStatus {
 				msg, _ := t.Unescape(retExpr.Ident().Str(g.tm))
 				isError = statusMsgIsError(msg)
+				isOK = statusMsgIsWarning(msg)
 			}
 			// TODO: check that retExpr has no call-suspendibles.
 			if err := g.writeExpr(
@@ -335,9 +336,10 @@
 			b.writes("goto ok;")
 		} else {
 			g.currFunk.hasGotoOK = true
-			b.writes("if (!status) { goto ok; }" +
+			// TODO: the "goto exit"s can be "goto ok".
+			b.writes("if (wuffs_base__status__is_error(status)) { goto exit; }" +
 				"else if (wuffs_base__status__is_suspension(status)) { " +
-				"status = wuffs_base__error__cannot_return_a_suspension; } goto exit;")
+				"status = wuffs_base__error__cannot_return_a_suspension; goto exit; } goto ok;")
 		}
 		return nil
 	}
diff --git a/doc/changelog.md b/doc/changelog.md
index 0ff0993..a1a85cd 100644
--- a/doc/changelog.md
+++ b/doc/changelog.md
@@ -37,6 +37,7 @@
 - Removed closed-for-read/write built-in status codes.
 - Changed the string messages for built-in status codes.
 - Changed `error "foo"` to `status "?foo"`.
+- Added warnings as another status code category.
 - Made the status type a `const char *`, not an `int32_t`.
 - Disallowed `__double_underscore` prefixed names.
 - Moved the base38 package from `lang/base38` to `lib/base38`.
diff --git a/example/gifplayer/gifplayer.c b/example/gifplayer/gifplayer.c
index e23f113..8baf804 100644
--- a/example/gifplayer/gifplayer.c
+++ b/example/gifplayer/gifplayer.c
@@ -321,7 +321,7 @@
     wuffs_base__status z =
         wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
     if (z) {
-      if (z == wuffs_base__suspension__end_of_data) {
+      if (z == wuffs_base__warning__end_of_data) {
         break;
       }
       return z;
@@ -337,7 +337,7 @@
     z = wuffs_gif__decoder__decode_frame(&dec, &pb, 0, 0, src_reader,
                                          ((wuffs_base__slice_u8){}));
     if (z) {
-      if (z == wuffs_base__suspension__end_of_data) {
+      if (z == wuffs_base__warning__end_of_data) {
         break;
       }
       return z;
diff --git a/fuzz/c/std/gif_fuzzer.c b/fuzz/c/std/gif_fuzzer.c
index 750790a..c3821a0 100644
--- a/fuzz/c/std/gif_fuzzer.c
+++ b/fuzz/c/std/gif_fuzzer.c
@@ -97,7 +97,7 @@
       z = wuffs_gif__decoder__decode_frame(&dec, &pb, 0, 0, src_reader,
                                            ((wuffs_base__slice_u8){}));
       if (z) {
-        if ((z != wuffs_base__suspension__end_of_data) || !seen_ok) {
+        if ((z != wuffs_base__warning__end_of_data) || !seen_ok) {
           ret = z;
         }
         goto exit;
diff --git a/lang/builtin/builtin.go b/lang/builtin/builtin.go
index f47f71f..e41718c 100644
--- a/lang/builtin/builtin.go
+++ b/lang/builtin/builtin.go
@@ -20,7 +20,7 @@
 )
 
 var StatusList = [...]string{
-	"$end of data",
+	"!end of data",
 	"$short read",
 	"$short write",
 	"?bad argument (length too short)",
@@ -225,6 +225,7 @@
 	"status.is_error() bool",
 	"status.is_ok() bool",
 	"status.is_suspension() bool",
+	"status.is_warning() bool",
 
 	// ---- frame_config
 	// Duration's upper bound is the maximum possible i64 value.
diff --git a/release/c/wuffs-unsupported-snapshot.h b/release/c/wuffs-unsupported-snapshot.h
index 167889e..89e8eeb 100644
--- a/release/c/wuffs-unsupported-snapshot.h
+++ b/release/c/wuffs-unsupported-snapshot.h
@@ -123,16 +123,16 @@
 
 // --------
 
-// A status is either NULL (meaning OK or no error) or string message. That
-// message is human-readable, for programmers, but it is not for end users. It
-// is not localized, and does not contain additional contextual information
-// such as a source filename.
+// A status is either NULL (meaning OK) or a string message. That message is
+// human-readable, for programmers, but it is not for end users. It is not
+// localized, and does not contain additional contextual information such as a
+// source filename.
 //
 // Status strings are statically allocated and should never be free'd. They can
 // be compared by the == operator and not just by strcmp.
 typedef const char* wuffs_base__status;
 
-extern const char* wuffs_base__suspension__end_of_data;
+extern const char* wuffs_base__warning__end_of_data;
 extern const char* wuffs_base__suspension__short_read;
 extern const char* wuffs_base__suspension__short_write;
 extern const char* wuffs_base__error__bad_argument_length_too_short;
@@ -148,7 +148,7 @@
 
 static inline bool  //
 wuffs_base__status__is_error(wuffs_base__status z) {
-  return z && (*z != '$');
+  return z && (*z == '?');
 }
 
 static inline bool  //
@@ -161,6 +161,11 @@
   return z && (*z == '$');
 }
 
+static inline bool  //
+wuffs_base__status__is_warning(wuffs_base__status z) {
+  return z && (*z != '$') && (*z != '?');
+}
+
 // --------
 
 // Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a
@@ -3616,7 +3621,7 @@
 
 #if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE)
 
-const char* wuffs_base__suspension__end_of_data = "$base: end of data";
+const char* wuffs_base__warning__end_of_data = "!base: end of data";
 const char* wuffs_base__suspension__short_read = "$base: short read";
 const char* wuffs_base__suspension__short_write = "$base: short write";
 const char* wuffs_base__error__bad_argument_length_too_short =
@@ -4514,12 +4519,13 @@
       }
       if (!wuffs_base__status__is_suspension(v_z)) {
         status = v_z;
-        if (!status) {
-          goto ok;
+        if (wuffs_base__status__is_error(status)) {
+          goto exit;
         } else if (wuffs_base__status__is_suspension(status)) {
           status = wuffs_base__error__cannot_return_a_suspension;
+          goto exit;
         }
-        goto exit;
+        goto ok;
       }
       v_written = ((wuffs_base__slice_u8){
           .ptr = a_dst.private_impl.mark,
@@ -6603,10 +6609,8 @@
       }
     }
     if (self->private_impl.f_end_of_data) {
-      while (true) {
-        status = wuffs_base__suspension__end_of_data;
-        WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4);
-      }
+      status = wuffs_base__warning__end_of_data;
+      goto ok;
     }
     v_blend = 0;
     if (!self->private_impl.f_gc_has_transparent_index) {
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index e7de4ef..1fcea16 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -205,9 +205,7 @@
 	// This is a new "if", not an "else", because the calls above can modify
 	// this.end_of_data.
 	if this.end_of_data {
-		while true {
-			yield status "$end of data"
-		}
+		return status "!end of data"
 	}
 
 	var blend base.u8 = 0
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 82c48b9..95f5b45 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -223,7 +223,7 @@
   while (true) {
     z = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
     if (z) {
-      if (z == wuffs_base__suspension__end_of_data) {
+      if (z == wuffs_base__warning__end_of_data) {
         break;
       }
       return z;
@@ -428,9 +428,9 @@
     wuffs_base__io_reader src_reader = wuffs_base__io_buffer__reader(&src);
     wuffs_base__status z = wuffs_gif__decoder__decode_frame(
         &dec, &pb, 0, 0, src_reader, ((wuffs_base__slice_u8){}));
-    if (z != wuffs_base__suspension__end_of_data) {
+    if (z != wuffs_base__warning__end_of_data) {
       FAIL("decode_frame: got \"%s\", want \"%s\"", z,
-           wuffs_base__suspension__end_of_data);
+           wuffs_base__warning__end_of_data);
       return false;
     }
     if (src.ri != src.wi) {
@@ -557,9 +557,9 @@
   for (i = 0; i < 3; i++) {
     z = wuffs_gif__decoder__decode_frame(&dec, &pb, 0, 0, src_reader,
                                          ((wuffs_base__slice_u8){}));
-    if (z != wuffs_base__suspension__end_of_data) {
+    if (z != wuffs_base__warning__end_of_data) {
       FAIL("decode_frame: got \"%s\", want \"%s\"", z,
-           wuffs_base__suspension__end_of_data);
+           wuffs_base__warning__end_of_data);
       return false;
     }
   }
@@ -769,7 +769,7 @@
     if (!z) {
       want++;
       continue;
-    } else if (z == wuffs_base__suspension__end_of_data) {
+    } else if (z == wuffs_base__warning__end_of_data) {
       end_of_data = true;
       continue;
     }
@@ -902,7 +902,7 @@
   }
 
   z = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
-  if (z != wuffs_base__suspension__end_of_data) {
+  if (z != wuffs_base__warning__end_of_data) {
     FAIL("decode_frame_config EOD: \"%s\"", z);
     return false;
   }
@@ -923,9 +923,6 @@
       return false;
     }
 
-    // TODO: delete this hack to get out of the end-of-data loop.
-    dec.private_impl.c_decode_frame_config[0].coro_susp_point = 0;
-
     int j;
     for (j = i + 1; j < 4; j++) {
       wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
@@ -953,7 +950,7 @@
     }
 
     z = wuffs_gif__decoder__decode_frame_config(&dec, NULL, src_reader);
-    if (z != wuffs_base__suspension__end_of_data) {
+    if (z != wuffs_base__warning__end_of_data) {
       FAIL("decode_frame_config EOD #%d: \"%s\"", i, z);
       return false;
     }