From 628e488d2570b19b86972ccdd86b6b21112b7bcd Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 22 Jan 2026 17:50:33 -0800 Subject: [PATCH 01/68] gradle-wrapper.properties - update gradle version --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34e8..2dcec856b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 8c82c05ee82ce0a605613ae32ec7b08bba01b610 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 22 Jan 2026 17:50:53 -0800 Subject: [PATCH 02/68] build.gradle - update for maven-publish --- build.gradle | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 0619f6fcd..b4b7447d2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,20 +1,14 @@ plugins { id "java" - id "application" + id("maven-publish") } - -mainClassName = "io.github.syst3ms.skriptparser.Parser" - -sourceCompatibility = 1.11 +group = "com.github.SkriptDev" +version = "1.0.0" repositories { mavenCentral() } -test { - useJUnitPlatform() -} - dependencies { implementation "org.jetbrains:annotations:15.0" implementation group: "com.google.code.findbugs", name: "jsr305", version: "3.0.2" @@ -24,10 +18,9 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.1" } -jar { - manifest { - attributes("Main-Class": mainClassName, - "Implementation-Title": "skript-parser", - "Implementation-Version": "alpha") +publishing { + publications { + mavenJava(MavenPublication) { + } } -} \ No newline at end of file +} From 7a54eaa65b67f68fa8bc16b084706d0d20b4697e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 22 Jan 2026 18:25:33 -0800 Subject: [PATCH 03/68] Expression - ignore "Objects" at parse time as that is what Variables will provide --- .../java/io/github/syst3ms/skriptparser/lang/Expression.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java index 9c343b528..9d58b6d19 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java @@ -121,7 +121,7 @@ default boolean acceptsChange(ChangeMode mode, Class needle, boolean isSingle continue; if (haystack.isArray()) haystack = haystack.getComponentType(); - if (haystack.isAssignableFrom(needle)) + if (needle == Object.class || haystack.isAssignableFrom(needle)) return true; } return false; From 8f9bd0c9c5fc178f08683b6916970928640e8f47 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 22 Jan 2026 18:26:56 -0800 Subject: [PATCH 04/68] build.gradle - fix missing jar and add sources --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index b4b7447d2..1bd8fbbeb 100644 --- a/build.gradle +++ b/build.gradle @@ -18,9 +18,14 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.1" } +java { + withSourcesJar() +} + publishing { publications { mavenJava(MavenPublication) { + from components.java } } } From f81821c24cdc233faa79e55696d6f7e2145eac98 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Thu, 22 Jan 2026 18:31:03 -0800 Subject: [PATCH 05/68] jitpack.yml - add jitpack file to configure java version --- jitpack.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 jitpack.yml diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 000000000..3b9f78a4f --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,5 @@ +before_install: + - source "$HOME/.sdkman/bin/sdkman-init.sh" + - sdk update + - sdk install java 21.0.2-open + - sdk use java 21.0.2-open From a6d6e4ebecce0ad27e58934f688cfa1c253e348a Mon Sep 17 00:00:00 2001 From: Shane Bee Date: Thu, 22 Jan 2026 21:31:51 -0800 Subject: [PATCH 06/68] Add documentation (#1) * Add documentation * SkriptRegistration - more dummy names --- .../skriptparser/docs/Documentation.java | 57 +++++++++++++ .../expressions/ExprRandomNumber.java | 11 ++- .../registration/ExpressionInfo.java | 11 +-- .../registration/SkriptEventInfo.java | 5 +- .../registration/SkriptRegistration.java | 83 +++++++++++++++++-- .../skriptparser/registration/SyntaxInfo.java | 13 ++- .../syst3ms/skriptparser/types/Type.java | 31 ++++--- 7 files changed, 183 insertions(+), 28 deletions(-) create mode 100644 src/main/java/io/github/syst3ms/skriptparser/docs/Documentation.java diff --git a/src/main/java/io/github/syst3ms/skriptparser/docs/Documentation.java b/src/main/java/io/github/syst3ms/skriptparser/docs/Documentation.java new file mode 100644 index 000000000..fd1ab5bc3 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/docs/Documentation.java @@ -0,0 +1,57 @@ +package io.github.syst3ms.skriptparser.docs; + +/** + * Represents the documentation for a syntax element + */ +public class Documentation { + + private String name; + private String[] description; + private String usage; + private String[] examples; + private String since; + + public Documentation() { + } + + public String getName() { + return this.name; + } + + public String[] getDescription() { + return this.description; + } + + public String getUsage() { + return this.usage; + } + + public String[] getExamples() { + return this.examples; + } + + public String getSince() { + return this.since; + } + + public void setName(String name) { + this.name = name; + } + + public void setDescription(String[] description) { + this.description = description; + } + + public void setUsage(String usage) { + this.usage = usage; + } + + public void setExamples(String[] examples) { + this.examples = examples; + } + + public void setSince(String since) { + this.since = since; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java index e856c2881..dd60d66ac 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java @@ -25,13 +25,18 @@ */ public class ExprRandomNumber implements Expression { static { - Parser.getMainRegistration().addExpression( + Parser.getMainRegistration().newExpression( ExprRandomNumber.class, Number.class, true, "[a] random integer [1:strictly] (from|between) %integer% (to|and) %integer%", - "[a] random number [1:strictly] (from|between) %number% (to|and) %number%" - ); + "[a] random number [1:strictly] (from|between) %number% (to|and) %number%") + .name("Random Number") + .description("Generates a random number between two numbers.") + .examples("set {_random} to random integer between 10 and 20", + "set {_random} to random number between 10.5 and 20.5") + .since("INSERT VERSION") + .register(); } private static final ThreadLocalRandom random = ThreadLocalRandom.current(); diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/ExpressionInfo.java b/src/main/java/io/github/syst3ms/skriptparser/registration/ExpressionInfo.java index 78c433e47..6230b8316 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/ExpressionInfo.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/ExpressionInfo.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.registration; +import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.pattern.PatternElement; import io.github.syst3ms.skriptparser.types.PatternType; @@ -17,16 +18,16 @@ public class ExpressionInfo extends SyntaxInfo { private final PatternType returnType; - public ExpressionInfo(SkriptAddon registerer, Class c, Type returnType, boolean isSingle, int priority, List patterns) { - this(registerer, c, returnType, isSingle, priority, patterns, new HashMap<>()); + public ExpressionInfo(SkriptAddon registerer, Class c, Type returnType, boolean isSingle, int priority, List patterns, Documentation documentation) { + this(registerer, c, returnType, isSingle, priority, patterns, documentation, new HashMap<>()); } - public ExpressionInfo(SkriptAddon registerer, Class c, Type returnType, boolean isSingle, int priority, List patterns, Map data) { - super(registerer, c, priority, patterns, data); + public ExpressionInfo(SkriptAddon registerer, Class c, Type returnType, boolean isSingle, int priority, List patterns, Documentation documentation, Map data) { + super(registerer, c, priority, patterns, documentation, data); this.returnType = new PatternType<>(returnType, isSingle); } public PatternType getReturnType() { return returnType; } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptEventInfo.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptEventInfo.java index 563969b33..a2c05fa64 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptEventInfo.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptEventInfo.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.registration; +import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.lang.SkriptEvent; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.pattern.PatternElement; @@ -15,8 +16,8 @@ public class SkriptEventInfo extends SyntaxInfo { private final Set> contexts; - public SkriptEventInfo(SkriptAddon registerer, Class c, Set> handledContexts, int priority, List patterns, Map data) { - super(registerer, c, priority, patterns, data); + public SkriptEventInfo(SkriptAddon registerer, Class c, Set> handledContexts, int priority, List patterns, Documentation documentation, Map data) { + super(registerer, c, priority, patterns, documentation, data); this.contexts = handledContexts; } diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java index 12f17bf63..48064d253 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.registration; +import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.lang.CodeSection; import io.github.syst3ms.skriptparser.lang.Effect; import io.github.syst3ms.skriptparser.lang.Expression; @@ -39,6 +40,7 @@ import io.github.syst3ms.skriptparser.types.conversions.Converters; import io.github.syst3ms.skriptparser.util.CollectionUtils; import io.github.syst3ms.skriptparser.util.MultiMap; +import io.github.syst3ms.skriptparser.util.StringUtils; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -616,11 +618,13 @@ public class TypeRegistrar implements Registrar { private Changer defaultChanger; @Nullable private Arithmetic arithmetic; + private final Documentation documentation = new Documentation(); public TypeRegistrar(Class c, String baseName, String pattern) { this.c = c; this.baseName = baseName; this.pattern = pattern; + this.documentation.setName(StringUtils.toTitleCase(baseName, true)); // Dummy name if not specified } /** @@ -659,13 +663,38 @@ public TypeRegistrar arithmetic(Arithmetic arithmetic) { return this; } + public TypeRegistrar name(String name) { + this.documentation.setName(name); + return this; + } + + public TypeRegistrar description(String... description) { + this.documentation.setDescription(description); + return this; + } + + public TypeRegistrar examples(String... examples) { + this.documentation.setExamples(examples); + return this; + } + + public TypeRegistrar usage(String usage) { + this.documentation.setUsage(usage); + return this; + } + + public TypeRegistrar since(String since) { + this.documentation.setSince(since); + return this; + } + /** * Adds this type to the list of currently registered syntaxes */ @Override public void register() { newTypes = true; - types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic)); + types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, this.documentation)); } } @@ -674,10 +703,12 @@ public abstract class SyntaxRegistrar implements Regist protected final List patterns = new ArrayList<>(); protected int priority; protected final Map data = new HashMap<>(); + protected final Documentation documentation = new Documentation(); SyntaxRegistrar(Class c, String... patterns) { this.c = c; Collections.addAll(this.patterns, patterns); + this.documentation.setName(c.getSimpleName()); // Dummy name if not specified typeCheck(); } @@ -691,6 +722,26 @@ public SyntaxRegistrar addPatterns(String... patterns) { return this; } + public SyntaxRegistrar name(String name) { + this.documentation.setName(name); + return this; + } + + public SyntaxRegistrar description(String... description) { + this.documentation.setDescription(description); + return this; + } + + public SyntaxRegistrar examples(String... examples) { + this.documentation.setExamples(examples); + return this; + } + + public SyntaxRegistrar since(String since) { + this.documentation.setSince(since); + return this; + } + /** * Sets the priority of the current syntax. Default is 5. * @param priority the priority @@ -747,7 +798,7 @@ public void register() { logger.error("Couldn't find a type corresponding to the class '" + returnType.getName() + "'", ErrorType.NO_MATCH); return; } - expressions.putOne(super.c, new ExpressionInfo<>(registerer, super.c, type.get(), isSingle, priority, parsePatterns(), super.data)); + expressions.putOne(super.c, new ExpressionInfo<>(registerer, super.c, type.get(), isSingle, priority, parsePatterns(), this.documentation, super.data)); } } @@ -761,7 +812,7 @@ public class EffectRegistrar extends SyntaxRegistrar { */ @Override public void register() { - effects.add(new SyntaxInfo<>(registerer, super.c, priority, parsePatterns(), super.data)); + effects.add(new SyntaxInfo<>(registerer, super.c, priority, parsePatterns(), this.documentation, super.data)); } } @@ -776,15 +827,17 @@ public class SectionRegistrar extends SyntaxRegistrar */ @Override public void register() { - sections.add(new SyntaxInfo<>(registerer, super.c, priority, parsePatterns(), super.data)); + sections.add(new SyntaxInfo<>(registerer, super.c, priority, parsePatterns(), this.documentation, super.data)); } } public class EventRegistrar extends SyntaxRegistrar { private Set> handledContexts = new HashSet<>(); + private final Documentation documentation = new Documentation(); EventRegistrar(Class c, String... patterns) { super(c, patterns); + this.documentation.setName(c.getSimpleName()); // Dummy name if not specified typeCheck(); } @@ -799,6 +852,26 @@ public final EventRegistrar setHandledContexts(Class name(String name) { + this.documentation.setName(name); + return this; + } + + public final EventRegistrar description(String... description) { + this.documentation.setDescription(description); + return this; + } + + public final EventRegistrar examples(String... examples) { + this.documentation.setExamples(examples); + return this; + } + + public final EventRegistrar since(String since) { + this.documentation.setSince(since); + return this; + } + /** * Adds this event to the list of currently registered syntaxes */ @@ -812,7 +885,7 @@ public void register() { super.patterns.set(i, "[on] " + pattern); } } - events.add(new SkriptEventInfo<>(registerer, super.c, handledContexts, priority, parsePatterns(), data)); + events.add(new SkriptEventInfo<>(registerer, super.c, handledContexts, priority, parsePatterns(), this.documentation, data)); registerer.addHandledEvent(this.c); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SyntaxInfo.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SyntaxInfo.java index a75712a4c..ac058bb85 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SyntaxInfo.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SyntaxInfo.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.registration; +import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.SkriptEvent; import io.github.syst3ms.skriptparser.lang.SyntaxElement; @@ -18,17 +19,19 @@ public class SyntaxInfo { private final List patterns; private final int priority; private final SkriptAddon registerer; + private final Documentation documentation; protected final Map data; - public SyntaxInfo(SkriptAddon registerer, Class c, int priority, List patterns) { - this(registerer, c, priority, patterns, new HashMap<>()); + public SyntaxInfo(SkriptAddon registerer, Class c, int priority, List patterns, Documentation documentation) { + this(registerer, c, priority, patterns, documentation, new HashMap<>()); } - public SyntaxInfo(SkriptAddon registerer, Class c, int priority, List patterns, Map data) { + public SyntaxInfo(SkriptAddon registerer, Class c, int priority, List patterns, Documentation documentation, Map data) { this.c = c; this.patterns = patterns; this.priority = priority; this.registerer = registerer; + this.documentation = documentation; this.data = data; } @@ -48,6 +51,10 @@ public List getPatterns() { return patterns; } + public Documentation getDocumentation() { + return this.documentation; + } + /** * Retrieves a data instance by its identifier. * @param identifier the identifier diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java index 5cfc651fd..4280a843a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.types; +import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.Changer; import io.github.syst3ms.skriptparser.util.StringUtils; @@ -25,6 +26,7 @@ public class Type { private final Changer defaultChanger; @Nullable private final Arithmetic arithmetic; + private final Documentation documentation; /** * Constructs a new Type. @@ -38,8 +40,8 @@ public class Type { *
  • {@code part¦y¦ies} -> {@literal party} and {@literal parties} (irregular plural)
  • * */ - public Type(Class typeClass, String baseName, String pattern) { - this(typeClass, baseName, pattern, null); + public Type(Class typeClass, String baseName, String pattern, Documentation documentation) { + this(typeClass, baseName, pattern, null, documentation); } /** @@ -59,8 +61,9 @@ public Type(Class typeClass, String baseName, String pattern) { public Type(Class typeClass, String baseName, String pattern, - @Nullable Function literalParser) { - this(typeClass, baseName, pattern, literalParser, Objects::toString); + @Nullable Function literalParser, + Documentation documentation) { + this(typeClass, baseName, pattern, literalParser, Objects::toString, documentation); } /** @@ -83,8 +86,9 @@ public Type(Class typeClass, String baseName, String pattern, @Nullable Function literalParser, - Function toStringFunction) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, null); + Function toStringFunction, + Documentation documentation) { + this(typeClass, baseName, pattern, literalParser, toStringFunction, null, documentation); } public Type(Class typeClass, @@ -92,8 +96,9 @@ public Type(Class typeClass, String pattern, @Nullable Function literalParser, Function toStringFunction, - @Nullable Changer defaultChanger) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null); + @Nullable Changer defaultChanger, + Documentation documentation) { + this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null, documentation); } @SuppressWarnings("unchecked") @@ -103,7 +108,8 @@ public Type(Class typeClass, @Nullable Function literalParser, Function toStringFunction, @Nullable Changer defaultChanger, - @Nullable Arithmetic arithmetic) { + @Nullable Arithmetic arithmetic, + Documentation documentation) { this.typeClass = typeClass; this.baseName = baseName; this.literalParser = literalParser; @@ -111,6 +117,7 @@ public Type(Class typeClass, this.pluralForms = StringUtils.getForms(pattern.strip()); this.defaultChanger = defaultChanger; this.arithmetic = arithmetic; + this.documentation = documentation; } public Class getTypeClass() { @@ -141,6 +148,10 @@ public Optional> getDefaultChanger() { return Optional.ofNullable(arithmetic); } + public Documentation getDocumentation() { + return this.documentation; + } + /** * Adds a proper English indefinite article to this type and applies the correct form. * @param plural whether this Type is plural or not @@ -172,4 +183,4 @@ public boolean equals(Object obj) { public int hashCode() { return Arrays.hashCode(pluralForms); } -} \ No newline at end of file +} From a17adc4903a77c51a5b712181cec0bc4b5252770 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sat, 24 Jan 2026 14:43:49 -0800 Subject: [PATCH 07/68] Variables - add methods to copy/clear local variables - this is needed when changing TriggerContext within a section --- .../skriptparser/variables/Variables.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index 1055750e4..cc00f66eb 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -117,4 +117,26 @@ public static void setVariable(String name, @Nullable Object value, @Nullable Tr public static void clearVariables() { variableMap.clearVariables(); } + + /** + * Copy local variables from one TriggerContext to another. + * + * @param from The source TriggerContext + * @param to The destination TriggerContext + */ + public static void copyLocalVariables(TriggerContext from, TriggerContext to) { + if (localVariables.containsKey(from)) { + localVariables.put(to, localVariables.get(from)); + } + } + + /** + * Clear local variables for a specific TriggerContext. + * + * @param ctx The TriggerContext to clear variables for + */ + public static void clearLocalVariables(TriggerContext ctx) { + localVariables.remove(ctx); + } + } From f3f2c0f1d31bff957795c20eb295e2d61e530d38 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 25 Jan 2026 15:18:23 -0800 Subject: [PATCH 08/68] VariableString - temp remove "<>" tags --- .../skriptparser/lang/VariableString.java | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/VariableString.java b/src/main/java/io/github/syst3ms/skriptparser/lang/VariableString.java index 917c40c6e..3c6612cea 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/VariableString.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/VariableString.java @@ -92,29 +92,29 @@ public static Optional newInstance(String s, ParserState parserS } data.add(expression.get()); i += content.get().length() + 1; - } else if (c == '<') { - if (i == charArray.length - 1 - || Character.isWhitespace(charArray[i + 1])) { - sb.append(c); - continue; - } - var content = StringUtils.getBracketContent(s, i + 1, '>'); - if (content.isEmpty()) { - sb.append(c); - continue; - } - logger.recurse(); - var tag = TagManager.parseTag(content.get(), logger); - logger.callback(); - if (tag.isEmpty()) { - return Optional.empty(); - } - if (sb.length() > 0) { - data.add(sb.toString()); - sb.setLength(0); - } - data.add(tag.get()); - i += content.get().length() + ">".length(); + // } else if (c == '<') { +// if (i == charArray.length - 1 +// || Character.isWhitespace(charArray[i + 1])) { +// sb.append(c); +// continue; +// } +// var content = StringUtils.getBracketContent(s, i + 1, '>'); +// if (content.isEmpty()) { +// sb.append(c); +// continue; +// } +// logger.recurse(); +// var tag = TagManager.parseTag(content.get(), logger); +// logger.callback(); +// if (tag.isEmpty()) { +// return Optional.empty(); +// } +// if (sb.length() > 0) { +// data.add(sb.toString()); +// sb.setLength(0); +// } +// data.add(tag.get()); +// i += content.get().length() + ">".length(); } else if (c == '\\') { if (i == charArray.length - 1) { return Optional.empty(); From 76969b859a7bb7cf8778e2eab76cef5eb1131e40 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 25 Jan 2026 20:12:21 -0800 Subject: [PATCH 09/68] Some reworking: - added a bunch of docs (need to do more) - reworked int/number to not use BigInt/Decimal - added float/double --- .../syst3ms/skriptparser/effects/EffAsk.java | 9 +- .../skriptparser/effects/EffPrint.java | 9 +- .../skriptparser/effects/EffShutdown.java | 9 +- .../expressions/CondExprCompare.java | 16 +- .../expressions/CondExprContains.java | 16 +- .../expressions/CondExprIsEmpty.java | 13 +- .../expressions/CondExprIsSet.java | 10 +- .../skriptparser/expressions/ExprAmount.java | 11 +- .../skriptparser/expressions/ExprAnswer.java | 13 +- .../expressions/ExprBooleanOperators.java | 16 +- .../skriptparser/expressions/ExprDateNow.java | 19 +- .../expressions/ExprDefaultValue.java | 13 +- .../expressions/ExprLoopValue.java | 19 +- .../expressions/LitScriptName.java | 12 +- .../registration/DefaultRegistration.java | 517 +++++++++--------- .../skriptparser/sections/SecConditional.java | 14 +- .../skriptparser/sections/SecLoop.java | 18 +- .../skriptparser/sections/SecWhile.java | 12 +- 18 files changed, 393 insertions(+), 353 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffAsk.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffAsk.java index 7a68cdae9..2e1090122 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffAsk.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffAsk.java @@ -23,10 +23,11 @@ public class EffAsk extends Effect { static { - Parser.getMainRegistration().addEffect( - EffAsk.class, - "(ask [for] [input]|input) %string%" - ); + // We don't need this +// Parser.getMainRegistration().addEffect( +// EffAsk.class, +// "(ask [for] [input]|input) %string%" +// ); } private Expression message; diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffPrint.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffPrint.java index 43f20816f..ec060fe71 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffPrint.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffPrint.java @@ -17,10 +17,11 @@ */ public class EffPrint extends Effect { static { - Parser.getMainRegistration().addEffect( - EffPrint.class, - "print %strings% [to [the] console]" - ); + // We don't really need this +// Parser.getMainRegistration().addEffect( +// EffPrint.class, +// "print %strings% [to [the] console]" +// ); } private Expression expression; diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffShutdown.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffShutdown.java index 5fcb8411c..78edafdbe 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffShutdown.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffShutdown.java @@ -18,10 +18,11 @@ */ public class EffShutdown extends Effect { static { - Parser.getMainRegistration().addEffect( - EffShutdown.class, - "shut[ ]down [[the] [current] session]" - ); + // We shouldn't be using this on a Hytale Server +// Parser.getMainRegistration().addEffect( +// EffShutdown.class, +// "shut[ ]down [[the] [current] session]" +// ); } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprCompare.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprCompare.java index 660d8720a..5fe64a20d 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprCompare.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprCompare.java @@ -51,13 +51,15 @@ public class CondExprCompare extends ConditionalExpression { }); static { - Parser.getMainRegistration().addExpression( - CondExprCompare.class, - Boolean.class, - true, - 1, - PATTERNS.getPatterns() - ); + Parser.getMainRegistration().newExpression(CondExprCompare.class, Boolean.class, true, PATTERNS.getPatterns()) + .name("Compare") + .description("Compares two expressions/objects.") + .examples("if {_number} is greater than {_number2}:", + "if {_list::*} is equal to {_list::*}:", + "if {_string} is not equal to {_string2}:", + "if 1 = 1:") + .since("INSERT VERSION") + .register(); } private Expression first; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprContains.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprContains.java index ecd93c775..da1be788f 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprContains.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprContains.java @@ -25,14 +25,18 @@ */ public class CondExprContains extends ConditionalExpression { static { - Parser.getMainRegistration().addExpression( - CondExprContains.class, - Boolean.class, - true, - 2, + Parser.getMainRegistration().newExpression( + CondExprContains.class, Boolean.class, true, "%string% [1:does(n't| not)] contain[s] %string%", "%objects% [1:do[es](n't| not)] contain[s] %objects%" - ); + ) + .setPriority(2) + .name("Contains") + .description("Checks if a given list of objects contain a given element. You can also check if a string contains another string.") + .examples("if {_string} contains \"hello\":", + "if {_list::*} contains player:") + .since("INSERT VERSION") + .register(); } private Expression first, second; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsEmpty.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsEmpty.java index b49956434..01f96a3e5 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsEmpty.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsEmpty.java @@ -20,12 +20,15 @@ */ public class CondExprIsEmpty extends ConditionalExpression { static { - Parser.getMainRegistration().addExpression(CondExprIsEmpty.class, - Boolean.class, - true, + Parser.getMainRegistration().newExpression(CondExprIsEmpty.class, Boolean.class, true, "%objects% (is|are)[1:( not|n't)] empty", - "%strings% (is|are)[1:( not|n't)] [an] empty string[s]" - ); + "%strings% (is|are)[1:( not|n't)] [an] empty string[s]") + .name("Is Empty") + .description("Checks if a given string or list is empty.") + .examples("if {_list::*} is empty:", + "if {_string} is not empty:") + .since("INSERT VERSION") + .register(); } private Expression expression; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java index 5d2ef4e6a..92822037b 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java @@ -15,12 +15,16 @@ */ public class CondExprIsSet extends PropertyConditional { static { - Parser.getMainRegistration().addPropertyConditional( + Parser.getMainRegistration().newPropertyConditional( CondExprIsSet.class, "~objects", ConditionalType.BE, - "set" - ); + "set") + .name("Is Set") + .description("Checks if an expression/object is set or not.") + .examples("if {_var} is set:") + .since("INSERT VERSION") + .register(); } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java index a4d11c74f..90b71f6af 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java @@ -26,12 +26,17 @@ */ public class ExprAmount extends PropertyExpression { static { - Parser.getMainRegistration().addPropertyExpression( + Parser.getMainRegistration().newPropertyExpression( ExprAmount.class, Number.class, "~objects", - "[1:recursive] (amount|number|size)" - ); + "[1:recursive] (amount|number|size)") + .name("Amount") + .description("Returns the amount of elements in a given list.") + .examples("set {_size} to size of {_var::*}", + "if size of all players > 10:") + .since("INSERT VERSION") + .register(); } private boolean recursive; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAnswer.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAnswer.java index a689640ab..e83080c9b 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAnswer.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAnswer.java @@ -22,12 +22,13 @@ public class ExprAnswer implements Expression { static { - Parser.getMainRegistration().addExpression( - ExprAnswer.class, - String.class, - true, - "[the] [last] (answer|response)" - ); + // We won't be needing this +// Parser.getMainRegistration().addExpression( +// ExprAnswer.class, +// String.class, +// true, +// "[the] [last] (answer|response)" +// ); } private static final Map answers = new HashMap<>(); diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprBooleanOperators.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprBooleanOperators.java index 86ff8c6d0..e23a70985 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprBooleanOperators.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprBooleanOperators.java @@ -20,14 +20,18 @@ */ public class ExprBooleanOperators implements Expression { static { - Parser.getMainRegistration().addExpression( - ExprBooleanOperators.class, - Boolean.class, - true, + Parser.getMainRegistration().newExpression( + ExprBooleanOperators.class, Boolean.class, true, "not %=boolean%", "%=boolean% (or|\\|\\|) %=boolean%", - "%=boolean% (and|&&) %=boolean%" - ); + "%=boolean% (and|&&) %=boolean%") + .name("Boolean Operators") + .description("Performs boolean operations on booleans.") + .examples("if {_boolean} and {_boolean2}:", + "if {_boolean} or {_boolean2}:", + "if not {_boolean}:") + .since("INSERT VERSION") + .register(); } private int pattern; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateNow.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateNow.java index fbc110709..b8a798de4 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateNow.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateNow.java @@ -18,14 +18,17 @@ * @author Mwexim */ public class ExprDateNow implements Expression { - static { - Parser.getMainRegistration().addExpression( - ExprDateNow.class, - SkriptDate.class, - true, - "(0:yesterday|1:now|2:tomorrow)" - ); - } + static { + Parser.getMainRegistration().newExpression( + ExprDateNow.class, + SkriptDate.class, + true, + "(0:yesterday|1:now|2:tomorrow)") + .name("Current Date") + .description("The current date, the one from yesterday or the one from tomorrow.") + .since("INSERT VERSION") + .register(); + } private int mark; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDefaultValue.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDefaultValue.java index 3e525b488..bb65514c5 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDefaultValue.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDefaultValue.java @@ -15,12 +15,13 @@ */ public class ExprDefaultValue implements Expression { static { - Parser.getMainRegistration().addExpression( - ExprDefaultValue.class, - Object.class, - false, - "%objects% (otherwise|?) %objects%" - ); + Parser.getMainRegistration().newExpression(ExprDefaultValue.class, Object.class, false, + "%objects% (otherwise|?) %objects%") + .name("Default Value") + .description("Returns the first expression if it's set, otherwise the second expression.") + .examples("set {_var} to {_otherVarIfSet} otherwise \"world\"", + "send {_varIfSet} ? \"ruh roh, no message\"") + .since("INSERT VERSION"); } private Expression firstValue, secondValue; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java index 804213270..7ec31186b 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java @@ -32,12 +32,19 @@ */ public class ExprLoopValue extends SectionValue { static { - Parser.getMainRegistration().addExpression( - ExprLoopValue.class, - Object.class, - true, - "[the] loop-<.+>" - ); + Parser.getMainRegistration().newExpression(ExprLoopValue.class, Object.class, true, + "[the] loop-<.+>") + .name("Loop Value/Index") + .description("The currently looped value or index.", + "Index will always be represented as a string.") + .examples("loop all players:", + "\tsend \"hi\" to loop-player", + "", + "loop {_var::*}", + "\tif loop-index = \"bob\":", + "\t\tadd loop-value to inventory of player") + .since("INSERT VERSION") + .register(); } private SecLoop loop; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/LitScriptName.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/LitScriptName.java index ef89db3d7..2e2cfb3cf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/LitScriptName.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/LitScriptName.java @@ -19,13 +19,13 @@ */ public class LitScriptName implements Literal { static { - Parser.getMainRegistration().addExpression( - LitScriptName.class, - String.class, - true, + Parser.getMainRegistration().newExpression(LitScriptName.class, String.class, true, "[the] script[['s] name]", - "name of [the] script" - ); + "name of [the] script") + .name("Script Name") + .description("The name of the current executed script, without the extension.") + .since("INSERT VERSION") + .register(); } private SkriptLogger logger; diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 8ef777551..87441e133 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -12,14 +12,8 @@ import io.github.syst3ms.skriptparser.util.SkriptDate; import io.github.syst3ms.skriptparser.util.Time; import io.github.syst3ms.skriptparser.util.color.Color; -import io.github.syst3ms.skriptparser.util.math.BigDecimalMath; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import java.util.stream.IntStream; @@ -27,8 +21,6 @@ * A class registering features such as types and comparators at startup. */ public class DefaultRegistration { - private static final String INTEGER_PATTERN = "-?[0-9]+"; - private static final String DECIMAL_PATTERN = "-?[0-9]+\\.[0-9]+"; public static void register() { SkriptRegistration registration = Parser.getMainRegistration(); @@ -36,297 +28,300 @@ public static void register() { /* * Classes */ - registration.addType( - Object.class, - "object", - "object@s" - ); + registration.newType(Object.class, "object", "object@s") + .name("Object") + .description("Any possible object.") + .since("INSERT VERSION") + .register(); + + registration.newType(Number.class, "number", "number@s") + .name("Number") + .description("A number (can be an integer/float/double/etc).") + .since("INSERT VERSION") + .literalParser(s -> { + if (s.contains(".")) return Double.parseDouble(s); + else return Long.parseLong(s); + }) + .toStringFunction(Object::toString) + .arithmetic(new Arithmetic() { + @Override + public Number difference(Number first, Number second) { + return first.doubleValue() - second.doubleValue(); + } - registration.newType(Number.class,"number", "number@s") - .literalParser(s -> { - if (s.startsWith("_") || s.endsWith("_")) - return null; - s = s.replaceAll("_", ""); - if (s.matches(DECIMAL_PATTERN)) { - return new BigDecimal(s); - } else if (s.matches(INTEGER_PATTERN)) { - return new BigInteger(s); - } else { - return null; - } - }) - .toStringFunction(o -> { - if (o instanceof BigDecimal) { - BigDecimal bd = (BigDecimal) o; - int significantDigits = bd.scale() <= 0 - ? bd.precision() + bd.stripTrailingZeros().scale() - : bd.precision(); - return ((BigDecimal) o).setScale(Math.min(10, significantDigits), RoundingMode.HALF_UP) - .stripTrailingZeros() - .toPlainString(); - } else { - return o.toString(); - } - }) - .arithmetic(new Arithmetic() { - @Override - public Number difference(Number first, Number second) { - if (first instanceof BigDecimal || second instanceof BigDecimal) { - var f = BigDecimalMath.getBigDecimal(first); - var s = BigDecimalMath.getBigDecimal(second); - return f.subtract(s).abs(); - } else { - assert first instanceof BigInteger && second instanceof BigInteger; - return ((BigInteger) first).subtract(((BigInteger) second)).abs(); - } - } - - @Override - public Number add(Number value, Number difference) { - if (value instanceof BigDecimal || difference instanceof BigDecimal) { - var v = BigDecimalMath.getBigDecimal(value); - var d = BigDecimalMath.getBigDecimal(difference); - return v.add(d); - } else { - assert value instanceof BigInteger && difference instanceof BigInteger; - return ((BigInteger) value).add(((BigInteger) difference)); - } - } - - @Override - public Number subtract(Number value, Number difference) { - if (value instanceof BigDecimal || difference instanceof BigDecimal) { - var v = BigDecimalMath.getBigDecimal(value); - var d = BigDecimalMath.getBigDecimal(difference); - return v.subtract(d); - } else { - assert value instanceof BigInteger && difference instanceof BigInteger; - return ((BigInteger) value).subtract(((BigInteger) difference)); - } - } - - @Override - public Class getRelativeType() { - return Number.class; - } - }).register(); - - registration.newType(BigInteger.class, "integer", "integer@s") - .literalParser(s -> { - if (s.startsWith("_") || s.endsWith("_")) - return null; - s = s.replaceAll("_", ""); - return s.matches(INTEGER_PATTERN) ? new BigInteger(s) : null; - }) - .arithmetic(new Arithmetic() { - @Override - public BigInteger difference(BigInteger first, BigInteger second) { - return first.subtract(second).abs(); - } - - @Override - public BigInteger add(BigInteger value, BigInteger difference) { - return value.add(difference); - } - - @Override - public BigInteger subtract(BigInteger value, BigInteger difference) { - return value.subtract(difference); - } - - @Override - public Class getRelativeType() { - return BigInteger.class; - } - }) - .register(); - - registration.addType( - String.class, - "string", - "string@s" - ); + @Override + public Number add(Number value, Number difference) { + return value.doubleValue() + difference.doubleValue(); + } + + @Override + public Number subtract(Number value, Number difference) { + return value.doubleValue() - difference.doubleValue(); + } + + @Override + public Class getRelativeType() { + return Number.class; + } + }).register(); + + registration.newType(Integer.class, "integer", "integer@s") + .literalParser(Integer::parseInt) + .name("Integer") + .description("A whole number.") + .since("INSERT VERSION") + .arithmetic(new Arithmetic() { + @Override + public Integer difference(Integer first, Integer second) { + return first - second; + } + + @Override + public Integer add(Integer value, Integer difference) { + return value + difference; + } + + @Override + public Integer subtract(Integer value, Integer difference) { + return value - difference; + } + + @Override + public Class getRelativeType() { + return Integer.class; + } + }) + .register(); + + registration.newType(Float.class, "float", "float@s") + .name("Float") + .description("A floating-point number.") + .since("INSERT VERSION") + .literalParser(Float::parseFloat) + .arithmetic(new Arithmetic() { + @Override + public Float difference(Float first, Float second) { + return first - second; + } + + @Override + public Float add(Float value, Float difference) { + return value + difference; + } + + @Override + public Float subtract(Float value, Float difference) { + return value - difference; + } + + @Override + public Class getRelativeType() { + return Float.class; + } + }) + .register(); + + registration.newType(Double.class, "double", "double@s") + .name("Double") + .description("A double floating-point number.") + .since("INSERT VERSION") + .literalParser(Double::parseDouble) + .arithmetic(new Arithmetic() { + @Override + public Double difference(Double first, Double second) { + return first - second; + } + + @Override + public Double add(Double value, Double difference) { + return value + difference; + } + + @Override + public Double subtract(Double value, Double difference) { + return value - difference; + } + + @Override + public Class getRelativeType() { + return Double.class; + } + }) + .register(); + + registration.newType(String.class, "string", "string@s") + .name("String") + .description("A string of characters.") + .examples("set {_string} to \"Hello World!\"") + .since("INSERT VERSION") + .register(); registration.newType(Boolean.class, "boolean", "boolean@s") - .literalParser(s -> { - if (s.equalsIgnoreCase("true")) { - return true; - } else if (s.equalsIgnoreCase("false")) { - return false; - } else { - return null; - } - }) - .toStringFunction(String::valueOf) - .register(); + .literalParser(s -> { + if (s.equalsIgnoreCase("true")) { + return true; + } else if (s.equalsIgnoreCase("false")) { + return false; + } else { + return null; + } + }) + .name("Boolean") + .description("A boolean value, represented as 'true' or 'false'.") + .since("INSERT VERSION") + .toStringFunction(String::valueOf) + .register(); registration.newType(Type.class, "type", "type@s") - .literalParser(s -> TypeManager.getByExactName(s.toLowerCase()).orElse(null)) - .toStringFunction(Type::getBaseName) - .register(); + .name("Type") + .description("Represents a type/class.") + .since("INSERT VERSION") + .literalParser(s -> TypeManager.getByExactName(s.toLowerCase()).orElse(null)) + .toStringFunction(Type::getBaseName) + .register(); registration.newType(Color.class, "color", "color@s") - .literalParser(s -> Color.ofLiteral(s).orElse(null)) - .toStringFunction(Color::toString) - .register(); + .name("Color") + .description("Represents a color.") + .since("INSERT VERSION") + .literalParser(s -> Color.ofLiteral(s).orElse(null)) + .toStringFunction(Color::toString) + .register(); registration.newType(Duration.class, "duration", "duration@s") - .literalParser(s -> DurationUtils.parseDuration(s).orElse(null)) - .toStringFunction(DurationUtils::toStringDuration) - .arithmetic(new Arithmetic() { - @Override - public Duration difference(Duration first, Duration second) { - return first.minus(second).abs(); - } - - @Override - public Duration add(Duration value, Duration difference) { - return value.plus(difference); - } - - @Override - public Duration subtract(Duration value, Duration difference) { - return value.minus(difference); - } - - @Override - public Class getRelativeType() { - return Duration.class; - } - }) - .register(); + .name("Duration") + .description("Represents a time-based amount of time, such as '34.5 seconds'.") + .literalParser(s -> DurationUtils.parseDuration(s).orElse(null)) + .toStringFunction(DurationUtils::toStringDuration) + .arithmetic(new Arithmetic() { + @Override + public Duration difference(Duration first, Duration second) { + return first.minus(second).abs(); + } + + @Override + public Duration add(Duration value, Duration difference) { + return value.plus(difference); + } + + @Override + public Duration subtract(Duration value, Duration difference) { + return value.minus(difference); + } + + @Override + public Class getRelativeType() { + return Duration.class; + } + }) + .register(); registration.newType(SkriptDate.class, "date", "date@s") - .toStringFunction(SkriptDate::toString) - .arithmetic(new Arithmetic() { - @Override - public Duration difference(SkriptDate first, SkriptDate second) { - return first.difference(second); - } - - @Override - public SkriptDate add(SkriptDate value, Duration difference) { - return value.plus(difference); - } - - @Override - public SkriptDate subtract(SkriptDate value, Duration difference) { - return value.minus(difference); - } - - @Override - public Class getRelativeType() { - return Duration.class; - } - }) - .register(); + .toStringFunction(SkriptDate::toString) + .arithmetic(new Arithmetic() { + @Override + public Duration difference(SkriptDate first, SkriptDate second) { + return first.difference(second); + } + + @Override + public SkriptDate add(SkriptDate value, Duration difference) { + return value.plus(difference); + } + + @Override + public SkriptDate subtract(SkriptDate value, Duration difference) { + return value.minus(difference); + } + + @Override + public Class getRelativeType() { + return Duration.class; + } + }) + .name("Date") + .description("A date, represented as a string in the format 'yyyy-MM-dd HH:mm:ss'.") + .since("INSERT VERSION") + .register(); registration.newType(Time.class, "time", "time@s") - .literalParser(s -> Time.parse(s).orElse(null)) - .toStringFunction(Time::toString) - .arithmetic(new Arithmetic() { - @Override - public Duration difference(Time first, Time second) { - return first.difference(second); - } - - @Override - public Time add(Time value, Duration difference) { - return value.plus(difference); - } - - @Override - public Time subtract(Time value, Duration difference) { - return value.minus(difference); - } - - @Override - public Class getRelativeType() { - return Duration.class; - } - }) - .register(); + .literalParser(s -> Time.parse(s).orElse(null)) + .toStringFunction(Time::toString) + .arithmetic(new Arithmetic() { + @Override + public Duration difference(Time first, Time second) { + return first.difference(second); + } + + @Override + public Time add(Time value, Duration difference) { + return value.plus(difference); + } + + @Override + public Time subtract(Time value, Duration difference) { + return value.minus(difference); + } + + @Override + public Class getRelativeType() { + return Duration.class; + } + }) + .name("Time") + .description("A time, represented as a string in the format 'HH:mm:ss'.") + .since("INSERT VERSION") + .register(); /* * Comparators */ Comparators.registerComparator( - Number.class, - Number.class, - new Comparator<>(true) { - @Override - public Relation apply(Number number, Number number2) { - if (number instanceof BigDecimal || number2 instanceof BigDecimal) { - BigDecimal bd = BigDecimalMath.getBigDecimal(number).setScale(10, RoundingMode.HALF_UP); - BigDecimal bd2 = BigDecimalMath.getBigDecimal(number2).setScale(10, RoundingMode.HALF_UP); - return Relation.get(bd.compareTo(bd2)); - } else { - assert number instanceof BigInteger && number2 instanceof BigInteger; - return Relation.get(((BigInteger) number).compareTo((BigInteger) number2)); - } - } + Number.class, + Number.class, + new Comparator<>(true) { + @Override + public Relation apply(Number number, Number number2) { + return Relation.get(number.equals(number2)); } + } ); Comparators.registerComparator( - Duration.class, - Duration.class, - new Comparator<>(true) { - @Override - public Relation apply(Duration duration, Duration duration2) { - return Relation.get(duration.compareTo(duration2)); - } + Duration.class, + Duration.class, + new Comparator<>(true) { + @Override + public Relation apply(Duration duration, Duration duration2) { + return Relation.get(duration.compareTo(duration2)); } + } ); /* * Ranges */ - Ranges.registerRange( - BigInteger.class, - BigInteger.class, - (l, r) -> { - if (l.compareTo(r) >= 0) { - return new BigInteger[0]; - } else { - List elements = new ArrayList<>(); - BigInteger current = l; - do { - elements.add(current); - current = current.add(BigInteger.ONE); - } while (current.compareTo(r) <= 0); - return elements.toArray(new BigInteger[0]); - } - } - ); // Actually a character range Ranges.registerRange( - String.class, - String.class, - (l, r) -> { - if (l.length() != 1 || r.length() != 1) - return new String[0]; - char leftChar = l.charAt(0), rightChar = r.charAt(0); - return IntStream.range(leftChar, rightChar + 1) - .mapToObj(i -> Character.toString((char) i)) - .toArray(String[]::new); - } + String.class, + String.class, + (l, r) -> { + if (l.length() != 1 || r.length() != 1) + return new String[0]; + char leftChar = l.charAt(0), rightChar = r.charAt(0); + return IntStream.range(leftChar, rightChar + 1) + .mapToObj(i -> Character.toString((char) i)) + .toArray(String[]::new); + } ); /* * Converters */ - registration.addConverter(Number.class, BigInteger.class, n -> { - if (n instanceof BigInteger) { - return Optional.of((BigInteger) n); - } else { - return Optional.of(BigInteger.valueOf(n.longValue())); - } - }); registration.addConverter(SkriptDate.class, Time.class, da -> Optional.of(Time.of(da))); registration.register(true); // Ignoring logs here, we control the input } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java index db44052a1..ef43ef4f4 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java @@ -29,13 +29,13 @@ */ public class SecConditional extends CodeSection { static { - Parser.getMainRegistration().addSection( - SecConditional.class, - 6, - "if %=boolean%", - "else if %=boolean%", - "else" - ); + Parser.getMainRegistration().newSection(SecConditional.class, + "if %=boolean%", "else if %=boolean%", "else") + .setPriority(6) + .name("Condition") + .description("Executes code within its section if a condition is verified to be true.") + .since("INSERT VERSION") + .register(); } private ConditionalMode mode; diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java index 0b85ab363..08d359076 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java @@ -35,13 +35,17 @@ * @author Mwexim */ public class SecLoop extends ArgumentSection implements Continuable, SelfReferencing { - static { - Parser.getMainRegistration().addSection( - SecLoop.class, - "loop %integer% times", - "loop %objects%" - ); - } + static { + Parser.getMainRegistration().newSection(SecLoop.class, "loop %integer% times", "loop %objects%") + .name("Loop") + .description("Loops over all the values in a list or loop x number of times.") + .examples("loop 10 times:", + "\tsend \"hello:%loop-number%\" to all players", + "loop all players:", + "\tadd {_item} to inventory of loop-player") + .since("INSERT VERSION") + .register(); + } @Nullable private Expression expression; diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecWhile.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecWhile.java index 2821dfda6..680a80762 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecWhile.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecWhile.java @@ -27,10 +27,14 @@ */ public class SecWhile extends CodeSection implements Continuable, SelfReferencing { static { - Parser.getMainRegistration().addSection( - SecWhile.class, - "while %=boolean%" - ); + Parser.getMainRegistration().newSection(SecWhile.class, "while %=boolean%") + .name("While Loop") + .description("This section will continue to execute/loop until the condition is no longer met.") + .examples("while {_var} > 10:", + "\tremove 1 from {_var}", + "\twait 1 minute") + .since("INSERT VERSION") + .register(); } @Nullable From 324a5556343d6ea9aa487c115b96a084967d2a79 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 25 Jan 2026 20:46:24 -0800 Subject: [PATCH 10/68] temp disable some stuff and fix some errors --- .../github/syst3ms/skriptparser/Skript.java | 32 +++++++++-------- .../skriptparser/parsing/ScriptLoader.java | 4 +-- .../registration/DefaultRegistration.java | 36 ++++++++++++++++--- .../registration/SkriptAddon.java | 5 +-- .../syst3ms/skriptparser/TestAddon.java | 10 +++--- .../skriptparser/log/SkriptLoggerTest.java | 7 ++-- .../skriptparser/parsing/VariablesTest.java | 6 ++-- 7 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/Skript.java b/src/main/java/io/github/syst3ms/skriptparser/Skript.java index b6c8ce1c1..37f95975b 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/Skript.java +++ b/src/main/java/io/github/syst3ms/skriptparser/Skript.java @@ -18,7 +18,9 @@ import java.time.Duration; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * The {@link SkriptAddon} representing Skript itself @@ -27,53 +29,53 @@ public class Skript extends SkriptAddon { private final String[] mainArgs; - private final List mainTriggers = new ArrayList<>(); - private final List periodicalTriggers = new ArrayList<>(); - private final List whenTriggers = new ArrayList<>(); - private final List atTimeTriggers = new ArrayList<>(); + private final Map> mainTriggers = new HashMap<>(); + private final Map> periodicalTriggers = new HashMap<>(); + private final Map> whenTriggers = new HashMap<>(); + private final Map> atTimeTriggers = new HashMap<>(); public Skript(String[] mainArgs) { this.mainArgs = mainArgs; } @Override - public void handleTrigger(Trigger trigger) { + public void handleTrigger(String scriptName, Trigger trigger) { SkriptEvent event = trigger.getEvent(); if (!canHandleEvent(event)) return; if (event instanceof EvtScriptLoad) { - mainTriggers.add(trigger); + mainTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); } else if (event instanceof EvtPeriodical) { - periodicalTriggers.add(trigger); + periodicalTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); } else if (event instanceof EvtWhen) { - whenTriggers.add(trigger); + whenTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); } else if (event instanceof EvtAtTime) { - atTimeTriggers.add(trigger); + atTimeTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); } } @Override public void finishedLoading() { - for (Trigger trigger : mainTriggers) { + for (Trigger trigger : mainTriggers.values().stream().flatMap(List::stream).toList()) { Statement.runAll(trigger, new ScriptLoadContext(mainArgs)); } - for (Trigger trigger : periodicalTriggers) { + for (Trigger trigger : periodicalTriggers.values().stream().flatMap(List::stream).toList()) { var ctx = new PeriodicalContext(); var dur = ((EvtPeriodical) trigger.getEvent()).getDuration().getSingle().orElseThrow(AssertionError::new); ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), dur); } - for (Trigger trigger : whenTriggers) { + for (Trigger trigger : whenTriggers.values().stream().flatMap(List::stream).toList()) { var ctx = new WhenContext(); ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), Duration.ofMillis(DurationUtils.TICK)); } - for (Trigger trigger : atTimeTriggers) { + for (Trigger trigger : atTimeTriggers.values().stream().flatMap(List::stream).toList()) { var ctx = new AtTimeContext(); var time = ((EvtAtTime) trigger.getEvent()).getTime().getSingle().orElseThrow(AssertionError::new); var initialDelay = (Time.now().getTime().isAfter(time.getTime()) - ? Time.now().difference(Time.LATEST).plus(time.difference(Time.MIDNIGHT)) - : Time.now().difference(time)); + ? Time.now().difference(Time.LATEST).plus(time.difference(Time.MIDNIGHT)) + : Time.now().difference(time)); ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), initialDelay, Duration.ofDays(1)); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java index ddfb07ffc..d01415308 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java @@ -88,7 +88,7 @@ public static List loadScript(Path scriptPath, SkriptLogger logger, bo logger.setLine(unloaded.getLine()); var loaded = unloaded.getTrigger(); loaded.loadSection(unloaded.getSection(), unloaded.getParserState(), logger); - unloaded.getEventInfo().getRegisterer().handleTrigger(loaded); + unloaded.getEventInfo().getRegisterer().handleTrigger(scriptName,loaded); triggerMap.putOne(scriptName, loaded); } logger.finalizeLogs(); @@ -140,4 +140,4 @@ public static List loadItems(FileSection section, ParserState parserS public static MultiMap getTriggerMap() { return triggerMap; } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 87441e133..d7ded0f29 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -39,8 +39,13 @@ public static void register() { .description("A number (can be an integer/float/double/etc).") .since("INSERT VERSION") .literalParser(s -> { - if (s.contains(".")) return Double.parseDouble(s); - else return Long.parseLong(s); + if (s == null) return null; + try { + if (s.contains(".")) return Double.parseDouble(s); + else return Long.parseLong(s); + } catch (NumberFormatException e) { + return null; + } }) .toStringFunction(Object::toString) .arithmetic(new Arithmetic() { @@ -66,7 +71,14 @@ public Class getRelativeType() { }).register(); registration.newType(Integer.class, "integer", "integer@s") - .literalParser(Integer::parseInt) + .literalParser(s -> { + if (s == null) return null; + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + return null; + } + }) .name("Integer") .description("A whole number.") .since("INSERT VERSION") @@ -97,7 +109,14 @@ public Class getRelativeType() { .name("Float") .description("A floating-point number.") .since("INSERT VERSION") - .literalParser(Float::parseFloat) + .literalParser(s -> { + if (s == null) return null; + try { + return Float.parseFloat(s); + } catch (NumberFormatException e) { + return null; + } + }) .arithmetic(new Arithmetic() { @Override public Float difference(Float first, Float second) { @@ -125,7 +144,14 @@ public Class getRelativeType() { .name("Double") .description("A double floating-point number.") .since("INSERT VERSION") - .literalParser(Double::parseDouble) + .literalParser(s -> { + if (s == null) return null; + try { + return Double.parseDouble(s); + } catch (NumberFormatException e) { + return null; + } + }) .arithmetic(new Arithmetic() { @Override public Double difference(Double first, Double second) { diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptAddon.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptAddon.java index 285117eeb..f0c0b44ed 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptAddon.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptAddon.java @@ -32,10 +32,11 @@ public static List getAddons() { /** * When a {@linkplain Trigger} is successfully parsed, it is "broadcast" to all addons through this method, * in the hopes that one of them will be able to handle it. + * @param scriptName the name of the script the trigger is in * @param trigger the trigger to be handled * @see #canHandleEvent(SkriptEvent) */ - public abstract void handleTrigger(Trigger trigger); + public abstract void handleTrigger(String scriptName, Trigger trigger); /** * Is called when a script has finished loading. Optionally overridable. @@ -44,7 +45,7 @@ public void finishedLoading() {} /** * Checks to see whether the given event has been registered by this SkriptAddon ; a basic way to filter out - * triggers you aren't able to deal with in {@link SkriptAddon#handleTrigger(Trigger)}. + * triggers you aren't able to deal with in {@link SkriptAddon#handleTrigger(String,Trigger)}. * A simple example of application can be found in {@link Skript#handleTrigger(Trigger)}. * @param event the event to check * @return whether the event can be handled by the addon or not diff --git a/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java b/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java index fc1e85115..e0879864a 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java +++ b/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java @@ -8,26 +8,28 @@ import io.github.syst3ms.skriptparser.syntax.TestContext; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class TestAddon extends SkriptAddon { - private final List testTriggers = new ArrayList<>(); + private final Map> testTriggers = new HashMap<>(); @Override - public void handleTrigger(Trigger trigger) { + public void handleTrigger(String scriptName, Trigger trigger) { SkriptEvent event = trigger.getEvent(); if (!canHandleEvent(event)) return; if (event instanceof EvtTest) { - testTriggers.add(trigger); + testTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); } } @Override public void finishedLoading() { - for (Trigger trigger : testTriggers) { + for (Trigger trigger : testTriggers.values().stream().flatMap(List::stream).toList()) { Statement.runAll(trigger, new TestContext.SubTestContext()); } diff --git a/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java b/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java index 55079656a..4cd8a5e6b 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java @@ -23,9 +23,10 @@ public void skriptLoggerTest() { logger.finalizeLogs(); assertTrue(noMatchFound.isEmpty() && logger.close().get(0).getMessage().startsWith("No expression")); logger = new SkriptLogger(); - Optional> wrongNumber = SyntaxParser.parseExpression("range from 1 to 3", SyntaxParser.OBJECT_PATTERN_TYPE, parserState, logger); - logger.finalizeLogs(); - assertTrue(wrongNumber.isEmpty() && logger.close().get(0).getMessage().startsWith("A single")); +// Optional> wrongNumber = SyntaxParser.parseExpression("range from 1 to 3", SyntaxParser.OBJECT_PATTERN_TYPE, parserState, logger); +// logger.finalizeLogs(); +// assertTrue(wrongNumber.isEmpty() && logger.close().get(0).getMessage().startsWith("A single")); +// logger = new SkriptLogger(); Optional> wrongRange = SyntaxParser.parseBooleanExpression("1 is between \"a\" and \"b\"", SyntaxParser.MAYBE_CONDITIONAL, parserState, logger); logger.finalizeLogs(); diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/VariablesTest.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/VariablesTest.java index 4bf297015..180603c58 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/parsing/VariablesTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/parsing/VariablesTest.java @@ -64,13 +64,13 @@ public void testVariables() { PatternType numberType = new PatternType<>(TypeManager.getByClassExact(Number.class).orElseThrow(AssertionError::new), true); run(SyntaxParser.parseEffect("set {number} to 5", parserState, logger)); assertExpressionTypeEquals( - BigInteger.class, + Number.class, SyntaxParser.parseExpression("{number}", numberType, parserState, logger) ); run(SyntaxParser.parseEffect("set {number} to 5.2", parserState, logger)); assertExpressionTypeEquals( - BigDecimal.class, + Number.class, SyntaxParser.parseExpression("{number}", numberType, parserState, logger) ); } -} \ No newline at end of file +} From 81ae9b0fd84fd4cc9c7b3a9092c0187e1b6206b4 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 25 Jan 2026 22:38:20 -0800 Subject: [PATCH 11/68] DurationUtils - add year and week, also add stringusage for Types --- .../registration/DefaultRegistration.java | 2 ++ .../skriptparser/util/DurationUtils.java | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index d7ded0f29..c34cac575 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -217,6 +217,8 @@ public Class getRelativeType() { registration.newType(Duration.class, "duration", "duration@s") .name("Duration") .description("Represents a time-based amount of time, such as '34.5 seconds'.") + .usage(DurationUtils.getUsage()) + .since("INSERT VERSION") .literalParser(s -> DurationUtils.parseDuration(s).orElse(null)) .toStringFunction(DurationUtils::toStringDuration) .arithmetic(new Arithmetic() { diff --git a/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java b/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java index 234065498..7da955817 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java +++ b/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java @@ -15,9 +15,9 @@ public class DurationUtils { * about a certain time unit. Sadly, Java does not allow to create a * clean alternative for this. */ - private static final String[] unitPatterns = {"days?", "hours?", "minutes?", "seconds?", "milli(second)?s?"}; - private static final String[] unitNames = {"day", "hour", "minute", "second", "millisecond"}; - private static final int[] unitMillis = {86_400_000, 3_600_000, 60_000, 1000, 1}; + private static final String[] unitPatterns = {"years?", "weeks?", "days?", "hours?", "minutes?", "seconds?", "milli(second)?s?"}; + private static final String[] unitNames = {"year", "week", "day", "hour", "minute", "second", "millisecond"}; + private static final long[] unitMillis = {31_536_000_000L, 604_800_000, 86_400_000, 3_600_000, 60_000, 1000, 1}; public static Optional parseDuration(String value) { if (value.isBlank()) @@ -66,7 +66,7 @@ public static Optional parseDuration(String value) { unit = unit.substring(0, unit.length() - 1); } - int millis = -1; + long millis = -1; int iteration = 0; for (int j = 0; j < unitPatterns.length; j++) { if (unit.matches(unitPatterns[j]) && !usedUnits[iteration]) { @@ -81,7 +81,7 @@ public static Optional parseDuration(String value) { if (millis == -1) return Optional.empty(); - duration += delta * millis; + duration += (long) (delta * millis); } return Optional.of(Duration.ofMillis(duration)); } @@ -95,10 +95,10 @@ public static String toStringDuration(Duration duration) { long result = Math.floorDiv(millis, unitMillis[i]); if (result > 0) { builder.append(first ? "" : ", ") - .append(result == 1 ? (unitNames[i].equals("hour") ? "an" : "a") : result) - .append(' ') - .append(unitNames[i]) - .append(result > 1 ? "s" : ""); + .append(result == 1 ? (unitNames[i].equals("hour") ? "an" : "a") : result) + .append(' ') + .append(unitNames[i]) + .append(result > 1 ? "s" : ""); millis -= result * unitMillis[i]; first = false; } @@ -109,4 +109,9 @@ public static String toStringDuration(Duration duration) { return builder.toString(); } + + public static String getUsage() { + return String.join(", ", unitNames); + } + } From 7ba0b46f8ccbd470e17772ca31a285b2e6a3ba8e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 06:50:11 -0800 Subject: [PATCH 12/68] PropertyExpression - refactor --- .../skriptparser/expressions/ExprAmount.java | 9 +- .../expressions/ExprColorValues.java | 87 +++++++++---------- .../expressions/ExprDateTimestamp.java | 14 +-- .../skriptparser/expressions/ExprLength.java | 13 ++- .../lang/properties/PropertyExpression.java | 4 +- .../registration/SkriptRegistration.java | 12 +-- 6 files changed, 64 insertions(+), 75 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java index 90b71f6af..3481b2cb8 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java @@ -24,13 +24,10 @@ * @since ALPHA * @author Olyno, Mwexim */ -public class ExprAmount extends PropertyExpression { +public class ExprAmount extends PropertyExpression { static { - Parser.getMainRegistration().newPropertyExpression( - ExprAmount.class, - Number.class, - "~objects", - "[1:recursive] (amount|number|size)") + Parser.getMainRegistration().newPropertyExpression(ExprAmount.class, Number.class, + "[1:recursive] (amount|number|size)", "~objects") .name("Amount") .description("Returns the amount of elements in a given list.") .examples("set {_size} to size of {_var::*}", diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorValues.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorValues.java index 6d61f9e29..d8ed9df4c 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorValues.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorValues.java @@ -7,61 +7,54 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.color.Color; -import java.math.BigInteger; - /** * Certain color values of a given color. * + * @author Mwexim * @name Date Values * @type EXPRESSION * @pattern [the] (hex[adecimal]|red|green|blue|alpha) value of %color% * @pattern %color%'[s] (hex[adecimal]|red|green|blue|alpha) * @since ALPHA - * @author Mwexim */ -public class ExprColorValues extends PropertyExpression { - static { - Parser.getMainRegistration().addPropertyExpression( - ExprColorValues.class, - Object.class, - "colors", - "(0:hex[adecimal]|1:red|2:green|3:blue|4:alpha) value" - ); - } - - private int mark; - - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - mark = parseContext.getNumericMark(); - return super.init(expressions, matchedPattern, parseContext); - } - - @Override - public Object getProperty(Color owner) { - switch (mark) { - case 0: - return owner.getHex(); - case 1: - return BigInteger.valueOf(owner.getRed()); - case 2: - return BigInteger.valueOf(owner.getGreen()); - case 3: - return BigInteger.valueOf(owner.getBlue()); - case 4: - return BigInteger.valueOf(owner.getAlpha()); - default: - throw new IllegalStateException(); - } - } - - @Override - public Class getReturnType() { - return mark == 0 ? String.class : BigInteger.class; - } +public class ExprColorValues extends PropertyExpression { + static { + Parser.getMainRegistration().newPropertyExpression(ExprColorValues.class, Object.class, + "(0:hex[adecimal]|1:red|2:green|3:blue|4:alpha) value", "colors") + .name("Color Value") + .description("The value of a certain color component.") + .since("INSERT VERSION") + .register(); + } + + private int mark; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + this.mark = parseContext.getNumericMark(); + return super.init(expressions, matchedPattern, parseContext); + } + + @Override + public Object getProperty(Color owner) { + return switch (this.mark) { + case 0 -> owner.getHex(); + case 1 -> owner.getRed(); + case 2 -> owner.getGreen(); + case 3 -> owner.getBlue(); + case 4 -> owner.getAlpha(); + default -> throw new IllegalStateException(); + }; + } + + @Override + public Class getReturnType() { + return this.mark == 0 ? String.class : Integer.class; + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + return toString(ctx, debug, new String[]{"hex", "red", "green", "blue", "alpha"}[this.mark] + " value"); + } - @Override - public String toString(TriggerContext ctx, boolean debug) { - return toString(ctx, debug, new String[] {"hex", "red", "green", "blue", "alpha"}[mark] + " value"); - } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java index 45869e592..ad6f033e5 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java @@ -22,14 +22,14 @@ * @since ALPHA * @author Mwexim */ -public class ExprDateTimestamp extends PropertyExpression { +public class ExprDateTimestamp extends PropertyExpression { static { - Parser.getMainRegistration().addPropertyExpression( - ExprDateTimestamp.class, - Number.class, - "*[date] %date%", - "[1:unix] timestamp" - ); + Parser.getMainRegistration().newPropertyExpression(ExprDateTimestamp.class, Number.class, + "[1:unix] timestamp", "*[date] %date%") + .name("Unix Timestamp") + .description("The unix timestamp of a date.") + .since("INSERT VERSION") + .register(); } private boolean unix; diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java index 594813d34..c420ea01d 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java @@ -14,14 +14,13 @@ * @since ALPHA * @author Romitou */ -public class ExprLength extends PropertyExpression { +public class ExprLength extends PropertyExpression { static { - Parser.getMainRegistration().addPropertyExpression( - ExprLength.class, - Number.class, - "string", - "length" - ); + Parser.getMainRegistration().newPropertyExpression(ExprLength.class, Number.class, "length", "string") + .name("Length") + .description("The length of a string.") + .since("INSERT VERSION") + .register(); } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java index 1c0fd3b22..596679838 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java @@ -24,7 +24,7 @@ * @param The type of the owner of this expression. * @author Mwexim */ -public abstract class PropertyExpression implements Expression { +public abstract class PropertyExpression implements Expression { public static final String PROPERTY_IDENTIFIER = "property"; private Expression owner; @@ -111,7 +111,7 @@ public boolean isGenitive() { return genitive; } - public static String[] composePatterns(String owner, String property) { + public static String[] composePatterns(String property, String owner) { var ownerType = owner.startsWith("*") ? owner.substring(1) : "%" + owner + "%"; return new String[] { ownerType + "'[s] " + property, diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java index 48064d253..cb3381bed 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -225,8 +225,8 @@ public , T> void addExpression(Class c, Class retu * @param the Expression's return type * @return an {@link ExpressionRegistrar} to continue the registration process */ - public , T> ExpressionRegistrar newPropertyExpression(Class c, Class returnType, String owner, String property) { - return (ExpressionRegistrar) newExpression(c, returnType, false, PropertyExpression.composePatterns(owner, property)) + public , T> ExpressionRegistrar newPropertyExpression(Class c, Class returnType, String property, String owner) { + return (ExpressionRegistrar) newExpression(c, returnType, false, PropertyExpression.composePatterns(property, owner)) .addData(PropertyExpression.PROPERTY_IDENTIFIER, property); } @@ -239,8 +239,8 @@ public , T> void addExpression(Class c, Class retu * @param the Expression * @param the Expression's return type */ - public , T> void addPropertyExpression(Class c, Class returnType, String owner, String property) { - newPropertyExpression(c, returnType, owner, property).register(); + public , T> void addPropertyExpression(Class c, Class returnType, String property, String owner) { + newPropertyExpression(c, returnType, property, owner).register(); } /** @@ -253,8 +253,8 @@ public , T> void addExpression(Class c, Class retu * @param the Expression * @param the Expression's return type */ - public , T> void addPropertyExpression(Class c, Class returnType, int priority, String owner, String property) { - newPropertyExpression(c, returnType, owner, property).setPriority(priority).register(); + public , T> void addPropertyExpression(Class c, Class returnType, int priority, String property, String owner) { + newPropertyExpression(c, returnType, property, owner).setPriority(priority).register(); } /** From 24c599ecac85a6508395578fe1169431709ebd31 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 08:00:32 -0800 Subject: [PATCH 13/68] Some pulls from ScrollLang/Scroll --- build.gradle | 5 +- .../github/syst3ms/skriptparser/Parser.java | 2 +- .../github/syst3ms/skriptparser/Skript.java | 3 + .../skriptparser/effects/EffReturn.java | 24 +- .../skriptparser/file/FileSection.java | 15 + .../skriptparser/lang/SkriptEvent.java | 5 +- .../syst3ms/skriptparser/lang/Structure.java | 37 ++ .../lang/entries/SectionConfiguration.java | 365 +++++++++-------- .../skriptparser/parsing/ParserState.java | 8 + .../registration/SkriptRegistration.java | 20 +- .../skriptparser/sections/SecLoop.java | 264 ++++++------ .../functions/DefaultFunctions.java | 20 + .../structures/functions/EffFunctionCall.java | 118 ++++++ .../functions/ExprFunctionCall.java | 117 ++++++ .../structures/functions/Function.java | 65 +++ .../structures/functions/FunctionContext.java | 22 + .../functions/FunctionParameter.java | 42 ++ .../structures/functions/Functions.java | 85 ++++ .../structures/functions/JavaFunction.java | 22 + .../functions/LitFunctionParameter.java | 70 ++++ .../structures/functions/ScriptFunction.java | 57 +++ .../structures/functions/StructFunction.java | 99 +++++ .../syst3ms/skriptparser/types/Type.java | 54 ++- .../skriptparser/types/TypeManager.java | 14 +- .../types/changers/TypeSerializer.java | 30 ++ .../variables/SerializedVariable.java | 58 +++ .../variables/VariableStorage.java | 383 ++++++++++++++++++ .../skriptparser/variables/Variables.java | 225 ++++++++-- 28 files changed, 1865 insertions(+), 364 deletions(-) create mode 100644 src/main/java/io/github/syst3ms/skriptparser/lang/Structure.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/EffFunctionCall.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/ExprFunctionCall.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/Function.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionContext.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionParameter.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/JavaFunction.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/LitFunctionParameter.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/ScriptFunction.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java diff --git a/build.gradle b/build.gradle index 1bd8fbbeb..83c4c820e 100644 --- a/build.gradle +++ b/build.gradle @@ -10,10 +10,11 @@ repositories { } dependencies { - implementation "org.jetbrains:annotations:15.0" - implementation group: "com.google.code.findbugs", name: "jsr305", version: "3.0.2" + implementation("org.jetbrains:annotations:15.0") + implementation( group: "com.google.code.findbugs", name: "jsr305", version: "3.0.2") testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.4.1" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.4.1" + implementation (group: 'com.google.code.gson', name: 'gson', version: '2.13.2') testImplementation "junit:junit:4.12" testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.1" } diff --git a/src/main/java/io/github/syst3ms/skriptparser/Parser.java b/src/main/java/io/github/syst3ms/skriptparser/Parser.java index b3b3d9c05..7604df229 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/Parser.java +++ b/src/main/java/io/github/syst3ms/skriptparser/Parser.java @@ -77,7 +77,7 @@ public static void init(String[] mainPackages, String[] subPackages, String[] pr mainPackages[mainPackages.length - 1] = "io.github.syst3ms.skriptparser"; List sub = new ArrayList<>(); sub.addAll(Arrays.asList(subPackages)); - sub.addAll(Arrays.asList("expressions", "effects", "event", "lang", "sections", "tags")); + sub.addAll(Arrays.asList("expressions", "effects", "event", "lang", "sections", "structures", "tags")); subPackages = sub.toArray(new String[0]); try { for (String mainPackage : mainPackages) { diff --git a/src/main/java/io/github/syst3ms/skriptparser/Skript.java b/src/main/java/io/github/syst3ms/skriptparser/Skript.java index 37f95975b..c0c913d59 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/Skript.java +++ b/src/main/java/io/github/syst3ms/skriptparser/Skript.java @@ -12,6 +12,7 @@ import io.github.syst3ms.skriptparser.lang.Statement; import io.github.syst3ms.skriptparser.lang.Trigger; import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import io.github.syst3ms.skriptparser.structures.functions.StructFunction; import io.github.syst3ms.skriptparser.util.DurationUtils; import io.github.syst3ms.skriptparser.util.ThreadUtils; import io.github.syst3ms.skriptparser.util.Time; @@ -53,6 +54,8 @@ public void handleTrigger(String scriptName, Trigger trigger) { whenTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); } else if (event instanceof EvtAtTime) { atTimeTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); + } else if (event instanceof StructFunction function) { + function.register(trigger); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffReturn.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffReturn.java index 624f27a1d..cd8632415 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffReturn.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffReturn.java @@ -9,6 +9,8 @@ import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.parsing.SkriptParserException; +import io.github.syst3ms.skriptparser.structures.functions.Function; +import io.github.syst3ms.skriptparser.structures.functions.FunctionContext; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.conversions.Converters; import io.github.syst3ms.skriptparser.util.StringUtils; @@ -31,6 +33,7 @@ public class EffReturn extends Effect { "return %objects%" ); } + private boolean isInFunction; private ReturnSection section; private Expression returned; @@ -39,6 +42,14 @@ public class EffReturn extends Effect { public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { returned = expressions[0]; var logger = parseContext.getLogger(); + Optional> optionalContext = parseContext.getParserState().getCurrentContexts().stream().findFirst(); + if (optionalContext.isPresent()) { + Class currentContext = optionalContext.get(); + if (currentContext.equals(FunctionContext.class)) { + isInFunction = true; + return true; + } + } var sec = Expression.getLinkedSection(parseContext.getParserState(), ReturnSection.class); if (sec.isEmpty()) { logger.error("Couldn't find a section matching this return statement", ErrorType.SEMANTIC_ERROR); @@ -67,18 +78,29 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex if (!section.getReturnType().isAssignableFrom(returned.getReturnType())) { // The value is convertible but not in the trivial way returned = returned.convertExpression(section.getReturnType()) - .orElseThrow(() -> new SkriptParserException("Return value should be convertible at this stage")); + .orElseThrow(() -> new SkriptParserException("Return value should be convertible at this stage")); } return true; } @Override protected void execute(TriggerContext ctx) { + /*if (isInFunction) { + FunctionContext functionContext = (FunctionContext) ctx; + Function function = functionContext.getOwningFunction(); + function.setReturnValue(returned.getValues(ctx)); + } else throw new UnsupportedOperationException();*/ throw new UnsupportedOperationException(); } @Override public Optional walk(TriggerContext ctx) { + if (isInFunction) { + FunctionContext functionContext = (FunctionContext) ctx; + Function function = functionContext.getOwningFunction(); + function.setReturnValue(returned.getValues(ctx)); + return Optional.empty(); // stop the trigger + } section.setReturned(returned.getValues(ctx)); section.step(this); return Optional.of(section); diff --git a/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java b/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java index 61c2142bd..826598e60 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java +++ b/src/main/java/io/github/syst3ms/skriptparser/file/FileSection.java @@ -1,12 +1,16 @@ package io.github.syst3ms.skriptparser.file; +import io.github.syst3ms.skriptparser.lang.entries.OptionLoader; + import java.util.List; +import java.util.Optional; /** * A class describing a section of a script inside a file (e.g a line ending with a colon and containing all the lines that * were indented after it. "all the lines" doesn't exclude sections. */ public class FileSection extends FileElement { + private final List elements; private int length = -1; @@ -37,6 +41,16 @@ public int length() { return length; } + public Optional get(String line) { + return elements.stream() + .filter(element -> { + String content = element.getLineContent(); + content = content.substring(0, content.lastIndexOf(OptionLoader.OPTION_SPLIT_PATTERN.trim())); + return content.equalsIgnoreCase(line); + }) + .findFirst(); + } + @Override public boolean equals(Object obj) { return super.equals(obj) && elements.equals(((FileSection) obj).elements); @@ -46,4 +60,5 @@ public boolean equals(Object obj) { public String toString() { return super.toString() + ":"; } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/SkriptEvent.java b/src/main/java/io/github/syst3ms/skriptparser/lang/SkriptEvent.java index 7798162cc..efc020da0 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/SkriptEvent.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/SkriptEvent.java @@ -41,12 +41,12 @@ public List loadSection(FileSection section, ParserState parserState, * This is undesirable if we don't want the restriction of having to declare functions before using them. This is especially * counter-productive if we're dealing with multiple scripts. * - * To solve this problem, {@link Trigger triggers} with with a higher loading priority number will be loaded first. + * To solve this problem, {@link Trigger triggers} with a higher loading priority number will be loaded first. * * @return the loading priority number. 0 by default */ public int getLoadingPriority() { - return 0; + return 500; } /** @@ -55,6 +55,7 @@ public int getLoadingPriority() { * 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 SkriptEvent + * or {@code null} if you don't want to allow any * @see #isRestrictingExpressions() */ protected Set> getAllowedSyntaxes() { diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/Structure.java b/src/main/java/io/github/syst3ms/skriptparser/lang/Structure.java new file mode 100644 index 000000000..2fc3cecbc --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Structure.java @@ -0,0 +1,37 @@ +package io.github.syst3ms.skriptparser.lang; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.lang.entries.SectionConfiguration; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParserState; +import io.github.syst3ms.skriptparser.parsing.ScriptLoader; + +import java.util.List; + +public abstract class Structure extends SkriptEvent { + + protected SectionConfiguration getConfiguration() { + return null; + } + + @Override + public List loadSection(FileSection section, ParserState parserState, SkriptLogger logger) { + SectionConfiguration configuration = getConfiguration(); + if (configuration != null) { + configuration.loadConfiguration(null, section, parserState, logger); + List elements = section.getElements(); + elements.subList(0, configuration.getEntries().size()).clear(); + } + return ScriptLoader.loadItems(section, parserState, logger); + } + + /** + * @return the default loading priority for structures + */ + @Override + public int getLoadingPriority() { + return 400; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionConfiguration.java b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionConfiguration.java index 25b3f3ff9..50ebaa8e8 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionConfiguration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionConfiguration.java @@ -35,182 +35,191 @@ * */ public class SectionConfiguration { - @Nullable - private CodeSection parent; - private final List entries; - private final Map data = new HashMap<>(); - private boolean loaded = false; - - private SectionConfiguration(List entries) { - this.entries = entries; - } - - /** - * Load the data of this {@link SectionConfiguration} into to the {@link #getData() data map} using - * a FileSection instance. This method should be called only once and will throw an error if attempting - * to load the configuration multiple times. The default use-case would be to load the configuration inside - * CodeSection's {@link CodeSection#loadSection(FileSection, ParserState, SkriptLogger) load} method. - * @param parent the parent section - * @param section the file section - * @param parserState the parse state - * @param logger the logger - * @return whether the section was loaded successfully or errors occurred - */ - public boolean loadConfiguration(@Nullable CodeSection parent, FileSection section, ParserState parserState, SkriptLogger logger) { - if (loaded) - throw new IllegalStateException("This section configuration has already been loaded once"); - - boolean successful = true; - this.parent = parent; - - outer: - for (var entry : entries) { - for (var el : section.getElements()) { - logger.setLine(el.getLine() - 1); - - if (logger.hasError()) { - /* - * If the execution of 'loadEntry' caused errors, it means that we - * should not continue parsing the other sections, as specified in - * the Javadoc. - * We finalize the logs and move on to the next entry. - */ - logger.finalizeLogs(); - successful = false; - continue outer; - } - if (el instanceof VoidElement) - continue; - if (entry.loadEntry(this, el, parserState, logger)) - continue outer; - } - if (entry.isOptional()) - continue; - // If we're here, it means no value matched and the entry hasn't been configured. - // Only the section line is relevant. - logger.setLine(section.getLine() - 1); - logger.error("The entry named '" + entry.key + "' has not been configured", ErrorType.SEMANTIC_ERROR); - logger.finalizeLogs(); - successful = false; - } - - if (!successful) { - // Add a final error message stating the section has not been configured correctly. - // Only the section line is relevant. - logger.setLine(section.getLine() - 1); - logger.error("The section '" + section.getLineContent() + "' has not been configured correctly", ErrorType.SEMANTIC_ERROR); - } - loaded = true; - return successful; - } - - @Nullable - public CodeSection getParent() { - return parent; - } - - /** - * @return a modifiable map containing the loaded data - */ - public Map getData() { - return data; - } - - public Object getValue(String key) { - return data.get(key); - } - - /** - * Tries to retrieve a value from its key and cast it to the correct class. - * This can only be used when you register your option as a {@link Builder#addLiteral(String, Class) literal}, - * otherwise, the option will likely be parsed as a String and throw an exception. - * Options that allow literal lists are saved as an array. - * Returns an empty Optional if the key is not present. This is only possible for optional keys. - * - * @param key the key - * @param cls the class to cast to - * @return the value cast to the given class, or an empty Optional if the key was not specified - * @param the type of the value - */ - @SuppressWarnings("unchecked") - public Optional getValue(String key, Class cls) { - var result = data.get(key); - if (result == null) - return Optional.empty(); - if (result.getClass() == String.class && result.getClass() != cls) - throw new UnsupportedOperationException("The key '" + key + "' was not registered as a literal, was parsed as a String and can, therefore, not be cast to " + cls.getName()); - return Optional.of((T) result); - } - - /** - * @param key the key - * @return the option value, or an empty Optional if the key was not specified - */ - public Optional getString(String key) { - return getValue(key, String.class); - } - - /** - * @param key the key - * @return the list values, or an empty Optional if the key was not specified - */ - public Optional getStringList(String key) { - return getValue(key, String[].class); - } - - /** - * @param key the key - * @return the enclosed code section, or an empty Optional if the key was not specified - */ - public Optional getSection(String key) { - return getValue(key, CodeSection.class); - } - - public static class Builder { - private final List entries = new ArrayList<>(); - - public Builder addKey(String key) { - entries.add(new OptionLoader(key, false, false)); - return this; - } - - public Builder addOptionalKey(String key) { - entries.add(new OptionLoader(key, false, true)); - return this; - } - - public Builder addList(String key) { - entries.add(new OptionLoader(key, true, false)); - return this; - } - - public Builder addOptionalList(String key) { - entries.add(new OptionLoader(key, true, true)); - return this; - } - - public Builder addLiteral(String key, Class typeClass) { - entries.add(new LiteralLoader<>(key, typeClass, false, false)); - return this; - } - - public Builder addLiteralList(String key, Class typeClass) { - entries.add(new LiteralLoader<>(key, typeClass, true, false)); - return this; - } - - public Builder addSection(String key) { - entries.add(new SectionLoader(key, false)); - return this; - } - - public Builder addLoader(EntryLoader loader) { - entries.add(loader); - return this; - } - - public SectionConfiguration build() { - return new SectionConfiguration(entries); - } - } + + private final Map data = new HashMap<>(); + private final List entries; + + @Nullable + private CodeSection parent; + private boolean loaded; + + private SectionConfiguration(List entries) { + this.entries = entries; + } + + /** + * Load the data of this {@link SectionConfiguration} into to the {@link #getData() data map} using + * a FileSection instance. This method should be called only once and will throw an error if attempting + * to load the configuration multiple times. The default use-case would be to load the configuration inside + * CodeSection's {@link CodeSection#loadSection(FileSection, ParserState, SkriptLogger) load} method. + * + * @param parent the parent section + * @param section the file section + * @param parserState the parse state + * @param logger the logger + * @return whether the section was loaded successfully or errors occurred + */ + public boolean loadConfiguration(@Nullable CodeSection parent, FileSection section, ParserState parserState, SkriptLogger logger) { + if (loaded) + throw new IllegalStateException("This section configuration has already been loaded once"); + + boolean successful = true; + this.parent = parent; + + outer: + for (var entry : entries) { + for (var element : section.getElements()) { + logger.setLine(element.getLine() - 1); + if (logger.hasError()) { + /* + * If the execution of 'loadEntry' caused errors, it means that we + * should not continue parsing the other sections, as specified in + * the Javadoc. + * We finalize the logs and move on to the next entry. + */ + logger.finalizeLogs(); + successful = false; + continue outer; + } + if (element instanceof VoidElement) + continue; + if (entry.loadEntry(this, element, parserState, logger)) + continue outer; + } + if (entry.isOptional()) + continue; + // If we're here, it means no value matched and the entry hasn't been configured. + // Only the section line is relevant. + logger.setLine(section.getLine() - 1); + logger.error("The entry named '" + entry.key + "' has not been configured", ErrorType.SEMANTIC_ERROR); + logger.finalizeLogs(); + successful = false; + } + + if (!successful) { + // Add a final error message stating the section has not been configured correctly. + // Only the section line is relevant. + logger.setLine(section.getLine() - 1); + logger.error("The section '" + section.getLineContent() + "' has not been configured correctly", ErrorType.SEMANTIC_ERROR); + } + loaded = true; + return successful; + } + + @Nullable + public CodeSection getParent() { + return parent; + } + + /** + * @return a modifiable map containing the loaded data + */ + public Map getData() { + return data; + } + + public List getEntries() { + return entries; + } + + public Object getValue(String key) { + return data.get(key); + } + + /** + * Tries to retrieve a value from its key and cast it to the correct class. + * This can only be used when you register your option as a {@link Builder#addLiteral(String, Class) literal}, + * otherwise, the option will likely be parsed as a String and throw an exception. + * Options that allow literal lists are saved as an array. + * Returns an empty Optional if the key is not present. This is only possible for optional keys. + * + * @param key the key + * @param cls the class to cast to + * @return the value cast to the given class, or an empty Optional if the key was not specified + * @param the type of the value + */ + @SuppressWarnings("unchecked") + public Optional getValue(String key, Class cls) { + var result = data.get(key); + if (result == null) + return Optional.empty(); + if (result.getClass() == String.class && result.getClass() != cls) + throw new UnsupportedOperationException("The key '" + key + "' was not registered as a literal, was parsed as a String and can, therefore, not be cast to " + cls.getName()); + return Optional.of((T) result); + } + + /** + * @param key the key + * @return the option value, or an empty Optional if the key was not specified + */ + public Optional getString(String key) { + return getValue(key, String.class); + } + + /** + * @param key the key + * @return the list values, or an empty Optional if the key was not specified + */ + public Optional getStringList(String key) { + return getValue(key, String[].class); + } + + /** + * @param key the key + * @return the enclosed code section, or an empty Optional if the key was not specified + */ + public Optional getSection(String key) { + return getValue(key, CodeSection.class); + } + + public static class Builder { + + private final List entries = new ArrayList<>(); + + public Builder addKey(String key) { + entries.add(new OptionLoader(key, false, false)); + return this; + } + + public Builder addOptionalKey(String key) { + entries.add(new OptionLoader(key, false, true)); + return this; + } + + public Builder addList(String key) { + entries.add(new OptionLoader(key, true, false)); + return this; + } + + public Builder addOptionalList(String key) { + entries.add(new OptionLoader(key, true, true)); + return this; + } + + public Builder addLiteral(String key, Class typeClass) { + entries.add(new LiteralLoader<>(key, typeClass, false, false)); + return this; + } + + public Builder addLiteralList(String key, Class typeClass) { + entries.add(new LiteralLoader<>(key, typeClass, true, false)); + return this; + } + + public Builder addSection(String key) { + entries.add(new SectionLoader(key, false)); + return this; + } + + public Builder addLoader(EntryLoader loader) { + entries.add(loader); + return this; + } + + public SectionConfiguration build() { + return new SectionConfiguration(entries); + } + + } + } \ No newline at end of file diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/ParserState.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/ParserState.java index 474de99ce..122199be0 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/ParserState.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/ParserState.java @@ -15,10 +15,12 @@ * An object that stores data about the current parsing, on the scale of the entire trigger. */ public class ParserState { + private Set> currentContexts = new HashSet<>(); private final LinkedList currentSections = new LinkedList<>(); private final LinkedList> currentStatements = new LinkedList<>(); private final LinkedList>, Boolean>> restrictions = new LinkedList<>(); + private boolean isntAllowingSyntax; { currentStatements.add(new LinkedList<>()); @@ -103,6 +105,8 @@ public void callbackCurrentStatements() { * @param restrictingExpressions whether expressions are also restricted */ public void setSyntaxRestrictions(Set> allowedSyntaxes, boolean restrictingExpressions) { + if (allowedSyntaxes == null) + isntAllowingSyntax = true; restrictions.addLast(new Pair<>(allowedSyntaxes, restrictingExpressions)); } @@ -110,6 +114,7 @@ public void setSyntaxRestrictions(Set> allowedSyn * Clears the previously enforced syntax restrictions */ public void clearSyntaxRestrictions() { + isntAllowingSyntax = false; restrictions.removeLast(); } @@ -118,6 +123,8 @@ public void clearSyntaxRestrictions() { * @return whether the current syntax restrictions forbid a given syntax or not */ public boolean forbidsSyntax(Class c) { + if (isntAllowingSyntax) + return true; var allowedSyntaxes = restrictions.getLast().getFirst(); return !allowedSyntaxes.isEmpty() && !allowedSyntaxes.contains(c); } @@ -128,4 +135,5 @@ public boolean forbidsSyntax(Class c) { public boolean isRestrictingExpressions() { return restrictions.getLast().getSecond(); } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java index cb3381bed..7f3b43b8d 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -36,6 +36,7 @@ import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.Changer; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; import io.github.syst3ms.skriptparser.types.conversions.ConverterInfo; import io.github.syst3ms.skriptparser.types.conversions.Converters; import io.github.syst3ms.skriptparser.util.CollectionUtils; @@ -226,7 +227,7 @@ public , T> void addExpression(Class c, Class retu * @return an {@link ExpressionRegistrar} to continue the registration process */ public , T> ExpressionRegistrar newPropertyExpression(Class c, Class returnType, String property, String owner) { - return (ExpressionRegistrar) newExpression(c, returnType, false, PropertyExpression.composePatterns(property, owner)) + return (ExpressionRegistrar) newExpression(c, returnType, false, PropertyExpression. composePatterns(property, owner)) .addData(PropertyExpression.PROPERTY_IDENTIFIER, property); } @@ -618,6 +619,8 @@ public class TypeRegistrar implements Registrar { private Changer defaultChanger; @Nullable private Arithmetic arithmetic; + @Nullable + private TypeSerializer serializer; private final Documentation documentation = new Documentation(); public TypeRegistrar(Class c, String baseName, String pattern) { @@ -663,6 +666,15 @@ public TypeRegistrar arithmetic(Arithmetic arithmetic) { return this; } + /** + * @param serializer add a type serializer that allows the type to be saved to databases. + * @return the registrar + */ + public TypeRegistrar serializer(TypeSerializer serializer) { + this.serializer = serializer; + return this; + } + public TypeRegistrar name(String name) { this.documentation.setName(name); return this; @@ -694,7 +706,7 @@ public TypeRegistrar since(String since) { @Override public void register() { newTypes = true; - types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, this.documentation)); + types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, documentation, serializer)); } } @@ -944,6 +956,10 @@ public void register() { } } + public SkriptLogger getLogger() { + return this.logger; + } + /** * Add a consumer to be called when this SkriptRegistration finishes registration. * diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java index 08d359076..21405541c 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java @@ -27,16 +27,18 @@ * One can also loop a certain amount of times instead. The looped expression will still be * valid in that case. * + * @author Mwexim/LimeGlass * @name Loop * @type SECTION * @pattern loop %integer% times * @pattern loop %objects% * @since ALPHA - * @author Mwexim */ public class SecLoop extends ArgumentSection implements Continuable, SelfReferencing { + static { - Parser.getMainRegistration().newSection(SecLoop.class, "loop %integer% times", "loop %objects%") + Parser.getMainRegistration().newSection(SecLoop.class, + "loop %integer% times", "loop %objects%") .name("Loop") .description("Loops over all the values in a list or loop x number of times.") .examples("loop 10 times:", @@ -47,131 +49,135 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen .register(); } - @Nullable - private Expression expression; - private Expression times; - private boolean isNumericLoop; - - @Nullable - private Statement actualNext; - @Nullable - private Iterator iterator; - - @Override - public boolean loadSection(FileSection section, ParserState parserState, SkriptLogger logger) { - super.setNext(this); - return super.loadSection(section, parserState, logger); - } - - @SuppressWarnings("unchecked") - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - isNumericLoop = matchedPattern == 0; - if (isNumericLoop) { - times = (Expression) expressions[0]; - // We can do some certainty checks with Literals. - if (times instanceof Literal) { - var t = ((Optional) ((Literal) times).getSingle()).orElse(BigInteger.ONE); - if (t.intValue() <= 0) { - parseContext.getLogger().error("Cannot loop a negative or zero amount of times", ErrorType.SEMANTIC_ERROR); - return false; - } else if (t.intValue() == 1) { - parseContext.getLogger().error( - "Cannot loop a single time", - ErrorType.SEMANTIC_ERROR, - "Remove this loop, because looping something once can be achieved without a loop-statement" - ); - return false; - } - } - } else { - expression = expressions[0]; - if (expression.isSingle()) { - parseContext.getLogger().error( - "Cannot loop a single value", - ErrorType.SEMANTIC_ERROR, - "Remove this loop, because you clearly don't need to loop a single value" - ); - return false; - } - } - return true; - } - - @Override - public Optional walk(TriggerContext ctx) { - if (isNumericLoop && expression == null) { - // We just set the looped expression to a range from 1 to the amount of times. - // This allows the usage of 'loop-number' to get the current iteration - expression = rangeOf(ctx, times); - } - - if (iterator == null) { - assert expression != null; - iterator = expression instanceof Variable ? ((Variable) expression).variablesIterator(ctx) : expression.iterator(ctx); - } - - if (iterator.hasNext()) { - setArguments(iterator.next()); - return start(); - } else { - finish(); - return Optional.ofNullable(actualNext); - } - } - - @Override - public Statement setNext(@Nullable Statement next) { - this.actualNext = next; - return this; - } - - @Override - public void finish() { - // Cache clearing - iterator = null; - } - - @Override - public Optional getContinued(TriggerContext ctx) { - return Optional.of(this); - } - - @Override - public Optional getActualNext() { - return Optional.ofNullable(actualNext); - } - - @Override - public String toString(TriggerContext ctx, boolean debug) { - assert expression != null; - return "loop " + (isNumericLoop ? times.toString(ctx, debug) + " times" : expression.toString(ctx, debug)); - } - - /** - * @return the expression whose values this loop is iterating over - */ - public Expression getLoopedExpression() { - if (isNumericLoop && expression == null) { - expression = rangeOf(TriggerContext.DUMMY, times); - } - return expression; - } - - /** - * Returns a SimpleLiteral containing the numbers 1 up until a certain amount, specified - * by the given expression. - * @param ctx the context - * @param size the expression - * @return the SimpleLiteral - */ - private static Expression rangeOf(TriggerContext ctx, Expression size) { - BigInteger[] range = (BigInteger[]) size.getSingle(ctx) - .filter(t -> t.compareTo(BigInteger.ZERO) > 0) - .map(t -> Ranges.getRange(BigInteger.class).orElseThrow() - .getFunction() - .apply(BigInteger.ONE, t)) // Upper bound is inclusive - .orElse(new BigInteger[0]); - return new SimpleLiteral<>(BigInteger.class, range); - } + @Nullable + private Expression expression; + private Expression times; + private boolean isNumericLoop; + + @Nullable + private Statement actualNext; + + @Nullable + private Iterator iterator; + + @Override + public boolean loadSection(FileSection section, ParserState parserState, SkriptLogger logger) { + if (!super.loadSection(section, parserState, logger)) + return false; + super.setNext(this); + return true; + } + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + isNumericLoop = matchedPattern == 0; + if (isNumericLoop) { + times = (Expression) expressions[0]; + // We can do some certainty checks with Literals. + if (times instanceof Literal) { + var t = ((Optional) ((Literal) times).getSingle()).orElse(BigInteger.ONE); + if (t.intValue() <= 0) { + parseContext.getLogger().error("Cannot loop a negative or zero amount of times", ErrorType.SEMANTIC_ERROR); + return false; + } else if (t.intValue() == 1) { + parseContext.getLogger().error( + "Cannot loop a single time", + ErrorType.SEMANTIC_ERROR, + "Remove this loop, because looping something once can be achieved without a loop-statement" + ); + return false; + } + } + } else { + expression = expressions[0]; + if (expression.isSingle()) { + parseContext.getLogger().error( + "Cannot loop a single value", + ErrorType.SEMANTIC_ERROR, + "Remove this loop, because you clearly don't need to loop a single value" + ); + return false; + } + } + return true; + } + + @Override + public Optional walk(TriggerContext ctx) { + if (isNumericLoop && expression == null) { + // We just set the looped expression to a range from 1 to the amount of times. + // This allows the usage of 'loop-number' to get the current iteration + expression = rangeOf(ctx, times); + } + + if (iterator == null) { + assert expression != null; + iterator = expression instanceof Variable ? ((Variable) expression).variablesIterator(ctx) : expression.iterator(ctx); + } + + if (iterator.hasNext()) { + setArguments(iterator.next()); + return start(); + } else { + finish(); + return Optional.ofNullable(actualNext); + } + } + + @Override + public Statement setNext(@Nullable Statement next) { + this.actualNext = next; + return this; + } + + @Override + public void finish() { + // Cache clearing + iterator = null; + } + + @Override + public Optional getContinued(TriggerContext ctx) { + return Optional.of(this); + } + + @Override + public Optional getActualNext() { + return Optional.ofNullable(actualNext); + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + assert expression != null; + return "loop " + (isNumericLoop ? times.toString(ctx, debug) + " times" : expression.toString(ctx, debug)); + } + + /** + * @return the expression whose values this loop is iterating over + */ + public Expression getLoopedExpression() { + if (isNumericLoop && expression == null) { + expression = rangeOf(TriggerContext.DUMMY, times); + } + return expression; + } + + /** + * Returns a SimpleLiteral containing the numbers 1 up until a certain amount, specified + * by the given expression. + * + * @param ctx the context + * @param size the expression + * @return the SimpleLiteral + */ + private static Expression rangeOf(TriggerContext ctx, Expression size) { + BigInteger[] range = (BigInteger[]) size.getSingle(ctx) + .filter(t -> t.compareTo(BigInteger.ZERO) > 0) + .map(t -> Ranges.getRange(BigInteger.class).orElseThrow() + .getFunction() + .apply(BigInteger.ONE, t)) // Upper bound is inclusive + .orElse(new BigInteger[0]); + return new SimpleLiteral<>(BigInteger.class, range); + } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java new file mode 100644 index 000000000..c3f61199d --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java @@ -0,0 +1,20 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +public class DefaultFunctions { + + static { + Functions.registerFunction(new JavaFunction<>( + "mod", + new FunctionParameter[]{new FunctionParameter<>("i", Integer.class, true), new FunctionParameter<>("m", Integer.class, true)}, + Number.class, + true) { + @Override + public Number[] executeSimple(Object[][] params) { + Number d = (Number) params[0][0]; + Number m = (Number) params[1][0]; + return new Number[] {d.intValue() % m.intValue()}; + } + }); + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/EffFunctionCall.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/EffFunctionCall.java new file mode 100644 index 000000000..a0e3fcada --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/EffFunctionCall.java @@ -0,0 +1,118 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.Effect; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.ExpressionList; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.parsing.SyntaxParser; +import io.github.syst3ms.skriptparser.types.PatternType; +import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.conversions.Converters; + +import java.util.Optional; +import java.util.regex.MatchResult; + +public class EffFunctionCall extends Effect { + + static { + Parser.getMainRegistration().addEffect( + EffFunctionCall.class, + 6, + Functions.FUNCTION_CALL_PATTERN); + } + + private Function function; + private Expression[] paramsExprs = new Expression[0]; + + private Expression parsedExpr; + + @Override + protected void execute(TriggerContext ctx) { + Object[][] params = new Object[paramsExprs.length][]; + for (int i = 0; i < paramsExprs.length; i++) { + params[i] = paramsExprs[i].getValues(ctx); + Optional converted = Converters.convertArray(params[i], function.getParameters()[i].getType()); + if (converted.isEmpty()) { + params[i] = new Object[0]; + } else { + params[i] = converted.get(); + } + } + function.execute(params, ctx); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + MatchResult result = parseContext.getMatches().get(0); // whole pattern matched because it is one single regex + String functionName = result.group(1); + Optional> optionalFunction = Functions.getFunctionByName(functionName, parseContext.getLogger().getFileName()); + SkriptLogger logger = parseContext.getLogger(); + if (optionalFunction.isEmpty()) { + logger.error("No function was found under the name '" + functionName + "'", ErrorType.SEMANTIC_ERROR); + return false; + } + function = optionalFunction.get(); + FunctionParameter[] functionParameters = function.getParameters(); + String exprString = result.group(2); + PatternType objectType = TypeManager.getPatternType("objects").get(); + Optional> optionalExpression = + SyntaxParser.parseExpression(exprString, objectType, parseContext.getParserState(), logger); + if (optionalExpression.isPresent()) { + parsedExpr = optionalExpression.get(); + if (functionParameters.length == 0) { + logger.error("This function has no parameters, but 1 or more parameters were provided.", ErrorType.SEMANTIC_ERROR); + return false; + } + if (parsedExpr instanceof ExpressionList expressionList) { + paramsExprs = expressionList.getExpressions(); + if (!(functionParameters.length == 1 && !functionParameters[0].isSingle())) { // allows for function f(ints: ints) | f(1, 2, 3, 4) + if (paramsExprs.length != functionParameters.length) { + logger.error("This function requires " + functionParameters.length + " parameters, but " + + paramsExprs.length + " were given.", ErrorType.SEMANTIC_ERROR); + return false; + } + for (int i = 0; i < functionParameters.length; i++) { + FunctionParameter functionParameter = functionParameters[i]; + Expression providedParamExpr = paramsExprs[i]; + if (functionParameter.isSingle() && !providedParamExpr.isSingle()) { + logger.error("The '" + functionParameter.getName() + "' parameter accepts a single " + + "value, but was given more.", ErrorType.SEMANTIC_ERROR); + return false; + } + // if (!functionParameter.getType().isAssignableFrom(providedParamExpr.getReturnType())) { // no converter check + if (!functionParameter.getType().isAssignableFrom(providedParamExpr.getReturnType()) + && !Converters.converterExists(functionParameter.getType(), providedParamExpr.getReturnType())) { + String typeText = TypeManager.getByClass(functionParameter.getType()).get().withIndefiniteArticle(false); + logger.error("The type of the provided value for the '" + functionParameter.getName() + + "' parameter is not " + typeText + "/couldn't be converted to " + + typeText, ErrorType.SEMANTIC_ERROR); + return false; + } + } + } else { + paramsExprs = new Expression[]{parsedExpr}; // single parameter setting it to multiple values + } + }/* else { + paramsExprs = new Expression[]{parsedExpr}; // + }*/ + } + else if (functionParameters.length > 0) { + logger.error("The function has more than 1 parameter, but none were provided.", ErrorType.SEMANTIC_ERROR); + return false; + } + if (function.getReturnType().isPresent()) { + logger.warn("The return value of the function provided isn't being used."); + } + return true; + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + return function.getName() + "(" + (parsedExpr != null ? parsedExpr.toString(ctx, debug) : "") + ")"; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ExprFunctionCall.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ExprFunctionCall.java new file mode 100644 index 000000000..4f8293468 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ExprFunctionCall.java @@ -0,0 +1,117 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.ExpressionList; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.parsing.SyntaxParser; +import io.github.syst3ms.skriptparser.types.PatternType; +import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.conversions.Converters; + +import java.util.Optional; +import java.util.regex.MatchResult; + +public class ExprFunctionCall implements Expression { + + static { + Parser.getMainRegistration().addExpression( + ExprFunctionCall.class, + Object.class, + true, + 6, + Functions.FUNCTION_CALL_PATTERN); + } + + private Function function; + private Expression[] paramsExprs = new Expression[0]; + + private Expression parsedExpr; + + @Override + public Object[] getValues(TriggerContext ctx) { + Object[][] params = new Object[paramsExprs.length][]; + for (int i = 0; i < paramsExprs.length; i++) { + params[i] = paramsExprs[i].getValues(ctx); + Optional converted = Converters.convertArray(params[i], function.getParameters()[i].getType()); + if (converted.isEmpty()) { + params[i] = new Object[0]; + } else { + params[i] = converted.get(); + } + } + Object[] o = function.execute(params, ctx); + return o; + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + MatchResult result = parseContext.getMatches().get(0); // whole pattern matched because it is one single regex + String functionName = result.group(1); + Optional> optionalFunction = Functions.getFunctionByName(functionName, parseContext.getLogger().getFileName()); + SkriptLogger logger = parseContext.getLogger(); + if (optionalFunction.isEmpty()) { + logger.error("No function was found under the name '" + functionName + "'", ErrorType.SEMANTIC_ERROR); + return false; + } + function = optionalFunction.get(); + FunctionParameter[] functionParameters = function.getParameters(); + String exprString = result.group(2); + PatternType objectType = TypeManager.getPatternType("objects").get(); + Optional> optionalExpression = + SyntaxParser.parseExpression(exprString, objectType, parseContext.getParserState(), logger); + if (optionalExpression.isPresent()) { + parsedExpr = optionalExpression.get(); + if (functionParameters.length == 0) { + logger.error("This function has no parameters, but 1 or more parameters were provided.", ErrorType.SEMANTIC_ERROR); + return false; + } + if (parsedExpr instanceof ExpressionList expressionList) { + paramsExprs = expressionList.getExpressions(); + if (!(functionParameters.length == 1 && !functionParameters[0].isSingle())) { // allows for function f(ints: ints) | f(1, 2, 3, 4) + if (paramsExprs.length != functionParameters.length) { + logger.error("This function requires " + functionParameters.length + " parameters, but " + + paramsExprs.length + " were given.", ErrorType.SEMANTIC_ERROR); + return false; + } + for (int i = 0; i < functionParameters.length; i++) { + FunctionParameter functionParameter = functionParameters[i]; + Expression providedParamExpr = paramsExprs[i]; + if (functionParameter.isSingle() && !providedParamExpr.isSingle()) { + logger.error("The '" + functionParameter.getName() + "' parameter accepts a single " + + "value, but was given more.", ErrorType.SEMANTIC_ERROR); + return false; + } + // if (!functionParameter.getType().isAssignableFrom(providedParamExpr.getReturnType())) { // no converter check + if (!functionParameter.getType().isAssignableFrom(providedParamExpr.getReturnType()) + && !Converters.converterExists(functionParameter.getType(), providedParamExpr.getReturnType())) { + String typeText = TypeManager.getByClass(functionParameter.getType()).get().withIndefiniteArticle(false); + logger.error("The type of the provided value for the '" + functionParameter.getName() + + "' parameter is not " + typeText + "/couldn't be converted to " + + typeText, ErrorType.SEMANTIC_ERROR); + return false; + } + } + } else { + paramsExprs = new Expression[]{parsedExpr}; // single parameter setting it to multiple values + } + } else { + paramsExprs = new Expression[]{parsedExpr}; // required for function calling otherwise it can break + } + } + else if (functionParameters.length > 0) { + logger.error("The function has more than 1 parameter, but none were provided.", ErrorType.SEMANTIC_ERROR); + return false; + } + return true; + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + return function.getName() + "(" + (parsedExpr != null ? parsedExpr.toString(ctx, debug) : "") + ")"; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Function.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Function.java new file mode 100644 index 000000000..5dded660b --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Function.java @@ -0,0 +1,65 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.lang.TriggerContext; + +import java.util.Arrays; +import java.util.Optional; + +public abstract sealed class Function permits ScriptFunction, JavaFunction { + + private final String name; + + protected final FunctionParameter[] parameters; + + private final Class returnType; + + private final boolean returnSingle; + + protected volatile Object[] returnValue; + + protected Function(String name, FunctionParameter[] parameters, Class returnType, boolean returnSingle) { + if (!Functions.isValidFunctionName(name)) + throw new IllegalArgumentException("'" + name + "' is not a valid function name."); + this.name = name; + if (parameters != null) { + this.parameters = parameters; + } else { + this.parameters = new FunctionParameter[0]; + } + this.returnType = returnType; + this.returnSingle = returnSingle; + } + + public abstract T[] execute(Object[][] params, TriggerContext ctx); + + public String getName() { + return name; + } + + public FunctionParameter[] getParameters() { + return parameters; + } + + public Optional> getReturnType() { + return Optional.ofNullable(returnType); + } + + public boolean isReturnSingle() { + return returnSingle; + } + + public void setReturnValue(Object[] returnValue) { + this.returnValue = returnValue; + } + + @Override + public String toString() { + return "Function{" + + "name='" + name + '\'' + + ", parameters=" + Arrays.toString(parameters) + + ", returnType=" + returnType + + ", returnSingle=" + returnSingle + + '}'; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionContext.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionContext.java new file mode 100644 index 000000000..27402fe38 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionContext.java @@ -0,0 +1,22 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.lang.TriggerContext; + +public class FunctionContext implements TriggerContext { + + private final Function owningFunction; + + public FunctionContext(Function owningFunction) { + this.owningFunction = owningFunction; + } + + public Function getOwningFunction() { + return owningFunction; + } + + @Override + public String getName() { + return "function"; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionParameter.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionParameter.java new file mode 100644 index 000000000..20041fab3 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/FunctionParameter.java @@ -0,0 +1,42 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +public class FunctionParameter { + + private final String name; + + private final Class type; + + private final boolean single; + + public FunctionParameter(String name, Class type) { + this(name, type, true); + } + + public FunctionParameter(String name, Class type, boolean single) { + this.name = name; + this.type = type; + this.single = single; + } + + public String getName() { + return name; + } + + public Class getType() { + return type; + } + + public boolean isSingle() { + return single; + } + + @Override + public String toString() { + return "FunctionParameter{" + + "name='" + name + '\'' + + ", type=" + type + + ", single=" + single + + '}'; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java new file mode 100644 index 000000000..526c129d5 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java @@ -0,0 +1,85 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class Functions { + + private static final List> functions = new ArrayList<>(); + + static final String FUNCTION_NAME_REGEX = "^[a-zA-Z0-9_]*"; + private static final Pattern FUNCTION_NAME_PATTERN = Pattern.compile(FUNCTION_NAME_REGEX); + static final String FUNCTION_CALL_PATTERN = "<(" + Functions.FUNCTION_NAME_REGEX + ")\\((.*)\\)>"; + + private Functions() {} + + static void preRegisterFunction(ScriptFunction function) { + functions.add(function); + } + + public static void registerFunction(ScriptFunction function, Trigger trigger) { + function.setTrigger(trigger); + } + + public static void registerFunction(JavaFunction function) { + functions.add(function); + } + + public static boolean isValidFunction(ScriptFunction function, SkriptLogger logger) { + for (Function registeredFunction : functions) { + String registeredFunctionName = registeredFunction.getName(); + String providedFunctionName = function.getName(); + if (!registeredFunctionName.equals(providedFunctionName)) continue; + if (registeredFunction instanceof JavaFunction) { // java functions take precedence over any script function + logger.error("A java function already exists with the name '" + providedFunctionName + "'.", + ErrorType.SEMANTIC_ERROR); + return false; + } + ScriptFunction registeredScriptFunction = (ScriptFunction) registeredFunction; + String registeredScriptName = registeredScriptFunction.getScriptName(); + if (!registeredScriptFunction.isLocal()) { // already registered function is global so it takes name precedence + logger.error("A global script function named '" + providedFunctionName + "' already exists in " + + registeredScriptName + ".", ErrorType.SEMANTIC_ERROR); + return false; + } + if (!function.isLocal()) { + // if a global function is trying to be defined when a local function already has that name, there will be problems in the script where the local function lies + logger.error("A local script function named '" + providedFunctionName + "' already exists in " + + registeredScriptName + ".", ErrorType.SEMANTIC_ERROR); + return false; + } + if (registeredScriptName.equals(function.getScriptName())) { + logger.error("Two local functions with the same name ('" + registeredFunctionName + "')" + + " can't exist in the same script.", ErrorType.SEMANTIC_ERROR); + return false; + } + } + return true; + } + + public static Optional> getFunctionByName(String name, String scriptName) { + for (Function registeredFunction : functions) { + if (!registeredFunction.getName().equals(name)) continue; // we don't care then!!!! goodbye continue to the next one + if (registeredFunction instanceof ScriptFunction registeredScriptFunction + && registeredScriptFunction.isLocal() + && !scriptName.equals(registeredScriptFunction.getScriptName())) { + continue; + //return Optional.of(registeredFunction); handled below + } + return Optional.of(registeredFunction); // java function or global scriptfunction at this point + } + return Optional.empty(); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public static boolean isValidFunctionName(String name) { + return FUNCTION_NAME_PATTERN.matcher(name).matches(); + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/JavaFunction.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/JavaFunction.java new file mode 100644 index 000000000..fe0279090 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/JavaFunction.java @@ -0,0 +1,22 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.lang.TriggerContext; + +public abstract non-sealed class JavaFunction extends Function { + + protected JavaFunction(String name, FunctionParameter[] parameters, Class returnType, boolean returnSingle) { + super(name, parameters, returnType, returnSingle); + } + + @Override + public final T[] execute(Object[][] params, TriggerContext ctx) { + for (Object[] param : params) { + if (param == null || param.length == 0 || param[0] == null) + return null; + } + return executeSimple(params); + } + + public abstract T[] executeSimple(Object[][] params); + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/LitFunctionParameter.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/LitFunctionParameter.java new file mode 100644 index 000000000..262795a96 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/LitFunctionParameter.java @@ -0,0 +1,70 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Literal; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; + +@SuppressWarnings("rawtypes") +public class LitFunctionParameter implements Literal { + + static { + Parser.getMainRegistration().addExpression( + LitFunctionParameter.class, + FunctionParameter.class, + true, + "<" + Functions.FUNCTION_NAME_REGEX + ">\\: <.+>" + ); + } + + private String name; + private String rawType; + private Type type; + private Class typeClass; + private boolean single = true; + + @Override + public FunctionParameter[] getValues() { + return new FunctionParameter[] {new FunctionParameter<>(name, typeClass, single)}; + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + name = parseContext.getMatches().get(0).group(); + rawType = parseContext.getMatches().get(1).group(); // I have to use generic regex and parse it manually so I can get the raw string to use in this method + type = TypeManager.parseType(rawType); + SkriptLogger logger = parseContext.getLogger(); + if (type == null) { + logger.error("The type provided was unable to be parsed.", ErrorType.SEMANTIC_ERROR); + return false; + } + typeClass = type.getTypeClass(); + if (typeClass == FunctionParameter.class) { + logger.error("This type should not be used as a parameter's type.", ErrorType.SEMANTIC_ERROR); + return false; + } + if (type.isPlural(rawType)) + single = false; + /*typeLiteral = ((Literal>) expressions[0]); + type = typeLiteral.getSingle().orElseThrow(AssertionError::new); + typeClass = type.getTypeClass(); + if (typeClass == FunctionParameter.class) { + parseContext.getLogger().error("This type should not be used as a parameter's type.", ErrorType.SEMANTIC_ERROR); + return false; + } + String rawType = parseContext.getMatches().get(0).group(); + if (type.isPlural(rawType)) single = false;*/ + return true; + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + return name + ": " + rawType; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ScriptFunction.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ScriptFunction.java new file mode 100644 index 000000000..9a054af42 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ScriptFunction.java @@ -0,0 +1,57 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.lang.Statement; +import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.variables.Variables; + +public final class ScriptFunction extends Function { + + private final String scriptName; + + private final boolean local; + private Trigger trigger; + + ScriptFunction(String scriptName, boolean local, String name, FunctionParameter[] parameters, Class returnType, boolean returnSingle) { + super(name, parameters, returnType, returnSingle); + this.scriptName = scriptName; + this.local = local; + } + + @Override + @SuppressWarnings("unchecked") + public T[] execute(Object[][] params, TriggerContext ctx) { + FunctionContext functionContext = new FunctionContext(this); + for (int i = 0; i < params.length; i++) { + //Variables.setVariable(parameters[i].getName(), params[i], functionContext, true); + FunctionParameter p = parameters[i]; + Object[] val = params[i]; + //System.out.println(val.getClass()); + if (p.isSingle() && val.length > 0) { + Variables.setVariable(p.getName(), val[0], functionContext, true); + } else { + for (int j = 0; j < val.length; j++) { + Variables.setVariable(p.getName() + "::" + (j + 1), val[j], functionContext, true); + } + } + } + Statement.runAll(trigger, functionContext); + if (!(getReturnType().isPresent()/* && getReturnType().get().isAssignableFrom(returnValue.getClass())*/)) { + return null; + } + return (T[]) returnValue; + } + + public String getScriptName() { + return scriptName; + } + + public boolean isLocal() { + return local; + } + + public void setTrigger(Trigger trigger) { + this.trigger = trigger; + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java new file mode 100644 index 000000000..d9ac1a1cf --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java @@ -0,0 +1,99 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.Literal; +import io.github.syst3ms.skriptparser.lang.Structure; +import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; + +public class StructFunction extends Structure { + + static { + Parser.getMainRegistration() + .newEvent(StructFunction.class, "*[:local[ ]] func[tion] <" + Functions.FUNCTION_NAME_REGEX + ">" + + "\\([params:%*functionparameters%]\\)[return: \\:\\: <.+>]") + .setHandledContexts(FunctionContext.class) + .register(); + } + + private Literal> params; + private boolean returnSingle = true; + private String rawReturnType; + private String functionName; + private boolean local; + + private ScriptFunction function; // to be registered in the register method + + @Override + public boolean check(TriggerContext ctx) { + return ctx instanceof FunctionContext; + } + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + local = parseContext.hasMark("local"); + functionName = parseContext.getMatches().get(0).group(); + SkriptLogger logger = parseContext.getLogger(); + if (!Functions.isValidFunctionName(functionName)) { + logger.error("'" + functionName + "' is not a valid function name.", ErrorType.SEMANTIC_ERROR); + return false; + } + FunctionParameter[] parameters = null; + if (parseContext.getMarks().contains("params")) { + params = (Literal>) expressions[0]; + parameters = params.getValues(); + for (FunctionParameter parameter : parameters) { + for (FunctionParameter p : parameters) { + if (p == parameter) continue; + if (parameter.getName().equals(p.getName())) { + logger.error("Functions parameters cannot have the same name.", ErrorType.SEMANTIC_ERROR); + return false; + } + } + } + } + Class returnType = null; + if (parseContext.getMarks().contains("return")) { + rawReturnType = parseContext.getMatches().get(1).group(); + Type type = TypeManager.parseType(rawReturnType); + if (type == null) { + logger.error("The type provided was unable to be parsed.", ErrorType.SEMANTIC_ERROR); + return false; + } + returnType = type.getTypeClass(); + if (returnType == FunctionParameter.class) { + logger.error("This type should not be used as a function's return type.", ErrorType.SEMANTIC_ERROR); + return false; + } + if (type.isPlural(rawReturnType)) returnSingle = false; + } + function = new ScriptFunction<>(parseContext.getLogger().getFileName(), local, functionName, parameters, returnType, returnSingle); + if (!Functions.isValidFunction(function, parseContext.getLogger())) { + return false; + } + Functions.preRegisterFunction(function); + return true; + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + return (local ? "local " : "") + "function " + functionName + "(" + params.toString(ctx, debug) + ")" + + (rawReturnType == null ? "" : " :: " + rawReturnType); + } + + public String getStringName() { + return functionName; + } + + public void register(Trigger trigger) { + Functions.registerFunction(function, trigger); + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java index 4280a843a..53d087140 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java @@ -3,6 +3,7 @@ import io.github.syst3ms.skriptparser.docs.Documentation; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.Changer; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; import io.github.syst3ms.skriptparser.util.StringUtils; import org.jetbrains.annotations.Nullable; @@ -16,16 +17,23 @@ * @see PatternType */ public class Type { - private final Class typeClass; - private final String baseName; - private final String[] pluralForms; - private final Function toStringFunction; + @Nullable private final Function literalParser; + @Nullable private final Changer defaultChanger; + + @Nullable + private final TypeSerializer serializer; + @Nullable private final Arithmetic arithmetic; + + private final Function toStringFunction; + private final String[] pluralForms; + private final Class typeClass; + private final String baseName; private final Documentation documentation; /** @@ -86,9 +94,8 @@ public Type(Class typeClass, String baseName, String pattern, @Nullable Function literalParser, - Function toStringFunction, - Documentation documentation) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, null, documentation); + Function toStringFunction, Documentation documentation) { + this(typeClass, baseName, pattern, literalParser, toStringFunction, null, documentation); } public Type(Class typeClass, @@ -96,9 +103,19 @@ public Type(Class typeClass, String pattern, @Nullable Function literalParser, Function toStringFunction, - @Nullable Changer defaultChanger, + @Nullable Changer defaultChanger, Documentation documentation) { + this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null, documentation); + } + + public Type(Class typeClass, + String baseName, + String pattern, + @Nullable Function literalParser, + Function toStringFunction, + @Nullable Changer defaultChanger, + @Nullable Arithmetic arithmetic, Documentation documentation) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null, documentation); + this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, documentation, null); } @SuppressWarnings("unchecked") @@ -108,8 +125,7 @@ public Type(Class typeClass, @Nullable Function literalParser, Function toStringFunction, @Nullable Changer defaultChanger, - @Nullable Arithmetic arithmetic, - Documentation documentation) { + @Nullable Arithmetic arithmetic, Documentation documentation, @Nullable TypeSerializer serializer) { this.typeClass = typeClass; this.baseName = baseName; this.literalParser = literalParser; @@ -118,6 +134,11 @@ public Type(Class typeClass, this.defaultChanger = defaultChanger; this.arithmetic = arithmetic; this.documentation = documentation; + this.serializer = serializer; + } + + public boolean isPlural(String input) { + return input.equalsIgnoreCase(getPluralForm()); } public Class getTypeClass() { @@ -132,10 +153,21 @@ public String[] getPluralForms() { return pluralForms; } + /** + * @return strictly the plural form of the type, not including the regular form like {@link Type#getPluralForms()} does + */ + public String getPluralForm() { + return pluralForms[1]; + } + public Function getToStringFunction() { return toStringFunction; } + public Optional> getSerializer() { + return Optional.ofNullable(serializer); + } + public Optional> getLiteralParser() { return Optional.ofNullable(literalParser); } diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java index d5bf10e0d..d32383b52 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -12,10 +13,12 @@ */ @SuppressWarnings("unchecked") public class TypeManager { + /** * The string equivalent of null */ public static final String NULL_REPRESENTATION = ""; + /** * The string equivalent of an empty array */ @@ -51,7 +54,6 @@ public static Optional> getByName(String name) { return Optional.empty(); } - /** * Gets a {@link Type} from its associated {@link Class}. * @param c the Class to get the Type from @@ -115,10 +117,18 @@ public static Optional> getPatternType(String name) { return Optional.empty(); } + public static Type parseType(String input) { + Optional> type = TypeManager.getByExactName(input.toLowerCase(Locale.ENGLISH)); + if (type.isPresent()) + return type.get(); + return TypeManager.getByName(input).orElse(null); // allows for plural inputs + } + public static void register(SkriptRegistration reg) { for (var type : reg.getTypes()) { nameToType.put(type.getBaseName(), type); classToType.put(type.getTypeClass(), type); } } -} \ No newline at end of file + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java b/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java new file mode 100644 index 000000000..947954fc4 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/types/changers/TypeSerializer.java @@ -0,0 +1,30 @@ +package io.github.syst3ms.skriptparser.types.changers; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; + +/** + * An interface for serializing type objects. + */ +public interface TypeSerializer { + + /** + * Serialize a value to a GSON json element. + * The Gson object is preserved from the VariableStorage. + * + * @param gson Gson context. + * @param value the value to serialize + * @return the classes of the objects that the implementing object can be changed to + */ + JsonElement serialize(Gson gson, T value); + + /** + * Deserialize a GSON json element to object. + * The Gson object is preserved from the VariableStorage. + * + * @param gson Gson context. + * @param element the GSON json element. + */ + T deserialize(Gson gson, JsonElement element); + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java b/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java new file mode 100644 index 000000000..9b5753afc --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java @@ -0,0 +1,58 @@ +package io.github.syst3ms.skriptparser.variables; + +import org.jetbrains.annotations.Nullable; + +public class SerializedVariable { + + /** + * The name of the variable. + */ + public final String name; + + /** + * The serialized value of the variable. + *

    + * A value of {@code null} indicates the variable will be deleted. + */ + @Nullable + public final Value value; + + /** + * Creates a new serialized variable with the given name and value. + * + * @param name the given name. + * @param value the given value, or {@code null} to indicate a deletion. + */ + public SerializedVariable(String name, @Nullable Value value) { + this.name = name; + this.value = value; + } + + /** + * A serialized value of a variable. + */ + public static final class Value { + + /** + * The type of this value. + */ + public final String type; + + /** + * The serialized value data. + */ + public final byte[] data; + + /** + * Creates a new serialized value. + * @param type the value type. + * @param data the serialized value data. + */ + public Value(String type, byte[] data) { + this.type = type; + this.data = data; + } + + } + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java new file mode 100644 index 000000000..3029cc00c --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java @@ -0,0 +1,383 @@ +package io.github.syst3ms.skriptparser.variables; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.lang.entries.OptionLoader; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; +import io.github.syst3ms.skriptparser.variables.SerializedVariable.Value; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * A variable storage is holds the means and methods of storing variables. + *

    + * This is usually some sort of database, and could be as simply as a text file. + * + * Must contain a constructor of just SkriptLogger. + */ +public abstract class VariableStorage implements Closeable { + + /** + * Whether this variable storage has been {@link #close() closed}. + */ + protected volatile boolean closed; + + /** + * The name of the database used in the configurations. + */ + protected final String[] names; + + /** + * The pattern of the variable name this storage accepts. + * {@code null} for '{@code .*}' or '{@code .*}'. + */ + @Nullable + private Pattern variableNamePattern; + + private final SkriptLogger logger; + protected final Gson gson; + private File file; + + protected final LinkedBlockingQueue changesQueue = new LinkedBlockingQueue<>(1000); + + /** + * Creates a new variable storage with the given name. + * Gson will be handled. + * + * @param logger the logger to print logs to. + * @param name the name. + */ + protected VariableStorage(SkriptLogger logger, @NotNull String... names) { + this(logger, new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) + .serializeNulls() + .setLenient() + .create(), + names); + } + + /** + * Creates a new variable storage with the given names. + * + * @param logger the logger to print logs to. + * @param gson the gson that controls the serialization of the json elements. + * @param name the name. + */ + protected VariableStorage(SkriptLogger logger, @NotNull Gson gson, @NotNull String... names) { + this.logger = logger; + this.names = names; + this.gson = gson; + // TODO allow this runnable to be interupted. + CompletableFuture.runAsync(() -> { + while (!closed) { + try { + SerializedVariable variable = changesQueue.take(); + SerializedVariable.Value value = variable.value; + + if (value != null) { + save(variable.name, value.type, value.data); + } else { + save(variable.name, null, null); + } + } catch (InterruptedException ignored) {} + } + }); + } + + /** + * Gets the string value at the given key of the given section node. + * + * @param section the file section. + * @param key the key node. + * @return the value, or {@code null} if the value was invalid, + * or not found. + */ + @Nullable + protected String getConfigurationValue(FileSection section, String key) { + return getConfigurationValue(section, key, String.class); + } + + /** + * Gets the value at the given key of the given section node, + * parsed with the given class type. + * + * @param section the file section. + * @param key the key node. + * @param type the class type. + * @return the parsed value, or {@code null} if the value was invalid, + * or not found. + * @param the class type generic. + */ + @Nullable + @SuppressWarnings("unchecked") + protected T getConfigurationValue(FileSection section, String key, Class classType) { + Optional value = section.get(key); + if (!value.isPresent()) { + logger.error("The configuration is missing the entry for '" + key + "' for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + return null; + } + String[] split = value.get().getLineContent().split(OptionLoader.OPTION_SPLIT_PATTERN); + if (split.length < 2) { + logger.error("The configuration entry '" + key + "' is not a option entry (key: value) for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + return null; + } + String content = split[1]; + if (classType.equals(String.class)) + return (T) content; + + Optional> type = TypeManager.getByClassExact(classType); + if (!type.isPresent()) { + logger.error("The class type '" + classType.getName() + "' is not registered. Cannot parse node '" + key + "' for database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + return null; + } + + Optional> parser = type.get().getLiteralParser(); + if (!parser.isPresent()) { + logger.error("Type " + type.get().withIndefiniteArticle(true) + " cannot be parsed as a literal.", ErrorType.SEMANTIC_ERROR); + return null; + } + + T parsedValue = parser.get().apply(content); + if (parsedValue == null) { + logger.error("The entry for '" + key + "' in the database '" + names[0] + "' must be " + + type.get().withIndefiniteArticle(true), ErrorType.SEMANTIC_ERROR); + return null; + } + return parsedValue; + } + + /** + * Loads the configuration for this variable storage + * from the given section node. + * + * @param sectionNode the section node. + * @return whether the loading succeeded. + */ + public final boolean loadConfiguration(FileSection section) { + String pattern = getConfigurationValue(section, "pattern"); + if (pattern == null) + return false; + + try { + // Set variable name pattern, see field javadoc for explanation of null value + variableNamePattern = pattern.equals(".*") || pattern.equals(".+") ? null : Pattern.compile(pattern); + } catch (PatternSyntaxException e) { + logger.error("Invalid pattern '" + pattern + "': " + e.getLocalizedMessage(), ErrorType.SEMANTIC_ERROR); + return false; + } + + if (requiresFile()) { + String fileName = getConfigurationValue(section, "file"); + if (fileName == null) + return false; + + this.file = getFile(fileName).getAbsoluteFile(); + if (file.exists() && !file.isFile()) { + logger.error("The database file '" + file.getName() + "' does not exist or is a directory.", ErrorType.SEMANTIC_ERROR); + return false; + } + + try { + file.createNewFile(); + } catch (IOException e) { + logger.error("Cannot create the database file '" + file.getName() + "': " + e.getLocalizedMessage(), ErrorType.SEMANTIC_ERROR); + return false; + } + + // Check for read & write permissions to the file + if (!file.canWrite()) { + logger.error("Cannot write to the database file '" + file.getName() + "'!", ErrorType.SEMANTIC_ERROR); + return false; + } + + if (!file.canRead()) { + logger.error("Cannot read from the database file '" + file.getName() + "'!", ErrorType.SEMANTIC_ERROR); + return false; + } + } + + // Get the subclass to load it's part of the configuration section. + return load(section); + } + + /** + * Loads configurations and should start loading variables too. + * + * @return Whether the database could be loaded successfully, + * i.e. whether the configuration is correct and all variables could be loaded. + */ + protected abstract boolean load(FileSection section); + + protected void loadVariable(String name, SerializedVariable variable) { + Value value = variable.value; + if (value == null) { + Variables.queueVariableChange(name, null); + return; + } + loadVariable(name, value.type, value.data); + } + + /** + * Loads a variable into Skript ram. Call this inside {@link #load(FileSection)} + * + * @param name the name of the variable. + * @param type the type of the variable. + * @param value the serialized value of the variable. + */ + protected void loadVariable(String name, @NotNull String type, @NotNull byte[] value) { + if (value == null || type == null) + throw new IllegalArgumentException("value and/or typeName cannot be null"); + Variables.queueVariableChange(name, deserialize(type, value)); + } + + /** + * Called after all storages have been loaded, and variables + * have been redistributed if settings have changed. + * This should commit the first transaction. + */ + protected abstract void allLoaded(); + + /** + * Checks if this storage requires a file for storing data, like SQLite. + * + * @return if this storage needs a file. + */ + protected abstract boolean requiresFile(); + + /** + * Gets the file needed for this variable storage from the given file name. + *

    + * Will only be called if {@link #requiresFile()} is {@code true}. + * + * @param fileName the given file name. + * @return the {@link File} object. + */ + @Nullable + protected abstract File getFile(String fileName); + + /** + * Checks if this variable storage accepts the given variable name. + * + * @param var the variable name. + * @return if this storage accepts the variable name. + * + * @see #variableNamePattern + */ + boolean accept(@Nullable String variableName) { + if (variableName == null) + return false; + return variableNamePattern == null || variableNamePattern.matcher(variableName).matches(); + } + + /** + * Creates a {@link SerializedVariable} from the given variable name and value. + * Can be overriden to add custom encryption in your implemented VariableStorage. + * Call super. + * + * @param name the variable name. + * @param value the variable value. + * @return the serialized variable. + */ + @SuppressWarnings("unchecked") + public SerializedVariable serialize(String name, @Nullable T value) { + if (value == null) + return new SerializedVariable(name, null); + Type type = (Type) TypeManager.getByClassExact(value.getClass()).orElse(null); + if (type == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type registered."); + TypeSerializer serializer = type.getSerializer().orElse(null); + if (serializer == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type serializer."); + JsonElement element = serializer.serialize(gson, value); + return new SerializedVariable(name, new Value(type.getBaseName(), gson.toJson(element).getBytes())); + } + + /** + * Used by {@link #load(String, String, byte[]). + * You don't need to use this method, but if you need to read the Object, this method allows for deserialization. + * + * @param typeName The name of the type. + * @param value The value that represents a object. + * @return The Object after deserialization, not present if not possible to deserialize due to missing serializer on Type. + */ + protected Optional deserialize(@NotNull String typeName, @NotNull byte[] value) { + if (value == null || typeName == null) + throw new IllegalArgumentException("value and/or typeName cannot be null"); + Type type = TypeManager.getByExactName(typeName).orElse(null); + if (type == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type registered."); + TypeSerializer serializer = type.getSerializer().orElse(null); + if (serializer == null) + throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type serializer."); + String json = new String(value); + JsonReader reader = gson.newJsonReader(new StringReader(json)); + return Optional.ofNullable(serializer.deserialize(gson, JsonParser.parseReader(reader))); + } + + private long lastError = Long.MIN_VALUE; + private long ERROR_INTERVAL = 10; + + /** + * Saves the given serialized variable. + * + * @param variable the serialized variable. + */ + final void save(SerializedVariable variable) { + if (!changesQueue.offer(variable)) { + if (lastError < System.currentTimeMillis() - ERROR_INTERVAL * 1000) { + // Inform console about overload of variable changes + System.out.println("Skript cannot save any variables to the database '" + names[0] + "'. " + + "The thread will hang to avoid losing variable."); + + lastError = System.currentTimeMillis(); + } + while (true) { + try { + changesQueue.put(variable); + break; + } catch (InterruptedException ignored) {} + } + } + } + + protected void clearChangesQueue() { + changesQueue.clear(); + } + + /** + * Saves a variable. + *

    + * {@code type} and {@code value} are both {@code null} + * if this call is to delete the variable. + * + * @param name the name of the variable. + * @param type the type of the variable. + * @param value the serialized value of the variable. + * @return Whether the variable was saved. + */ + protected abstract boolean save(String name, @Nullable String type, @Nullable byte[] value); + +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index cc00f66eb..58ecb0037 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -1,5 +1,22 @@ package io.github.syst3ms.skriptparser.variables; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.ReentrantLock; +import java.util.regex.Pattern; + +import org.jetbrains.annotations.Nullable; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.lang.Variable; @@ -7,45 +24,113 @@ import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParserState; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Pattern; +import io.github.syst3ms.skriptparser.util.MultiMap; /** - * A class handling operations on variables + * A class handling operations on variables. */ public class Variables { - public static final String LIST_SEPARATOR = "::"; - public static final String LOCAL_VARIABLE_TOKEN = "_"; - public static final Pattern REGEX_PATTERN = Pattern.compile("\\{([^{}]|%\\{|}%)+}"); + + static final MultiMap, String> AVAILABLE_STORAGES = new MultiMap<>(); + static final List STORAGES = new ArrayList<>(); + + private static final Map localVariables = new HashMap<>(); // Make trigger-specific private static final VariableMap variableMap = new VariableMap(); - // Yes, I know it should be trigger-specific, but I haven't got to that part yet, ok ? TODO make the change - private static final Map localVariables = new HashMap<>(); + private static final ReentrantLock LOCK = new ReentrantLock(); + + public static final Pattern REGEX_PATTERN = Pattern.compile("\\{([^{}]|%\\{|}%)+}"); + public static final String LOCAL_VARIABLE_TOKEN = "_"; + public static final String LIST_SEPARATOR = "::"; + + public static boolean hasStorages() { + return !STORAGES.isEmpty(); + } + + /** + * Register a VariableStorage class. + * + * @param storage The class of the VariableStorage implementation. + * @param names The names used to reference this storage. + * @param Generic representing class that extends VariableStorage. + * @return if the storage was registered, false if it's already registered. + */ + public static boolean registerStorage(Class storage, String... names) { + if (AVAILABLE_STORAGES.containsKey(storage)) + return false; + for (String name : names) { + if (AVAILABLE_STORAGES.getAllValues().contains(name.toLowerCase(Locale.ENGLISH))) + return false; + } + for (String name : names) + AVAILABLE_STORAGES.putOne(storage, name.toLowerCase(Locale.ENGLISH)); + return true; + } + + /** + * Loads a section configuration containing all the database info. + * Parent section node name must be 'databases:' + * + * @param logger the SkriptLogger to print errors to. + * @param section the FileSection to load for the configurations. + * @throws IllegalArgumentException throws when the section is not valid. + */ + public static void load(SkriptLogger logger, FileSection section) throws IllegalArgumentException { + for (FileElement databaseElement : section.getElements()) { + if (!(databaseElement instanceof FileSection)) { + logger.error("The file node 'databases." + databaseElement.getLineContent() + "' was not a section.", ErrorType.STRUCTURE_ERROR); + continue; + } + String databaseName = databaseElement.getLineContent(); + if (databaseName.isBlank()) { + logger.error("A database name was incorrect, cannot be an empty string. (line " + databaseElement.getLine() + ")", ErrorType.SEMANTIC_ERROR); + continue; + } + FileSection databaseSection = (FileSection) databaseElement; + Class storageClass = AVAILABLE_STORAGES.entrySet().stream() + .filter(entry -> entry.getValue().contains(databaseName.toLowerCase(Locale.ENGLISH))) + .map(entry -> entry.getKey()) + .findFirst() + .orElse(null); + if (storageClass == null) { + logger.error("There is no database registered with the name '" + databaseName + "'", ErrorType.SEMANTIC_ERROR); + continue; + } + + try { + Constructor constructor = storageClass.getConstructor(SkriptLogger.class); + VariableStorage storage = constructor.newInstance(logger); + if (storage.loadConfiguration(databaseSection)) + STORAGES.add(storage); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e) { + logger.error("VariableStorage class '" + storageClass.getName() + "' does not implement the correct constructors.", ErrorType.SEMANTIC_ERROR); + logger.debug("Exception: " + e.getLocalizedMessage()); + } + } + STORAGES.forEach(VariableStorage::allLoaded); + processChangeQueue(); + } - public static Optional> parseVariable(String s, Class types, ParserState parserState, SkriptLogger logger) { - s = s.strip(); - if (REGEX_PATTERN.matcher(s).matches()) { - s = s.substring(1, s.length() - 1); + public static Optional> parseVariable(String input, Class types, ParserState parserState, SkriptLogger logger) { + input = input.strip(); + if (REGEX_PATTERN.matcher(input).matches()) { + input = input.substring(1, input.length() - 1); } else { return Optional.empty(); } - if (!isValidVariableName(s, true, logger)) { + if (!isValidVariableName(input, true, logger)) { return Optional.empty(); } var vs = VariableString.newInstance( - s.startsWith(LOCAL_VARIABLE_TOKEN) ? s.substring(LOCAL_VARIABLE_TOKEN.length()).strip() : s, + input.startsWith(LOCAL_VARIABLE_TOKEN) ? input.substring(LOCAL_VARIABLE_TOKEN.length()).strip() : input, parserState, logger ); - var finalS = s; + var finalS = input; return vs.map(v -> new Variable<>(v, finalS.startsWith(LOCAL_VARIABLE_TOKEN), finalS.endsWith(LIST_SEPARATOR + "*"), types)); } /** - * Checks whether a string is a valid variable name. This is used to verify variable names as well as command and function arguments. + * Checks whether a string is a valid variable name. * * @param name The name to test * @param printErrors Whether to print errors when they are encountered @@ -53,8 +138,7 @@ public static Optional> parseVariable(String s, Clas * @return true if the name is valid, false otherwise. */ public static boolean isValidVariableName(String name, boolean printErrors, SkriptLogger logger) { - name = name.startsWith(LOCAL_VARIABLE_TOKEN) ? name.substring(LOCAL_VARIABLE_TOKEN.length()).strip() - : name.strip(); + name = name.startsWith(LOCAL_VARIABLE_TOKEN) ? name.substring(LOCAL_VARIABLE_TOKEN.length()).strip() : name.strip(); if (name.startsWith(LIST_SEPARATOR) || name.endsWith(LIST_SEPARATOR)) { if (printErrors) { logger.error("A variable name cannot start nor end with the list separator " + LIST_SEPARATOR, ErrorType.MALFORMED_INPUT); @@ -75,13 +159,13 @@ public static boolean isValidVariableName(String name, boolean printErrors, Skri } /** - * Returns the internal value of the requested variable. - *

    - * Do not modify the returned value! - * - * @param name the name of the variable - * @return an Object for a normal Variable or a Map for a list variable, or null if the variable is not set. - */ + * Returns the internal value of the requested variable. + *

    + * Do not modify the returned value! + * + * @param name the name of the variable + * @return an Object for a normal Variable or a Map for a list variable, or null if the variable is not set. + */ public static Optional getVariable(String name, TriggerContext e, boolean local) { if (local) { var map = localVariables.get(e); @@ -94,11 +178,11 @@ public static Optional getVariable(String name, TriggerContext e, boolea } /** - * Sets a variable. - * - * @param name The variable's name. Can be a "list variable::*" (value must be null in this case) - * @param value The variable's value. Use null to delete the variable. - */ + * Sets a variable. + * + * @param name The variable's name. Can be a "list variable::*" (value must be null in this case) + * @param value The variable's value. Use null to delete the variable. + */ public static void setVariable(String name, @Nullable Object value, @Nullable TriggerContext e, boolean local) { if (local) { assert e != null : name; @@ -108,13 +192,52 @@ public static void setVariable(String name, @Nullable Object value, @Nullable Tr map.setVariable(name, value); } else { variableMap.setVariable(name, value); + if (!hasStorages()) + return; + queueVariableChange(name, value); + try { + if (LOCK.tryLock()) + processChangeQueue(); + } finally { + LOCK.unlock(); + } } } /** - * Clears all variables. + * Changes to variables that have not yet been performed. + */ + private static final Queue VARIABLE_CHANGE_QUEUE = new ConcurrentLinkedQueue<>(); + + static void queueVariableChange(String name, @Nullable Object value) { + if (!hasStorages()) + return; + VARIABLE_CHANGE_QUEUE.add(new VariableChange(name, value)); + } + + /** + * Processes all entries in variable change queue. + *

    + * Note that caller must acquire write lock before calling this, + * then release it. */ - public static void clearVariables() { + private static void processChangeQueue() { + while (true) { + VariableChange change = VARIABLE_CHANGE_QUEUE.poll(); + if (change == null) + break; + + variableMap.setVariable(change.name, change.value); + STORAGES.stream() + .filter(storage -> storage.accept(change.name)) + .forEach(storage -> { + SerializedVariable serialized = storage.serialize(change.name, change.value); + storage.save(serialized); + }); + } + } + + public static final void clearVariables() { variableMap.clearVariables(); } @@ -139,4 +262,34 @@ public static void clearLocalVariables(TriggerContext ctx) { localVariables.remove(ctx); } + /** + * Represents a variable that is to be changed. + * Key-value pair. Key being the variable name. + */ + private static class VariableChange { + + /** + * The variable name of the changed variable. + */ + public final String name; + + /** + * The value of the variable change. + */ + @Nullable + public final Object value; + + /** + * Creates a new {@link VariableChange} with the given name and value. + * + * @param name the variable name. + * @param value the new variable value. + */ + public VariableChange(String name, @Nullable Object value) { + this.name = name; + this.value = value; + } + + } + } From 16c6cede9e215d2913cae5940b6cf01a9efd6965 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 08:09:26 -0800 Subject: [PATCH 14/68] DefaultFunctions - fix mod func to take in number not int --- .../skriptparser/structures/functions/DefaultFunctions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java index c3f61199d..184be196b 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/DefaultFunctions.java @@ -5,14 +5,14 @@ public class DefaultFunctions { static { Functions.registerFunction(new JavaFunction<>( "mod", - new FunctionParameter[]{new FunctionParameter<>("i", Integer.class, true), new FunctionParameter<>("m", Integer.class, true)}, + new FunctionParameter[]{new FunctionParameter<>("i", Number.class, true), new FunctionParameter<>("m", Number.class, true)}, Number.class, true) { @Override public Number[] executeSimple(Object[][] params) { Number d = (Number) params[0][0]; Number m = (Number) params[1][0]; - return new Number[] {d.intValue() % m.intValue()}; + return new Number[] {d.doubleValue() % m.doubleValue()}; } }); } From 4736a89dada2c93bfe3b8ab7bd25000c1ad942aa Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 13:52:26 -0800 Subject: [PATCH 15/68] Events - handle event triggers internally rather than forcing this upon addons --- .../github/syst3ms/skriptparser/Skript.java | 75 +++---------------- .../syst3ms/skriptparser/event/EvtAtTime.java | 21 +++++- .../skriptparser/event/EvtPeriodical.java | 15 +++- .../skriptparser/event/EvtScriptLoad.java | 31 +++++--- .../syst3ms/skriptparser/event/EvtWhen.java | 18 ++++- .../syst3ms/skriptparser/lang/Structure.java | 1 + .../syst3ms/skriptparser/lang/Trigger.java | 1 + .../skriptparser/lang/UnloadedTrigger.java | 1 + .../lang/{ => event}/SkriptEvent.java | 37 ++++++++- .../lang/event/StartOnLoadEvent.java | 17 +++++ .../skriptparser/parsing/ScriptLoader.java | 8 +- .../skriptparser/parsing/SyntaxParser.java | 1 + .../registration/SkriptAddon.java | 11 +-- .../registration/SkriptEventInfo.java | 2 +- .../registration/SkriptRegistration.java | 2 +- .../skriptparser/registration/SyntaxInfo.java | 2 +- 16 files changed, 142 insertions(+), 101 deletions(-) rename src/main/java/io/github/syst3ms/skriptparser/lang/{ => event}/SkriptEvent.java (73%) create mode 100644 src/main/java/io/github/syst3ms/skriptparser/lang/event/StartOnLoadEvent.java diff --git a/src/main/java/io/github/syst3ms/skriptparser/Skript.java b/src/main/java/io/github/syst3ms/skriptparser/Skript.java index c0c913d59..b04bd5abf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/Skript.java +++ b/src/main/java/io/github/syst3ms/skriptparser/Skript.java @@ -1,27 +1,8 @@ package io.github.syst3ms.skriptparser; -import io.github.syst3ms.skriptparser.event.AtTimeContext; -import io.github.syst3ms.skriptparser.event.EvtAtTime; -import io.github.syst3ms.skriptparser.event.EvtPeriodical; -import io.github.syst3ms.skriptparser.event.EvtScriptLoad; -import io.github.syst3ms.skriptparser.event.EvtWhen; -import io.github.syst3ms.skriptparser.event.PeriodicalContext; -import io.github.syst3ms.skriptparser.event.ScriptLoadContext; -import io.github.syst3ms.skriptparser.event.WhenContext; -import io.github.syst3ms.skriptparser.lang.SkriptEvent; -import io.github.syst3ms.skriptparser.lang.Statement; -import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.lang.event.StartOnLoadEvent; +import io.github.syst3ms.skriptparser.parsing.ScriptLoader; import io.github.syst3ms.skriptparser.registration.SkriptAddon; -import io.github.syst3ms.skriptparser.structures.functions.StructFunction; -import io.github.syst3ms.skriptparser.util.DurationUtils; -import io.github.syst3ms.skriptparser.util.ThreadUtils; -import io.github.syst3ms.skriptparser.util.Time; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; /** * The {@link SkriptAddon} representing Skript itself @@ -30,56 +11,18 @@ public class Skript extends SkriptAddon { private final String[] mainArgs; - private final Map> mainTriggers = new HashMap<>(); - private final Map> periodicalTriggers = new HashMap<>(); - private final Map> whenTriggers = new HashMap<>(); - private final Map> atTimeTriggers = new HashMap<>(); - public Skript(String[] mainArgs) { this.mainArgs = mainArgs; } - @Override - public void handleTrigger(String scriptName, Trigger trigger) { - SkriptEvent event = trigger.getEvent(); - - if (!canHandleEvent(event)) - return; - - if (event instanceof EvtScriptLoad) { - mainTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); - } else if (event instanceof EvtPeriodical) { - periodicalTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); - } else if (event instanceof EvtWhen) { - whenTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); - } else if (event instanceof EvtAtTime) { - atTimeTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); - } else if (event instanceof StructFunction function) { - function.register(trigger); - } - } - @Override public void finishedLoading() { - for (Trigger trigger : mainTriggers.values().stream().flatMap(List::stream).toList()) { - Statement.runAll(trigger, new ScriptLoadContext(mainArgs)); - } - for (Trigger trigger : periodicalTriggers.values().stream().flatMap(List::stream).toList()) { - var ctx = new PeriodicalContext(); - var dur = ((EvtPeriodical) trigger.getEvent()).getDuration().getSingle().orElseThrow(AssertionError::new); - ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), dur); - } - for (Trigger trigger : whenTriggers.values().stream().flatMap(List::stream).toList()) { - var ctx = new WhenContext(); - ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), Duration.ofMillis(DurationUtils.TICK)); - } - for (Trigger trigger : atTimeTriggers.values().stream().flatMap(List::stream).toList()) { - var ctx = new AtTimeContext(); - var time = ((EvtAtTime) trigger.getEvent()).getTime().getSingle().orElseThrow(AssertionError::new); - var initialDelay = (Time.now().getTime().isAfter(time.getTime()) - ? Time.now().difference(Time.LATEST).plus(time.difference(Time.MIDNIGHT)) - : Time.now().difference(time)); - ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), initialDelay, Duration.ofDays(1)); - } + ScriptLoader.getTriggerMap().values().forEach(triggers -> + triggers.forEach(trigger -> { + if (trigger.getEvent() instanceof StartOnLoadEvent event) { + event.onInitialLoad(trigger); + } + })); } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/event/EvtAtTime.java b/src/main/java/io/github/syst3ms/skriptparser/event/EvtAtTime.java index 6b1d94d72..de552586a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/event/EvtAtTime.java +++ b/src/main/java/io/github/syst3ms/skriptparser/event/EvtAtTime.java @@ -3,11 +3,17 @@ import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.Literal; -import io.github.syst3ms.skriptparser.lang.SkriptEvent; +import io.github.syst3ms.skriptparser.lang.Statement; +import io.github.syst3ms.skriptparser.lang.Trigger; +import io.github.syst3ms.skriptparser.lang.event.SkriptEvent; import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.event.StartOnLoadEvent; import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.util.ThreadUtils; import io.github.syst3ms.skriptparser.util.Time; +import java.time.Duration; + /** * This event will trigger each day at a given time. * Note that this may have some delay. The event certainly isn't accurate to the millisecond. @@ -18,7 +24,7 @@ * @since ALPHA * @author Mwexim */ -public class EvtAtTime extends SkriptEvent { +public class EvtAtTime extends SkriptEvent implements StartOnLoadEvent { static { Parser.getMainRegistration() .newEvent(EvtAtTime.class, "*at %*time%") @@ -48,4 +54,15 @@ public String toString(TriggerContext ctx, boolean debug) { public Literal

    * Skript-parser's event system is composed of three interacting parts : {@link Trigger}, {@link SkriptEvent} and {@link TriggerContext}. * This is directly parallel to Skript's event system, with Bukkit's own Event class replacing TriggerContext. - * + *

    * Let's explain how this system works using a simple analogy : skript-parser is like a giant kitchen : *

      *
    • The goal is to prepare food (write code).
    • @@ -41,8 +41,21 @@ public List getTriggers() { return TRIGGER_MAP.values().stream().flatMap(List::stream).toList(); } + public void addTrigger(String scriptName, Trigger trigger) { + //TRIGGER_MAP.computeIfAbsent(scriptName, k -> Collections.synchronizedList(Collections.emptyList())).add(trigger); + if (!TRIGGER_MAP.containsKey(scriptName)) { + TRIGGER_MAP.put(scriptName, new ArrayList<>()); + } + TRIGGER_MAP.get(scriptName).add(trigger); + } + + public void clearTrigger(String scriptName) { + TRIGGER_MAP.put(scriptName, new ArrayList<>()); + } + /** * Whether this event should trigger, given the {@link TriggerContext} + * * @param ctx the TriggerContext to check * @return whether the event should trigger */ @@ -62,7 +75,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex * code too often. Skript is no exception, however, by default, every trigger is loaded in the order it appears in the file, * This is undesirable if we don't want the restriction of having to declare functions before using them. This is especially * counter-productive if we're dealing with multiple scripts. - * + *

      * To solve this problem, {@link Trigger triggers} with a higher loading priority number will be loaded first. * * @return the loading priority number. 0 by default @@ -76,6 +89,7 @@ public int getLoadingPriority() { * 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 SkriptEvent * or {@code null} if you don't want to allow any * @see #isRestrictingExpressions() @@ -87,20 +101,13 @@ public Set> getAllowedSyntaxes() { /** * Whether the syntax restrictions outlined in {@link #getAllowedSyntaxes()} should also apply to expressions. * This is usually undesirable, so it is false by default. - * + *

      * This should return true if and only if {@link #getAllowedSyntaxes()} contains an {@linkplain Expression} class. + * * @return whether the use of expressions is also restricted by {@link #getAllowedSyntaxes()}. False by default. */ public boolean isRestrictingExpressions() { return false; } - public void addTrigger(String scriptName, Trigger trigger) { - TRIGGER_MAP.computeIfAbsent(scriptName, k -> Collections.synchronizedList(Collections.emptyList())).add(trigger); - } - - public void clearTrigger(String scriptName) { - TRIGGER_MAP.put(scriptName, new ArrayList<>()); - } - } diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java index 73f59db09..af95f2632 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java @@ -84,14 +84,26 @@ public static List loadScript(Path scriptPath, SkriptLogger logger, bo } } unloadedTriggers.sort((a, b) -> b.getTrigger().getEvent().getLoadingPriority() - a.getTrigger().getEvent().getLoadingPriority()); + + + // Clear triggers from unloaded events + triggerMap.forEach((key, value) -> { + if (key.equals(scriptName)) { + List triggers = triggerMap.get(key); + triggers.forEach(trigger -> trigger.getEvent().clearTrigger(scriptName)); + } + }); + // Clear triggers of a script from the map + triggerMap.remove(scriptName); + for (var unloaded : unloadedTriggers) { logger.finalizeLogs(); logger.setLine(unloaded.getLine()); var loaded = unloaded.getTrigger(); loaded.loadSection(unloaded.getSection(), unloaded.getParserState(), logger); //unloaded.getEventInfo().getRegisterer().handleTrigger(scriptName,loaded); + SkriptEvent event = unloaded.getTrigger().getEvent(); - event.clearTrigger(scriptName); event.addTrigger(scriptName, loaded); triggerMap.putOne(scriptName, loaded); } From 90bff901adad20d62f4825f870f0d56d8d870d72 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 16:44:14 -0800 Subject: [PATCH 18/68] Skript - fix empty list --- src/main/java/io/github/syst3ms/skriptparser/Skript.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/Skript.java b/src/main/java/io/github/syst3ms/skriptparser/Skript.java index 9c0d7f121..65826f9d2 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/Skript.java +++ b/src/main/java/io/github/syst3ms/skriptparser/Skript.java @@ -25,7 +25,7 @@ public void finishedLoading(@Nullable String scriptName) { if (scriptName == null) { triggers = ScriptLoader.getTriggerMap().values().stream().flatMap(List::stream).toList(); } else { - triggers = ScriptLoader.getTriggerMap().get(scriptName); + triggers = ScriptLoader.getTriggerMap().computeIfAbsent(scriptName, k -> List.of()); } triggers.forEach(trigger -> { if (trigger.getEvent() instanceof StartOnLoadEvent event) { From 129d047fd757bab472211053631849a051d8d19a Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 18:40:02 -0800 Subject: [PATCH 19/68] tests got missed, sorry guys --- .../io/github/syst3ms/skriptparser/TestAddon.java | 15 --------------- .../syst3ms/skriptparser/syntax/EvtTest.java | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java b/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java index e0879864a..e9c879f71 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java +++ b/src/test/java/io/github/syst3ms/skriptparser/TestAddon.java @@ -1,13 +1,10 @@ package io.github.syst3ms.skriptparser; -import io.github.syst3ms.skriptparser.lang.SkriptEvent; import io.github.syst3ms.skriptparser.lang.Statement; import io.github.syst3ms.skriptparser.lang.Trigger; import io.github.syst3ms.skriptparser.registration.SkriptAddon; -import io.github.syst3ms.skriptparser.syntax.EvtTest; import io.github.syst3ms.skriptparser.syntax.TestContext; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -15,18 +12,6 @@ public class TestAddon extends SkriptAddon { private final Map> testTriggers = new HashMap<>(); - @Override - public void handleTrigger(String scriptName, Trigger trigger) { - SkriptEvent event = trigger.getEvent(); - - if (!canHandleEvent(event)) - return; - - if (event instanceof EvtTest) { - testTriggers.getOrDefault(scriptName, new ArrayList<>()).add(trigger); - } - } - @Override public void finishedLoading() { for (Trigger trigger : testTriggers.values().stream().flatMap(List::stream).toList()) { diff --git a/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java b/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java index 4480f16a5..7095051c4 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java @@ -2,7 +2,7 @@ import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.lang.Expression; -import io.github.syst3ms.skriptparser.lang.SkriptEvent; +import io.github.syst3ms.skriptparser.lang.event.SkriptEvent; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.registration.context.ContextValue; From f0264520b898ec37a150846780934f10916de3b5 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 19:14:35 -0800 Subject: [PATCH 20/68] EffChange - fix null error --- .../java/io/github/syst3ms/skriptparser/effects/EffChange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java index b58353db8..7a366d96f 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java @@ -118,7 +118,7 @@ public void execute(TriggerContext ctx) { changed.change(ctx, mode, new Object[0]); } else { var values = changeWith.getValues(ctx); - if (values.length == 0) + if (values == null || values.length == 0) return; changed.change(ctx, mode, values); } From a94b94c458e46ab5ea94afa0bb5926337ba5cb4d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 20:15:59 -0800 Subject: [PATCH 21/68] DefaultRegistration - fix missing function param type --- .../skriptparser/registration/DefaultRegistration.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index c34cac575..30deab76c 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -1,6 +1,7 @@ package io.github.syst3ms.skriptparser.registration; import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.structures.functions.FunctionParameter; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; @@ -206,6 +207,10 @@ public Class getRelativeType() { .toStringFunction(Type::getBaseName) .register(); + registration.newType(FunctionParameter.class, "functionparameter", "functionparameter@s") + .toStringFunction(parameter -> parameter.getName() + ": " + parameter.getType().getName()) + .register(); + registration.newType(Color.class, "color", "color@s") .name("Color") .description("Represents a color.") From bf1cfe19bacaa2e8d230f8b82fafcec25c2cf11e Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 21:10:38 -0800 Subject: [PATCH 22/68] Function fixes --- .../skriptparser/parsing/ScriptLoader.java | 27 +++++++++--------- .../structures/functions/Functions.java | 28 +++++++++++++++---- .../structures/functions/StructFunction.java | 14 +++++++++- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java index af95f2632..5a600f654 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java @@ -47,10 +47,22 @@ public static List loadScript(Path scriptPath, boolean debug) { */ public static List loadScript(Path scriptPath, SkriptLogger logger, boolean debug) { List elements; - String scriptName; + String scriptName = scriptPath.getFileName().toString().replaceAll("(.+)\\..+", "$1"); + + // Clear triggers from unloaded events + triggerMap.forEach((key, value) -> { + if (key.equals(scriptName)) { + List triggers = triggerMap.get(key); + triggers.forEach(trigger -> trigger.getEvent().clearTrigger(scriptName)); + } + }); + // Clear triggers of a script from the map + triggerMap.remove(scriptName); + + try { var lines = FileUtils.readAllLines(scriptPath); - scriptName = scriptPath.getFileName().toString().replaceAll("(.+)\\..+", "$1"); + elements = FileParser.parseFileLines(scriptName, lines, 0, @@ -85,17 +97,6 @@ public static List loadScript(Path scriptPath, SkriptLogger logger, bo } unloadedTriggers.sort((a, b) -> b.getTrigger().getEvent().getLoadingPriority() - a.getTrigger().getEvent().getLoadingPriority()); - - // Clear triggers from unloaded events - triggerMap.forEach((key, value) -> { - if (key.equals(scriptName)) { - List triggers = triggerMap.get(key); - triggers.forEach(trigger -> trigger.getEvent().clearTrigger(scriptName)); - } - }); - // Clear triggers of a script from the map - triggerMap.remove(scriptName); - for (var unloaded : unloadedTriggers) { logger.finalizeLogs(); logger.setLine(unloaded.getLine()); diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java index 526c129d5..0b52125cf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Functions.java @@ -5,14 +5,17 @@ import io.github.syst3ms.skriptparser.log.SkriptLogger; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; public class Functions { - private static final List> functions = new ArrayList<>(); + private static final Map>> functionsMap = new HashMap<>(); + private static final String GLOBAL_FUNCTIONS_NAME = "global_functions_dont_change"; static final String FUNCTION_NAME_REGEX = "^[a-zA-Z0-9_]*"; private static final Pattern FUNCTION_NAME_PATTERN = Pattern.compile(FUNCTION_NAME_REGEX); static final String FUNCTION_CALL_PATTERN = "<(" + Functions.FUNCTION_NAME_REGEX + ")\\((.*)\\)>"; @@ -20,19 +23,32 @@ public class Functions { private Functions() {} static void preRegisterFunction(ScriptFunction function) { - functions.add(function); + String scriptName = function.getScriptName(); + functionsMap.computeIfAbsent(scriptName, k -> new ArrayList<>()).add(function); } public static void registerFunction(ScriptFunction function, Trigger trigger) { function.setTrigger(trigger); } + public static void removeFunctions(String scriptName) { + if (functionsMap.containsKey(scriptName)) { + for (Function function : functionsMap.get(scriptName)) { + if (function instanceof ScriptFunction sf) { + sf.setTrigger(null); + } + } + } + functionsMap.put(scriptName, new ArrayList<>()); + } + public static void registerFunction(JavaFunction function) { - functions.add(function); + functionsMap.computeIfAbsent(GLOBAL_FUNCTIONS_NAME, k -> new ArrayList<>()).add(function); } public static boolean isValidFunction(ScriptFunction function, SkriptLogger logger) { - for (Function registeredFunction : functions) { + String scriptName = function.getScriptName(); + for (Function registeredFunction : functionsMap.computeIfAbsent(scriptName, k -> new ArrayList<>())) { String registeredFunctionName = registeredFunction.getName(); String providedFunctionName = function.getName(); if (!registeredFunctionName.equals(providedFunctionName)) continue; @@ -64,7 +80,9 @@ public static boolean isValidFunction(ScriptFunction function, SkriptLogger l } public static Optional> getFunctionByName(String name, String scriptName) { - for (Function registeredFunction : functions) { + if (scriptName.endsWith(".sk")) scriptName = scriptName.substring(0, scriptName.length() - 3); + + for (Function registeredFunction : functionsMap.computeIfAbsent(scriptName, k -> new ArrayList<>())) { if (!registeredFunction.getName().equals(name)) continue; // we don't care then!!!! goodbye continue to the next one if (registeredFunction instanceof ScriptFunction registeredScriptFunction && registeredScriptFunction.isLocal() diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java index d9ac1a1cf..916440f13 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java @@ -74,7 +74,8 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex } if (type.isPlural(rawReturnType)) returnSingle = false; } - function = new ScriptFunction<>(parseContext.getLogger().getFileName(), local, functionName, parameters, returnType, returnSingle); + String scriptName = parseContext.getLogger().getFileName().replace(".sk", ""); + function = new ScriptFunction<>(scriptName, local, functionName, parameters, returnType, returnSingle); if (!Functions.isValidFunction(function, parseContext.getLogger())) { return false; } @@ -96,4 +97,15 @@ public void register(Trigger trigger) { Functions.registerFunction(function, trigger); } + @Override + public void addTrigger(String scriptName, Trigger trigger) { + register(trigger); + super.addTrigger(scriptName, trigger); + } + + @Override + public void clearTrigger(String scriptName) { + Functions.removeFunctions(scriptName); + super.clearTrigger(scriptName); + } } From 5deb3f6db90d4334d30cd9b98ce065ed646f10bc Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Mon, 26 Jan 2026 22:43:17 -0800 Subject: [PATCH 23/68] StructFunction - add docs --- .../skriptparser/structures/functions/StructFunction.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java index 916440f13..6cafc33c6 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java @@ -19,6 +19,9 @@ public class StructFunction extends Structure { .newEvent(StructFunction.class, "*[:local[ ]] func[tion] <" + Functions.FUNCTION_NAME_REGEX + ">" + "\\([params:%*functionparameters%]\\)[return: \\:\\: <.+>]") .setHandledContexts(FunctionContext.class) + .name("Function") + .description("Creates a function that can be called from other scripts.") + .since("INSERT VERSION") .register(); } From 145bad401ba762797cfdf0b8992a935f221b3f6d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 11:40:41 -0800 Subject: [PATCH 24/68] Variable - change some storage stuff --- .../registration/DefaultRegistration.java | 97 ++++++++++++++++++- .../skriptparser/sections/SecLoop.java | 23 +++-- .../variables/VariableStorage.java | 28 +++--- .../skriptparser/variables/Variables.java | 36 +++++-- 4 files changed, 148 insertions(+), 36 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 30deab76c..503eb6c7a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -1,10 +1,13 @@ package io.github.syst3ms.skriptparser.registration; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.structures.functions.FunctionParameter; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.changers.TypeSerializer; import io.github.syst3ms.skriptparser.types.comparisons.Comparator; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; import io.github.syst3ms.skriptparser.types.comparisons.Relation; @@ -15,6 +18,8 @@ import io.github.syst3ms.skriptparser.util.color.Color; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.stream.IntStream; @@ -42,8 +47,7 @@ public static void register() { .literalParser(s -> { if (s == null) return null; try { - if (s.contains(".")) return Double.parseDouble(s); - else return Long.parseLong(s); + return Double.parseDouble(s); } catch (NumberFormatException e) { return null; } @@ -69,7 +73,19 @@ public Number subtract(Number value, Number difference) { public Class getRelativeType() { return Number.class; } - }).register(); + }) + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Number value) { + return gson.toJsonTree(value.doubleValue()); + } + + @Override + public Number deserialize(Gson gson, JsonElement element) { + return gson.fromJson(element, Double.class); + } + }) + .register(); registration.newType(Integer.class, "integer", "integer@s") .literalParser(s -> { @@ -104,6 +120,16 @@ public Class getRelativeType() { return Integer.class; } }) + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Integer value) { + return gson.toJsonTree(value, Integer.class); + } + @Override + public Integer deserialize(Gson gson, JsonElement element) { + return gson.fromJson(element, Integer.class); + } + }) .register(); registration.newType(Float.class, "float", "float@s") @@ -139,6 +165,16 @@ public Class getRelativeType() { return Float.class; } }) + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Float value) { + return gson.toJsonTree(value, Float.class); + } + @Override + public Float deserialize(Gson gson, JsonElement element) { + return gson.fromJson(element, Float.class); + } + }) .register(); registration.newType(Double.class, "double", "double@s") @@ -174,6 +210,16 @@ public Class getRelativeType() { return Double.class; } }) + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Double value) { + return gson.toJsonTree(value, Double.class); + } + @Override + public Double deserialize(Gson gson, JsonElement element) { + return gson.fromJson(element, Double.class); + } + }) .register(); registration.newType(String.class, "string", "string@s") @@ -181,6 +227,17 @@ public Class getRelativeType() { .description("A string of characters.") .examples("set {_string} to \"Hello World!\"") .since("INSERT VERSION") + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, String value) { + return gson.toJsonTree(value, String.class); + } + + @Override + public String deserialize(Gson gson, JsonElement element) { + return gson.fromJson(element, String.class); + } + }) .register(); registration.newType(Boolean.class, "boolean", "boolean@s") @@ -197,6 +254,18 @@ public Class getRelativeType() { .description("A boolean value, represented as 'true' or 'false'.") .since("INSERT VERSION") .toStringFunction(String::valueOf) + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Boolean value) { + return gson.toJsonTree(value, Boolean.class); + } + + @Override + public Boolean deserialize(Gson gson, JsonElement jsonElement) { + return gson.fromJson(jsonElement, Boolean.class); + } + + }) .register(); registration.newType(Type.class, "type", "type@s") @@ -348,13 +417,33 @@ public Relation apply(Duration duration, Duration duration2) { .toArray(String[]::new); } ); + Ranges.registerRange( + Number.class, + Number.class, + (l, r) -> { + if (l.doubleValue() >= 0) { + return new Number[0]; + } else { + List elements = new ArrayList<>(); + Number current = l; + do { + elements.add(current); + current = current.doubleValue() + 1; + } while (current.intValue() <= 0); + return elements.toArray(new Number[0]); + } + } + ); /* * Converters */ registration.addConverter(SkriptDate.class, Time.class, da -> Optional.of(Time.of(da))); - + registration.addConverter(Long.class, Integer.class, l -> Optional.of(l.intValue())); + registration.addConverter(Integer.class, Long.class, i -> Optional.of(i.longValue())); + registration.addConverter(Double.class, Float.class, d -> Optional.of(d.floatValue())); + registration.addConverter(Float.class, Double.class, f -> Optional.of(f.doubleValue())); registration.register(true); // Ignoring logs here, we control the input } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java index 21405541c..7153c2f36 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java @@ -18,7 +18,6 @@ import io.github.syst3ms.skriptparser.types.ranges.Ranges; import org.jetbrains.annotations.Nullable; -import java.math.BigInteger; import java.util.Iterator; import java.util.Optional; @@ -38,7 +37,7 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen static { Parser.getMainRegistration().newSection(SecLoop.class, - "loop %integer% times", "loop %objects%") + "loop %number% times", "loop %objects%") .name("Loop") .description("Loops over all the values in a list or loop x number of times.") .examples("loop 10 times:", @@ -51,7 +50,7 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen @Nullable private Expression expression; - private Expression times; + private Expression times; private boolean isNumericLoop; @Nullable @@ -73,10 +72,10 @@ public boolean loadSection(FileSection section, ParserState parserState, SkriptL public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { isNumericLoop = matchedPattern == 0; if (isNumericLoop) { - times = (Expression) expressions[0]; + times = (Expression) expressions[0]; // We can do some certainty checks with Literals. if (times instanceof Literal) { - var t = ((Optional) ((Literal) times).getSingle()).orElse(BigInteger.ONE); + var t = ((Optional) ((Literal) times).getSingle()).orElse(1); if (t.intValue() <= 0) { parseContext.getLogger().error("Cannot loop a negative or zero amount of times", ErrorType.SEMANTIC_ERROR); return false; @@ -171,13 +170,13 @@ public Expression getLoopedExpression() { * @param size the expression * @return the SimpleLiteral */ - private static Expression rangeOf(TriggerContext ctx, Expression size) { - BigInteger[] range = (BigInteger[]) size.getSingle(ctx) - .filter(t -> t.compareTo(BigInteger.ZERO) > 0) - .map(t -> Ranges.getRange(BigInteger.class).orElseThrow() + private static Expression rangeOf(TriggerContext ctx, Expression size) { + Number[] range = (Number[]) size.getSingle(ctx) + .filter(t -> t.intValue() > 0) + .map(t -> Ranges.getRange(Number.class).orElseThrow() .getFunction() - .apply(BigInteger.ONE, t)) // Upper bound is inclusive - .orElse(new BigInteger[0]); - return new SimpleLiteral<>(BigInteger.class, range); + .apply(1, t)) // Upper bound is inclusive + .orElse(new Number[0]); + return new SimpleLiteral<>(Number.class, range); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java index 3029cc00c..c78fd819a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java @@ -48,7 +48,7 @@ public abstract class VariableStorage implements Closeable { /** * The name of the database used in the configurations. */ - protected final String[] names; + protected final String name; /** * The pattern of the variable name this storage accepts. @@ -70,13 +70,13 @@ public abstract class VariableStorage implements Closeable { * @param logger the logger to print logs to. * @param name the name. */ - protected VariableStorage(SkriptLogger logger, @NotNull String... names) { + protected VariableStorage(SkriptLogger logger, @NotNull String name) { this(logger, new GsonBuilder() .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE) .serializeNulls() .setLenient() .create(), - names); + name); } /** @@ -86,9 +86,9 @@ protected VariableStorage(SkriptLogger logger, @NotNull String... names) { * @param gson the gson that controls the serialization of the json elements. * @param name the name. */ - protected VariableStorage(SkriptLogger logger, @NotNull Gson gson, @NotNull String... names) { + protected VariableStorage(SkriptLogger logger, @NotNull Gson gson, @NotNull String name) { this.logger = logger; - this.names = names; + this.name = name; this.gson = gson; // TODO allow this runnable to be interupted. CompletableFuture.runAsync(() -> { @@ -126,7 +126,7 @@ protected String getConfigurationValue(FileSection section, String key) { * * @param section the file section. * @param key the key node. - * @param type the class type. + * @param classType the class type. * @return the parsed value, or {@code null} if the value was invalid, * or not found. * @param the class type generic. @@ -136,12 +136,12 @@ protected String getConfigurationValue(FileSection section, String key) { protected T getConfigurationValue(FileSection section, String key, Class classType) { Optional value = section.get(key); if (!value.isPresent()) { - logger.error("The configuration is missing the entry for '" + key + "' for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + logger.error("The configuration is missing the entry for '" + key + "' for the database '" + name + "'", ErrorType.SEMANTIC_ERROR); return null; } String[] split = value.get().getLineContent().split(OptionLoader.OPTION_SPLIT_PATTERN); if (split.length < 2) { - logger.error("The configuration entry '" + key + "' is not a option entry (key: value) for the database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + logger.error("The configuration entry '" + key + "' is not a option entry (key: value) for the database '" + name + "'", ErrorType.SEMANTIC_ERROR); return null; } String content = split[1]; @@ -150,7 +150,7 @@ protected T getConfigurationValue(FileSection section, String key, Class Optional> type = TypeManager.getByClassExact(classType); if (!type.isPresent()) { - logger.error("The class type '" + classType.getName() + "' is not registered. Cannot parse node '" + key + "' for database '" + names[0] + "'", ErrorType.SEMANTIC_ERROR); + logger.error("The class type '" + classType.getName() + "' is not registered. Cannot parse node '" + key + "' for database '" + name + "'", ErrorType.SEMANTIC_ERROR); return null; } @@ -162,7 +162,7 @@ protected T getConfigurationValue(FileSection section, String key, Class T parsedValue = parser.get().apply(content); if (parsedValue == null) { - logger.error("The entry for '" + key + "' in the database '" + names[0] + "' must be " + + logger.error("The entry for '" + key + "' in the database '" + name + "' must be " + type.get().withIndefiniteArticle(true), ErrorType.SEMANTIC_ERROR); return null; } @@ -173,7 +173,7 @@ protected T getConfigurationValue(FileSection section, String key, Class * Loads the configuration for this variable storage * from the given section node. * - * @param sectionNode the section node. + * @param section the section node. * @return whether the loading succeeded. */ public final boolean loadConfiguration(FileSection section) { @@ -281,7 +281,7 @@ protected void loadVariable(String name, @NotNull String type, @NotNull byte[] v /** * Checks if this variable storage accepts the given variable name. * - * @param var the variable name. + * @param variableName the variable name. * @return if this storage accepts the variable name. * * @see #variableNamePattern @@ -316,7 +316,7 @@ public SerializedVariable serialize(String name, @Nullable T value) { } /** - * Used by {@link #load(String, String, byte[]). + * Used by {@link #loadVariable(String, String, byte[])}. * You don't need to use this method, but if you need to read the Object, this method allows for deserialization. * * @param typeName The name of the type. @@ -349,7 +349,7 @@ final void save(SerializedVariable variable) { if (!changesQueue.offer(variable)) { if (lastError < System.currentTimeMillis() - ERROR_INTERVAL * 1000) { // Inform console about overload of variable changes - System.out.println("Skript cannot save any variables to the database '" + names[0] + "'. " + + System.out.println("Skript cannot save any variables to the database '" + name + "'. " + "The thread will hang to avoid losing variable."); lastError = System.currentTimeMillis(); diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java index 58ecb0037..818709a43 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/Variables.java @@ -13,6 +13,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; +import io.github.syst3ms.skriptparser.lang.entries.OptionLoader; import org.jetbrains.annotations.Nullable; import io.github.syst3ms.skriptparser.file.FileElement; @@ -76,19 +77,42 @@ public static boolean registerStorage(Class stora */ public static void load(SkriptLogger logger, FileSection section) throws IllegalArgumentException { for (FileElement databaseElement : section.getElements()) { - if (!(databaseElement instanceof FileSection)) { + if (!(databaseElement instanceof FileSection databaseSection)) { logger.error("The file node 'databases." + databaseElement.getLineContent() + "' was not a section.", ErrorType.STRUCTURE_ERROR); continue; } String databaseName = databaseElement.getLineContent(); + String databaseType = null; + + for (FileElement element : databaseSection.getElements()) { + if (!(element instanceof FileSection)) { + Optional value = databaseSection.get("type"); + if (value.isEmpty()) { + logger.error("The configuration is missing the entry for 'type' for the database '" + databaseName + "'", ErrorType.SEMANTIC_ERROR); + } else { + String[] split = value.get().getLineContent().split(OptionLoader.OPTION_SPLIT_PATTERN); + if (split.length < 2) { + logger.error("The configuration entry 'type' is not a option entry (key: value) for the database '" + databaseName + "'", ErrorType.SEMANTIC_ERROR); + continue; + } + databaseType = split[1]; + } + } + } + + if (databaseType == null) { + continue; + } + + if (databaseName.isBlank()) { logger.error("A database name was incorrect, cannot be an empty string. (line " + databaseElement.getLine() + ")", ErrorType.SEMANTIC_ERROR); continue; } - FileSection databaseSection = (FileSection) databaseElement; + String finalDatabaseType = databaseType; Class storageClass = AVAILABLE_STORAGES.entrySet().stream() - .filter(entry -> entry.getValue().contains(databaseName.toLowerCase(Locale.ENGLISH))) - .map(entry -> entry.getKey()) + .filter(entry -> entry.getValue().contains(finalDatabaseType.toLowerCase(Locale.ENGLISH))) + .map(Map.Entry::getKey) .findFirst() .orElse(null); if (storageClass == null) { @@ -97,8 +121,8 @@ public static void load(SkriptLogger logger, FileSection section) throws Illegal } try { - Constructor constructor = storageClass.getConstructor(SkriptLogger.class); - VariableStorage storage = constructor.newInstance(logger); + Constructor constructor = storageClass.getConstructor(SkriptLogger.class, String.class); + VariableStorage storage = constructor.newInstance(logger, databaseName); if (storage.loadConfiguration(databaseSection)) STORAGES.add(storage); } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | InvocationTargetException e) { From 1acf142e0cf6b9c683c705512446b7887c043a47 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 11:47:52 -0800 Subject: [PATCH 25/68] SkriptLoggerTest - broken -- fix later --- .../syst3ms/skriptparser/log/SkriptLoggerTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java b/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java index 4cd8a5e6b..9d00bc5c2 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java @@ -23,13 +23,16 @@ public void skriptLoggerTest() { logger.finalizeLogs(); assertTrue(noMatchFound.isEmpty() && logger.close().get(0).getMessage().startsWith("No expression")); logger = new SkriptLogger(); + + // TODO fix // Optional> wrongNumber = SyntaxParser.parseExpression("range from 1 to 3", SyntaxParser.OBJECT_PATTERN_TYPE, parserState, logger); // logger.finalizeLogs(); // assertTrue(wrongNumber.isEmpty() && logger.close().get(0).getMessage().startsWith("A single")); // - logger = new SkriptLogger(); - Optional> wrongRange = SyntaxParser.parseBooleanExpression("1 is between \"a\" and \"b\"", SyntaxParser.MAYBE_CONDITIONAL, parserState, logger); - logger.finalizeLogs(); - assertTrue(wrongRange.isEmpty() && logger.close().get(0).getMessage().startsWith("'1' cannot")); +// logger = new SkriptLogger(); +// Optional> wrongRange = SyntaxParser.parseBooleanExpression("1 is between \"a\" and \"b\"", SyntaxParser.MAYBE_CONDITIONAL, parserState, logger); +// logger.finalizeLogs(); +// assertTrue(wrongRange.isEmpty() && logger.close().get(0).getMessage().startsWith("'1' cannot")); +// } } From aebdbebe05454500d606e57ccc7972746fb528ce Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 12:09:29 -0800 Subject: [PATCH 26/68] Variables - change how they're serialized - also change the load order of some types, putting "number" after all other numbers --- .../registration/DefaultRegistration.java | 114 ++++++++++-------- .../variables/SerializedVariable.java | 5 +- .../variables/VariableStorage.java | 12 +- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 503eb6c7a..9525a5e6a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -18,6 +18,7 @@ import io.github.syst3ms.skriptparser.util.color.Color; import java.time.Duration; +import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -40,53 +41,6 @@ public static void register() { .since("INSERT VERSION") .register(); - registration.newType(Number.class, "number", "number@s") - .name("Number") - .description("A number (can be an integer/float/double/etc).") - .since("INSERT VERSION") - .literalParser(s -> { - if (s == null) return null; - try { - return Double.parseDouble(s); - } catch (NumberFormatException e) { - return null; - } - }) - .toStringFunction(Object::toString) - .arithmetic(new Arithmetic() { - @Override - public Number difference(Number first, Number second) { - return first.doubleValue() - second.doubleValue(); - } - - @Override - public Number add(Number value, Number difference) { - return value.doubleValue() + difference.doubleValue(); - } - - @Override - public Number subtract(Number value, Number difference) { - return value.doubleValue() - difference.doubleValue(); - } - - @Override - public Class getRelativeType() { - return Number.class; - } - }) - .serializer(new TypeSerializer<>() { - @Override - public JsonElement serialize(Gson gson, Number value) { - return gson.toJsonTree(value.doubleValue()); - } - - @Override - public Number deserialize(Gson gson, JsonElement element) { - return gson.fromJson(element, Double.class); - } - }) - .register(); - registration.newType(Integer.class, "integer", "integer@s") .literalParser(s -> { if (s == null) return null; @@ -222,6 +176,53 @@ public Double deserialize(Gson gson, JsonElement element) { }) .register(); + registration.newType(Number.class, "number", "number@s") + .name("Number") + .description("A number (can be an integer/float/double/etc).") + .since("INSERT VERSION") + .literalParser(s -> { + if (s == null) return null; + try { + return Double.parseDouble(s); + } catch (NumberFormatException e) { + return null; + } + }) + .toStringFunction(Object::toString) + .arithmetic(new Arithmetic() { + @Override + public Number difference(Number first, Number second) { + return first.doubleValue() - second.doubleValue(); + } + + @Override + public Number add(Number value, Number difference) { + return value.doubleValue() + difference.doubleValue(); + } + + @Override + public Number subtract(Number value, Number difference) { + return value.doubleValue() - difference.doubleValue(); + } + + @Override + public Class getRelativeType() { + return Number.class; + } + }) + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Number value) { + return gson.toJsonTree(value.doubleValue()); + } + + @Override + public Number deserialize(Gson gson, JsonElement element) { + return gson.fromJson(element, Double.class); + } + }) + .register(); + registration.newType(String.class, "string", "string@s") .name("String") .description("A string of characters.") @@ -347,6 +348,9 @@ public Class getRelativeType() { .register(); registration.newType(Time.class, "time", "time@s") + .name("Time") + .description("A time, represented as a string in the format 'HH:mm:ss'.") + .since("INSERT VERSION") .literalParser(s -> Time.parse(s).orElse(null)) .toStringFunction(Time::toString) .arithmetic(new Arithmetic() { @@ -370,9 +374,19 @@ public Class getRelativeType() { return Duration.class; } }) - .name("Time") - .description("A time, represented as a string in the format 'HH:mm:ss'.") - .since("INSERT VERSION") + .serializer(new TypeSerializer<>() { + @Override + public JsonElement serialize(Gson gson, Time value) { + int nano = value.getTime().getNano(); + return gson.toJsonTree(nano, Integer.class); + } + @Override + public Time deserialize(Gson gson, JsonElement element) { + int asInt = element.getAsInt(); + LocalTime localTime = LocalTime.ofNanoOfDay(asInt); + return Time.of(localTime); + } + }) .register(); /* diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java b/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java index 9b5753afc..c44737ef0 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/SerializedVariable.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser.variables; +import com.google.gson.JsonElement; import org.jetbrains.annotations.Nullable; public class SerializedVariable { @@ -41,14 +42,14 @@ public static final class Value { /** * The serialized value data. */ - public final byte[] data; + public final JsonElement data; /** * Creates a new serialized value. * @param type the value type. * @param data the serialized value data. */ - public Value(String type, byte[] data) { + public Value(String type, JsonElement data) { this.type = type; this.data = data; } diff --git a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java index c78fd819a..dacefe1e8 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java +++ b/src/main/java/io/github/syst3ms/skriptparser/variables/VariableStorage.java @@ -247,7 +247,7 @@ protected void loadVariable(String name, SerializedVariable variable) { * @param type the type of the variable. * @param value the serialized value of the variable. */ - protected void loadVariable(String name, @NotNull String type, @NotNull byte[] value) { + protected void loadVariable(String name, @NotNull String type, @NotNull JsonElement value) { if (value == null || type == null) throw new IllegalArgumentException("value and/or typeName cannot be null"); Variables.queueVariableChange(name, deserialize(type, value)); @@ -312,7 +312,7 @@ public SerializedVariable serialize(String name, @Nullable T value) { if (serializer == null) throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be serialized. No type serializer."); JsonElement element = serializer.serialize(gson, value); - return new SerializedVariable(name, new Value(type.getBaseName(), gson.toJson(element).getBytes())); + return new SerializedVariable(name, new Value(type.getBaseName(), element)); } /** @@ -323,7 +323,7 @@ public SerializedVariable serialize(String name, @Nullable T value) { * @param value The value that represents a object. * @return The Object after deserialization, not present if not possible to deserialize due to missing serializer on Type. */ - protected Optional deserialize(@NotNull String typeName, @NotNull byte[] value) { + protected Optional deserialize(@NotNull String typeName, @NotNull JsonElement value) { if (value == null || typeName == null) throw new IllegalArgumentException("value and/or typeName cannot be null"); Type type = TypeManager.getByExactName(typeName).orElse(null); @@ -332,9 +332,7 @@ protected Optional deserialize(@NotNull String typeName, @NotNull byte[] valu TypeSerializer serializer = type.getSerializer().orElse(null); if (serializer == null) throw new UnsupportedOperationException("Class '" + value.getClass().getName() + "' cannot be deserialized. No type serializer."); - String json = new String(value); - JsonReader reader = gson.newJsonReader(new StringReader(json)); - return Optional.ofNullable(serializer.deserialize(gson, JsonParser.parseReader(reader))); + return Optional.ofNullable(serializer.deserialize(gson, value)); } private long lastError = Long.MIN_VALUE; @@ -378,6 +376,6 @@ protected void clearChangesQueue() { * @param value the serialized value of the variable. * @return Whether the variable was saved. */ - protected abstract boolean save(String name, @Nullable String type, @Nullable byte[] value); + protected abstract boolean save(String name, @Nullable String type, @Nullable JsonElement value); } From 28e843a7e81d85823d51fd1428a2b57f12476b90 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 12:57:38 -0800 Subject: [PATCH 27/68] Secloop - fix number stuff --- .../expressions/ExprLoopValue.java | 4 +++ .../registration/DefaultRegistration.java | 25 +++++++++++++++++-- .../skriptparser/sections/SecLoop.java | 25 ++++++++++--------- .../skriptparser/types/ranges/Ranges.java | 3 ++- .../skriptparser/log/SkriptLoggerTest.java | 19 +++++++------- 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java index 7ec31186b..f24074933 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLoopValue.java @@ -139,6 +139,10 @@ public Object[] getSectionValues(SecLoop loop, TriggerContext ctx) { one[0] = current.getSecond(); return one; } + Object[] arguments = loop.getArguments(); + if (arguments == null || arguments.length == 0) { + return new Object[0]; + } one[0] = loop.getArguments()[0]; return one; } diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 9525a5e6a..119793bb0 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -431,11 +431,28 @@ public Relation apply(Duration duration, Duration duration2) { .toArray(String[]::new); } ); + Ranges.registerRange( + Integer.class, + Integer.class, + (l, r) -> { + if (l >= r) { + return new Integer[0]; + } else { + List elements = new ArrayList<>(); + int current = l; + do { + elements.add(current); + current = current + 1; + } while (current <= r); + return elements.toArray(new Integer[0]); + } + } + ); Ranges.registerRange( Number.class, Number.class, (l, r) -> { - if (l.doubleValue() >= 0) { + if (l.doubleValue() >= r.doubleValue()) { return new Number[0]; } else { List elements = new ArrayList<>(); @@ -443,7 +460,7 @@ public Relation apply(Duration duration, Duration duration2) { do { elements.add(current); current = current.doubleValue() + 1; - } while (current.intValue() <= 0); + } while (current.doubleValue() <= r.doubleValue()); return elements.toArray(new Number[0]); } } @@ -458,6 +475,10 @@ public Relation apply(Duration duration, Duration duration2) { registration.addConverter(Integer.class, Long.class, i -> Optional.of(i.longValue())); registration.addConverter(Double.class, Float.class, d -> Optional.of(d.floatValue())); registration.addConverter(Float.class, Double.class, f -> Optional.of(f.doubleValue())); + registration.addConverter(Number.class, Integer.class, n -> Optional.of(n.intValue())); + registration.addConverter(Number.class, Long.class, n -> Optional.of(n.longValue())); + registration.addConverter(Number.class, Float.class, n -> Optional.of(n.floatValue())); + registration.addConverter(Number.class, Double.class, n -> Optional.of(n.doubleValue())); registration.register(true); // Ignoring logs here, we control the input } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java index 7153c2f36..7d799d241 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java @@ -18,6 +18,7 @@ import io.github.syst3ms.skriptparser.types.ranges.Ranges; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; import java.util.Iterator; import java.util.Optional; @@ -37,7 +38,7 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen static { Parser.getMainRegistration().newSection(SecLoop.class, - "loop %number% times", "loop %objects%") + "loop %integer% times", "loop %objects%") .name("Loop") .description("Loops over all the values in a list or loop x number of times.") .examples("loop 10 times:", @@ -50,7 +51,7 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen @Nullable private Expression expression; - private Expression times; + private Expression times; private boolean isNumericLoop; @Nullable @@ -72,14 +73,14 @@ public boolean loadSection(FileSection section, ParserState parserState, SkriptL public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { isNumericLoop = matchedPattern == 0; if (isNumericLoop) { - times = (Expression) expressions[0]; + times = (Expression) expressions[0]; // We can do some certainty checks with Literals. if (times instanceof Literal) { - var t = ((Optional) ((Literal) times).getSingle()).orElse(1); - if (t.intValue() <= 0) { + int t = ((Optional) ((Literal) times).getSingle()).orElse(1); + if (t <= 0) { parseContext.getLogger().error("Cannot loop a negative or zero amount of times", ErrorType.SEMANTIC_ERROR); return false; - } else if (t.intValue() == 1) { + } else if (t == 1) { parseContext.getLogger().error( "Cannot loop a single time", ErrorType.SEMANTIC_ERROR, @@ -170,13 +171,13 @@ public Expression getLoopedExpression() { * @param size the expression * @return the SimpleLiteral */ - private static Expression rangeOf(TriggerContext ctx, Expression size) { - Number[] range = (Number[]) size.getSingle(ctx) - .filter(t -> t.intValue() > 0) - .map(t -> Ranges.getRange(Number.class).orElseThrow() + private static Expression rangeOf(TriggerContext ctx, Expression size) { + Integer[] range = (Integer[]) size.getSingle(ctx) + .filter(t -> t > 0) + .map(t -> Ranges.getRange(Integer.class).orElseThrow() .getFunction() .apply(1, t)) // Upper bound is inclusive - .orElse(new Number[0]); - return new SimpleLiteral<>(Number.class, range); + .orElse(new Integer[0]); + return new SimpleLiteral<>(Integer.class, range); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java b/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java index 12438e09d..7482ab8b3 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java @@ -1,6 +1,7 @@ package io.github.syst3ms.skriptparser.types.ranges; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; @@ -9,7 +10,7 @@ * A class handling registration of ranges */ public class Ranges { - private static final Map, RangeInfo> rangeMap = new HashMap<>(); + private static final Map, RangeInfo> rangeMap = new LinkedHashMap<>(); public static void registerRange(Class bound, Class to, BiFunction function) { rangeMap.put( diff --git a/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java b/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java index 9d00bc5c2..55b7e4eb6 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/log/SkriptLoggerTest.java @@ -24,15 +24,14 @@ public void skriptLoggerTest() { assertTrue(noMatchFound.isEmpty() && logger.close().get(0).getMessage().startsWith("No expression")); logger = new SkriptLogger(); - // TODO fix -// Optional> wrongNumber = SyntaxParser.parseExpression("range from 1 to 3", SyntaxParser.OBJECT_PATTERN_TYPE, parserState, logger); -// logger.finalizeLogs(); -// assertTrue(wrongNumber.isEmpty() && logger.close().get(0).getMessage().startsWith("A single")); -// -// logger = new SkriptLogger(); -// Optional> wrongRange = SyntaxParser.parseBooleanExpression("1 is between \"a\" and \"b\"", SyntaxParser.MAYBE_CONDITIONAL, parserState, logger); -// logger.finalizeLogs(); -// assertTrue(wrongRange.isEmpty() && logger.close().get(0).getMessage().startsWith("'1' cannot")); -// + Optional> wrongNumber = SyntaxParser.parseExpression("range from 1 to 3", SyntaxParser.OBJECT_PATTERN_TYPE, parserState, logger); + logger.finalizeLogs(); + assertTrue(wrongNumber.isEmpty() && logger.close().get(0).getMessage().startsWith("A single")); + + logger = new SkriptLogger(); + Optional> wrongRange = SyntaxParser.parseBooleanExpression("1 is between \"a\" and \"b\"", SyntaxParser.MAYBE_CONDITIONAL, parserState, logger); + logger.finalizeLogs(); + assertTrue(wrongRange.isEmpty() && logger.close().get(0).getMessage().startsWith("'1' cannot")); + } } From 44ac5e9108bf25a72482ecdcc9fdfd83cac43cf5 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 13:11:34 -0800 Subject: [PATCH 28/68] EffWait - disable (handle in HySkript) --- .../github/syst3ms/skriptparser/effects/EffWait.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffWait.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffWait.java index bd78a7936..04e3f0aac 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffWait.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffWait.java @@ -31,11 +31,12 @@ */ public class EffWait extends Effect { static { - Parser.getMainRegistration().addEffect( - EffWait.class, - "(wait|halt) [for] %duration%", - "(wait|halt) (0:until|1:while) %=boolean% [for %*duration%]" - ); + // Handle this in HySkript (different scheduler) +// Parser.getMainRegistration().addEffect( +// EffWait.class, +// "(wait|halt) [for] %duration%", +// "(wait|halt) (0:until|1:while) %=boolean% [for %*duration%]" +// ); } private Expression duration; From 4377cad729ca9ffa9a77ecf5d455cb32713870ed Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 13:51:57 -0800 Subject: [PATCH 29/68] DurationUtils - some changes: - Fix a bug with array length - Add an option to override default tick speed - Add "tick" as a pattern --- .../syst3ms/skriptparser/util/DurationUtils.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java b/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java index 7da955817..e9a43d2a1 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java +++ b/src/main/java/io/github/syst3ms/skriptparser/util/DurationUtils.java @@ -8,16 +8,22 @@ public class DurationUtils { * The amount of milliseconds it takes for a tick to pass. * A 'tick' is a predetermined time-unit of 50 milliseconds. */ - public static final int TICK = 50; + public static long TICK = 50; /* * See these arrays as one single array with triplets of information * about a certain time unit. Sadly, Java does not allow to create a * clean alternative for this. */ - private static final String[] unitPatterns = {"years?", "weeks?", "days?", "hours?", "minutes?", "seconds?", "milli(second)?s?"}; - private static final String[] unitNames = {"year", "week", "day", "hour", "minute", "second", "millisecond"}; - private static final long[] unitMillis = {31_536_000_000L, 604_800_000, 86_400_000, 3_600_000, 60_000, 1000, 1}; + private static final String[] unitPatterns = {"years?", "weeks?", "days?", "hours?", "minutes?", "seconds?", "ticks?", "milli(second)?s?"}; + private static final String[] unitNames = {"year", "week", "day", "hour", "minute", "second", "tick", "millisecond"}; + private static long[] unitMillis = {31_536_000_000L, 604_800_000, 86_400_000, 3_600_000, 60_000, 1000, TICK, 1}; + + public static void overrideTickLength(long nanoTick) { + long milliTick = nanoTick / 1_000_000; + TICK = nanoTick; + unitMillis = new long[]{31_536_000_000L, 604_800_000, 86_400_000, 3_600_000, 60_000, 1000, milliTick, 1}; + } public static Optional parseDuration(String value) { if (value.isBlank()) @@ -26,7 +32,7 @@ public static Optional parseDuration(String value) { // Normal duration long duration = 0; var split = value.toLowerCase().split("\\s+"); - var usedUnits = new boolean[5]; // Defaults to false + var usedUnits = new boolean[unitNames.length]; // Defaults to false for (int i = 0; i < split.length; i++) { var unit = split[i]; From 99bf96fd3842206e0e9fc60c420e1e621db78cbb Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 27 Jan 2026 14:19:01 -0800 Subject: [PATCH 30/68] Expressions - remove more BigInt --- .../expressions/CondExprIsDivisible.java | 27 +-- .../expressions/ExecExprListOperators.java | 33 ++- .../skriptparser/expressions/ExprAmount.java | 5 +- .../expressions/ExprArithmeticOperators.java | 1 + .../expressions/ExprColorFromRGB.java | 10 +- .../expressions/ExprDateFromUnix.java | 6 +- .../expressions/ExprDateInformation.java | 142 +++++------ .../expressions/ExprDateTimestamp.java | 51 ++-- .../skriptparser/expressions/ExprElement.java | 225 +++++++++--------- .../skriptparser/expressions/ExprLength.java | 7 +- .../expressions/ExprNumberConvertBase.java | 1 + .../expressions/ExprPrimeNumber.java | 128 +++++----- .../expressions/ExprRandomNumber.java | 1 + .../expressions/ExprStringCharAt.java | 8 +- .../expressions/ExprStringOccurrence.java | 67 +++--- .../expressions/ExprStringSplitJoin.java | 7 +- .../expressions/ExprSubstring.java | 158 ++++++------ .../expressions/LitTimeConstants.java | 40 ++-- 18 files changed, 445 insertions(+), 472 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java index ba2ae11a1..8fc5867f5 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java @@ -6,54 +6,45 @@ import io.github.syst3ms.skriptparser.lang.properties.ConditionalType; import io.github.syst3ms.skriptparser.lang.properties.PropertyConditional; import io.github.syst3ms.skriptparser.parsing.ParseContext; -import io.github.syst3ms.skriptparser.util.math.BigDecimalMath; - -import java.math.BigInteger; /** * Check if a given number is divisible by another number. * Note that when the number is a decimal number, * the check will automatically fail. * + * @author Mwexim * @name Is Divisible * @type CONDITION * @pattern %numbers% (is|are)[ not|n't] divisible by %integer% * @since ALPHA - * @author Mwexim */ public class CondExprIsDivisible extends PropertyConditional { static { Parser.getMainRegistration().addPropertyConditional( - CondExprIsDivisible.class, - "numbers", - ConditionalType.BE, - "divisible by %integer%" + CondExprIsDivisible.class, + "numbers", + ConditionalType.BE, + "divisible by %integer%" ); } - private Expression divider; + private Expression divider; @SuppressWarnings("unchecked") @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - divider = (Expression) expressions[1]; + divider = (Expression) expressions[1]; return super.init(expressions, matchedPattern, parseContext); } @Override public boolean check(TriggerContext ctx) { - return getPerformer().check( - ctx, - performer -> divider.getSingle(ctx) - .filter(__ -> BigDecimalMath.isIntValue(BigDecimalMath.getBigDecimal(performer))) - .filter(val -> BigDecimalMath.getBigInteger(performer).mod(val).equals(BigInteger.ZERO)) - .isPresent(), - isNegated() - ); + return getPerformer().check(ctx, (p) -> this.divider.check(ctx, (d) -> p.intValue() % d == 0)); } @Override public String toString(TriggerContext ctx, boolean debug) { return toString(ctx, debug, "divisible by " + divider.toString(ctx, debug)); } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExecExprListOperators.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExecExprListOperators.java index c7239ddf3..bbd0576b8 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExecExprListOperators.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExecExprListOperators.java @@ -9,7 +9,6 @@ import io.github.syst3ms.skriptparser.types.changers.ChangeMode; import io.github.syst3ms.skriptparser.util.CollectionUtils; -import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; @@ -56,10 +55,10 @@ public class ExecExprListOperators extends ExecutableExpression { // 0 = last, 1 = first, 2 = indexed, 3 = spliced private int type; private Expression list; - private Expression index; - private Expression lower; - private Expression upper; - private Expression step; + private Expression index; + private Expression lower; + private Expression upper; + private Expression step; @SuppressWarnings("unchecked") @Override @@ -69,7 +68,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex type = parseContext.getNumericMark(); list = (Expression) (type == 2 ? expressions[1] : expressions[0]); if (type == 2) - index = (Expression) expressions[0]; + index = (Expression) expressions[0]; break; case 1: case 2: @@ -81,20 +80,20 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex list = (Expression) expressions[0]; switch (parseContext.getNumericMark()) { case 0: - lower = (Expression) expressions[1]; - upper = (Expression) expressions[2]; + lower = (Expression) expressions[1]; + upper = (Expression) expressions[2]; if (expressions.length == 4) - step = (Expression) expressions[3]; + step = (Expression) expressions[3]; break; case 1: - lower = (Expression) expressions[1]; + lower = (Expression) expressions[1]; if (expressions.length == 3) - step = (Expression) expressions[2]; + step = (Expression) expressions[2]; break; case 2: - upper = (Expression) expressions[1]; + upper = (Expression) expressions[1]; if (expressions.length == 3) - step = (Expression) expressions[2]; + step = (Expression) expressions[2]; break; default: throw new IllegalStateException(); @@ -136,7 +135,7 @@ public Object[] getValues(TriggerContext ctx, boolean isEffect) { return new Object[] {values[0]}; case 2: int ind = index.getSingle(ctx) - .filter(n -> n.signum() > 0 && n.compareTo(BigInteger.valueOf(values.length)) <= 0) + .filter(n -> Integer.signum(n) > 0 && n.compareTo(Integer.valueOf(values.length)) <= 0) .map(n -> n.intValue() - 1) .orElse(-1); if (ind == -1) { @@ -157,13 +156,13 @@ public Object[] getValues(TriggerContext ctx, boolean isEffect) { return new Object[] {values[ind]}; case 3: var low = lower != null - ? lower.getSingle(ctx).filter(n -> n.signum() > 0).map(n -> n.intValue() - 1).orElse(-1) + ? lower.getSingle(ctx).filter(n -> Integer.signum(n) > 0).map(n -> n.intValue() - 1).orElse(-1) : 0; var up = upper != null - ? upper.getSingle(ctx).filter(n -> n.compareTo(BigInteger.valueOf(values.length)) <= 0).map(BigInteger::intValue).orElse(values.length) + ? upper.getSingle(ctx).filter(n -> n.compareTo(Integer.valueOf(values.length)) <= 0).map(Integer::intValue).orElse(values.length) : values.length; var st = step != null - ? step.getSingle(ctx).filter(n -> n.signum() != 0 && n.compareTo(BigInteger.valueOf(-values.length)) >= 0 && n.compareTo(BigInteger.valueOf(values.length)) <= 0).map(BigInteger::intValue).orElse(0) + ? step.getSingle(ctx).filter(n -> Integer.signum(n) != 0 && n.compareTo(Integer.valueOf(-values.length)) >= 0 && n.compareTo(Integer.valueOf(values.length)) <= 0).map(Integer::intValue).orElse(0) : 1; if (st < 0) { diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java index 3481b2cb8..d3a8aa7be 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprAmount.java @@ -9,7 +9,6 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import org.jetbrains.annotations.NotNull; -import java.math.BigInteger; import java.util.Map; /** @@ -56,10 +55,10 @@ public Number[] getValues(TriggerContext ctx) { var var = ((Variable) getOwner()).getRaw(ctx); if (var.isPresent()) return new Number[] { - BigInteger.valueOf(getRecursiveSize((Map) var.get())) + Integer.valueOf(Math.toIntExact(getRecursiveSize((Map) var.get()))) }; } - return new Number[] {BigInteger.valueOf(getOwner().getValues(ctx).length)}; + return new Number[] {Integer.valueOf(getOwner().getValues(ctx).length)}; } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprArithmeticOperators.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprArithmeticOperators.java index e5221ce42..3a0d7c3de 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprArithmeticOperators.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprArithmeticOperators.java @@ -43,6 +43,7 @@ * @author Syst3ms */ public class ExprArithmeticOperators implements Expression { + // TODO remove Big public static final PatternInfos PATTERNS = new PatternInfos<>(new Object[][]{ {"%number%[ ]+[ ]%number%", Operator.PLUS}, {"%number%[ ]-[ ]%number%", Operator.MINUS}, diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorFromRGB.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorFromRGB.java index 116709c10..bc7cb75da 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorFromRGB.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprColorFromRGB.java @@ -6,8 +6,6 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.color.Color; -import java.math.BigInteger; - /** * A color specified by its RGB value. * Each value, this means red, green and blue, can be a range from 0 to 255. @@ -31,18 +29,18 @@ public class ExprColorFromRGB implements Expression { ); } - private Expression rgb; + private Expression rgb; @SuppressWarnings("unchecked") @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - rgb = (Expression) expressions[0]; + rgb = (Expression) expressions[0]; return true; } @Override public Color[] getValues(TriggerContext ctx) { - var values = rgb.stream(ctx).map(BigInteger::intValue).toArray(Integer[]::new); + var values = rgb.stream(ctx).map(Integer::intValue).toArray(Integer[]::new); if (values.length == 3) { return Color.of(values[0], values[1], values[2]) .map(val -> new Color[] {val}) @@ -60,4 +58,4 @@ public Color[] getValues(TriggerContext ctx) { public String toString(TriggerContext ctx, boolean debug) { return "color from rgb " + rgb.toString(ctx, debug); } -} \ No newline at end of file +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateFromUnix.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateFromUnix.java index 8ecafe5b4..daacf0a45 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateFromUnix.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateFromUnix.java @@ -6,8 +6,6 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.SkriptDate; -import java.math.BigInteger; - /** * The date from a (unix) timestamp. * The default timestamp returns the amount of milliseconds since the Unix Epoch. @@ -30,14 +28,14 @@ public class ExprDateFromUnix implements Expression { ); } - private Expression timestamp; + private Expression timestamp; private boolean unix; @SuppressWarnings("unchecked") @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { unix = parseContext.getNumericMark() == 1; - timestamp = (Expression) expressions[0]; + timestamp = (Expression) expressions[0]; return true; } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateInformation.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateInformation.java index 0efd998c4..6d6c2f51d 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateInformation.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateInformation.java @@ -8,7 +8,6 @@ import io.github.syst3ms.skriptparser.util.SkriptDate; import io.github.syst3ms.skriptparser.util.Time; -import java.math.BigInteger; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.TextStyle; @@ -17,89 +16,90 @@ /** * Information of a certain date. * + * @author Mwexim * @name Date Information * @type EXPRESSION * @pattern [the] (year[s]|month[s]|day[s] (of|in) year|day[s] (of|in) month|day[s] (of|in) week|hour[s]|minute[s]|second[s]|milli[second][s]) (of|in) %date/time% * @pattern [the] (era|month|weekday|day [(of|in) week]) [name] (of|in) [date] %date% * @since ALPHA - * @author Mwexim */ public class ExprDateInformation implements Expression { - private static final PatternInfos> NUMBER_VALUES = new PatternInfos<>(new Object[][] { - {"year[s]", (Function) LocalDateTime::getYear}, - {"month[s]", (Function) LocalDateTime::getMonthValue}, - {"day[s] (of|in) year", (Function) LocalDateTime::getDayOfYear}, - {"day[s] (of|in) month", (Function) LocalDateTime::getDayOfMonth}, - {"day[s] (of|in) week", (Function) val -> val.getDayOfWeek().getValue()}, - {"hour[s]", (Function) LocalDateTime::getHour}, - {"minute[s]", (Function) LocalDateTime::getMinute}, - {"second[s]", (Function) LocalDateTime::getSecond}, - {"milli[second][s]", (Function) val -> val.getNano() / 1_000_000} - }); + private static final PatternInfos> NUMBER_VALUES = new PatternInfos<>(new Object[][]{ + {"year[s]", (Function) LocalDateTime::getYear}, + {"month[s]", (Function) LocalDateTime::getMonthValue}, + {"day[s] (of|in) year", (Function) LocalDateTime::getDayOfYear}, + {"day[s] (of|in) month", (Function) LocalDateTime::getDayOfMonth}, + {"day[s] (of|in) week", (Function) val -> val.getDayOfWeek().getValue()}, + {"hour[s]", (Function) LocalDateTime::getHour}, + {"minute[s]", (Function) LocalDateTime::getMinute}, + {"second[s]", (Function) LocalDateTime::getSecond}, + {"milli[second][s]", (Function) val -> val.getNano() / 1_000_000} + }); + + private static final PatternInfos> STRING_VALUES = new PatternInfos<>(new Object[][]{ + {"era", (Function) val -> val.toLocalDate().getEra().getDisplayName(TextStyle.SHORT, SkriptDate.DATE_LOCALE)}, + {"month", (Function) val -> val.getMonth().getDisplayName(TextStyle.FULL, SkriptDate.DATE_LOCALE)}, + {"(weekday|day [(of|in) week])", (Function) val -> val.getDayOfWeek().getDisplayName(TextStyle.FULL, SkriptDate.DATE_LOCALE)}, + }); - private static final PatternInfos> STRING_VALUES = new PatternInfos<>(new Object[][] { - {"era", (Function) val -> val.toLocalDate().getEra().getDisplayName(TextStyle.SHORT, SkriptDate.DATE_LOCALE)}, - {"month", (Function) val -> val.getMonth().getDisplayName(TextStyle.FULL, SkriptDate.DATE_LOCALE)}, - {"(weekday|day [(of|in) week])", (Function) val -> val.getDayOfWeek().getDisplayName(TextStyle.FULL, SkriptDate.DATE_LOCALE)}, - }); + static { + Parser.getMainRegistration().addExpression( + ExprDateInformation.class, + Object.class, + true, + "[the] " + NUMBER_VALUES.toChoiceGroup() + " (of|in) %date/time%", + "[the] " + STRING_VALUES.toChoiceGroup() + " [name] (of|in) [date] %date%" + ); + } - static { - Parser.getMainRegistration().addExpression( - ExprDateInformation.class, - Object.class, - true, - "[the] " + NUMBER_VALUES.toChoiceGroup() + " (of|in) %date/time%", - "[the] " + STRING_VALUES.toChoiceGroup() + " [name] (of|in) [date] %date%" - ); - } + private Expression value; + private boolean returnsNumber; + private int mark; - private Expression value; - private boolean returnsNumber; - private int mark; + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + returnsNumber = matchedPattern == 0; + mark = parseContext.getNumericMark(); - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - returnsNumber = matchedPattern == 0; - mark = parseContext.getNumericMark(); + value = expressions[0]; + return value.getReturnType() != Time.class || 5 <= mark && mark <= 8; + } - value = expressions[0]; - return value.getReturnType() != Time.class || 5 <= mark && mark <= 8; - } + @Override + public Object[] getValues(TriggerContext ctx) { + return value.getSingle(ctx) + .map(val -> { + if (returnsNumber) { + if (val instanceof SkriptDate) { + return NUMBER_VALUES.getInfo(mark).apply(((SkriptDate) val).toLocalDateTime()); + } else { + assert val instanceof Time; + var todayAt = SkriptDate.today().plus(Duration.ofMillis(((Time) val).toMillis())); + return NUMBER_VALUES.getInfo(mark).apply(todayAt.toLocalDateTime()); + } + } else { + assert val instanceof SkriptDate; + return STRING_VALUES.getInfo(mark).apply(((SkriptDate) val).toLocalDateTime()); + } + }) + .map(val -> new Object[]{val}) + .orElse(new Object[0]); + } - @Override - public Object[] getValues(TriggerContext ctx) { - return value.getSingle(ctx) - .map(val -> { - if (returnsNumber) { - if (val instanceof SkriptDate) { - return BigInteger.valueOf(NUMBER_VALUES.getInfo(mark).apply(((SkriptDate) val).toLocalDateTime())); - } else { - assert val instanceof Time; - var todayAt = SkriptDate.today().plus(Duration.ofMillis(((Time) val).toMillis())); - return BigInteger.valueOf(NUMBER_VALUES.getInfo(mark).apply(todayAt.toLocalDateTime())); - } - } else { - assert val instanceof SkriptDate; - return STRING_VALUES.getInfo(mark).apply(((SkriptDate) val).toLocalDateTime()); - } - }) - .map(val -> new Object[] {val}) - .orElse(new Object[0]); - } + @Override + public Class getReturnType() { + return returnsNumber ? Number.class : String.class; + } - @Override - public Class getReturnType() { - return returnsNumber ? Number.class : String.class; - } + @Override + public String toString(TriggerContext ctx, boolean debug) { + if (returnsNumber) { + return new String[]{"year", "month", "day of year", "day of month", "day of week", "hours", "minutes", "seconds", "milliseconds"}[mark] + + " of " + + value.toString(ctx, debug); + } else { + return new String[]{"era", "month", "weekday"}[mark] + " of " + value.toString(ctx, debug); + } + } - @Override - public String toString(TriggerContext ctx, boolean debug) { - if (returnsNumber) { - return new String[] {"year", "month", "day of year", "day of month", "day of week", "hours", "minutes", "seconds", "milliseconds"}[mark] - + " of " - + value.toString(ctx, debug); - } else { - return new String[] {"era", "month", "weekday"}[mark] + " of " + value.toString(ctx, debug); - } - } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java index ad6f033e5..a9dded1a1 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDateTimestamp.java @@ -7,47 +7,46 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.SkriptDate; -import java.math.BigInteger; - /** * The timestamp of a date. * The default timestamp returns the amount of milliseconds since the Unix Epoch. * The unix timestamp returns the amount of seconds since that same date. * The Unix Epoch is defined as January 1st 1970. * + * @author Mwexim * @name Timestamp * @type EXPRESSION * @pattern [the] [unix] timestamp of [date] %date% * @pattern [date] %date%'[s] [unix] timestamp * @since ALPHA - * @author Mwexim */ public class ExprDateTimestamp extends PropertyExpression { - static { - Parser.getMainRegistration().newPropertyExpression(ExprDateTimestamp.class, Number.class, - "[1:unix] timestamp", "*[date] %date%") - .name("Unix Timestamp") - .description("The unix timestamp of a date.") - .since("INSERT VERSION") - .register(); - } + static { + Parser.getMainRegistration().newPropertyExpression(ExprDateTimestamp.class, Number.class, + "[1:unix] timestamp", "*[date] %date%") + .name("Unix Timestamp") + .description("The unix timestamp of a date.") + .since("INSERT VERSION") + .register(); + } + + private boolean unix; - private boolean unix; + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + unix = parseContext.getNumericMark() == 1; + return super.init(expressions, matchedPattern, parseContext); + } - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - unix = parseContext.getNumericMark() == 1; - return super.init(expressions, matchedPattern, parseContext); - } + @Override + public Number getProperty(SkriptDate owner) { + return unix ? Math.floorDiv(owner.getTimestamp(), 1000) + : owner.getTimestamp(); + } - @Override - public Number getProperty(SkriptDate owner) { - return unix ? BigInteger.valueOf(Math.floorDiv(owner.getTimestamp(), 1000)) - : BigInteger.valueOf(owner.getTimestamp()); - } + @Override + public String toString(TriggerContext ctx, boolean debug) { + return toString(ctx, debug, (unix ? "unix " : "") + "timestamp"); + } - @Override - public String toString(TriggerContext ctx, boolean debug) { - return toString(ctx, debug, (unix ? "unix " : "") + "timestamp"); - } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprElement.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprElement.java index 2fbcf502a..55d4fd80e 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprElement.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprElement.java @@ -6,7 +6,6 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.math.NumberMath; -import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.ThreadLocalRandom; @@ -15,134 +14,134 @@ * A certain element or multiple elements out of a list of objects. * Remember that in Skript, indices start from 1. * + * @author Mwexim * @name Element * @type EXPRESSION - * @pattern ([the] first|[the] last|[a] random|[the] %integer%(st|nd|rd|th)) element out [of] %objects% + * @pattern ([the] first | [the] last | [a] random | [the] % integer % ( st | nd | rd | th)) element out [of] %objects% * @pattern [the] (first|last) %integer% elements out [of] %objects% * @pattern %integer% random elements out [of] %objects% * @pattern %objects%\[%integer%\] * @since ALPHA - * @author Mwexim */ public class ExprElement implements Expression { - static { - Parser.getMainRegistration().addExpression( - ExprElement.class, - Object.class, - true, - "(0:[the] first|1:[the] last|2:[a] random|3:[the] %integer%(st|nd|rd|th)) element out [of] %objects%", - "[the] (0:first|1:last) %integer% elements out [of] %objects%", - "%integer% random elements out [of] %objects%", - "%objects%\\[%integer%\\]"); - } + static { + Parser.getMainRegistration().addExpression( + ExprElement.class, + Object.class, + true, + "(0:[the] first|1:[the] last|2:[a] random|3:[the] %integer%(st|nd|rd|th)) element out [of] %objects%", + "[the] (0:first|1:last) %integer% elements out [of] %objects%", + "%integer% random elements out [of] %objects%", + "%objects%\\[%integer%\\]"); + } - private static final ThreadLocalRandom random = ThreadLocalRandom.current(); + private static final ThreadLocalRandom random = ThreadLocalRandom.current(); - private Expression expr; - private Expression range; - private int pattern; - private int parseMark; + private Expression expr; + private Expression range; + private int pattern; + private int parseMark; - @SuppressWarnings("unchecked") - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - pattern = matchedPattern; - parseMark = parseContext.getNumericMark(); + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + pattern = matchedPattern; + parseMark = parseContext.getNumericMark(); - switch (pattern) { - case 0: - if (parseMark == 3) { - range = (Expression) expressions[0]; - expr = (Expression) expressions[1]; - } else { - expr = (Expression) expressions[0]; - } - break; - case 1: - case 2: - range = (Expression) expressions[0]; - expr = (Expression) expressions[1]; - break; - case 3: - expr = (Expression) expressions[0]; - range = (Expression) expressions[1]; - break; - default: - throw new IllegalStateException(); - } - return true; - } + switch (pattern) { + case 0: + if (parseMark == 3) { + range = (Expression) expressions[0]; + expr = (Expression) expressions[1]; + } else { + expr = (Expression) expressions[0]; + } + break; + case 1: + case 2: + range = (Expression) expressions[0]; + expr = (Expression) expressions[1]; + break; + case 3: + expr = (Expression) expressions[0]; + range = (Expression) expressions[1]; + break; + default: + throw new IllegalStateException(); + } + return true; + } - @Override - public Object[] getValues(TriggerContext ctx) { - Object[] values = expr.getValues(ctx); - if (values.length == 0) - return new Object[0]; - int r = 0; + @Override + public Object[] getValues(TriggerContext ctx) { + Object[] values = expr.getValues(ctx); + if (values.length == 0) + return new Object[0]; + int r = 0; - if (range != null) { - if (range.getSingle(ctx).isEmpty()) - return new Object[0]; - r = range.getSingle(ctx).get().intValue(); - if (r > values.length && pattern == 1) { - return values; - } else if (r > values.length || r <= 0) { - return new Object[0]; - } - } + if (range != null) { + if (range.getSingle(ctx).isEmpty()) + return new Object[0]; + r = range.getSingle(ctx).get().intValue(); + if (r > values.length && pattern == 1) { + return values; + } else if (r > values.length || r <= 0) { + return new Object[0]; + } + } - switch (pattern) { - case 0: - switch (parseMark) { - case 0: - return new Object[] {values[0]}; - case 1: - return new Object[] {values[values.length - 1]}; - case 2: - return new Object[] {values[NumberMath.random(0, values.length - 1, true, random).intValue()]}; - case 3: - return new Object[] {values[r - 1]}; - default: - return new Object[0]; - } - case 1: - if (parseMark == 0) { - return Arrays.copyOfRange(values, 0, r); - } else { - return Arrays.copyOfRange(values, values.length - r, values.length); - } - case 2: - var shuffled = Arrays.asList(values); - Collections.shuffle(shuffled, random); - return shuffled.subList(0, r).toArray(); - case 3: - return new Object[] {values[r - 1]}; - default: - throw new IllegalStateException(); - } - } + switch (pattern) { + case 0: + switch (parseMark) { + case 0: + return new Object[]{values[0]}; + case 1: + return new Object[]{values[values.length - 1]}; + case 2: + return new Object[]{values[NumberMath.random(0, values.length - 1, true, random).intValue()]}; + case 3: + return new Object[]{values[r - 1]}; + default: + return new Object[0]; + } + case 1: + if (parseMark == 0) { + return Arrays.copyOfRange(values, 0, r); + } else { + return Arrays.copyOfRange(values, values.length - r, values.length); + } + case 2: + var shuffled = Arrays.asList(values); + Collections.shuffle(shuffled, random); + return shuffled.subList(0, r).toArray(); + case 3: + return new Object[]{values[r - 1]}; + default: + throw new IllegalStateException(); + } + } - @Override - public boolean isSingle() { - return pattern == 0 || pattern == 3; - } + @Override + public boolean isSingle() { + return pattern == 0 || pattern == 3; + } - @Override - public Class getReturnType() { - return expr.getReturnType(); - } + @Override + public Class getReturnType() { + return expr.getReturnType(); + } - @Override - public String toString(TriggerContext ctx, boolean debug) { - switch (pattern) { - case 0: - case 3: - return "element out of " + expr.toString(ctx, debug); - case 1: - case 2: - return "elements out of " + expr.toString(ctx, debug); - default: - throw new IllegalStateException(); - } - } + @Override + public String toString(TriggerContext ctx, boolean debug) { + switch (pattern) { + case 0: + case 3: + return "element out of " + expr.toString(ctx, debug); + case 1: + case 2: + return "elements out of " + expr.toString(ctx, debug); + default: + throw new IllegalStateException(); + } + } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java index c420ea01d..be973be01 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprLength.java @@ -3,16 +3,14 @@ import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.lang.properties.PropertyExpression; -import java.math.BigInteger; - /** * Length of a string. * + * @author Romitou * @name Length * @pattern [the] length of %string% * @pattern %string%'s length * @since ALPHA - * @author Romitou */ public class ExprLength extends PropertyExpression { static { @@ -25,6 +23,7 @@ public class ExprLength extends PropertyExpression { @Override public Number getProperty(String owner) { - return BigInteger.valueOf(owner.length()); + return owner.length(); } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprNumberConvertBase.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprNumberConvertBase.java index 3c10656ae..8744ae362 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprNumberConvertBase.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprNumberConvertBase.java @@ -22,6 +22,7 @@ * @author Mwexim, WeeskyBDW */ public class ExprNumberConvertBase implements Expression { + // TODO remove Big static { Parser.getMainRegistration().addExpression( ExprNumberConvertBase.class, diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprPrimeNumber.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprPrimeNumber.java index 682f66a1d..0cca05ab8 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprPrimeNumber.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprPrimeNumber.java @@ -1,65 +1,63 @@ -package io.github.syst3ms.skriptparser.expressions; - -import io.github.syst3ms.skriptparser.Parser; -import io.github.syst3ms.skriptparser.lang.Expression; -import io.github.syst3ms.skriptparser.lang.TriggerContext; -import io.github.syst3ms.skriptparser.parsing.ParseContext; -import io.github.syst3ms.skriptparser.util.math.NumberMath; - -import java.math.BigInteger; - -/** - * Returns the nth prime, where n is the given number. - * Note that this expression may have slow results when used with a high number. - * - * @name Prime - * @type EXPRESSION - * @pattern [the] %integer%(st|nd|rd|th) prime [number] - * @since ALPHA - * @author Mwexim - */ -public class ExprPrimeNumber implements Expression { - static { - Parser.getMainRegistration().addExpression( - ExprPrimeNumber.class, - Number.class, - true, - "[the] %integer%(st|nd|rd|th) prime [number]" - ); - } - - private Expression ordinal; - - @SuppressWarnings("unchecked") - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - ordinal = (Expression) expressions[0]; - return true; - } - - @Override - public Number[] getValues(TriggerContext ctx) { - return ordinal.getSingle(ctx) - .filter(n -> n.compareTo(BigInteger.ZERO) > 0) - .map(n -> { - if (NumberMath.getCachedPrimes().size() >= n.intValue()) { - return new Number[] { - BigInteger.valueOf(NumberMath.getCachedPrimes().get(n.intValue() - 1)) - }; - } - int candidate, count; - for (candidate = 2, count = 0; count < n.intValue(); candidate++) { - if (NumberMath.isPrime(BigInteger.valueOf(candidate))) - count++; - } - return new Number[] {BigInteger.valueOf(candidate - 1)}; - }) - .orElse(new Number[0]); - - } - - @Override - public String toString(TriggerContext ctx, boolean debug) { - return "prime number " + ordinal.toString(ctx, debug); - } -} +//package io.github.syst3ms.skriptparser.expressions; +// +//import io.github.syst3ms.skriptparser.Parser; +//import io.github.syst3ms.skriptparser.lang.Expression; +//import io.github.syst3ms.skriptparser.lang.TriggerContext; +//import io.github.syst3ms.skriptparser.parsing.ParseContext; +//import io.github.syst3ms.skriptparser.util.math.NumberMath; +// +///** +// * Returns the nth prime, where n is the given number. +// * Note that this expression may have slow results when used with a high number. +// * +// * @name Prime +// * @type EXPRESSION +// * @pattern [the] %integer%(st|nd|rd|th) prime [number] +// * @since ALPHA +// * @author Mwexim +// */ +//public class ExprPrimeNumber implements Expression { +// static { +// Parser.getMainRegistration().addExpression( +// ExprPrimeNumber.class, +// Number.class, +// true, +// "[the] %integer%(st|nd|rd|th) prime [number]" +// ); +// } +// +// private Expression ordinal; +// +// @SuppressWarnings("unchecked") +// @Override +// public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { +// ordinal = (Expression) expressions[0]; +// return true; +// } +// +// @Override +// public Number[] getValues(TriggerContext ctx) { +// return ordinal.getSingle(ctx) +// .filter(n -> n.compareTo(0) > 0) +// .map(n -> { +// if (NumberMath.getCachedPrimes().size() >= n.intValue()) { +// return new Number[] { +// Integer.valueOf(NumberMath.getCachedPrimes().get(n.intValue() - 1)) +// }; +// } +// int candidate, count; +// for (candidate = 2, count = 0; count < n.intValue(); candidate++) { +// if (Math.prime(candidate)) +// count++; +// } +// return new Number[] {Integer.valueOf(candidate - 1)}; +// }) +// .orElse(new Number[0]); +// +// } +// +// @Override +// public String toString(TriggerContext ctx, boolean debug) { +// return "prime number " + ordinal.toString(ctx, debug); +// } +//} diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java index dd60d66ac..018e28f89 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRandomNumber.java @@ -24,6 +24,7 @@ * @author WeeskyBDW */ public class ExprRandomNumber implements Expression { + // TODO get rid of BigInt/BigDecimal static { Parser.getMainRegistration().newExpression( ExprRandomNumber.class, diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringCharAt.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringCharAt.java index b28728d39..54e0ede96 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringCharAt.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringCharAt.java @@ -5,8 +5,6 @@ import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.parsing.ParseContext; -import java.math.BigInteger; - /** * The character at a given position in a string. Note that indices in Skript start at 1. * @@ -25,13 +23,13 @@ public class ExprStringCharAt implements Expression { ); } - private Expression position; + private Expression position; private Expression value; @SuppressWarnings("unchecked") @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - position = (Expression) expressions[0]; + position = (Expression) expressions[0]; value = (Expression) expressions[1]; return true; } @@ -40,7 +38,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex public String[] getValues(TriggerContext ctx) { return value.getSingle(ctx) .map(val -> position.stream(ctx) - .filter(pos -> pos.signum() > 0 && pos.compareTo(BigInteger.valueOf(val.length())) <= 0) + .filter(pos -> Integer.signum(pos) > 0 && pos.compareTo(Integer.valueOf(val.length())) <= 0) .map(pos -> String.valueOf(val.charAt(pos.intValue() - 1))) .toArray(String[]::new)) .orElse(new String[0]); diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringOccurrence.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringOccurrence.java index f7d0df835..35ac20633 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringOccurrence.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringOccurrence.java @@ -6,52 +6,51 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.DoubleOptional; -import java.math.BigInteger; - /** * The index of the first or last occurrence of a given string in a string. * Note that indices in Skript start at 1. * + * @author Mwexim * @name Occurrence * @type EXPRESSION * @pattern [the] (first|last) occurrence of %string% in %string% * @since ALPHA - * @author Mwexim */ public class ExprStringOccurrence implements Expression { - static { - Parser.getMainRegistration().addExpression( - ExprStringOccurrence.class, - Number.class, - true, - "[the] (0:first|1:last) occurrence of %string% in %string%" - ); - } + static { + Parser.getMainRegistration().addExpression( + ExprStringOccurrence.class, + Number.class, + true, + "[the] (0:first|1:last) occurrence of %string% in %string%" + ); + } + + private Expression haystack, needle; + private boolean first; - private Expression haystack, needle; - private boolean first; + // TODO let this support nth occurrence + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + first = parseContext.getNumericMark() == 0; + needle = (Expression) expressions[0]; + haystack = (Expression) expressions[1]; + return true; + } - // TODO let this support nth occurrence - @SuppressWarnings("unchecked") - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - first = parseContext.getNumericMark() == 0; - needle = (Expression) expressions[0]; - haystack = (Expression) expressions[1]; - return true; - } + @Override + public Number[] getValues(TriggerContext ctx) { + return DoubleOptional.ofOptional(haystack.getSingle(ctx), needle.getSingle(ctx)) + .mapToOptional((h, n) -> first ? h.indexOf(n) : h.lastIndexOf(n)) + .filter(i -> i != -1) + .map(i -> new Number[]{i + 1}) // Return i + 1, since Skript indices start at 1. + .orElse(new Number[0]); + } - @Override - public Number[] getValues(TriggerContext ctx) { - return DoubleOptional.ofOptional(haystack.getSingle(ctx), needle.getSingle(ctx)) - .mapToOptional((h, n) -> first ? h.indexOf(n) : h.lastIndexOf(n)) - .filter(i -> i != -1) - .map(i -> new Number[] {BigInteger.valueOf(i + 1)}) // Return i + 1, since Skript indices start at 1. - .orElse(new Number[0]); - } + @Override + public String toString(TriggerContext ctx, boolean debug) { + return (first ? "first" : "last") + " occurrence of " + needle.toString(ctx, debug) + " in " + haystack.toString(ctx, debug); + } - @Override - public String toString(TriggerContext ctx, boolean debug) { - return (first ? "first" : "last") + " occurrence of " + needle.toString(ctx, debug) + " in " + haystack.toString(ctx, debug); - } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringSplitJoin.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringSplitJoin.java index 75d5ae0f0..84dac6718 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringSplitJoin.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprStringSplitJoin.java @@ -7,7 +7,6 @@ import io.github.syst3ms.skriptparser.util.DoubleOptional; import org.jetbrains.annotations.Nullable; -import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.function.Function; @@ -42,7 +41,7 @@ public class ExprStringSplitJoin implements Expression { private Expression expression; @Nullable private Expression delimiter; - private Expression step; + private Expression step; private int pattern; private boolean regex; @@ -63,7 +62,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex delimiter = (Expression) expressions[1]; break; case 2: - step = (Expression) expressions[1]; + step = (Expression) expressions[1]; break; default: throw new IllegalStateException(); @@ -86,7 +85,7 @@ public String[] getValues(TriggerContext ctx) { .orElse(new String[0]); case 2: return DoubleOptional.ofOptional(expression.getSingle(ctx), step.getSingle(ctx)) - .map(Function.identity(), BigInteger::intValue) + .map(Function.identity(), Integer::intValue) .mapToOptional((val, step) -> { List ret = new ArrayList<>(); for (int i = 0; i < val.length(); i += step) { diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprSubstring.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprSubstring.java index e9a37c5d7..f73225a09 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprSubstring.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprSubstring.java @@ -6,97 +6,95 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.DoubleOptional; -import java.math.BigInteger; - /** * Extracts a part of the string. * Note that indices in Skript start at 1. * + * @author Mwexim * @name Substring * @pattern [the] (part|sub[ ](text|string)) of %string% (between|from) [(ind(ex[es]|ices)|char[acter][s])] %integer% (and|to) [(index|char[acter])] %integer% * @since ALPHA - * @author Mwexim */ public class ExprSubstring implements Expression { - static { - Parser.getMainRegistration().addExpression( - ExprSubstring.class, - String.class, - true, - "[the] (part|sub[ ](text|string)) of %string% (between|from) [(ind(ex[es]|ices)|char[acter][s])] %integer% (and|to) [(index|char[acter])] %integer%", - "[the] (0:first|1:last) [%integer%] char[acter][s] (of|in) %string%", - "[the] %integer% (0:first|1:last) char[acter]s (of|in) %string%" - ); - } + static { + Parser.getMainRegistration().addExpression( + ExprSubstring.class, + String.class, + true, + "[the] (part|sub[ ](text|string)) of %string% (between|from) [(ind(ex[es]|ices)|char[acter][s])] %integer% (and|to) [(index|char[acter])] %integer%", + "[the] (0:first|1:last) [%integer%] char[acter][s] (of|in) %string%", + "[the] %integer% (0:first|1:last) char[acter]s (of|in) %string%" + ); + } - private Expression value; - private Expression lower, upper; - private int pattern; - private boolean first; + private Expression value; + private Expression lower, upper; + private int pattern; + private boolean first; - @SuppressWarnings("unchecked") - @Override - public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - pattern = matchedPattern; - switch (pattern) { - case 0: - value = (Expression) expressions[0]; - lower = (Expression) expressions[1]; - upper = (Expression) expressions[2]; - break; - case 1: - if (expressions.length == 2) { - lower = (Expression) expressions[0]; - value = (Expression) expressions[1]; - } else { - value = (Expression) expressions[0]; - } - first = parseContext.getNumericMark() == 0; - break; - case 2: - lower = (Expression) expressions[0]; - value = (Expression) expressions[1]; - first = parseContext.getNumericMark() == 0; - break; - default: - throw new IllegalStateException(); - } - return true; - } + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + pattern = matchedPattern; + switch (pattern) { + case 0: + value = (Expression) expressions[0]; + lower = (Expression) expressions[1]; + upper = (Expression) expressions[2]; + break; + case 1: + if (expressions.length == 2) { + lower = (Expression) expressions[0]; + value = (Expression) expressions[1]; + } else { + value = (Expression) expressions[0]; + } + first = parseContext.getNumericMark() == 0; + break; + case 2: + lower = (Expression) expressions[0]; + value = (Expression) expressions[1]; + first = parseContext.getNumericMark() == 0; + break; + default: + throw new IllegalStateException(); + } + return true; + } - @Override - public String[] getValues(TriggerContext ctx) { - if (pattern == 0) { - return value.getSingle(ctx) - .flatMap(val -> DoubleOptional.ofOptional(lower.getSingle(ctx), upper.getSingle(ctx)) - .filter((low, up) -> low.compareTo(up) < 0 - && low.compareTo(BigInteger.ZERO) > 0 - && up.compareTo(BigInteger.valueOf(val.length())) <= 0 - ) - .mapToOptional((low, up) -> val.substring(low.intValue() - 1, up.intValue()))) - .map(val -> new String[] {val}) - .orElse(new String[0]); - } else if (lower != null) { - return DoubleOptional.ofOptional(value.getSingle(ctx), lower.getSingle(ctx)) - .filter((val, n) -> n.compareTo(BigInteger.ZERO) > 0 && n.compareTo(BigInteger.valueOf(val.length())) <= 0) - .mapToOptional((val, n) -> first ? val.substring(0, n.intValue()) : val.substring(val.length() - n.intValue())) - .map(val -> new String[] {val}) - .orElse(new String[0]); - } else { - return value.getSingle(ctx) - .map(val -> new String[] {String.valueOf(val.charAt(first ? 0 : val.length() - 1))}) - .orElse(new String[0]); - } - } + @Override + public String[] getValues(TriggerContext ctx) { + if (pattern == 0) { + return value.getSingle(ctx) + .flatMap(val -> DoubleOptional.ofOptional(lower.getSingle(ctx), upper.getSingle(ctx)) + .filter((low, up) -> low.compareTo(up) < 0 + && low.compareTo(0) > 0 + && up.compareTo(Integer.valueOf(val.length())) <= 0 + ) + .mapToOptional((low, up) -> val.substring(low.intValue() - 1, up.intValue()))) + .map(val -> new String[]{val}) + .orElse(new String[0]); + } else if (lower != null) { + return DoubleOptional.ofOptional(value.getSingle(ctx), lower.getSingle(ctx)) + .filter((val, n) -> n.compareTo(0) > 0 && n.compareTo(Integer.valueOf(val.length())) <= 0) + .mapToOptional((val, n) -> first ? val.substring(0, n.intValue()) : val.substring(val.length() - n.intValue())) + .map(val -> new String[]{val}) + .orElse(new String[0]); + } else { + return value.getSingle(ctx) + .map(val -> new String[]{String.valueOf(val.charAt(first ? 0 : val.length() - 1))}) + .orElse(new String[0]); + } + } - @Override - public String toString(TriggerContext ctx, boolean debug) { - if (pattern == 0) { - return "substring of " + value.toString(ctx, debug) - + " from " + lower.toString(ctx, debug) + " to " + upper.toString(ctx, debug); - } else { - return (first ? "first " : "last ") + (lower != null ? lower.toString(ctx, debug) : "") - + " characters of " + value.toString(ctx, debug); - } - } + @Override + public String toString(TriggerContext ctx, boolean debug) { + if (pattern == 0) { + return "substring of " + value.toString(ctx, debug) + + " from " + lower.toString(ctx, debug) + " to " + upper.toString(ctx, debug); + } else { + return (first ? "first " : "last ") + (lower != null ? lower.toString(ctx, debug) : "") + + " characters of " + value.toString(ctx, debug); + } + } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/LitTimeConstants.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/LitTimeConstants.java index 8e95d14e7..a365a830a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/LitTimeConstants.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/LitTimeConstants.java @@ -8,30 +8,28 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.util.Time; -import java.math.BigInteger; - /** * Midnight and noon time constants and the ability to use * expressions like {@code 12 o' clock} for literal usage. * + * @author Mwexim * @name Time Constants - * @pattern (midnight|noon|midday) + * @pattern (midnight | noon | midday) * @pattern %*integer% o'[ ]clock * @since ALPHA - * @author Mwexim */ public class LitTimeConstants implements Literal