All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.aya.producer.ModifierParser Maven / Gradle / Ivy

The newest version!
// Copyright (c) 2020-2024 Tesla (Yinsen) Zhang.
// Use of this source code is governed by the MIT license that can be found in the LICENSE.md file.
package org.aya.producer;

import com.intellij.psi.tree.IElementType;
import kala.collection.Seq;
import kala.collection.immutable.ImmutableMap;
import kala.collection.immutable.ImmutableSeq;
import org.aya.generic.Modifier;
import org.aya.producer.error.ModifierProblem;
import org.aya.syntax.concrete.stmt.Stmt;
import org.aya.util.error.SourcePos;
import org.aya.util.error.WithPos;
import org.aya.util.reporter.Reporter;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.EnumMap;
import java.util.EnumSet;
import java.util.function.Predicate;

import static org.aya.parser.AyaPsiElementTypes.*;
import static org.aya.producer.ModifierParser.ModifierGroup.*;

/**
 * Generalized modifier parser. There are two assumptions in this parser:
 * 1. Availability. Whether a modifier is usable (can occur) in a declaration.
 * 2. Presence. Whether a modifier is present (is specified) in a declaration.
 *
 * @author Hoshino Tened
 */
public record ModifierParser(@NotNull Reporter reporter) {
  public enum ModifierGroup {
    None,
    Accessibility,
    Alpha
  }

  public enum CModifier {
    // Common Modifiers
    Private(KW_PRIVATE, Accessibility, "private"),

    // ModuleLike Modifiers
    Open(KW_OPEN, None, "open"),
    Example(KW_EXAMPLE, None, "example"),

    // Function Modifiers
    Opaque(KW_OPAQUE, Alpha, "opaque"),
    Inline(KW_INLINE, Alpha, "inline"),
    Partial(KW_PARTIAL, None, "partial", Opaque),
    Overlap(KW_OVERLAP, None, "overlap");

    public final @NotNull IElementType type;
    public final @NotNull ModifierGroup group;
    public final @NotNull String keyword;

    /**
     * {@code implies} will/should expand only once
     */
    public final @NotNull CModifier[] implies;

    CModifier(
      @NotNull IElementType type, @NotNull ModifierGroup group,
      @NotNull String keyword, @NotNull CModifier @NotNull ... implies
    ) {
      this.type = type;
      this.group = group;
      this.keyword = keyword;
      this.implies = implies;
    }
  }

  /***
   * For different declarations we have different modifiers that are available.
   * @param defaultMods If a modifier is available, but not present in the declaration, we use the default value from here.
   * @param available Checks if a modifier is available.
   */
  public record Filter(
    @NotNull Modifiers defaultMods,
    @NotNull Predicate available
  ) {
    public @NotNull Filter and(@NotNull Predicate and) {
      return new Filter(defaultMods, available.and(and));
    }

    /**
     * @param defaultAcc Default {@link Stmt.Accessibility}
     * @param miscAvail  Available miscellaneous modifiers, see {@link DefaultModifiers#miscAvail}
     */
    public static @NotNull Filter create(
      @NotNull WithPos defaultAcc,
      @NotNull EnumSet miscAvail
    ) {
      return new Filter(new DefaultModifiers(defaultAcc, miscAvail), mod -> switch (mod) {
        case Private -> true;
        case Open, Opaque, Inline, Overlap, Example, Partial -> miscAvail.contains(mod);
      });
    }
  }

  /**
   * @param miscAvail Miscellaneous modifiers (open, inline, opaque, overlap) availability map.
   *                  If a modifier is in the map (as the key), it is available.
   */
  record DefaultModifiers(
    @NotNull WithPos accessibility,
    @NotNull EnumSet miscAvail
  ) implements Modifiers {
    @Override public boolean isExample() { return false; }
    @Override public @Nullable SourcePos misc(@NotNull ModifierParser.CModifier key) {
      // Do not throw anything here, even the modifier is not available.
      return null; // always not present, because miscAvail only says availability, not presence.
    }
  }

  /** Only "open" is available to (data/struct) decls */
  public static final Filter DECL_FILTER = Filter.create(
    new WithPos<>(SourcePos.NONE, Stmt.Accessibility.Public),
    EnumSet.of(CModifier.Open, CModifier.Example)
  );

  /** "opaque", "inline" and "overlap" is available to functions. */
  public static final Filter FN_FILTER = Filter.create(
    new WithPos<>(SourcePos.NONE, Stmt.Accessibility.Public),
    EnumSet.of(CModifier.Opaque, CModifier.Inline, CModifier.Overlap, CModifier.Partial, CModifier.Example));

