Add std/json quirk_allow_inf_nan_numbers
diff --git a/internal/cgen/base/token-public.h b/internal/cgen/base/token-public.h
index 9f48d51..1c4c7c2 100644
--- a/internal/cgen/base/token-public.h
+++ b/internal/cgen/base/token-public.h
@@ -180,6 +180,11 @@
 #define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED 0x00002
 #define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED 0x00004
 
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_INF 0x00010
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_INF 0x00020
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_NAN 0x00040
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_NAN 0x00080
+
 // The number 300 might be represented as "\x01\x2C", "\x2C\x01\x00\x00" or
 // "300", which are big-endian, little-endian or text. For binary formats, the
 // token length discriminates e.g. u16 little-endian vs u32 little-endian.
diff --git a/internal/cgen/data.go b/internal/cgen/data.go
index dac8f33..3f35fff 100644
--- a/internal/cgen/data.go
+++ b/internal/cgen/data.go
@@ -371,8 +371,8 @@
 	"" +
 	"// --------\n\n#define WUFFS_BASE__TOKEN__VBD__LITERAL__UNDEFINED 0x00001\n#define WUFFS_BASE__TOKEN__VBD__LITERAL__NULL 0x00002\n#define WUFFS_BASE__TOKEN__VBD__LITERAL__FALSE 0x00004\n#define WUFFS_BASE__TOKEN__VBD__LITERAL__TRUE 0x00008\n\n  " +
 	"" +
-	"// --------\n\n// For a source string of \"123\" or \"0x9A\", it is valid for a tokenizer to\n// return any one of:\n//  - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT.\n//  - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED.\n//  - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED.\n//\n// For a source string of \"+123\" or \"-0x9A\", only the first two are valid.\n//\n// For a source string of \"123.\", only the first one is valid.\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT 0x00001\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED 0x00002\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED 0x00004\n\n// The number 300 might be represented as \"\\x01\\x2C\", \"\\x2C\\x01\\x00\\x00\" or\n// \"300\", which are big-endian, little-endian or text. For binary formats, the\n// token length discriminates e.g. u16 little-endian vs u32 little-endian.\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_BIG_ENDIAN 0x00100\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_LITTLE_END" +
-	"IAN 0x00200\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_TEXT 0x00400\n\n" +
+	"// --------\n\n// For a source string of \"123\" or \"0x9A\", it is valid for a tokenizer to\n// return any one of:\n//  - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT.\n//  - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED.\n//  - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED.\n//\n// For a source string of \"+123\" or \"-0x9A\", only the first two are valid.\n//\n// For a source string of \"123.\", only the first one is valid.\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT 0x00001\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED 0x00002\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED 0x00004\n\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_INF 0x00010\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_INF 0x00020\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_NAN 0x00040\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_NAN 0x00080\n\n// The number 300 might be represented as \"\\x01\\x2C\", \"\\x2C\\x01\\x00\\x00\" or\n// \"300\", which are big-endian, li" +
+	"ttle-endian or text. For binary formats, the\n// token length discriminates e.g. u16 little-endian vs u32 little-endian.\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_BIG_ENDIAN 0x00100\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_LITTLE_ENDIAN 0x00200\n#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_TEXT 0x00400\n\n" +
 	"" +
 	"// --------\n\nstatic inline uint64_t  //\nwuffs_base__token__value(const wuffs_base__token* t) {\n  return (t->repr >> WUFFS_BASE__TOKEN__VALUE__SHIFT) &\n         WUFFS_BASE__TOKEN__VALUE__MASK;\n}\n\nstatic inline uint64_t  //\nwuffs_base__token__value_major(const wuffs_base__token* t) {\n  return (t->repr >> WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT) &\n         WUFFS_BASE__TOKEN__VALUE_MAJOR__MASK;\n}\n\nstatic inline uint64_t  //\nwuffs_base__token__value_minor(const wuffs_base__token* t) {\n  return (t->repr >> WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) &\n         WUFFS_BASE__TOKEN__VALUE_MINOR__MASK;\n}\n\nstatic inline uint64_t  //\nwuffs_base__token__value_base_category(const wuffs_base__token* t) {\n  return (t->repr >> WUFFS_BASE__TOKEN__VALUE_BASE_CATEGORY__SHIFT) &\n         WUFFS_BASE__TOKEN__VALUE_BASE_CATEGORY__MASK;\n}\n\nstatic inline uint64_t  //\nwuffs_base__token__value_base_detail(const wuffs_base__token* t) {\n  return (t->repr >> WUFFS_BASE__TOKEN__VALUE_BASE_DETAIL__SHIFT) &\n         WUFFS_BASE__TOKEN__VALUE_BASE_DETA" +
 	"IL__MASK;\n}\n\nstatic inline bool  //\nwuffs_base__token__link_prev(const wuffs_base__token* t) {\n  return t->repr & WUFFS_BASE__TOKEN__LINK_PREV;\n}\n\nstatic inline bool  //\nwuffs_base__token__link_next(const wuffs_base__token* t) {\n  return t->repr & WUFFS_BASE__TOKEN__LINK_NEXT;\n}\n\nstatic inline uint64_t  //\nwuffs_base__token__length(const wuffs_base__token* t) {\n  return (t->repr >> WUFFS_BASE__TOKEN__LENGTH__SHIFT) &\n         WUFFS_BASE__TOKEN__LENGTH__MASK;\n}\n\n#ifdef __cplusplus\n\ninline uint64_t  //\nwuffs_base__token::value() const {\n  return wuffs_base__token__value(this);\n}\n\ninline uint64_t  //\nwuffs_base__token::value_major() const {\n  return wuffs_base__token__value_major(this);\n}\n\ninline uint64_t  //\nwuffs_base__token::value_minor() const {\n  return wuffs_base__token__value_minor(this);\n}\n\ninline uint64_t  //\nwuffs_base__token::value_base_category() const {\n  return wuffs_base__token__value_base_category(this);\n}\n\ninline uint64_t  //\nwuffs_base__token::value_base_detail() const {\n  return wuffs_base__to" +
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index b615ecc..d242f9a 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -1939,6 +1939,11 @@
 #define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED 0x00002
 #define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED 0x00004
 
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_INF 0x00010
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_INF 0x00020
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_NAN 0x00040
+#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_NAN 0x00080
+
 // The number 300 might be represented as "\x01\x2C", "\x2C\x01\x00\x00" or
 // "300", which are big-endian, little-endian or text. For binary formats, the
 // token length discriminates e.g. u16 little-endian vs u32 little-endian.
