Skip to content

feature: SecSwitch and SecCase/EffCase #92

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

Merged
merged 5 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public boolean init(Expression<?>[] expressions, int matchedPattern, ParseContex
}
}
if (loops.size() == 0) {
parseContext.getLogger().error("You can only use the 'continue' in a loop!", ErrorType.SEMANTIC_ERROR);
parseContext.getLogger().error("You can only use 'continue' in a loop!", ErrorType.SEMANTIC_ERROR);
return false;
}
// Closest loop will be the first item
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ protected final Optional<? extends Statement> getLast() {

/**
* A list of the classes of every syntax that is allowed to be used inside of this CodeSection. The default behavior
* is to return an empty list, which equates to no restrictions. If overriden, this allows the creation of specialized,
* is to return an empty list, which equates to no restrictions. If overridden, this allows the creation of specialized,
* DSL-like sections in which only select {@linkplain Statement statements} and other {@linkplain CodeSection sections}
* (and potentially, but not necessarily, expressions).
* @return a list of the classes of each syntax allowed inside this CodeSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public ExpressionList(Expression<? extends T>[] expressions, Class<T> returnType
this(expressions, returnType, and, null);
}

protected ExpressionList(@Nullable Expression<? extends T>[] expressions, Class<T> returnType, boolean and, @Nullable ExpressionList<?> source) {
assert expressions != null && expressions.length > 1;
protected ExpressionList(Expression<? extends T>[] expressions, Class<T> returnType, boolean and, @Nullable ExpressionList<?> source) {
assert expressions.length > 1;
this.expressions = expressions;
this.returnType = returnType;
this.and = and;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.github.syst3ms.skriptparser.lang.CodeSection;
import io.github.syst3ms.skriptparser.lang.SyntaxElement;
import io.github.syst3ms.skriptparser.lang.TriggerContext;
import io.github.syst3ms.skriptparser.util.Pair;

import java.util.Collections;
import java.util.LinkedList;
Expand All @@ -16,6 +17,7 @@ public class ParserState {
private final LinkedList<CodeSection> currentSections = new LinkedList<>();
private List<Class<? extends SyntaxElement>> allowedSyntaxes = Collections.emptyList();
private boolean restrictingExpressions = false;
private final LinkedList<Pair<List<Class<? extends SyntaxElement>>, Boolean>> restrictionsReference = new LinkedList<>();

/**
* @return the {@link TriggerContext}s handled by the currently parsed event
Expand Down Expand Up @@ -60,6 +62,7 @@ public void removeCurrentSection() {
* @param restrictingExpressions whether expressions are also restricted
*/
public void setSyntaxRestrictions(List<Class<? extends SyntaxElement>> allowedSyntaxes, boolean restrictingExpressions) {
restrictionsReference.addLast(new Pair<>(this.allowedSyntaxes, this.restrictingExpressions));
this.allowedSyntaxes = allowedSyntaxes;
this.restrictingExpressions = restrictingExpressions;
}
Expand All @@ -68,8 +71,9 @@ public void setSyntaxRestrictions(List<Class<? extends SyntaxElement>> allowedSy
* Clears the previously enforced syntax restrictions
*/
public void clearSyntaxRestrictions() {
allowedSyntaxes = Collections.emptyList();
restrictingExpressions = false;
allowedSyntaxes = restrictionsReference.getLast().getFirst();
restrictingExpressions = restrictionsReference.getLast().getSecond();
restrictionsReference.removeLast();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,20 @@ public static List<Statement> loadItems(FileSection section, ParserState parserS
}
} else {
var content = element.getLineContent();
SyntaxParser.parseStatement(content, parserState, logger).ifPresent(items::add);
var statement = SyntaxParser.parseStatement(content, parserState, logger);
if (statement.isEmpty()) {
continue;
} else if (parserState.forbidsSyntax(statement.get().getClass())) {
logger.setContext(ErrorContext.RESTRICTED_SYNTAXES);
logger.error(
"The enclosing section does not allow the use of this effect: "
+ statement.get().toString(null, logger.isDebug()),
ErrorType.SEMANTIC_ERROR,
"The current section limits the usage of syntax. This means that certain syntax cannot be used here, which was the case. Remove the line entirely and refer to the documentation for the correct usage of this section"
);
continue;
}
items.add(statement.get());
}
}
logger.finalizeLogs();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public static <T> Optional<? extends Expression<? extends T>> parseExpression(St
if (!expectedType.isSingle()) {
var listLiteral = parseListLiteral(s, expectedType, parserState, logger);
if (listLiteral.isPresent()) {
logger.clearErrors();
return listLiteral;
}
}
Expand Down
115 changes: 115 additions & 0 deletions src/main/java/io/github/syst3ms/skriptparser/sections/SecCase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.github.syst3ms.skriptparser.sections;

import io.github.syst3ms.skriptparser.Parser;
import io.github.syst3ms.skriptparser.lang.CodeSection;
import io.github.syst3ms.skriptparser.lang.Expression;
import io.github.syst3ms.skriptparser.lang.Statement;
import io.github.syst3ms.skriptparser.lang.TriggerContext;
import io.github.syst3ms.skriptparser.log.ErrorType;
import io.github.syst3ms.skriptparser.parsing.ParseContext;
import io.github.syst3ms.skriptparser.types.comparisons.Comparators;
import io.github.syst3ms.skriptparser.types.comparisons.Relation;
import org.jetbrains.annotations.Nullable;

import java.util.Optional;

/**
* This section is written underneath the {@link SecSwitch switch} section to match the given expression.
* The content of this section will only be executed if it matches the given expression.
* One may use 'or'-lists to match multiple expressions at once.
* The default part can be used to provide actions when no match was found.
* Note that you can only use one default statement and that equivalent matches are prohibited.
*
* @name Case
* @type SECTION
* @pattern (case|when) %*objects%
* @pattern ([by] default|otherwise)
* @since ALPHA
* @author Mwexim
* @see SecSwitch
*/
@SuppressWarnings("unchecked")
public class SecCase extends CodeSection {
static {
Parser.getMainRegistration().addSection(
SecCase.class,
"(case|when) %*objects%",
"([by] default|otherwise)"
);
}

private Expression<Object> matchWith;
private SecSwitch switchSection;
private boolean isMatching;

@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, ParseContext parseContext) {
var logger = parseContext.getLogger();
var latest = parseContext.getParserState().getCurrentSections().get(0);
if (!(latest instanceof SecSwitch)) {
logger.error("You can only use a 'case'-statement inside a 'switch'-section!", ErrorType.SEMANTIC_ERROR);
return false;
}

switchSection = (SecSwitch) latest;

isMatching = matchedPattern == 0;
if (isMatching) {
matchWith = (Expression<Object>) expressions[0];
if (!matchWith.isSingle() && matchWith.isAndList()) {
logger.error(
"Only 'or'-lists may be used, found '" + matchWith.toString(null, logger.isDebug()),
ErrorType.SEMANTIC_ERROR
);
return false;
} else if (switchSection.getDefault().isPresent()) {
logger.error(
"A 'case'-section cannot be placed behind a 'default'-statement.",
ErrorType.SEMANTIC_ERROR,
"Place this statement before the 'default'-statement to provide the same behavior."
);
return false;
}
switchSection.getCases().add(this);
} else if (switchSection.getDefault().isPresent()) {
logger.error(
"Only one 'default'-statement may be used inside a 'switch'-section",
ErrorType.SEMANTIC_ERROR,
"Merge this section with the other 'default'-section to provide the same behavior."
);
return false;
} else {
switchSection.setDefault(this);
}
return true;
}

@Override
public Optional<? extends Statement> walk(TriggerContext ctx) {
if (isMatching) {
switchSection.getMatch().getSingle(ctx)
.filter(val -> Expression.check(
matchWith.getValues(ctx),
val2 -> Comparators.compare(val, val2).is(Relation.EQUAL),
false,
false
))
.ifPresent(__ -> {
switchSection.setDone(true);
var item = getFirst();
while (!item.equals(getNext())) // Calling equals() on optionals calls equals() on their values
item = item.flatMap(i -> i.walk(ctx));
});
} else {
var item = getFirst();
while (!item.equals(getNext())) // Calling equals() on optionals calls equals() on their values
item = item.flatMap(i -> i.walk(ctx));
}
return Optional.empty();
}

@Override
public String toString(@Nullable TriggerContext ctx, boolean debug) {
return isMatching ? ("case " + matchWith.toString(ctx, debug)) : "default";
}
}
114 changes: 114 additions & 0 deletions src/main/java/io/github/syst3ms/skriptparser/sections/SecSwitch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package io.github.syst3ms.skriptparser.sections;

import io.github.syst3ms.skriptparser.Parser;
import io.github.syst3ms.skriptparser.lang.*;
import io.github.syst3ms.skriptparser.parsing.ParseContext;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* Basic switch control statement. Only {@link SecCase case} sections/effects are allowed within this section.
* The given expression will be matched against each case and all matching ones will be run.
* Note that unlike in Java, each case is run separately.
* <br>
* <pre>
* switch (2) {
* case 1:
* print("Hello");
* case 2:
* print("World!");
* break;
* default:
* print("Nothing");
* }
* </pre>
* Would result in the following Skript code:
* <br>
* <pre>
* switch 2:
* case 1:
* print "Hello"
* case 1 or 2:
* print "World!"
* default:
* print "Nothing"
* </pre>
* Note that the default part is only executed if no match was found.
*
* @name Switch
* @type SECTION
* @pattern (switch|given|match) %object%
* @since ALPHA
* @author Mwexim
* @see SecCase
*/
@SuppressWarnings("unchecked")
public class SecSwitch extends CodeSection {
static {
Parser.getMainRegistration().addSection(
SecSwitch.class,
"(switch|given|match) %object%"
);
}

private Expression<Object> matched;
private final List<Statement> cases = new ArrayList<>();
@Nullable
private Statement byDefault = null;
private boolean isDone = false;

@Override
public boolean init(Expression<?>[] expressions, int matchedPattern, ParseContext parseContext) {
matched = (Expression<Object>) expressions[0];
return true;
}

@Override
public Optional<? extends Statement> walk(TriggerContext ctx) {
for (Statement element : cases) {
element.walk(ctx);
}
if (!isDone) {
if (byDefault != null)
byDefault.walk(ctx);
}
return getNext();
}

@Override
protected List<Class<? extends SyntaxElement>> getAllowedSyntaxes() {
return List.of(SecCase.class);
}

@Override
public String toString(@Nullable TriggerContext ctx, boolean debug) {
return "switch " + matched.toString(ctx, debug);
}

public Expression<Object> getMatch() {
return matched;
}

public List<Statement> getCases() {
return cases;
}

public Optional<? extends Statement> getDefault() {
return Optional.ofNullable(byDefault);
}

public void setDefault(Statement byDefault) {
this.byDefault = byDefault;
}

public boolean isDone() {
return isDone;
}

public void setDone(boolean isDone) {
this.isDone = isDone;
}
}
55 changes: 55 additions & 0 deletions src/test/resources/sections/SecSwitch.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Author(s):
# - Mwexim
# Date: 2021/01/02

test:
set {var} to "Hello"
switch {var}:
case "World":
add 1 to {flag::*}
when "Hello":
add 2 to {flag::*}
set {var2} to 2
add 1 to {var2}
add {var2} to {flag::*}
case 2 or 3:
add 4 to {flag::*}
case 4 or "Hello":
add 5 to {flag::*}
case "Hello":
add 8 to {flag::*}
default:
add 6 to {flag::*}

# 2, 3, 5 and 8
assert {flag::*} = 2, 3, 5 and 8 with "{flag::*} should be 2, 3, 5 and 8: %{flag::*}%"

set {var} to 3
clear {flag::*}
given {var}:
case 1:
add 1 to {flag::*}
when 2:
add 2 to {flag::*}
default:
add 3 to {flag::*}

# 3
assert {flag::*} = 3 with "{flag::*} should be 3: %{flag::*}%"

set {var} to true
clear {flag::*}
match {var}:
case false:
add 1 to {flag::*}
case true:
add 2 to {flag::*}
set {var} to -1
case -1:
add 3 to {flag::*} # This should be possible
default:
add 4 to {flag::*}

# 2 and 3
assert {flag::*} = 2 and 3 with "{flag::*} should be 2 and 3: %{flag::*}%"
assert {var} = -1 with "{var} should be -1: %{var}%"