Skip to content

Commit

Permalink
✨ EntryLoader (to customize the parsing process)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mwexim authored and Mwexim committed Dec 21, 2021
1 parent 766be8a commit ad33642
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Boolean> optionEntries = new HashMap<>();
private Map<String, Boolean> sectionEntries = new HashMap<>();

private Map<String, String> configuredOptions = new HashMap<>();
private Map<String, CodeSection> configuredSections = new HashMap<>();
@Nullable
private CodeSection parent;
private final List<EntryLoader> entries = new ArrayList<>();
private final Map<String, Object> 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;
}

Expand All @@ -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;
}
Expand All @@ -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<String, Object> getData() {
return data;
}

@SuppressWarnings("unchecked")
public <T> T getValue(String key, Class<T> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -42,7 +43,8 @@ public boolean loadSection(FileSection section, ParserState parserState, SkriptL

@Override
public Optional<? extends Statement> 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"));
}

Expand Down
11 changes: 10 additions & 1 deletion src/test/resources/general/categories.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
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}%"

0 comments on commit ad33642

Please sign in to comment.