@@ -6023,6 +6028,7 @@
     uint32_t p_decode_tokens[1];
     uint32_t p_decode_leading[1];
     uint32_t p_decode_comment[1];
+    uint32_t p_decode_inf_nan[1];
     uint32_t p_decode_trailing_new_line[1];
   } private_impl;
 
@@ -6037,6 +6043,9 @@
     struct {
       uint32_t v_link_prev;
     } s_decode_comment[1];
+    struct {
+      uint32_t v_neg;
+    } s_decode_inf_nan[1];
   } private_data;
 
 #ifdef __cplusplus
@@ -20295,10 +20304,10 @@
     WUFFS_BASE__POTENTIALLY_UNUSED = {
         15, 15, 15, 15, 15, 15, 15, 15, 15, 0,  0,  15, 15, 0,  15, 15, 15, 15,
         15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0,  15, 1,  15,
-        15, 15, 15, 15, 15, 15, 15, 15, 2,  4,  15, 12, 4,  4,  4,  4,  4,  4,
+        15, 15, 15, 15, 15, 15, 15, 11, 2,  4,  15, 12, 4,  4,  4,  4,  4,  4,
         4,  4,  4,  4,  3,  15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
-        15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
-        15, 7,  15, 8,  15, 15, 15, 15, 15, 15, 15, 15, 9,  15, 15, 15, 15, 15,
+        15, 11, 15, 15, 15, 15, 11, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+        15, 7,  15, 8,  15, 15, 15, 15, 15, 15, 15, 15, 9,  15, 15, 11, 15, 15,
         15, 15, 11, 15, 15, 15, 15, 15, 10, 15, 15, 15, 15, 15, 15, 5,  15, 6,
         15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
         15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
@@ -20376,6 +20385,11 @@
                                     wuffs_base__io_buffer* a_src);
 
 static wuffs_base__status  //
+wuffs_json__decoder__decode_inf_nan(wuffs_json__decoder* self,
+                                    wuffs_base__token_buffer* a_dst,
+                                    wuffs_base__io_buffer* a_src);
+
+static wuffs_base__status  //
 wuffs_json__decoder__decode_trailing_new_line(wuffs_json__decoder* self,
                                               wuffs_base__token_buffer* a_dst,
                                               wuffs_base__io_buffer* a_src);
