docs: @Override
    public MtasTokenCollection createTokenCollection(Reader reader)
        throws MtasParserException, MtasConfigException {
      AtomicInteger position = new AtomicInteger(0);
      MtasCRMAncestors unknownAncestors = new MtasCRMAncestors();
  
      Map<String, Set<Integer>> idPositions = new HashMap<>();
      Map<String, Integer[]> idOffsets = new HashMap<>();
  
      Map<String, Map<Integer, Set<String>>> updateList = createUpdateList();
      Map<String, List<MtasParserObject>> currentList = createCurrentList();
  
      tokenCollection = new MtasTokenCollection();
      MtasTokenIdFactory mtasTokenIdFactory = new MtasTokenIdFactory();
      try (MtasBufferedReader br = new MtasBufferedReader(reader)) {
        String line;
        int currentOffset;
        int previousOffset = br.getPosition();
        MtasParserObject currentObject;
        Pattern headerPattern = Pattern.compile("^@ @ @(.*)$");
        Pattern regularPattern = Pattern.compile(
            "^([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ ]+)$");
        Matcher matcherHeader;
        Matcher matcherRegular = null;
        Set<MtasParserObject> newPreviousSentence = new HashSet<>();
        Set<MtasParserObject> previousSentence = new HashSet<>();
        Set<MtasParserObject> newPreviousClause = new HashSet<>();
        Set<MtasParserObject> previousClause = new HashSet<>(); 
        String[] matcherList = new String[8];
        while ((line = br.readLine()) != null) {
          currentOffset = br.getPosition();
          matcherHeader = headerPattern.matcher(line.trim());
          matcherRegular = regularPattern.matcher(line.trim());
          if (matcherRegular.matches()) {
            newPreviousSentence.clear();
            matcherList = createMatcherList(matcherRegular); 
            for (int i = 4; i < 8; i++) {
              List<MtasCRMParserFunctionOutput> functionOutputList = new ArrayList<>();
              Set<MtasParserObject> tmpList = processCRMSentence(
                  mtasTokenIdFactory, String.valueOf(i),
                  matcherList[i], currentOffset,
                  functionOutputList, unknownAncestors, currentList, updateList,
                  idPositions, idOffsets, previousSentence, previousClause);
              if (tmpList != null) {
                newPreviousSentence.addAll(tmpList);
              }
              for (MtasCRMParserFunctionOutput functionOutput : functionOutputList) {
                tmpList = processCRMSentence(mtasTokenIdFactory,
                    functionOutput.name, functionOutput.value, currentOffset,
                    functionOutputList, unknownAncestors, currentList, updateList,
                    idPositions, idOffsets, previousSentence, previousClause);
                if (tmpList != null) {
                  newPreviousSentence.addAll(tmpList);
                }
              }
            }
            if (!newPreviousSentence.isEmpty()) {
              previousSentence.clear();
              previousSentence.addAll(newPreviousSentence);
            }
            newPreviousClause.clear();
            for (int i = 4; i < 8; i++) {
              ArrayList<MtasCRMParserFunctionOutput> functionOutputList = new ArrayList<>();
              Set<MtasParserObject> tmpList = processCRMClause(mtasTokenIdFactory,
                  String.valueOf(i), matcherList[i], currentOffset,
                  functionOutputList, unknownAncestors, currentList, updateList,
                  idPositions, idOffsets, previousClause);
              if (tmpList != null) {
                newPreviousClause.addAll(tmpList);
              }
              for (MtasCRMParserFunctionOutput functionOutput : functionOutputList) {
                tmpList = processCRMClause(mtasTokenIdFactory,
                    functionOutput.name, functionOutput.value, currentOffset,
                    functionOutputList, unknownAncestors, currentList, updateList,
                    idPositions, idOffsets, previousClause);
                if (tmpList != null) {
                  newPreviousClause.addAll(tmpList);
                }
              }
            }
            if (!newPreviousClause.isEmpty()) {
              previousClause.clear();
              previousClause.addAll(newPreviousClause);
            }
          }
  
          if (matcherRegular.matches() && !matcherHeader.matches()) {
            matcherRegular = regularPattern.matcher(line.trim());
            if (matcherRegular.matches()) {
              // regular line - start word
              currentObject = new MtasParserObject(wordType);
              currentObject.setOffsetStart(previousOffset);
              currentObject.setRealOffsetStart(previousOffset);
              currentObject.setUnknownAncestorNumber(unknownAncestors.unknown);
              if (!prevalidateObject(currentObject, currentList)) {
                unknownAncestors.unknown++;
              } else {
                int p = position.getAndIncrement();
                currentObject.addPosition(p);
                currentObject.objectId = "word_" + p;
                currentList.get(MAPPING_TYPE_WORD).add(currentObject);
                unknownAncestors.unknown = 0;
                // check for crmPair
                for (int i = 0; i < 8; i++) {
                  List<MtasCRMParserFunctionOutput> functionOutputList = new ArrayList<>();
                  processCRMPair(mtasTokenIdFactory, p, String.valueOf(i),
                      matcherList[i], currentOffset,
                      functionOutputList, unknownAncestors, currentList,
                      updateList, idPositions, idOffsets);
                  for (MtasCRMParserFunctionOutput functionOutput : functionOutputList) {
                    processCRMPair(mtasTokenIdFactory, p, functionOutput.name,
                        functionOutput.value, currentOffset, functionOutputList,
                        unknownAncestors, currentList, updateList, idPositions,
                        idOffsets);
                  }
                }
                // compute word annotations
                for (int i = 0; i < 8; i++) {
                  ArrayList<MtasCRMParserFunctionOutput> functionOutputList = new ArrayList<>();
                  functionOutputList
                      .addAll(processWordAnnotation(mtasTokenIdFactory,
                          String.valueOf(i), matcherList[i],
                          previousOffset, currentOffset, unknownAncestors,
                          currentList, updateList, idPositions, idOffsets));
                  for (MtasCRMParserFunctionOutput functionOutput : functionOutputList) {
                    processWordAnnotation(mtasTokenIdFactory, functionOutput.name,
                        functionOutput.value, previousOffset, currentOffset,
                        unknownAncestors, currentList, updateList, idPositions,
                        idOffsets);
                  }
                }
              }
              // finish word
              if (unknownAncestors.unknown > 0) {
                unknownAncestors.unknown--;
              } else {
                currentObject = currentList.get(MAPPING_TYPE_WORD)
                    .remove(currentList.get(MAPPING_TYPE_WORD).size() - 1);
                assert unknownAncestors.unknown == 0 : "error in administration "
                    + currentObject.getType().getName();
                currentObject.setText(null);
                currentObject.setOffsetEnd(currentOffset - 1);
                currentObject.setRealOffsetEnd(currentOffset - 1);
                // update ancestor groups with position and offset
                for (MtasParserObject currentGroup : currentList
                    .get(MAPPING_TYPE_GROUP)) {
                  currentGroup.addPositions(currentObject.getPositions());
                  currentGroup.addOffsetStart(currentObject.getOffsetStart());
                  currentGroup.addOffsetEnd(currentObject.getOffsetEnd());
                }
                idPositions.put(currentObject.getId(),
                    currentObject.getPositions());
                idOffsets.put(currentObject.getId(), currentObject.getOffset());
                currentObject.updateMappings(idPositions, idOffsets);
                unknownAncestors.unknown = currentObject
                    .getUnknownAncestorNumber();
                computeMappingsFromObject(mtasTokenIdFactory, currentObject,
                    currentList, updateList);
              }
  
            } else {
              // System.out.println("PROBLEM: " + line);
            }
          }
          previousOffset = br.getPosition();
        }
        closePrevious(mtasTokenIdFactory, previousSentence, previousOffset,
            unknownAncestors, currentList, updateList, idPositions, idOffsets);
        closePrevious(mtasTokenIdFactory, previousClause, previousOffset,
            unknownAncestors, currentList, updateList, idPositions, idOffsets);
      } catch (IOException e) {
        log.debug(e);
        throw new MtasParserException(e.getMessage());
      }
      // final check
      tokenCollection.check(autorepair, makeunique);
      return tokenCollection;
  
    }