docs: def managed_reader(reader, catalog=None):
      """Managed reader wrapping another reader.
  
      Args:
          reader (Coroutine): The underlying non-blocking reader co-routine.
          catalog (Optional[SymbolTableCatalog]): The catalog to use for resolving imports.
  
      Yields:
          Events from the underlying reader delegating to symbol table processing as needed.
          The user will never see things like version markers or local symbol tables.
      """
      if catalog is None:
          catalog = SymbolTableCatalog()
  
      ctx = _ManagedContext(catalog)
      symbol_trans = Transition(None, None)
      ion_event = None
      while True:
          if symbol_trans.delegate is not None \
                  and ion_event is not None \
                  and not ion_event.event_type.is_stream_signal:
              # We have a symbol processor active, do not yield to user.
              delegate = symbol_trans.delegate
              symbol_trans = delegate.send(Transition(ion_event, delegate))
              if symbol_trans.delegate is None:
                  # When the symbol processor terminates, the event is the context
                  # and there is no delegate.
                  ctx = symbol_trans.event
                  data_event = NEXT_EVENT
              else:
                  data_event = symbol_trans.event
          else:
              data_event = None
  
              if ion_event is not None:
                  event_type = ion_event.event_type
                  ion_type = ion_event.ion_type
                  depth = ion_event.depth
  
                  # System values only happen at the top-level
                  if depth == 0:
                      if event_type is IonEventType.VERSION_MARKER:
                          if ion_event != ION_VERSION_MARKER_EVENT:
                              raise IonException('Invalid IVM: %s' % (ion_event,))
  
                          # Reset and swallow IVM
                          ctx = _ManagedContext(ctx.catalog)
                          data_event = NEXT_EVENT
  
                      elif ion_type is IonType.SYMBOL \
                              and len(ion_event.annotations) == 0 \
                              and ion_event.value is not None \
                              and ctx.resolve(ion_event.value).text == TEXT_ION_1_0:
                          assert symbol_trans.delegate is None
  
                          # A faux IVM is a NOP
                          data_event = NEXT_EVENT
  
                      elif event_type is IonEventType.CONTAINER_START \
                              and ion_type is IonType.STRUCT \
                              and ctx.has_symbol_table_annotation(ion_event.annotations):
                          assert symbol_trans.delegate is None
  
                          # Activate a new symbol processor.
                          delegate = _local_symbol_table_handler(ctx)
                          symbol_trans = Transition(None, delegate)
                          data_event = NEXT_EVENT
  
              if data_event is None:
                  # No system processing or we have to get data, yield control.
                  if ion_event is not None:
                      ion_event = _managed_thunk_event(ctx, ion_event)
                  data_event = yield ion_event
  
          ion_event = reader.send(data_event)