docs: public static CcgBinaryRule parseFrom(String line) {
      String[] chunks = new CsvParser(',', CsvParser.DEFAULT_QUOTE,
          CsvParser.NULL_ESCAPE).parseLine(line.trim());
      Preconditions.checkArgument(chunks.length >= 1);
  
      String[] syntacticParts = chunks[0].split(" ");
      Preconditions.checkArgument(syntacticParts.length == 3);
      HeadedSyntacticCategory leftSyntax = HeadedSyntacticCategory.parseFrom(syntacticParts[0]);
      HeadedSyntacticCategory rightSyntax = HeadedSyntacticCategory.parseFrom(syntacticParts[1]);
      HeadedSyntacticCategory returnSyntax = HeadedSyntacticCategory.parseFrom(syntacticParts[2]);
  
      Expression2 logicalForm = null;
      if (chunks.length >= 2 && chunks[1].trim().length() > 0) {
        logicalForm = ExpressionParser.expression2().parse(chunks[1]);
      }
  
      // Parse the type of combinator, if one is given.
      Combinator.Type type = Combinator.Type.OTHER;
      if (chunks.length >= 3) {
        type = Combinator.Type.valueOf(chunks[2]);
      }
  
      // Parse any dependencies, if given.
      List<String> subjects = Lists.newArrayList();
      List<HeadedSyntacticCategory> subjectSyntacticCategories = Lists.newArrayList();
      List<Integer> argNums = Lists.newArrayList();
      List<Integer> objects = Lists.newArrayList();
      if (chunks.length >= 4) {  
        for (int i = 3; i < chunks.length; i++) {
          String[] newDeps = chunks[i].split(" ");
          Preconditions.checkArgument(newDeps.length == 3);
          subjects.add(newDeps[0]);
          subjectSyntacticCategories.add(rightSyntax.getCanonicalForm());
          argNums.add(Integer.parseInt(newDeps[1]));
          objects.add(Integer.parseInt(newDeps[2]));
        }
      }
  
      return new CcgBinaryRule(leftSyntax, rightSyntax, returnSyntax, logicalForm,
          subjects, subjectSyntacticCategories, argNums, objects, type);
    }