Skip to content
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

Secspressions! #7037

Merged
merged 27 commits into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e18971f
Add expression section basics.
Moderocky Sep 5, 2024
9cbc963
Sections & tests.
Moderocky Sep 5, 2024
c611988
Script loader thing.
Moderocky Sep 5, 2024
c5ce4c0
Remove finaliser.
Moderocky Sep 5, 2024
9c138a9
Add backup method.
Moderocky Sep 5, 2024
3cff9ea
Retain logs from parse attempts.
Moderocky Sep 5, 2024
fe95a41
Add failure tests.
Moderocky Sep 5, 2024
c158c87
Add more failure tests.
Moderocky Sep 6, 2024
0d8de33
Error only if a match was found.
Moderocky Sep 6, 2024
34dec7a
Revert change.
Moderocky Sep 6, 2024
66f785d
Filter for specific section errors.
Moderocky Sep 6, 2024
897aadc
Check expected failure errors.
Moderocky Sep 6, 2024
20a2327
Add some documentation.
Moderocky Sep 6, 2024
1297403
Change error message.
Moderocky Sep 6, 2024
ed4e49d
Merge branch 'dev/feature' into expression-sections
Moderocky Sep 13, 2024
393edee
Stupid annotation :(
Moderocky Sep 13, 2024
4e40c46
Merge branch 'dev/feature' into expression-sections
sovdeeth Oct 31, 2024
ee11717
Fix Sovde's mistake.
Moderocky Nov 1, 2024
30f1eb3
Merge branch 'dev/feature' into expression-sections
Moderocky Nov 3, 2024
ae3f49f
Merge branch 'dev/feature' into expression-sections
Moderocky Nov 7, 2024
8fed25f
Merge branch 'dev/feature' into expression-sections
Moderocky Nov 17, 2024
b341e08
Apply suggestions from code review
Moderocky Nov 20, 2024
3cdc641
Remove method.
Moderocky Nov 20, 2024
43d14e4
Merge branch 'dev/feature' into expression-sections
Moderocky Nov 20, 2024
c678bca
Test stuff.
Moderocky Nov 20, 2024
7b1c1a0
Add proper support for loading code normally
APickledWalrus Nov 20, 2024
ba41756
Merge branch 'dev/feature' into expression-sections
sovdeeth Nov 23, 2024
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
166 changes: 82 additions & 84 deletions src/main/java/ch/njol/skript/ScriptLoader.java

Large diffs are not rendered by default.

171 changes: 171 additions & 0 deletions src/main/java/ch/njol/skript/expressions/base/SectionExpression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package ch.njol.skript.expressions.base;

import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.lang.*;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
* An implementation of the {@link Expression} interface that is allowed to start a section.
* <pre>{@code
* set {var} to (my section expression):
* this is inside a section
* this section can be 'managed' by (my section expression)
* }</pre>
* <br/>
* <br/>
* A section expression can be used in any expression slot, provided that:
* <ol>
* <li>The line itself is not already a {@link Section} or an {@link EffectSection}.</li>
* <li>The line does not already contain another {@link SectionExpression}.</li>
* </ol>
* <br/>
* <br/>
* The intended purpose of section expressions is to make features like the below possible,
* without the need to modify individual effect classes:
* <pre>{@code
* set {var} to a new lambda:
* broadcast "hello"
* # this section is managed by `a new lambda` rather than EffChange
* }</pre>
* <br/>
* <br/>
* Please note that section expressions will not be given a {@link SectionNode} during `init`
* if they are used in a line <b>that does not start a section</b>!
* It is up to the implementation to check whether this is provided.
* <p>
* Section node provided:
* <pre>{@code
* set {var} to a new lambda:
* broadcast "hello"
* }</pre>
* <p>
* Section node not provided:
* <pre>{@code
* set {var} to a new lambda
* }</pre>
* <br/>
* <br/>
* Please also note that expressions are not 'executed' individually as effects/sections are,
* and so some behaviour (such as trigger items, orders, etc.) may be different from a regular section.
*
* @see Skript#registerExpression(Class, Class, ExpressionType, String...)
* @see Section
* @see SimpleExpression
*/
public abstract class SectionExpression<Value> extends SimpleExpression<Value> implements Expression<Value> {
Moderocky marked this conversation as resolved.
Show resolved Hide resolved

protected final ExpressionSection section = new ExpressionSection(this);

/**
* Called just after the constructor.
*
* @param expressions all %expr%s included in the matching pattern in the order they appear in the pattern. If an optional value was left out, it will still be included in this list
* holding the default value of the desired type, which usually depends on the event
* @param pattern The index of the pattern which matched
* @param delayed Whether this expression is used after a delay or not (i.e. if the event has already passed when this expression will be called)
* @param result Additional information about the match
* @param node The section node representing the un-handled code in the body of the approaching section. If this expression does not start a section, the node will be null.
* @param triggerItems A list of preceding trigger nodes before this line
*
* @return Whether this expression was initialised successfully. An error should be printed prior to returning false to specify the cause.
*/
public abstract boolean init(Expression<?>[] expressions,
int pattern,
Kleenean delayed,
ParseResult result,
@Nullable SectionNode node,
@Nullable List<TriggerItem> triggerItems);

@Override
public final boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed,
ParseResult parseResult) {
return section.init(expressions, matchedPattern, isDelayed, parseResult);
}

/**
* @return A dummy trigger item representing the section belonging to this
*/
public final Section getAsSection() {
return section;
}

/**
* Loads the code in the given {@link SectionNode},
* appropriately modifying {@link ParserInstance#getCurrentSections()}.
* <br>
* This method differs from {@link #loadCode(SectionNode)} in that it
* is meant for code that will be executed at another time and potentially with different context.
* The section's contents are parsed with the understanding that they have no relation
* to the section itself, along with any other code that may come before and after the section.
* The {@link ParserInstance} is modified to reflect that understanding.
*
* @param sectionNode The section node to load.
* @param name The name of the event(s) being used.
* @param afterLoading A Runnable to execute after the SectionNode has been loaded.
* This occurs before {@link ParserInstance} states are reset.
* @param events The event(s) during the section's execution.
* @return A trigger containing the loaded section. This should be stored and used
* to run the section one or more times.
*/
protected Trigger loadCode(SectionNode sectionNode, String name,
@Nullable Runnable afterLoading, Class<? extends Event>... events) {
return section.loadCodeTask(sectionNode, name, afterLoading, events);
}

/**
* Loads the code in the given {@link SectionNode},
* appropriately modifying {@link ParserInstance#getCurrentSections()}.
* <br>
* This method itself does not modify {@link ParserInstance#getHasDelayBefore()}
* (although the loaded code may change it), the calling code must deal with this.
*/
protected void loadCode(SectionNode sectionNode) {
this.section.loadCode(sectionNode);
}

/**
* Loads the code using {@link #loadCode(SectionNode)}.
* <br>
* This method also adjusts {@link ParserInstance#getHasDelayBefore()} to expect the code
* to be called zero or more times. This is done by setting {@code hasDelayBefore} to {@link Kleenean#UNKNOWN}
* if the loaded section has a possible or definite delay in it.
*/
protected void loadOptionalCode(SectionNode sectionNode) {
this.section.loadOptionalCode(sectionNode);
}

/**
* Remember to add this section to {@link ParserInstance#getCurrentSections()} before parsing child elements!
*
* <pre>{@code
* ScriptLoader.currentSections.add(this);
* setTriggerItems(ScriptLoader.loadItems(node));
* ScriptLoader.currentSections.remove(ScriptLoader.currentSections.size() - 1);
* }</pre>
APickledWalrus marked this conversation as resolved.
Show resolved Hide resolved
*/
APickledWalrus marked this conversation as resolved.
Show resolved Hide resolved
protected void setTriggerItems(List<TriggerItem> items) {
this.section.setTriggerItems(items);
}

protected TriggerSection setNext(@Nullable TriggerItem next) {
return this.section.setNext(next);
}

protected TriggerSection setParent(@Nullable TriggerSection parent) {
return this.section.setParent(parent);
}

protected @Nullable TriggerItem getNext() {
return this.section.getNext();
}

}

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

