Parser.java

package org.klojang.templates;

import org.klojang.templates.x.ClassPathResolver;
import org.klojang.templates.x.FilePathResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;

import static org.klojang.templates.InlineTemplateParser.CommentType;
import static org.klojang.templates.ParseUtils.removeEmptyParts;
import static org.klojang.templates.ParseUtils.trimBoilerplate;
import static org.klojang.templates.Regex.*;
import static org.klojang.templates.Template.ROOT_TEMPLATE_NAME;

final class Parser {

  private static final Logger LOG = LoggerFactory.getLogger(Parser.class);

  private interface PartialParser {

    List<Part> parse(UnparsedPart unparsed, Set<String> names) throws ParseException;

  }

  private final String name; // template name
  private final TemplateLocation location;
  private final String src;

  Parser(TemplateLocation location, String name) throws ParseException {
    this(location, name, location.read());
  }

  Parser(TemplateLocation location, String name, String src) {
    this.name = name;
    this.location = location;
    this.src = src;
  }

  Template parse() throws ParseException {
    return new Template(name, location, List.copyOf(getParts()));
  }

  List<Part> getParts() throws ParseException {
    log(name, location);
    // Accumulates template names for duplicate checks:
    Set<String> names = new HashSet<>();
    List<Part> parts = purgeDitchBlocks();
    InlineTemplateParser p1 = new InlineTemplateParser(src, location);
    parts = parse(parts, names, (x, y) -> p1.parse(x, y, CommentType.TAGS));
    parts = parse(parts, names, (x, y) -> p1.parse(x, y, CommentType.BLOCK));
    parts = parse(parts, names, (x, y) -> p1.parse(x, y, CommentType.NONE));
    IncludedTemplateParser p2 = new IncludedTemplateParser(src, location);
    parts = parse(parts, names, (x, y) -> p2.parse(x, y, CMT_INCLUDED_TEMPLATE));
    parts = parse(parts, names, (x, y) -> p2.parse(x, y, INCLUDED_TEMPLATE));
    VarParser p3 = new VarParser(src);
    parts = parse(parts, names, (x, y) -> p3.parse(x, y, CMT_VARIABLE));
    parts = parse(parts, names, (x, y) -> p3.parse(x, y, VARIABLE));
    BoilerplateCollector bc = new BoilerplateCollector(src);
    parts = bc.collectBoilerplate(parts);
    trimBoilerplate(parts);
    parts = removeEmptyParts(parts);
    return parts;
  }

  private List<Part> purgeDitchBlocks() {
    Matcher m = Regex.DITCH_BLOCK.matcher(src);
    if (!m.find()) {
      return Collections.singletonList(new UnparsedPart(src, 0));
    }
    List<Part> parts = new ArrayList<>();
    int end = 0;
    do {
      int start = m.start();
      if (start > end) {
        parts.add(new UnparsedPart(src.substring(end, start), end));
      }
      end = m.end();
    } while (m.find());
    if (end < src.length()) {
      parts.add(new UnparsedPart(src.substring(end), end));
    }
    return parts;
  }

  private static List<Part> parse(List<Part> in,
        Set<String> names,
        PartialParser parser)
        throws ParseException {
    List<Part> out = new ArrayList<>(in.size() + 10);
    for (Part p : in) {
      if (p instanceof UnparsedPart unparsed) {
        out.addAll(parser.parse(unparsed, names));
      } else {
        out.add(p);
      }
    }
    return out;
  }

  private static void log(String name, TemplateLocation location) {
    if (LOG.isTraceEnabled()) {
      if (name.equals(ROOT_TEMPLATE_NAME)) {
        LOG.trace("Parsing root template");
      } else if (location.isString()) {
        LOG.trace("Parsing inline template \"{}\"", name);
      } else {
        LOG.trace("Parsing included template \"{}\"", name);
      }
    }
  }

}