diff --git a/build.gradle b/build.gradle index 0619f6fc..02af6fc1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,12 @@ plugins { + id "com.github.johnrengelman.shadow" version "8.1.1" id "java" id "application" } mainClassName = "io.github.syst3ms.skriptparser.Parser" -sourceCompatibility = 1.11 +sourceCompatibility = 1.17 repositories { mavenCentral() diff --git a/src/main/java/io/github/syst3ms/skriptparser/Parser.java b/src/main/java/io/github/syst3ms/skriptparser/Parser.java index b3b3d9c0..ca21ac77 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", "javafunctions", "tags")); subPackages = sub.toArray(new String[0]); try { for (String mainPackage : mainPackages) { @@ -138,7 +138,10 @@ public static void init(String[] mainPackages, String[] subPackages, String[] pr public static void run(String scriptName, boolean debug, boolean tipsEnabled) { Calendar time = Calendar.getInstance(); Path scriptPath = Paths.get(scriptName); + long start = System.currentTimeMillis(); logs = ScriptLoader.loadScript(scriptPath, debug); + long elapsed = System.currentTimeMillis()-start; + System.out.println("Script \"" + scriptName + "\" has been parsed in " + elapsed + "ms"); if (!logs.isEmpty()) { System.out.print(ConsoleColors.PURPLE); System.out.println("Parsing log:"); diff --git a/src/main/java/io/github/syst3ms/skriptparser/Skript.java b/src/main/java/io/github/syst3ms/skriptparser/Skript.java index b6c8ce1c..3ef6730a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/Skript.java +++ b/src/main/java/io/github/syst3ms/skriptparser/Skript.java @@ -1,17 +1,11 @@ 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.event.*; 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.structures.functions.StructFunction; import io.github.syst3ms.skriptparser.util.DurationUtils; import io.github.syst3ms.skriptparser.util.ThreadUtils; import io.github.syst3ms.skriptparser.util.Time; @@ -51,6 +45,8 @@ public void handleTrigger(Trigger trigger) { whenTriggers.add(trigger); } else if (event instanceof EvtAtTime) { atTimeTriggers.add(trigger); + } else if (event instanceof StructFunction function) { + function.register(trigger); } } @@ -77,4 +73,5 @@ public void finishedLoading() { ThreadUtils.runPeriodically(() -> Statement.runAll(trigger, ctx), initialDelay, Duration.ofDays(1)); } } + } 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 624f27a1..a4207b81 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,46 +42,68 @@ public class EffReturn extends Effect { public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { returned = expressions[0]; var logger = parseContext.getLogger(); - 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); - return false; - } - section = (ReturnSection) sec.get(); - if (section.isSingle() && !returned.isSingle()) { - logger.error("Only a single return value was expected, but multiple were given", ErrorType.SEMANTIC_ERROR); - return false; - } else if (!Converters.converterExists(returned.getReturnType(), section.getReturnType())) { - var secType = TypeManager.getByClass(section.getReturnType()) - .map(t -> StringUtils.withIndefiniteArticle(t.toString(), false)) - .orElse(section.getReturnType().getName()); - var exprType = TypeManager.getByClass(returned.getReturnType()) - .map(t -> StringUtils.withIndefiniteArticle(t.toString(), false)) - .orElseThrow(AssertionError::new); - logger.error( - "Expected " + - secType + - " return value, but found " + - exprType, - ErrorType.SEMANTIC_ERROR - ); - return false; - } - 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")); + Optional> optionalContext = parseContext.getParserState().getCurrentContexts().stream().findFirst(); + if (optionalContext.isPresent()) { + Class currentContext = optionalContext.get(); + if (currentContext.equals(FunctionContext.class)) { + isInFunction = true; + // cannot verify + } else { + 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); + return false; + } + section = (ReturnSection) sec.get(); + if (section.isSingle() && !returned.isSingle()) { + logger.error("Only a single return value was expected, but multiple were given", ErrorType.SEMANTIC_ERROR); + return false; + } else if (!Converters.converterExists(returned.getReturnType(), section.getReturnType())) { + var secType = TypeManager.getByClass(section.getReturnType()) + .map(t -> StringUtils.withIndefiniteArticle(t.toString(), false)) + .orElse(section.getReturnType().getName()); + var exprType = TypeManager.getByClass(returned.getReturnType()) + .map(t -> StringUtils.withIndefiniteArticle(t.toString(), false)) + .orElseThrow(AssertionError::new); + logger.error( + "Expected " + + secType + + " return value, but found " + + exprType, + ErrorType.SEMANTIC_ERROR + ); + return false; + } + 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")); + } + } + } else { + } 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/javafunctions/DefaultFunctions.java b/src/main/java/io/github/syst3ms/skriptparser/javafunctions/DefaultFunctions.java new file mode 100644 index 00000000..0803d3e2 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/javafunctions/DefaultFunctions.java @@ -0,0 +1,30 @@ +package io.github.syst3ms.skriptparser.javafunctions; + +import io.github.syst3ms.skriptparser.structures.functions.FunctionParameter; +import io.github.syst3ms.skriptparser.structures.functions.Functions; +import io.github.syst3ms.skriptparser.structures.functions.JavaFunction; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class DefaultFunctions { + + static { + Functions.registerFunction(new JavaFunction<>( + "mod", + new FunctionParameter[]{new FunctionParameter<>("d", BigInteger.class, true), new FunctionParameter<>("m", BigInteger.class, true)}, + Number.class, + true) { + @Override + public Number[] executeSimple(Object[][] params) { + Number d = (Number) params[0][0]; + Number m = (Number) params[1][0]; + double mm = m.doubleValue(); + if (mm == 0) + return new Number[] {BigDecimal.valueOf(Double.NaN)}; + return new Number[] {BigDecimal.valueOf((d.doubleValue() % mm + mm) % mm)}; + } + }); + } + +} 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 7798162c..efc020da 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 00000000..eab69df2 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Structure.java @@ -0,0 +1,38 @@ +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 25b3f3ff..2900379a 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 @@ -116,6 +116,10 @@ public Map getData() { return data; } + public List getEntries() { + return entries; + } + public Object getValue(String key) { return data.get(key); } 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 474de99c..12b2f3b1 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/ParserState.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/ParserState.java @@ -19,6 +19,7 @@ public class ParserState { private final LinkedList currentSections = new LinkedList<>(); private final LinkedList> currentStatements = new LinkedList<>(); private final LinkedList>, Boolean>> restrictions = new LinkedList<>(); + private boolean isntAllowingSyntax = false; { currentStatements.add(new LinkedList<>()); @@ -103,6 +104,7 @@ 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 +112,7 @@ public void setSyntaxRestrictions(Set> allowedSyn * Clears the previously enforced syntax restrictions */ public void clearSyntaxRestrictions() { + isntAllowingSyntax = false; restrictions.removeLast(); } @@ -118,6 +121,7 @@ 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); } 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 ddfb07ff..bde44c24 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/ScriptLoader.java @@ -4,6 +4,7 @@ import io.github.syst3ms.skriptparser.file.FileParser; import io.github.syst3ms.skriptparser.file.FileSection; import io.github.syst3ms.skriptparser.file.VoidElement; +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.UnloadedTrigger; @@ -73,6 +74,13 @@ public static List loadScript(Path scriptPath, SkriptLogger logger, bo trig.ifPresent(t -> { logger.setLine(logger.getLine() + ((FileSection) element).length()); unloadedTriggers.add(t); + //SkriptEvent skriptEvent = t.getTrigger().getEvent(); + /* TODO + validate that this is a function + parse & save it + have a separate parser to parse a whole folder that does this step before anything + */ + //System.out.println(skriptEvent); }); } else { logger.error( diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java index 5e77ceba..a2f7cef5 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java @@ -170,7 +170,7 @@ public static Optional> parseExpression(St if (expr.isPresent()) { if (parserState.isRestrictingExpressions() && parserState.forbidsSyntax(expr.get().getClass())) { logger.setContext(ErrorContext.RESTRICTED_SYNTAXES); - logger.error("The enclosing section does not allow the use of this expression: " + expr.get().toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR); + logger.error("The enclosing code section does not allow the use of this expression: " + expr.get().toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR); return Optional.empty(); } recentExpressions.acknowledge(info); @@ -329,7 +329,7 @@ public static Optional> parseBooleanExpression(Str return Optional.empty(); } else if (parserState.isRestrictingExpressions() && parserState.forbidsSyntax(ContextExpression.class)) { logger.setContext(ErrorContext.RESTRICTED_SYNTAXES); - logger.error("The enclosing section does not allow the use of context expressions.", ErrorType.SEMANTIC_ERROR); + logger.error("The enclosing code section does not allow the use of context expressions.", ErrorType.SEMANTIC_ERROR); return Optional.empty(); } @@ -392,7 +392,7 @@ private static Optional> matchExpressionIn if (parserState.isRestrictingExpressions() && parserState.forbidsSyntax(expression.getClass())) { logger.setContext(ErrorContext.RESTRICTED_SYNTAXES); logger.error( - "The enclosing section does not allow the use of this expression: " + "The enclosing code section does not allow the use of this expression: " + expression.toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR, "The current section limits the usage of syntax. This means that certain syntax cannot be used here, which was the case. Remove this expression entirely and refer to the documentation for the correct usage of this section" @@ -566,7 +566,7 @@ public static Optional parseEffect(String s, ParserState parse if (eff.isPresent()) { if (parserState.forbidsSyntax(eff.get().getClass())) { logger.setContext(ErrorContext.RESTRICTED_SYNTAXES); - logger.error("The enclosing section does not allow the use of this effect: " + eff.get().toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR); + logger.error("The enclosing code section does not allow the use of this effect: " + eff.get().toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR); return Optional.empty(); } recentEffects.acknowledge(recentEffect); @@ -627,7 +627,7 @@ public static Optional parseSection(FileSection section, if (sec.isPresent()) { if (parserState.forbidsSyntax(sec.get().getClass())) { logger.setContext(ErrorContext.RESTRICTED_SYNTAXES); - logger.error("The enclosing section does not allow the use of this section: " + sec.get().toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR); + logger.error("The enclosing code section does not allow the use of this section: " + sec.get().toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR); return Optional.empty(); } recentSections.acknowledge(toParse); 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 8ef77755..5aa7f9eb 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; @@ -141,11 +142,10 @@ public Class getRelativeType() { }) .register(); - registration.addType( - String.class, - "string", - "string@s" - ); + registration.newType(String.class, "string", "string@s") + .literalParser(s -> s) + .toStringFunction(s -> s) + .register(); registration.newType(Boolean.class, "boolean", "boolean@s") .literalParser(s -> { @@ -161,10 +161,14 @@ public Class getRelativeType() { .register(); registration.newType(Type.class, "type", "type@s") - .literalParser(s -> TypeManager.getByExactName(s.toLowerCase()).orElse(null)) + .literalParser(TypeManager::parseType) .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") .literalParser(s -> Color.ofLiteral(s).orElse(null)) .toStringFunction(Color::toString) @@ -324,6 +328,8 @@ public Relation apply(Duration duration, Duration duration2) { return Optional.of(BigInteger.valueOf(n.longValue())); } }); + registration.addConverter(Number.class, BigDecimal.class, n -> Optional.of(BigDecimal.valueOf(n.doubleValue()))); + registration.addConverter(Number.class, BigInteger.class, n -> Optional.of(BigInteger.valueOf(n.longValue()))); registration.addConverter(SkriptDate.class, Time.class, da -> Optional.of(Time.of(da))); 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 12f17bf6..da79f8dd 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -919,4 +919,9 @@ private static int findAppropriatePriority(PatternElement el) { return containsRegex ? Math.min(priority, 3) : priority; } } + + public SkriptLogger getLogger() { + return logger; + } + } diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecFilter.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecFilter.java index 798fba57..4f58e386 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecFilter.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecFilter.java @@ -38,6 +38,10 @@ public class SecFilter extends ReturnSection implements SelfReferencing SecFilter.class, "filter %~objects%" ); + /** + * filter {_test::*}: + * return type of input is pig + */ } private Expression filtered; 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 0b85ab36..782e2ae4 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java @@ -16,6 +16,7 @@ import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.parsing.ParserState; import io.github.syst3ms.skriptparser.types.ranges.Ranges; +import io.github.syst3ms.skriptparser.variables.Variables; import org.jetbrains.annotations.Nullable; import java.math.BigInteger; @@ -55,8 +56,9 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen @Override public boolean loadSection(FileSection section, ParserState parserState, SkriptLogger logger) { + if (!super.loadSection(section, parserState, logger)) return false; super.setNext(this); - return super.loadSection(section, parserState, logger); + return true; } @SuppressWarnings("unchecked") 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 2821dfda..aa7a8194 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecWhile.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecWhile.java @@ -39,8 +39,9 @@ public class SecWhile extends CodeSection implements Continuable, SelfReferencin @Override public boolean loadSection(FileSection section, ParserState parserState, SkriptLogger logger) { + if (!super.loadSection(section, parserState, logger)) return false; super.setNext(this); - return super.loadSection(section, parserState, logger); + return true; } @SuppressWarnings("unchecked") 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 00000000..cfb8c126 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/EffFunctionCall.java @@ -0,0 +1,120 @@ +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); + System.out.println(toString(ctx, true)); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + @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 00000000..e86cdd21 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ExprFunctionCall.java @@ -0,0 +1,118 @@ +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); + System.out.println(toString(ctx, true)); + 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 00000000..b6039557 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/Function.java @@ -0,0 +1,62 @@ +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 00000000..d3e5459c --- /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 00000000..527c9cfb --- /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 00000000..db83214b --- /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 00000000..90530a39 --- /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 00000000..2a4e1695 --- /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)}; + } + + @SuppressWarnings("unchecked") + @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 00000000..0b110a22 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/ScriptFunction.java @@ -0,0 +1,58 @@ +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; + +import java.util.Arrays; + +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 + 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 00000000..00a46d5d --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/structures/functions/StructFunction.java @@ -0,0 +1,95 @@ +package io.github.syst3ms.skriptparser.structures.functions; + +import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.*; +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 boolean local = false; + private String functionName; + private Literal> params; + private boolean returnSingle = true; + private String rawReturnType; + + 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 5cfc651f..5bce6e22 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java @@ -113,6 +113,10 @@ public Type(Class typeClass, this.arithmetic = arithmetic; } + public boolean isPlural(String input) { + return input.equalsIgnoreCase(getPluralForm()); + } + public Class getTypeClass() { return typeClass; } @@ -125,6 +129,13 @@ 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; } 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 d5bf10e0..c0969e2e 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java @@ -2,10 +2,7 @@ import io.github.syst3ms.skriptparser.registration.SkriptRegistration; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; +import java.util.*; /** * Manages the registration and usage of {@link Type} @@ -115,6 +112,12 @@ 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);