import ch.njol.skript.config.SectionNode;
import ch.njol.skript.expressions.base.SectionExpression;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
* A dummy trigger item representing the 'section' aspect of a {@link SectionExpression}.
* This is not a parsed or registered syntax in itself, and can be used for getting access to the parse-time features
* of a section syntax (e.g. loading body code).
* <p>
* This is not safe to be used during runtime, since it is not a part of the trigger item tree.
*
* @see SectionExpression
* @see Section
* @see EffectSectionEffect
*/
@ApiStatus.Internal
public class ExpressionSection extends Section {

protected final SectionExpression<?> expression;

public ExpressionSection(SectionExpression<?> expression) {
this.expression = expression;
}

@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) {
SectionContext context = getParser().getData(SectionContext.class);
return this.init(expressions, matchedPattern, isDelayed, parseResult, context.sectionNode, context.triggerItems)
&& context.claim(expression);
}

@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed,
SkriptParser.ParseResult parseResult,
@Nullable SectionNode sectionNode, List<TriggerItem> triggerItems) {
return expression.init(expressions, matchedPattern, isDelayed, parseResult, sectionNode, triggerItems);
}

@Override
protected @Nullable TriggerItem walk(Event event) {
return super.walk(event, false);
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return expression.toString(event, debug);
}

