Skip to content

Meta-syntax and annotations. #6571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/main/java/ch/njol/skript/effects/EffAnnotate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ch.njol.skript.effects;

import ch.njol.skript.Skript;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.MetaSyntaxElement;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.skriptlang.skript.lang.script.Annotation;

@Name("Annotation (Code)")
@Description({
"A special metadata note visible to the next real line of code.",
"This does nothing by itself, but may change the behaviour of the following line.",
"If the annotation does not exist (or the following line does not use it) then it will have no effect.",
"There is no penalty for using annotations that do not exist."
})
@Examples({
"on join:",
"\t@suppress warnings",
"\t@suppress errors",
"\tbroadcast \"hello there!\""
})
@Since("INSERT VERSION")
public class EffAnnotate extends Effect implements MetaSyntaxElement {

static {
Skript.registerEffect(EffAnnotate.class, "@<.+>");
}

private @UnknownNullability Annotation annotation;

@Override
public boolean init(Expression<?>[] expressions, int pattern, Kleenean delayed, ParseResult result) {
String text = result.regexes.get(0).group().trim();
if (text.isEmpty()) {
Skript.error("An empty annotation (@) is not allowed. Please specify an annotation.");
return false;
}
this.annotation = Annotation.create(text);
this.getParser().addAnnotation(annotation);
Skript.debug("found annotation: " + annotation);
return true;
}

@Override
protected void execute(Event event) {
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return '@' + annotation.value();
}

}
14 changes: 14 additions & 0 deletions src/main/java/ch/njol/skript/lang/MetaSyntaxElement.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ch.njol.skript.lang;

/**
* Represents a type of syntax element used to modify other syntax elements rather than to provide any function
* itself.
*/
public interface MetaSyntaxElement extends SyntaxElement {

@Override
default boolean consumeAnnotations() {
return false;
}

}
48 changes: 41 additions & 7 deletions src/main/java/ch/njol/skript/lang/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.script.Annotation;
import org.skriptlang.skript.lang.structure.Structure;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.function.Supplier;

/**
Expand Down Expand Up @@ -48,6 +48,7 @@ public abstract class Section extends TriggerSection implements SyntaxElement {
@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
SectionContext sectionContext = getParser().getData(SectionContext.class);
sectionContext.setAnnotations(this.getParser().copyAnnotations());
return init(expressions, matchedPattern, isDelayed, parseResult, sectionContext.sectionNode, sectionContext.triggerItems)
&& sectionContext.claim(this);
}
Expand All @@ -67,7 +68,10 @@ public abstract boolean init(Expression<?>[] expressions,
* (although the loaded code may change it), the calling code must deal with this.
*/
protected void loadCode(SectionNode sectionNode) {
ParserInstance parser = getParser();
ParserInstance parser = this.getParser();
Set<Annotation> annotations = parser.copyAnnotations();
parser.forgetAnnotations(); // scope annotations correctly for section headers

List<TriggerSection> previousSections = parser.getCurrentSections();

List<TriggerSection> sections = new ArrayList<>(previousSections);
Expand All @@ -78,6 +82,7 @@ protected void loadCode(SectionNode sectionNode) {
setTriggerItems(ScriptLoader.loadItems(sectionNode));
} finally {
parser.setCurrentSections(previousSections);
parser.replaceAnnotations(annotations);
}
}

Expand Down Expand Up @@ -158,10 +163,17 @@ protected void loadOptionalCode(SectionNode sectionNode) {

@Nullable
public static Section parse(String expr, @Nullable String defaultError, SectionNode sectionNode, List<TriggerItem> triggerItems) {
Copy link
Member

@APickledWalrus APickledWalrus Dec 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the goal of the changes for this method? It doesn't actually seem to do anything, as the resetting of annotations occurs before the SkriptParser is invoked. That is, nothing happens between saving them and resetting them.

SectionContext sectionContext = ParserInstance.get().getData(SectionContext.class);
//noinspection unchecked,rawtypes
ParserInstance parser = ParserInstance.get();
SectionContext sectionContext = parser.getData(SectionContext.class);
Set<Annotation> annotations = parser.copyAnnotations();
parser.forgetAnnotations();
return sectionContext.modify(sectionNode, triggerItems,
() -> (Section) SkriptParser.parse(expr, (Iterator) Skript.getSections().iterator(), defaultError));
() -> {
ParserInstance local = ParserInstance.get();
local.forgetAnnotations();
local.replaceAnnotations(annotations);
return (Section) SkriptParser.parse(expr, (Iterator) Skript.getSections().iterator(), defaultError);
});
}

