docs: def parse(
          self,
          assertion: Union[str, Mapping[str, str]],
          strict: bool = False,
          parseinfo: bool = False,
          rule_name: str = "start",
          error_level: str = "WARNING",
      ) -> "BEL":
          """Parse and semantically validate BEL statement
  
          Parses a BEL statement given as a string and returns an AST, Abstract Syntax Tree (defined in ast.py)
          if the statement is valid, self.parse_valid. Else, the AST attribute is None and there will be validation error messages
          in self.validation_messages.  self.validation_messages will contain WARNINGS if
          warranted even if the statement parses correctly.
  
          Error Levels are similar to log levels - selecting WARNING includes both
          WARNING and ERROR, selecting ERROR just includes ERROR
  
          Args:
              assertion: BEL statement (if str -> 'S R O', if dict {'subject': S, 'relation': R, 'object': O})
              strict: specify to use strict or loose parsing; defaults to loose
              parseinfo: specify whether or not to include Tatsu parse information in AST
              rule_name: starting point in parser - defaults to 'start'
              error_level: return ERRORs only or also WARNINGs
  
          Returns:
              ParseObject: The ParseObject which contain either an AST or error messages.
          """
  
          self.ast = None
          self.parse_valid = False
          self.parse_visualize_error = ""
          self.validation_messages = []  # Reset messages when parsing a new BEL Statement
  
          if isinstance(assertion, dict):
              if assertion.get("relation", False) and assertion.get("object", False):
                  statement = f"{assertion['subject']} {assertion['relation']} {assertion['object']}"
              elif assertion.get("subject"):
                  statement = f"{assertion['subject']}"
              else:
                  statement = ""
          else:
              statement = assertion
  
          self.original_bel_stmt = statement
  
          # pre-process to remove extra white space, add space after commas, etc.
          self.bel_stmt = bel_utils.preprocess_bel_stmt(statement)
  
          # TODO - double check these tests before enabling
          # is_valid, messages = bel_utils.simple_checks(self.bel_stmt)
          # if not is_valid:
          #     self.validation_messages.extend(messages)
          #     return self
  
          # Check to see if empty string for bel statement
          if len(self.bel_stmt) == 0:
              self.validation_messages.append(
                  ("ERROR", "Please include a valid BEL statement - found empty string.")
              )
              return self
  
          try:
              # see if an AST is returned without any parsing errors
  
              ast_dict = self.parser.parse(
                  self.bel_stmt, rule_name=rule_name, trace=False, parseinfo=parseinfo
              )
              self.ast = lang_ast.ast_dict_to_objects(ast_dict, self)
  
              self.parse_valid = True
  
          except FailedParse as e:
              # if an error is returned, send to handle_syntax, error
              error, visualize_error = bel_utils.handle_parser_syntax_error(e)
              self.parse_visualize_error = visualize_error
              if visualize_error:
                  self.validation_messages.append(
                      ("ERROR", f"{error}\n{visualize_error}")
                  )
              else:
                  self.validation_messages.append(
                      ("ERROR", f"{error}\nBEL: {self.bel_stmt}")
                  )
              self.ast = None
  
          except Exception as e:
              log.error("Error {}, error type: {}".format(e, type(e)))
              self.validation_messages.append(
                  ("ERROR", "Error {}, error type: {}".format(e, type(e)))
              )
  
          return self