public SectionExpression<?> getAsExpression() {
return expression;
}

@Override
public void loadCode(SectionNode sectionNode) {
Moderocky marked this conversation as resolved.
Show resolved Hide resolved
super.loadCode(sectionNode);
}

@Override
public void loadOptionalCode(SectionNode sectionNode) {
super.loadOptionalCode(sectionNode);
}

@Override
public void setTriggerItems(List<TriggerItem> items) {
super.setTriggerItems(items);
}

@Override
public TriggerSection setNext(@Nullable TriggerItem next) {
return super.setNext(next);
}

@Override
public TriggerSection setParent(@Nullable TriggerSection parent) {
return super.setParent(parent);
}

@Override
public @Nullable TriggerItem getNext() {
return super.getNext();
}

public Trigger loadCodeTask(SectionNode sectionNode, String name, @Nullable Runnable afterLoading, Class<? extends Event>... events) {
return super.loadCode(sectionNode, name, afterLoading, events);
}

@ApiStatus.Internal
protected boolean canInitSafely() {
Copy link
Member

Choose a reason for hiding this comment

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

Is this used anywhere?

return this.getParser().getData(SectionContext.class).claim(expression);
}

}
46 changes: 45 additions & 1 deletion src/main/java/ch/njol/skript/lang/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
Expand Down Expand Up @@ -64,7 +65,8 @@ 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);
return init(expressions, matchedPattern, isDelayed, parseResult, sectionContext.sectionNode, sectionContext.triggerItems);
return init(expressions, matchedPattern, isDelayed, parseResult, sectionContext.sectionNode, sectionContext.triggerItems)
&& sectionContext.claim(this);
}

public abstract boolean init(Expression<?>[] expressions,
Expand Down Expand Up @@ -188,6 +190,7 @@ protected static class SectionContext extends ParserInstance.Data {

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

public SectionContext(ParserInstance parserInstance) {
super(parserInstance);
Expand All @@ -205,18 +208,59 @@ public SectionContext(ParserInstance parserInstance) {
protected <T> T modify(SectionNode sectionNode, List<TriggerItem> triggerItems, Supplier<T> supplier) {
SectionNode prevSectionNode = this.sectionNode;
List<TriggerItem> prevTriggerItems = this.triggerItems;
Debuggable owner = this.owner;

this.sectionNode = sectionNode;
this.triggerItems = triggerItems;
this.owner = null;

T result = supplier.get();

this.sectionNode = prevSectionNode;
this.triggerItems = prevTriggerItems;
this.owner = owner;

return result;
}

/**
* Marks the section this context represents as having been 'claimed' by the current syntax.
* Once a syntax has claimed a section, another syntax may not claim it.
*
* @param syntax The syntax that wants to own this section
* @return True if this was successfully claimed, false if it was already owned
*/
@ApiStatus.Internal
public <Syntax extends SyntaxElement & Debuggable> boolean claim(Syntax syntax) {
if (sectionNode == null)
return true;
if (this.claimed()) {
if (owner == syntax)
return true;
assert owner != null;
Skript.error("The syntax '" + syntax.toString(null, false)
+ "' tried to claim the current section, but it was already claimed by '"
+ this.owner.toString(null, false)
+ "'. You cannot have two section-starters in the same line.");
return false;
}
this.owner = syntax;
return true;
}

/**
* Used to keep track of whether a syntax is managing the current section.
* Every section needs exactly one manager. This is used to detect errors such as:
* <ol>
* <li>Two syntax both want to manage the section (e.g. an effectsection and an expression or two expressions).</li>
* <li>No syntax wants to manage the section.</li>
* </ol>
* @return Whether a syntax is already managing this section context
*/
public boolean claimed() {
return owner != null;
}

}

}
Loading