  /** nothing is available to sub-level decls (ctor/field). */
  public static final Filter SUBDECL_FILTER = Filter.create(
    new WithPos<>(SourcePos.NONE, Stmt.Accessibility.Public),
    EnumSet.noneOf(CModifier.class)
  ).and(_ -> false);

  /** All parsed modifiers */
  public interface Modifiers {
    @Contract(pure = true) @NotNull WithPos accessibility();
    @Contract(pure = true) boolean isExample();
    /**
     * Miscellaneous modifiers are function modifiers ({@link Modifier}) plus "open".
     *
     * @return non-null source position if the modifier is present.
     */
    @Contract(pure = true) @Nullable SourcePos misc(@NotNull ModifierParser.CModifier key);
    default @NotNull EnumSet toFnModifiers() {
      var fnMods = EnumSet.noneOf(Modifier.class);
      if (misc(CModifier.Inline) != null) fnMods.add(Modifier.Inline);
      if (misc(CModifier.Opaque) != null) fnMods.add(Modifier.Opaque);
      if (misc(CModifier.Overlap) != null) fnMods.add(Modifier.Overlap);
      if (misc(CModifier.Partial) != null) fnMods.add(Modifier.Partial);
      return fnMods;
    }
  }

  private record ModifierSet(
    @NotNull ImmutableMap mods,
    @NotNull Modifiers parent
  ) implements Modifiers {
    @Override @Contract(pure = true) public @NotNull WithPos accessibility() {
      return mods.getOption(CModifier.Private)
        .map(pos -> new WithPos<>(pos, Stmt.Accessibility.Private))
        .getOrElse(parent::accessibility);
    }
    @Override public boolean isExample() {
      return mods.containsKey(CModifier.Example) || parent.isExample();
    }

    @Override public @Nullable SourcePos misc(@NotNull ModifierParser.CModifier key) {
      return mods.getOrElse(key, () -> parent.misc(key));
    }
  }

  private @NotNull ImmutableSeq> implication(@NotNull ImmutableSeq> modifiers) {
    return modifiers.view()
      .flatMap(modi -> Seq.from(modi.data().implies).map(imply -> new WithPos<>(modi.sourcePos(), imply)))
      .collect(ImmutableMap.collector(WithPos::data, x -> x))
      // ^ distinctBy(WithPos::data)
      .valuesView()
      .toImmutableSeq();
  }

  /**
   * @param filter The filter also performs on the modifiers that expanded from input.
   */
  public @NotNull Modifiers parse(@NotNull ImmutableSeq> modifiers, @NotNull Filter filter) {
    EnumMap> map = new EnumMap<>(ModifierGroup.class);

    modifiers = implication(modifiers).concat(modifiers);

    // parsing
    for (var data : modifiers) {
      var pos = data.sourcePos();
      var modifier = data.data();

      // do filter
      if (!filter.available.test(data.data())) {
        reportUnsuitableModifier(data);
        continue;
      }

      map.computeIfAbsent(modifier.group, _ -> new EnumMap<>(CModifier.class));
      var exists = map.get(modifier.group);

      if (exists.containsKey(modifier)) {
        reportDuplicatedModifier(data);
        continue;
      }

      if (modifier.group != None
        && !exists.isEmpty()
        // In fact, this boolean expression is always true
        && !exists.containsKey(modifier)) {
        // one (not None) group one modifier
        assert exists.size() == 1;
        var contradict = Seq.from(exists.entrySet()).getFirst();
        reportContradictModifier(data, new WithPos<>(contradict.getValue(), contradict.getKey()));
        continue;
      }

      // no contradict modifier, no redundant modifier, everything is good
      exists.put(modifier, pos);
    }

    return new ModifierSet(ImmutableMap.from(
      ImmutableSeq.from(map.values()).flatMap(EnumMap::entrySet)
    ), filter.defaultMods);
  }

  public void reportUnsuitableModifier(@NotNull WithPos data) {
    reporter.report(new ModifierProblem(data.sourcePos(), data.data(), ModifierProblem.Reason.Inappropriate));
  }

  public void reportDuplicatedModifier(@NotNull WithPos data) {
    reporter.report(new ModifierProblem(data.sourcePos(), data.data(), ModifierProblem.Reason.Duplicative));
  }

  public void reportContradictModifier(@NotNull WithPos current, @NotNull WithPos that) {
    reporter.report(new ModifierProblem(current.sourcePos(), current.data(), ModifierProblem.Reason.Contradictory));
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy