docs: def _symbol_or_keyword_handler(c, ctx, is_field_name=False):
      """Handles the start of an unquoted text token.
  
      This may be an operator (if in an s-expression), an identifier symbol, or a keyword.
      """
      in_sexp = ctx.container.ion_type is IonType.SEXP
      if c not in _IDENTIFIER_STARTS:
          if in_sexp and c in _OPERATORS:
              c_next, _ = yield
              ctx.queue.unread(c_next)
              yield ctx.immediate_transition(_operator_symbol_handler(c, ctx))
          _illegal_character(c, ctx)
      assert not ctx.value
      ctx.set_unicode().set_ion_type(IonType.SYMBOL)
      val = ctx.value
      val.append(c)
      maybe_null = c == _N_LOWER
      maybe_nan = maybe_null
      maybe_true = c == _T_LOWER
      maybe_false = c == _F_LOWER
      c, self = yield
      trans = ctx.immediate_transition(self)
      keyword_trans = None
      match_index = 0
      while True:
          def check_keyword(name, keyword_sequence, ion_type, value, match_transition=lambda: None):
              maybe_keyword = True
              transition = None
              if match_index < len(keyword_sequence):
                  maybe_keyword = c == keyword_sequence[match_index]
              else:
                  transition = match_transition()
                  if transition is not None:
                      pass
                  elif _ends_value(c):
                      if is_field_name:
                          _illegal_character(c, ctx, '%s keyword as field name not allowed.' % (name,))
                      transition = ctx.event_transition(IonEvent, IonEventType.SCALAR, ion_type, value)
                  elif c == _COLON:
                      message = ''
                      if is_field_name:
                          message = '%s keyword as field name not allowed.' % (name,)
                      _illegal_character(c, ctx, message)
                  elif in_sexp and c in _OPERATORS:
                      transition = ctx.event_transition(IonEvent, IonEventType.SCALAR, ion_type, value)
                  else:
                      maybe_keyword = False
              return maybe_keyword, transition
          if maybe_null:
              def check_null_dot():
                  transition = None
                  found = c == _DOT
                  if found:
                      if is_field_name:
                          _illegal_character(c, ctx, "Illegal character in field name.")
                      transition = ctx.immediate_transition(_typed_null_handler(c, ctx))
                  return transition
              maybe_null, keyword_trans = check_keyword('null', _NULL_SUFFIX.sequence,
                                                        IonType.NULL, None, check_null_dot)
          if maybe_nan:
              maybe_nan, keyword_trans = check_keyword('nan', _NAN_SUFFIX, IonType.FLOAT, _NAN)
          elif maybe_true:
              maybe_true, keyword_trans = check_keyword('true', _TRUE_SUFFIX, IonType.BOOL, True)
          elif maybe_false:
              maybe_false, keyword_trans = check_keyword('false', _FALSE_SUFFIX, IonType.BOOL, False)
          if maybe_null or maybe_nan or maybe_true or maybe_false:
              if keyword_trans is not None:
                  trans = keyword_trans
              else:
                  val.append(c)
                  match_index += 1
          else:
              if c in _SYMBOL_TOKEN_TERMINATORS:
                  # This might be an annotation or a field name
                  ctx.set_pending_symbol(val)
                  trans = ctx.immediate_transition(ctx.whence)
              elif _ends_value(c) or (in_sexp and c in _OPERATORS):
                  trans = ctx.event_transition(IonEvent, IonEventType.SCALAR, IonType.SYMBOL, val.as_symbol())
              else:
                  trans = ctx.immediate_transition(_unquoted_symbol_handler(c, ctx, is_field_name=is_field_name))
          c, _ = yield trans