docs: def check_function_args(args, signatures, function_name):
      """Check function args - return message if function args don't match function signature
  
      Called from validate_functions
  
      We have following types of arguments to validate:
          1. Required, position_dependent arguments, e.g. p(HGNC:AKT1), NSArg HGNC:AKT1 is required and must be first argument
          2. Optional, position_dependent arguments, e.g. pmod(P, T, 308) - T and 308 are optional and position_dependent
          3. Optional, e.g. loc() modifier can only be found once, but anywhere after the position_dependent arguments
          4. Multiple, e.g. var(), can have more than one var() modifier in p() function
  
      Args:
          args (Union['Function', 'NSArg', 'StrArg']): AST Function arguments
          signatures (Mapping[str, Any]): function signatures from spec_dict, may be more than one per function
          function_name (str): passed in to improve error messaging
  
      Returns:
          Tuple[bool, List[str]]: (function_valid?, list of error messages per signature)
      """
  
      messages = []
  
      arg_types = []
      for arg in args:
          arg_type = arg.__class__.__name__
          if arg_type == "Function":
              arg_types.append((arg.name, ""))
          elif arg_type == "NSArg":
              arg_types.append((arg_type, f"{arg.namespace}:{arg.value}"))
          elif arg_type == "StrArg":
              arg_types.append((arg_type, arg.value))
      log.debug(f"Arg_types {arg_types}")
  
      matched_signature_idx = -1
      valid_function = False
      for sig_argset_idx, sig_argset in enumerate(signatures):
          sig_req_args = sig_argset["req_args"]  # required position_dependent arguments
          sig_pos_args = sig_argset["pos_args"]  # optional position_dependent arguments
          sig_opt_args = sig_argset["opt_args"]  # optional arguments
          sig_mult_args = sig_argset["mult_args"]  # multiple arguments
  
          log.debug(f"{sig_argset_idx} Req: {sig_req_args}")
          log.debug(f"{sig_argset_idx} Pos: {sig_pos_args}")
          log.debug(f"{sig_argset_idx} Opt: {sig_opt_args}")
          log.debug(f"{sig_argset_idx} Mult: {sig_mult_args}")
  
          # Check required arguments
          reqs_mismatch_flag = False
          for sig_idx, sig_req in enumerate(sig_req_args):
              if len(arg_types) > sig_idx:
                  log.debug(
                      "Req args: arg_type {} vs sig_req {}".format(
                          arg_types[sig_idx][0], sig_req
                      )
                  )
                  if arg_types[sig_idx][0] not in sig_req:
                      reqs_mismatch_flag = True
                      msg = f"Missing required arguments for {function_name} signature: {sig_argset_idx}"
                      messages.append(msg)
                      log.debug(msg)
                      break
  
          if reqs_mismatch_flag:
              continue  # test next argset
  
          # Check position_dependent optional arguments
          pos_dep_arg_types = arg_types[len(sig_req_args) :]
          log.debug(f"Optional arg types {pos_dep_arg_types}")
          log.debug(f"{sig_argset_idx} Pos: {sig_pos_args}")
          pos_mismatch_flag = False
          for sig_pos_idx, sig_pos in enumerate(sig_pos_args):
              if sig_pos_idx == len(pos_dep_arg_types):
                  break  # stop checking position dependent arguments when we run out of them vs signature optional position dependent arguments
              if pos_dep_arg_types[sig_pos_idx][0] not in sig_pos:
                  pos_mismatch_flag = True
                  msg = f"Missing position_dependent arguments for {function_name} signature: {sig_argset_idx}"
                  messages.append(msg)
                  log.debug(msg)
                  break
          if pos_mismatch_flag:
              continue  # test next argset
  
          reqpos_arglen = len(sig_req_args) + len(sig_pos_args)
          optional_arg_types = arg_types[reqpos_arglen:]
  
          # Remove function args that are found in the mult_args signature
          optional_types = [
              (opt_type, opt_val)
              for opt_type, opt_val in optional_arg_types
              if opt_type not in sig_mult_args
          ]
          log.debug(f"Optional types after sig mult args removed {optional_types}")
  
          # Check if any remaining function args are duplicated and therefore not unique opt_args
          if len(optional_types) != len(set(optional_types)):
              msg = f"Duplicate optional arguments {optional_types} for {function_name} signature: {sig_argset_idx}"
              messages.append(msg)
              log.debug(msg)
              continue
  
          optional_types = [
              (opt_type, opt_val)
              for opt_type, opt_val in optional_types
              if opt_type not in sig_opt_args
          ]
          if len(optional_types) > 0:
              msg = f"Invalid arguments {optional_types} for {function_name} signature: {sig_argset_idx}"
              messages.append(msg)
              log.debug(msg)
              continue
  
          matched_signature_idx = sig_argset_idx
          messages = []  # reset messages if signature is matched
          valid_function = True
          break
  
      # Add NSArg and StrArg value types (e.g. Protein, Complex, ec)
      if matched_signature_idx > -1:
          # Shouldn't have single optional NSArg arguments - not currently checking for that
          log.debug(
              f'AST1, Sigs {signatures[matched_signature_idx]["arguments"]}  Args: {args}'
          )
          for arg_idx, arg in enumerate(args):
              log.debug(f"Arg type {arg.type}")
              for sig_idx, sig_arg in enumerate(
                  signatures[matched_signature_idx]["arguments"]
              ):
                  if arg.type == "Function" or sig_arg["type"] in [
                      "Function",
                      "Modifier",
                  ]:
                      pass  # Skip Function arguments
                  elif sig_arg.get("position", None):
                      if sig_arg["position"] == arg_idx + 1:
                          arg.add_value_types(sig_arg["values"])
                          log.debug(f'AST2  {arg} {sig_arg["values"]}')
                  elif arg.type in ["NSArg", "StrArg", "StrArgNSArg"]:
                      log.debug(f"Arg {arg}")
                      arg.add_value_types(sig_arg["values"])
                      log.debug(f'AST2  {arg} {sig_arg["values"]}')
  
      for arg in args:
          if arg.__class__.__name__ in ["NSArg", "StrArg"]:
              log.debug(f"Arg: {arg.to_string()} Value_types: {arg.value_types}")
  
      return (valid_function, messages)