From ad33642bfb5a8d0c3cc39adf5c3aaa3a0eeb4b86 Mon Sep 17 00:00:00 2001 From: Mwexim Date: Tue, 21 Dec 2021 13:56:27 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20EntryLoader=20(to=20customize=20the?= =?UTF-8?q?=20parsing=20process)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lang/entries/EntryLoader.java | 31 +++++ .../lang/entries/OptionLoader.java | 56 +++++++++ .../lang/entries/SectionConfiguration.java | 111 +++++++++--------- .../lang/entries/SectionLoader.java | 28 +++++ .../skriptparser/syntax/test/SecCategory.java | 10 +- src/test/resources/general/categories.txt | 11 +- 6 files changed, 186 insertions(+), 61 deletions(-) create mode 100644 src/main/java/io/github/syst3ms/skriptparser/lang/entries/EntryLoader.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/lang/entries/OptionLoader.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionLoader.java diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/entries/EntryLoader.java b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/EntryLoader.java new file mode 100644 index 00000000..ce83b71b --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/EntryLoader.java @@ -0,0 +1,31 @@ +package io.github.syst3ms.skriptparser.lang.entries; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParserState; + +public abstract class EntryLoader { + protected final String key; + private final boolean optional; + + public EntryLoader(String key, boolean optional) { + this.key = key; + this.optional = optional; + } + + /** + * This {@link EntryLoader} will attempt to load the entry + * using its {@linkplain FileElement}. One can use this method + * to create specific error messages or to load the value correctly. + * @param config the configuration + * @param element the element + * @param parserState the parser state + * @param logger the logger + * @return {@code true} if loaded successfully, {@code false} if an error occurred + */ + public abstract boolean loadEntry(SectionConfiguration config, FileElement element, ParserState parserState, SkriptLogger logger); + + public boolean isOptional() { + return optional; + } +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/entries/OptionLoader.java b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/OptionLoader.java new file mode 100644 index 00000000..e694e90b --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/OptionLoader.java @@ -0,0 +1,56 @@ +package io.github.syst3ms.skriptparser.lang.entries; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.file.VoidElement; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParserState; + +public class OptionLoader extends EntryLoader { + public static final String OPTION_SPLIT_PATTERN = ": "; + + private final boolean multiple; + + public OptionLoader(boolean multiple, String key, boolean optional) { + super(key, optional); + this.multiple = multiple; + } + + @Override + public boolean loadEntry(SectionConfiguration config, FileElement element, ParserState parserState, SkriptLogger logger) { + var content = element.getLineContent().split(OPTION_SPLIT_PATTERN); + if (content.length == 0) + return false; + var key = content[0]; + var entry = content.length > 1 ? content[1] : null; + + if (!key.equalsIgnoreCase(this.key)) + return false; + if (element instanceof FileSection) { + if (!multiple) { + logger.error("The entry '" + key + "' does not support multiple values.", ErrorType.SEMANTIC_ERROR); + return false; + } else if (entry != null) { + logger.error("The entry '" + key + "' has been configured incorrectly.", ErrorType.SEMANTIC_ERROR); + return false; + } + config.getData().put(this.key, ((FileSection) element).getElements().stream() + .filter(el -> !(el instanceof VoidElement)) + .map(FileElement::getLineContent) + .toArray(String[]::new) + ); + } else { + if (entry == null) { + logger.error("The entry '" + key + "' has been configured incorrectly.", ErrorType.SEMANTIC_ERROR); + return false; + } + config.getData().put(this.key, multiple ? new String[] {entry} : entry); + } + return true; + } + + public boolean isMultiple() { + return multiple; + } +} 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 10feaae2..491cd3f8 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 @@ -8,25 +8,27 @@ import io.github.syst3ms.skriptparser.parsing.ParserState; import org.jetbrains.annotations.Nullable; -import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class SectionConfiguration { - public static final String OPTION_SPLIT_PATTERN = ": "; - - private Map optionEntries = new HashMap<>(); - private Map sectionEntries = new HashMap<>(); - - private Map configuredOptions = new HashMap<>(); - private Map configuredSections = new HashMap<>(); + @Nullable + private CodeSection parent; + private final List entries = new ArrayList<>(); + private final Map data = new HashMap<>(); public SectionConfiguration addOption(String key) { - return addOption(key, false); + return addOption(false, key); + } + + public SectionConfiguration addOption(boolean multiple, String key) { + return addOption(multiple, key, false); } - public SectionConfiguration addOption(String key, boolean optional) { - optionEntries.put(key, optional); + public SectionConfiguration addOption(boolean multiple, String key, boolean optional) { + entries.add(new OptionLoader(multiple, key, optional)); return this; } @@ -35,64 +37,46 @@ public SectionConfiguration addSection(String key) { } public SectionConfiguration addSection(String key, boolean optional) { - sectionEntries.put(key, optional); + entries.add(new SectionLoader(key, optional)); return this; } - public SectionConfiguration build() { - optionEntries = Collections.unmodifiableMap(optionEntries); - sectionEntries = Collections.unmodifiableMap(sectionEntries); + public SectionConfiguration addLoader(EntryLoader loader) { + entries.add(loader); return this; } public boolean loadConfiguration(@Nullable CodeSection parent, FileSection section, ParserState parserState, SkriptLogger logger) { boolean successful = true; + this.parent = parent; - // Checking if all option entries are configured outer: - for (var option : optionEntries.entrySet()) { + for (var entry : entries) { for (var el : section.getElements()) { - if (el instanceof VoidElement || el instanceof FileSection) - continue; - var content = el.getLineContent().split(OPTION_SPLIT_PATTERN); - if (content.length != 2) - continue; - var key = content[0]; - var entry = content[1]; - - if (key.equalsIgnoreCase(option.getKey())) { - configuredOptions.put(option.getKey(), entry); + 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 (option.getValue()) - 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 option entry named '" + option.getKey() + "' has not been configured", ErrorType.SEMANTIC_ERROR); - logger.finalizeLogs(); - successful = false; - } - - // Checking if all section entries are configured. - outer: - for (var option : sectionEntries.entrySet()) { - for (var el : section.getElements()) { - if (el.getLineContent().equalsIgnoreCase(option.getKey())) { - var entry = new EntrySection((FileSection) el, parserState, logger, section.getLineContent()); - if (parent != null) - entry.setParent(parent); - configuredSections.put(option.getKey(), entry); + if (el instanceof VoidElement) + continue; + if (entry.loadEntry(this, el, parserState, logger)) continue outer; - } } - if (option.getValue()) + 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 section entry named '" + option.getKey() + "' has not been configured", ErrorType.SEMANTIC_ERROR); + logger.error("The entry named '" + entry.key + "' has not been configured", ErrorType.SEMANTIC_ERROR); logger.finalizeLogs(); successful = false; } @@ -103,17 +87,32 @@ public boolean loadConfiguration(@Nullable CodeSection parent, FileSection secti logger.setLine(section.getLine() - 1); logger.error("The section '" + section.getLineContent() + "' has not been configured correctly", ErrorType.SEMANTIC_ERROR); } - - configuredOptions = Collections.unmodifiableMap(configuredOptions); - configuredSections = Collections.unmodifiableMap(configuredSections); return successful; } - public String getOption(String key) { - return configuredOptions.get(key); + @Nullable + public CodeSection getParent() { + return parent; + } + + public Map getData() { + return data; + } + + @SuppressWarnings("unchecked") + public T getValue(String key, Class cls) { + return (T) data.get(key); + } + + public String getString(String key) { + return getValue(key, String.class); + } + + public String[] getStringList(String key) { + return getValue(key, String[].class); } public CodeSection getSection(String key) { - return configuredSections.get(key); + return getValue(key, CodeSection.class); } } \ No newline at end of file diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionLoader.java b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionLoader.java new file mode 100644 index 00000000..005145f4 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/entries/SectionLoader.java @@ -0,0 +1,28 @@ +package io.github.syst3ms.skriptparser.lang.entries; + +import io.github.syst3ms.skriptparser.file.FileElement; +import io.github.syst3ms.skriptparser.file.FileSection; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParserState; + +public class SectionLoader extends EntryLoader { + public SectionLoader(String key, boolean optional) { + super(key, optional); + } + + @Override + public boolean loadEntry(SectionConfiguration config, FileElement element, ParserState parserState, SkriptLogger logger) { + if (!element.getLineContent().equalsIgnoreCase(this.key)) + return false; + if (!(element instanceof FileSection)) { + logger.error("The entry '" + key + "' has been configured incorrectly.", ErrorType.SEMANTIC_ERROR); + return false; + } + var entry = new EntrySection((FileSection) element, parserState, logger, element.getLineContent()); + if (config.getParent() != null) + entry.setParent(config.getParent()); + config.getData().put(key, entry); + return true; + } +} diff --git a/src/test/java/io/github/syst3ms/skriptparser/syntax/test/SecCategory.java b/src/test/java/io/github/syst3ms/skriptparser/syntax/test/SecCategory.java index 8d24b42e..7223b369 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/syntax/test/SecCategory.java +++ b/src/test/java/io/github/syst3ms/skriptparser/syntax/test/SecCategory.java @@ -25,10 +25,11 @@ public class SecCategory extends CodeSection { private final SectionConfiguration config = new SectionConfiguration() .addOption("number") + .addOption(true, "multiple") + .addOption(true, "more multiple values") .addOption("unused") - .addOption("optional", true) - .addSection("die") - .build(); + .addOption(false, "optional", true) + .addSection("die"); @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { @@ -42,7 +43,8 @@ public boolean loadSection(FileSection section, ParserState parserState, SkriptL @Override public Optional walk(TriggerContext ctx) { - Variables.setVariable("the_number", new BigInteger(config.getOption("number")), null, false); + Variables.setVariable("the_number", new BigInteger(config.getString("number")), null, false); + Variables.setVariable("multiple", String.join(";", config.getStringList("multiple")), null, false); return Optional.of(config.getSection("die")); } diff --git a/src/test/resources/general/categories.txt b/src/test/resources/general/categories.txt index d27e41d5..3218f1cb 100644 --- a/src/test/resources/general/categories.txt +++ b/src/test/resources/general/categories.txt @@ -16,6 +16,15 @@ test: funny: assert false this doesn't get parsed anyway + multiple: + This is the first value + This is the second value + + more multiple values: This one just has one value + # This is not accounted for # In the code, it sets this variable to the 'number' option. - assert {the_number} + 5 = 12 \ No newline at end of file + assert {the_number} + 5 = 12 + + # In the code, it sets this variable to the joined values + assert {multiple} is equal to "This is the first value;This is the second value" with "{multiple} has not been set correctly: %{multiple}%" \ No newline at end of file