code: def parse_substitution_(self, reversal):
      assert self.is_cur_keyword_("AS_SUBSTITUTION")
      location = self.cur_token_location_
      src = []
      dest = []
      if self.next_token_ != "SUB":
          raise VoltLibError("Expected SUB", location)
      while self.next_token_ == "SUB":
          self.expect_keyword_("SUB")
          src.append(self.parse_coverage_())
          self.expect_keyword_("WITH")
          dest.append(self.parse_coverage_())
          self.expect_keyword_("END_SUB")
      self.expect_keyword_("END_SUBSTITUTION")
      max_src = max([len(cov) for cov in src])
      max_dest = max([len(cov) for cov in dest])
      # many to many or mixed is invalid
      if (max_src > 1 and max_dest > 1) or (
          reversal and (max_src > 1 or max_dest > 1)
      ):
          raise VoltLibError("Invalid substitution type", location)
      mapping = dict(zip(tuple(src), tuple(dest)))
      if max_src == 1 and max_dest == 1:
          if reversal:
              sub = ast.SubstitutionReverseChainingSingleDefinition(
                  mapping, location=location
              )
          else:
              sub = ast.SubstitutionSingleDefinition(
                  mapping, location=location
              )
      elif max_src == 1 and max_dest > 1:
          sub = ast.SubstitutionMultipleDefinition(
              mapping, location=location
          )
      elif max_src > 1 and max_dest == 1:
          sub = ast.SubstitutionLigatureDefinition(
              mapping, location=location
          )
      return sub
  
imports: import fontTools.voltLib.ast
  from fontTools.voltLib.lexer import Lexer
  from fontTools.voltLib.error import VoltLibError
  from io import open
label_desc: Use image node_id 11 for calling the Parser obj's underlying member method code with example usage: obj.parse_substitution_(reversal) and returns: sub