@@ -21374,6 +21388,27 @@
               }
             }
             if (v_number_status == 1) {
+              if (self->private_impl.f_quirk_enabled_allow_inf_nan_numbers) {
+                if (a_dst) {
+                  a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
+                }
+                if (a_src) {
+                  a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+                }
+                WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16);
+                status =
+                    wuffs_json__decoder__decode_inf_nan(self, a_dst, a_src);
+                if (a_dst) {
+                  iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
+                }
+                if (a_src) {
+                  iop_a_src = a_src->data.ptr + a_src->meta.ri;
+                }
+                if (status.repr) {
+                  goto suspend;
+                }
+                goto label__3__break;
+              }
               status = wuffs_base__make_status(wuffs_json__error__bad_input);
               goto exit;
             } else if (v_number_status == 2) {
@@ -21383,11 +21418,11 @@
             } else {
               status =
                   wuffs_base__make_status(wuffs_base__suspension__short_read);
-              WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(16);
+              WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(17);
               while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) {
                 status = wuffs_base__make_status(
                     wuffs_base__suspension__short_write);
-                WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(17);
+                WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(18);
               }
             }
           }
@@ -21521,7 +21556,7 @@
           } else if (v_match == 1) {
             status =
                 wuffs_base__make_status(wuffs_base__suspension__short_read);
-            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(18);
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(19);
             goto label__outer__continue;
           }
         } else if (v_class == 10) {
@@ -21542,7 +21577,7 @@
           } else if (v_match == 1) {
             status =
                 wuffs_base__make_status(wuffs_base__suspension__short_read);
-            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(19);
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(20);
             goto label__outer__continue;
           }
         } else if (v_class == 11) {
@@ -21563,9 +21598,29 @@
           } else if (v_match == 1) {
             status =
                 wuffs_base__make_status(wuffs_base__suspension__short_read);
-            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(20);
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(21);
             goto label__outer__continue;
           }