static {
Expand All @@ -173,6 +185,7 @@ protected static class SectionContext extends ParserInstance.Data {

protected SectionNode sectionNode;
protected List<TriggerItem> triggerItems;
protected @Nullable Collection<Annotation> annotations;
protected @Nullable Debuggable owner;

public SectionContext(ParserInstance parserInstance) {
Expand Down Expand Up @@ -244,6 +257,27 @@ public boolean claimed() {
return owner != null;
}

/**
* Sets the annotation container for this section.
*
* @param annotations The annotations container
*/
public void setAnnotations(@Nullable Collection<Annotation> annotations) {
this.annotations = annotations;
}

/**
* Returns the annotations visible to the section header (the line that it started from).
* The collection may be empty if no annotations were placed before the header line.
*
* @return The annotation applied to the current section
*/
public @NotNull Collection<Annotation> getAnnotations() {
if (annotations == null)
return Collections.emptySet();
return annotations;
}

}

@Override
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/ch/njol/skript/lang/SkriptParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ public boolean hasTag(String tag) {
boolean success = element.init(parseResult.exprs, patternIndex, getParser().getHasDelayBefore(), parseResult);
if (success) {
log.printLog();
if (element.consumeAnnotations())
ParserInstance.get().forgetAnnotations();
return element;
}
}
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/ch/njol/skript/lang/SyntaxElement.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,39 @@ default ParserInstance getParser() {
@Contract(pure = true)
@NotNull String getSyntaxTypeName();

/**
* Whether this syntax element consumes annotations.
* Consuming means the annotations are discarded for the following syntax.
* <p>
* <b>Most syntax should leave this as 'true'.</b>
* <p>
* If the return value is true (as expected), annotations placed before this element WILL NOT be available to
* the lines (or statements) following it.
*
* <pre>{@code
* on event:
* @annotation
* my effect # can see @annotation
* my effect # cannot see @annotation
* }</pre>
*
* If the return value is false, annotations placed before this element WILL be available to
* the lines (or statements) following it.
*
* <pre>{@code
* on event:
* @annotation
* my effect # can see @annotation
* my effect # can see @annotation
* }</pre>
*
* This behaviour is used by meta-syntax (including @annotations themselves).
*
* @return True if annotations will be discarded, false if they should be kept for the next statement.
* @see org.skriptlang.skript.lang.script.Annotation
*/
default boolean consumeAnnotations() {
return true;
}

}
69 changes: 61 additions & 8 deletions src/main/java/ch/njol/skript/lang/parser/ParserInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,16 @@
import org.skriptlang.skript.lang.experiment.Experiment;
import org.skriptlang.skript.lang.experiment.ExperimentSet;
import org.skriptlang.skript.lang.experiment.Experimented;
import org.skriptlang.skript.lang.script.Annotated;
import org.skriptlang.skript.lang.script.Annotation;
import org.skriptlang.skript.lang.script.Script;
import org.skriptlang.skript.lang.structure.Structure;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;

public final class ParserInstance implements Experimented {
public final class ParserInstance implements Experimented, Annotated {

private static final ThreadLocal<ParserInstance> PARSER_INSTANCES = ThreadLocal.withInitial(ParserInstance::new);

Expand Down Expand Up @@ -89,7 +87,8 @@ public void reset() {
this.currentSections = new ArrayList<>();
this.hasDelayBefore = Kleenean.FALSE;
this.node = null;
dataMap.clear();
this.dataMap.clear();
this.annotations.clear();
}

// Script API
Expand Down Expand Up @@ -481,7 +480,7 @@ public boolean hasExperiment(Experiment experiment) {
@ApiStatus.Internal
public void addExperiment(Experiment experiment) {
Script script = this.getCurrentScript();
ExperimentSet set = script.getData(ExperimentSet.class, () -> new ExperimentSet());
ExperimentSet set = script.getData(ExperimentSet.class, ExperimentSet::new);
set.add(experiment);
}

Expand Down Expand Up @@ -513,6 +512,56 @@ public Experimented experimentSnapshot() {
return new ExperimentSet(set);
}

// Annotations API

private final @NotNull Set<Annotation> annotations = new LinkedHashSet<>(4);

public @NotNull Collection<Annotation> annotations() {
return annotations;
}

/**
* Registers an annotation visible to the upcoming syntax element.
* Annotations are (typically) disposed of after the next non-meta line of code.
* @param annotation The annotation to be registered.
*/
public void addAnnotation(Annotation annotation) {
this.annotations.add(annotation);
}

/**
* Disposes of all registered annotations.
* This is usually run after a line of code.
*/
public void forgetAnnotations() {
this.annotations.clear();
}

/**
* Checks whether an annotation by exact text is visible to the parser at this stage.
* @param text The exact content of the annotation
* @return Whether an annotation with this content is present
*/
@Override
public boolean hasAnnotation(String text) {
if (annotations.isEmpty())
return false;
for (Annotation annotation : annotations) {
if (annotation.valueEquals(text))
return true;
}
return false;
}

/**
* Replaces the current set of visible annotations with the given collection.
* @param annotations The new set of visible annotations.
*/
public void replaceAnnotations(Collection<Annotation> annotations) {
this.annotations.clear();
this.annotations.addAll(annotations);
}

// ParserInstance Data API

/**
Expand Down Expand Up @@ -635,6 +684,7 @@ public static class Backup {
private final List<TriggerSection> currentSections;
private final Kleenean hasDelayBefore;
private final Map<Class<? extends Data>, Data> dataMap;
private final Set<Annotation> annotations;

private Backup(ParserInstance parser) {
//noinspection ConstantConditions - parser will be active, meaning there is a current script
Expand All @@ -647,6 +697,7 @@ private Backup(ParserInstance parser) {
this.currentSections = new ArrayList<>(parser.currentSections);
this.hasDelayBefore = parser.hasDelayBefore;
this.dataMap = new HashMap<>(parser.dataMap);
this.annotations = new HashSet<>(parser.annotations);
}

private void apply(ParserInstance parser) {
Expand All @@ -658,6 +709,8 @@ private void apply(ParserInstance parser) {
parser.hasDelayBefore = this.hasDelayBefore;
parser.dataMap.clear();
parser.dataMap.putAll(this.dataMap);
parser.annotations.clear();
parser.annotations.addAll(this.annotations);
}

}
Expand Down
Loading
Loading