+          if (self->private_impl.f_quirk_enabled_allow_inf_nan_numbers) {
+            if (a_dst) {
+              a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
+            }
+            if (a_src) {
+              a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+            }
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(22);
+            status = wuffs_json__decoder__decode_inf_nan(self, a_dst, a_src);
+            if (a_dst) {
+              iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
+            }
+            if (a_src) {
+              iop_a_src = a_src->data.ptr + a_src->meta.ri;
+            }
+            if (status.repr) {
+              goto suspend;
+            }
+            goto label__goto_parsed_a_leaf_value__break;
+          }
         } else if (v_class == 12) {
           if (self->private_impl.f_quirk_enabled_allow_comment_block ||
               self->private_impl.f_quirk_enabled_allow_comment_line) {
@@ -21575,7 +21630,7 @@
             if (a_src) {
               a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
             }
-            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(21);
+            WUFFS_BASE__COROUTINE_SUSPENSION_POINT(23);
             status = wuffs_json__decoder__decode_comment(self, a_dst, a_src);
             if (a_dst) {
               iop_a_dst = a_dst->data.ptr + a_dst->meta.wi;
@@ -21606,7 +21661,7 @@
       if (a_src) {
         a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
       }
-      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(22);
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(24);
       status =
           wuffs_json__decoder__decode_trailing_new_line(self, a_dst, a_src);
       if (a_dst) {
@@ -22155,6 +22210,174 @@
   return status;
 }
 
+// -------- func json.decoder.decode_inf_nan
+
+static wuffs_base__status  //
+wuffs_json__decoder__decode_inf_nan(wuffs_json__decoder* self,
+                                    wuffs_base__token_buffer* a_dst,
+                                    wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  uint32_t v_c4 = 0;
+  uint32_t v_neg = 0;
+
+  wuffs_base__token* iop_a_dst = NULL;
+  wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_dst) {
+    io0_a_dst = a_dst->data.ptr;
+    io1_a_dst = io0_a_dst + a_dst->meta.wi;
+    iop_a_dst = io1_a_dst;
+    io2_a_dst = io0_a_dst + a_dst->data.len;
+    if (a_dst->meta.closed) {
+      io2_a_dst = iop_a_dst;
+    }
+  }
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_decode_inf_nan[0];
+  if (coro_susp_point) {
+    v_neg = self->private_data.s_decode_inf_nan[0].v_neg;
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+  label__0__continue:;
+    while (true) {
+      if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) {
+        status = wuffs_base__make_status(wuffs_base__suspension__short_write);
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+        goto label__0__continue;
+      }
+      if (((uint64_t)(io2_a_src - iop_a_src)) <= 2) {
+        if (a_src && a_src->meta.closed) {
+          status = wuffs_base__make_status(wuffs_json__error__bad_input);
+          goto exit;
+        }
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2);
+        goto label__0__continue;
+      }
+      v_c4 = ((uint32_t)(wuffs_base__load_u24le__no_bounds_check(iop_a_src)));
+      if ((v_c4 | 2105376) == 6712937) {
+        if (((uint64_t)(io2_a_src - iop_a_src)) > 7) {
+          if ((wuffs_base__load_u64le__no_bounds_check(iop_a_src) |
+               2314885530818453536) == 8751735898823356009) {
+            *iop_a_dst++ = wuffs_base__make_token(
+                (((uint64_t)(10485792))
+                 << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
+                (((uint64_t)(8)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
+            (iop_a_src += 8, wuffs_base__make_empty_struct());
+            status = wuffs_base__make_status(NULL);
+            goto ok;
+          }
+        } else if (!(a_src && a_src->meta.closed)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3);
+          goto label__0__continue;
+        }
+        *iop_a_dst++ = wuffs_base__make_token(
+            (((uint64_t)(10485792)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
+            (((uint64_t)(3)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
+        (iop_a_src += 3, wuffs_base__make_empty_struct());
+        status = wuffs_base__make_status(NULL);
+        goto ok;
+      } else if ((v_c4 | 2105376) == 7233902) {
+        *iop_a_dst++ = wuffs_base__make_token(
+            (((uint64_t)(10485888)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
+            (((uint64_t)(3)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
+        (iop_a_src += 3, wuffs_base__make_empty_struct());
+        status = wuffs_base__make_status(NULL);
+        goto ok;
+      } else if ((v_c4 & 255) == 43) {
+        v_neg = 0;
+      } else if ((v_c4 & 255) == 45) {
+        v_neg = 1;
+      } else {
+        status = wuffs_base__make_status(wuffs_json__error__bad_input);
+        goto exit;
+      }
+      if (((uint64_t)(io2_a_src - iop_a_src)) <= 3) {
+        if (a_src && a_src->meta.closed) {
+          status = wuffs_base__make_status(wuffs_json__error__bad_input);
+          goto exit;
+        }
+        status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+        WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4);
+        goto label__0__continue;
+      }
+      v_c4 = (wuffs_base__load_u32le__no_bounds_check(iop_a_src) >> 8);
+      if ((v_c4 | 2105376) == 6712937) {
+        if (((uint64_t)(io2_a_src - iop_a_src)) > 8) {
+          if ((wuffs_base__load_u64le__no_bounds_check(iop_a_src + 1) |
+               2314885530818453536) == 8751735898823356009) {
+            *iop_a_dst++ = wuffs_base__make_token(
+                (((uint64_t)((10485760 | (((uint32_t)(32)) >> v_neg))))
+                 << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
+                (((uint64_t)(9)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
+            (iop_a_src += 9, wuffs_base__make_empty_struct());
+            status = wuffs_base__make_status(NULL);
+            goto ok;
+          }
+        } else if (!(a_src && a_src->meta.closed)) {
+          status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+          WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5);
+          goto label__0__continue;
+        }
+        *iop_a_dst++ = wuffs_base__make_token(
+            (((uint64_t)((10485760 | (((uint32_t)(32)) >> v_neg))))
+             << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
+            (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
+        (iop_a_src += 4, wuffs_base__make_empty_struct());
+        status = wuffs_base__make_status(NULL);
+        goto ok;
+      } else if ((v_c4 | 2105376) == 7233902) {
+        *iop_a_dst++ = wuffs_base__make_token(
+            (((uint64_t)((10485760 | (((uint32_t)(128)) >> v_neg))))
+             << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) |
+            (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT));
+        (iop_a_src += 4, wuffs_base__make_empty_struct());
+        status = wuffs_base__make_status(NULL);
+        goto ok;
+      }
+      status = wuffs_base__make_status(wuffs_json__error__bad_input);
+      goto exit;
+    }
+
+    goto ok;
+  ok:
+    self->private_impl.p_decode_inf_nan[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+suspend:
+  self->private_impl.p_decode_inf_nan[0] =
+      wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_data.s_decode_inf_nan[0].v_neg = v_neg;
+
+  goto exit;
+exit:
+  if (a_dst) {
+    a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
+  }
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
 // -------- func json.decoder.decode_trailing_new_line
 
 static wuffs_base__status  //
diff --git a/std/json/common_consts.wuffs b/std/json/common_consts.wuffs
index 8a43b71..f92383d 100644
--- a/std/json/common_consts.wuffs
+++ b/std/json/common_consts.wuffs
@@ -196,7 +196,7 @@
 //  - 0x08 (bitmask 0x0100) is CLASS_CLOSE_SQUARE_BRACKET.
 //  - 0x09 (bitmask 0x0200) is CLASS_FALSE.
 //  - 0x0A (bitmask 0x0400) is CLASS_TRUE.
-//  - 0x0B (bitmask 0x0800) is CLASS_NULL.
+//  - 0x0B (bitmask 0x0800) is CLASS_NULL_NAN_INF.
 //  - 0x0C (bitmask 0x1000) is CLASS_COMMENT.
 //  - 0x0D (bitmask 0x2000) is reserved.
 //  - 0x0E (bitmask 0x4000) is reserved.
@@ -236,16 +236,16 @@
 	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x10 ..= 0x17.
 	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x18 ..= 0x1F.
 	0x00, 0x0F, 0x01, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x20 ..= 0x27. ' ', '"'.
-	0x0F, 0x0F, 0x0F, 0x0F, 0x02, 0x04, 0x0F, 0x0C,  // 0x28 ..= 0x2F. ',', '-', '/'.
+	0x0F, 0x0F, 0x0F, 0x0B, 0x02, 0x04, 0x0F, 0x0C,  // 0x28 ..= 0x2F. '+', ',', '-', '/'.
 	0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,  // 0x30 ..= 0x37. '0'-'7'.
 	0x04, 0x04, 0x03, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x38 ..= 0x3F. '8'-'9', ':'.
 
 	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x40 ..= 0x47.
-	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x48 ..= 0x4F.
+	0x0F, 0x0B, 0x0F, 0x0F, 0x0F, 0x0F, 0x0B, 0x0F,  // 0x48 ..= 0x4F. 'I', 'N'.
 	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,  // 0x50 ..= 0x57.
 	0x0F, 0x0F, 0x0F, 0x07, 0x0F, 0x08, 0x0F, 0x0F,  // 0x58 ..= 0x5F. '[', ']'.
 	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x09, 0x0F,  // 0x60 ..= 0x67. 'f'.
-	0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0B, 0x0F,  // 0x68 ..= 0x6F. 'n'.
+	0x0F, 0x0B, 0x0F, 0x0F, 0x0F, 0x0F, 0x0B, 0x0F,  // 0x68 ..= 0x6F. 'i', 'n'.
 	0x0F, 0x0F, 0x0F, 0x0F, 0x0A, 0x0F, 0x0F, 0x0F,  // 0x70 ..= 0x77. 't'.
 	0x0F, 0x0F, 0x0F, 0x05, 0x0F, 0x06, 0x0F, 0x0F,  // 0x78 ..= 0x7F. '{', '}'.
 
diff --git a/std/json/decode_json.wuffs b/std/json/decode_json.wuffs
index 566811d..3d99554 100644
--- a/std/json/decode_json.wuffs
+++ b/std/json/decode_json.wuffs
@@ -888,6 +888,10 @@
 					}
 
 					if number_status == 1 {
+						if this.quirk_enabled_allow_inf_nan_numbers {
+							this.decode_inf_nan?(dst: args.dst, src: args.src)
+							break
+						}
 						return "#bad input"
 					} else if number_status == 2 {
 						return "#unsupported number length"
@@ -1061,7 +1065,7 @@
 					continue.outer
 				}
 
-			} else if class == 0x0B {  // 0x0B is CLASS_NULL.
+			} else if class == 0x0B {  // 0x0B is CLASS_NULL_NAN_INF.
 				match = args.src.match7(a: 0x6C_6C75_6E04)  // 4 bytes "null".
 				if match == 0 {
 					args.dst.write_fast_token!(
@@ -1079,6 +1083,11 @@
 					continue.outer
 				}
 
+				if this.quirk_enabled_allow_inf_nan_numbers {
+					this.decode_inf_nan?(dst: args.dst, src: args.src)
+					break.goto_parsed_a_leaf_value
+				}
+
 			} else if class == 0x0C {  // 0x0C is CLASS_COMMENT.
 				if this.quirk_enabled_allow_comment_block or this.quirk_enabled_allow_comment_line {
 					this.decode_comment?(dst: args.dst, src: args.src)
@@ -1463,6 +1472,111 @@
 	return "#bad input"
 }
 
+pri func decoder.decode_inf_nan?(dst: base.token_writer, src: base.io_reader) {
+	var c4  : base.u32
+	var neg : base.u32[..= 1]
+
+	while true {
+		if args.dst.available() <= 0 {
+			yield? base."$short write"
+			continue
+		}
+		if args.src.available() <= 2 {
+			if args.src.is_closed() {
+				return "#bad input"
+			}
+			yield? base."$short read"
+			continue
+		}
+
+		// Bitwise or'ing with 0x20 converts upper case ASCII to lower case.
+
+		c4 = args.src.peek_u24le_as_u32()
+		if (c4 | 0x20_2020) == 0x66_6E69 {
+			if args.src.available() > 7 {
+				if (args.src.peek_u64le() | 0x2020_2020_2020_2020) == 0x7974_696E_6966_6E69 {
+					args.dst.write_fast_token!(
+						value_major: 0,
+						value_minor: 0xA0_0020,
+						link: 0x0,
+						length: 8)
+					args.src.skip32_fast!(actual: 8, worst_case: 8)
+					return ok
+				}
+			} else if not args.src.is_closed() {
+				yield? base."$short read"
+				continue
+			}
+			args.dst.write_fast_token!(
+				value_major: 0,
+				value_minor: 0xA0_0020,
+				link: 0x0,
+				length: 3)
+			args.src.skip32_fast!(actual: 3, worst_case: 3)
+			return ok
+
+		} else if (c4 | 0x20_2020) == 0x6E_616E {
+			args.dst.write_fast_token!(
+				value_major: 0,
+				value_minor: 0xA0_0080,
+				link: 0x0,
+				length: 3)
+			args.src.skip32_fast!(actual: 3, worst_case: 3)
+			return ok
+		} else if (c4 & 0xFF) == 0x2B {  // 0x2B is '+'.
+			neg = 0
+		} else if (c4 & 0xFF) == 0x2D {  // 0x2D is '-'.
+			neg = 1
+		} else {
+			return "#bad input"
+		}
+
+		if args.src.available() <= 3 {
+			if args.src.is_closed() {
+				return "#bad input"
+			}
+			yield? base."$short read"
+			continue
+		}
+
+		c4 = args.src.peek_u32le() >> 8
+		if (c4 | 0x20_2020) == 0x66_6E69 {
+			if args.src.available() > 8 {
+				if (args.src.peek_u64le_at(offset: 1) | 0x2020_2020_2020_2020) == 0x7974_696E_6966_6E69 {
+					args.dst.write_fast_token!(
+						value_major: 0,
+						value_minor: 0xA0_0000 | ((0x20 as base.u32) >> neg),
+						link: 0x0,
+						length: 9)
+					args.src.skip32_fast!(actual: 9, worst_case: 9)
+					return ok
+				}
+			} else if not args.src.is_closed() {
+				yield? base."$short read"
+				continue
+			}
+			args.dst.write_fast_token!(
+				value_major: 0,
+				value_minor: 0xA0_0000 | ((0x20 as base.u32) >> neg),
+				link: 0x0,
+				length: 4)
+			args.src.skip32_fast!(actual: 4, worst_case: 4)
+			return ok
+
+		} else if (c4 | 0x20_2020) == 0x6E_616E {
+			args.dst.write_fast_token!(
+				value_major: 0,
+				value_minor: 0xA0_0000 | ((0x80 as base.u32) >> neg),
+				link: 0x0,
+				length: 4)
+			args.src.skip32_fast!(actual: 4, worst_case: 4)
+			return ok
+		}
+
+		return "#bad input"
+	}
+}
+
 pri func decoder.decode_trailing_new_line?(dst: base.token_writer, src: base.io_reader) {
 	var c                 : base.u8
 	var whitespace_length : base.u32[..= 0xFFFE]
diff --git a/test/c/std/json.c b/test/c/std/json.c
index f1bfc1a..3cce8fd 100644
--- a/test/c/std/json.c
+++ b/test/c/std/json.c
@@ -1398,6 +1398,82 @@
 }
 
 const char*  //
+test_wuffs_json_decode_quirk_allow_inf_nan_numbers() {
+  CHECK_FOCUS(__func__);
+
+  struct {
+    // want has 2 bytes, one for each possible q:
+    //  - q&1 sets WUFFS_JSON__QUIRK_ALLOW_INF_NAN_NUMBERS.
+    // An 'X', '+' or '-' means that decoding should succeed (and consume the
+    // entire input), succeed (without consuming the entire input) or fail.
+    const char* want;
+    const char* str;
+  } test_cases[] = {
+      {.want = "-X", .str = "InFiniTy"},
+      {.want = "-X", .str = "[+inf, -infinity, +nan,-NaN,NAN]"},
+      {.want = "-X", .str = "inf"},
+      {.want = "-+", .str = "infinit"},
+      {.want = "-+", .str = "infiQity"},
+      {.want = "-+", .str = "nana"},
+      {.want = "--", .str = "+-inf"},
+      {.want = "--", .str = "-+inf"},
+      {.want = "--", .str = "[infinit,"},
+      {.want = "--", .str = "[infiQity,"},
+      {.want = "--", .str = "[nana,"},
+      {.want = "--", .str = "∞"},
+  };
+
+  int tc;
+  for (tc = 0; tc < WUFFS_TESTLIB_ARRAY_SIZE(test_cases); tc++) {
+    int q;
+    for (q = 0; q < 2; q++) {
+      wuffs_json__decoder dec;
+      CHECK_STATUS("initialize", wuffs_json__decoder__initialize(
+                                     &dec, sizeof dec, WUFFS_VERSION,
+                                     WUFFS_INITIALIZE__DEFAULT_OPTIONS));
+      wuffs_json__decoder__set_quirk_enabled(
+          &dec, WUFFS_JSON__QUIRK_ALLOW_INF_NAN_NUMBERS, q & 1);
+
+      wuffs_base__token_buffer tok =
+          wuffs_base__make_token_buffer_writer(global_have_token_slice);
+      wuffs_base__io_buffer src = wuffs_base__make_io_buffer_reader(
+          wuffs_base__make_slice_u8((void*)(test_cases[tc].str),
+                                    strlen(test_cases[tc].str)),
+          true);
+      const char* have =
+          wuffs_json__decoder__decode_tokens(&dec, &tok, &src).repr;
+      const char* want =
+          (test_cases[tc].want[q] != '-') ? NULL : wuffs_json__error__bad_input;
+      if (have != want) {
+        RETURN_FAIL("tc=%d, q=%d: decode_tokens: have \"%s\", want \"%s\"", tc,
+                    q, have, want);
+      }
+
+      size_t total_length = 0;
+      while (tok.meta.ri < tok.meta.wi) {
+        total_length += wuffs_base__token__length(&tok.data.ptr[tok.meta.ri++]);
+      }
+      if (total_length != src.meta.ri) {
+        RETURN_FAIL("tc=%d, q=%d: total_length: have %zu, want %zu", tc, q,
+                    total_length, src.meta.ri);
+      }
+      if (test_cases[tc].want[q] == 'X') {
+        if (total_length != src.data.len) {
+          RETURN_FAIL("tc=%d, q=%d: total_length: have %zu, want %zu", tc, q,
+                      total_length, src.data.len);
+        }
+      } else if (test_cases[tc].want[q] == '+') {
+        if (total_length >= src.data.len) {
+          RETURN_FAIL("tc=%d, q=%d: total_length: have %zu, want < %zu", tc, q,
+                      total_length, src.data.len);
+        }
+      }
+    }
+  }
+  return NULL;
+}
+
+const char*  //
 test_wuffs_json_decode_quirk_allow_comment_etc() {
   CHECK_FOCUS(__func__);
 
@@ -2115,6 +2191,7 @@
     test_wuffs_json_decode_quirk_allow_backslash_x,
     test_wuffs_json_decode_quirk_allow_comment_etc,
     test_wuffs_json_decode_quirk_allow_final_comma,
+    test_wuffs_json_decode_quirk_allow_inf_nan_numbers,
     test_wuffs_json_decode_quirk_allow_leading_etc,
     test_wuffs_json_decode_quirk_allow_trailing_etc,
     test_wuffs_json_decode_quirk_replace_invalid_utf_8,