Skip to content

Commit

Permalink
Parsing stack (#5277) (#7534)
Browse files Browse the repository at this point in the history
* Adds ParsingStack data type

* Add ParseStackOverflowException

* Store ParsingStack in ParserInstance

* Implement the parsing stack in SkriptParser
Document SkriptParser#parse_i a bit better

* Improve robustness of ParsingStack
Add more operations to ParsingStack

* Add usage note to ParsingStack

* Add modification notice to ParserInstance#getParsingStack

* Switch from Stack class to LinkedList class
Add notice on iteration order

* Change brackets of ignored catch block

Co-authored-by: Patrick Miller <apickledwalrus@gmail.com>

* Review response, slight documentation changes, variable rename, formatting changes

* Update src/main/java/ch/njol/skript/lang/parser/ParsingStack.java

Co-authored-by: Kiip <25848425+kiip1@users.noreply.github.com>

* Add `@param` tags to method Javadocs for completeness sake

Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com>

* Forgot import because of GH suggested change commit

* Fix newer merge changes.

---------

Co-authored-by: TPGamesNL <pet.teun.03@gmail.com>
Co-authored-by: TPGamesNL <29547183+TPGamesNL@users.noreply.github.com>
Co-authored-by: Patrick Miller <apickledwalrus@gmail.com>
Co-authored-by: Kiip <25848425+kiip1@users.noreply.github.com>
Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com>
  • Loading branch information
6 people authored Jan 27, 2025
1 parent 2f2f7fc commit e1565dc
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 11 deletions.
18 changes: 16 additions & 2 deletions src/main/java/ch/njol/skript/lang/SkriptParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import ch.njol.skript.lang.function.ExprFunctionCall;
import ch.njol.skript.lang.function.FunctionReference;
import ch.njol.skript.lang.function.Functions;
import ch.njol.skript.lang.parser.ParseStackOverflowException;
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.skript.lang.parser.ParsingStack;
import ch.njol.skript.lang.util.SimpleLiteral;
import ch.njol.skript.localization.Language;
import ch.njol.skript.localization.Message;
Expand Down Expand Up @@ -183,6 +185,7 @@ public boolean hasTag(String tag) {
}

private <T extends SyntaxElement> @Nullable T parse(Iterator<? extends SyntaxElementInfo<? extends T>> source) {
ParsingStack parsingStack = getParser().getParsingStack();
try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) {
while (source.hasNext()) {
SyntaxElementInfo<? extends T> info = source.next();
Expand All @@ -193,15 +196,24 @@ public boolean hasTag(String tag) {
assert pattern != null;
ParseResult parseResult;
try {
parsingStack.push(new ParsingStack.Element(info, patternIndex));
parseResult = parse_i(pattern);
} catch (MalformedPatternException e) {
String message = "pattern compiling exception, element class: " + info.getElementClass().getName();
try {
JavaPlugin providingPlugin = JavaPlugin.getProvidingPlugin(info.getElementClass());
message += " (provided by " + providingPlugin.getName() + ")";
} catch (IllegalArgumentException | IllegalStateException ignored) {}
throw new RuntimeException(message, e);
} catch (IllegalArgumentException | IllegalStateException ignored) { }

throw new RuntimeException(message, e);
} catch (StackOverflowError e) {
// Parsing caused a stack overflow, possibly due to too long lines
throw new ParseStackOverflowException(e, new ParsingStack(parsingStack));
} finally {
// Recursive parsing call done, pop the element from the parsing stack
ParsingStack.Element stackElement = parsingStack.pop();

assert stackElement.syntaxElementInfo() == info && stackElement.patternIndex() == patternIndex;
}
if (parseResult != null) {
assert parseResult.source != null; // parse results from parse_i have a source
Expand Down Expand Up @@ -249,6 +261,8 @@ public boolean hasTag(String tag) {
}
}
}

// No successful syntax elements parsed, print errors and return
log.printError();
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ch.njol.skript.lang.parser;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

/**
* An exception noting that a {@link StackOverflowError} has occurred
* during Skript parsing. Contains information about the {@link ParsingStack}
* from when the stack overflow occurred.
*/
public class ParseStackOverflowException extends RuntimeException {

protected final ParsingStack parsingStack;

public ParseStackOverflowException(StackOverflowError cause, ParsingStack parsingStack) {
super(createMessage(parsingStack), cause);
this.parsingStack = parsingStack;
}

/**
* Creates the exception message from the given {@link ParsingStack}.
*/
private static String createMessage(ParsingStack stack) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();

PrintStream printStream = new PrintStream(stream);
stack.print(printStream);

return stream.toString();
}

}
27 changes: 18 additions & 9 deletions src/main/java/ch/njol/skript/lang/parser/ParserInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,7 @@
import org.skriptlang.skript.lang.structure.Structure;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;

public final class ParserInstance implements Experimented {
Expand Down Expand Up @@ -461,6 +457,19 @@ public String getIndentation() {
return indentation;
}

// Parsing stack

private final ParsingStack parsingStack = new ParsingStack();

/**
* Gets the current parsing stack.
* <p>
* Although the stack can be modified, doing so is not recommended.
*/
public ParsingStack getParsingStack() {
return parsingStack;
}

// Experiments API

@Override
Expand Down Expand Up @@ -705,8 +714,8 @@ public HashMap<String, String> getCurrentOptions() {
@Deprecated
public @Nullable SkriptEvent getCurrentSkriptEvent() {
Structure structure = getCurrentStructure();
if (structure instanceof SkriptEvent)
return (SkriptEvent) structure;
if (structure instanceof SkriptEvent event)
return event;
return null;
}

Expand All @@ -715,15 +724,15 @@ public HashMap<String, String> getCurrentOptions() {
*/
@Deprecated
public void setCurrentSkriptEvent(@Nullable SkriptEvent currentSkriptEvent) {
setCurrentStructure(currentSkriptEvent);
this.setCurrentStructure(currentSkriptEvent);
}

/**
* @deprecated Use {@link #setCurrentStructure(Structure)} with 'null'.
*/
@Deprecated
public void deleteCurrentSkriptEvent() {
setCurrentStructure(null);
this.setCurrentStructure(null);
}

/**
Expand Down
192 changes: 192 additions & 0 deletions src/main/java/ch/njol/skript/lang/parser/ParsingStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package ch.njol.skript.lang.parser;

import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.SyntaxElement;
import ch.njol.skript.lang.SyntaxElementInfo;
import ch.njol.util.Kleenean;

import java.io.PrintStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;

/**
* A stack that keeps track of what Skript is currently parsing.
* <p>
* When accessing the stack from within
* {@link SyntaxElement#init(Expression[], int, Kleenean, SkriptParser.ParseResult)},
* the stack element corresponding to that {@link SyntaxElement} is <b>not</b>
* on the parsing stack.
*/
public class ParsingStack implements Iterable<ParsingStack.Element> {

private final LinkedList<Element> stack;

/**
* Creates an empty parsing stack.
*/
public ParsingStack() {
this.stack = new LinkedList<>();
}

/**
* Creates a parsing stack containing all elements
* of another given parsing stack.
*/
public ParsingStack(ParsingStack parsingStack) {
this.stack = new LinkedList<>(parsingStack.stack);
}

/**
* Removes and returns the top element of this stack.
*
* @throws IllegalStateException if the stack is empty.
*/
public Element pop() throws IllegalStateException {
if (stack.isEmpty()) {
throw new IllegalStateException("Stack is empty");
}

return stack.pop();
}

/**
* Returns the element at the given index in the stack,
* starting with the top element at index 0.
*
* @param index the index in stack.
* @throws IndexOutOfBoundsException if the index is not appointed
* to an element in the stack.
*/
public Element peek(int index) throws IndexOutOfBoundsException {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException("Index: " + index);
}

return stack.get(index);
}

/**
* Returns the top element of the stack.
* Equivalent to {@code peek(0)}.
*
* @throws IllegalStateException if the stack is empty.
*/
public Element peek() throws IllegalStateException {
if (stack.isEmpty()) {
throw new IllegalStateException("Stack is empty");
}

return stack.peek();
}

/**
* Adds the given element to the top of the stack.
*/
public void push(Element element) {
stack.push(element);
}

/**
* Check if this stack is empty.
*/
public boolean isEmpty() {
return stack.isEmpty();
}

/**
* Gets the size of the stack.
*/
public int size() {
return stack.size();
}

/**
* Prints this stack to the given {@link PrintStream}.
*
* @param printStream a {@link PrintStream} to print the stack to.
*/
public void print(PrintStream printStream) {
// Synchronized to assure it'll all be printed at once,
// PrintStream uses synchronization on itself internally, justifying warning suppression

//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (printStream) {
printStream.println("Stack:");

if (stack.isEmpty()) {
printStream.println("<empty>");
} else {
for (Element element : stack) {
printStream.println("\t" + element.getSyntaxElementClass().getName() +
" @ " + element.patternIndex());
}
}
}
}

/**
* Iterate over the stack, starting at the top.
*/
@Override
public Iterator<Element> iterator() {
return Collections.unmodifiableList(stack).iterator();
}

/**
* A stack element, containing details about the syntax element it is about.
*/
public record Element(SyntaxElementInfo<?> syntaxElementInfo, int patternIndex) {

public Element {
assert patternIndex >= 0 && patternIndex < syntaxElementInfo.getPatterns().length;
}

/**
* Gets the raw {@link SyntaxElementInfo} of this stack element.
* <p>
* For ease of use, consider using other getters of this class.
*
* @see #getSyntaxElementClass()
* @see #getPattern()
*/
@Override
public SyntaxElementInfo<?> syntaxElementInfo() {
return syntaxElementInfo;
}

/**
* Gets the index to the registered patterns for the syntax element
* of this stack element.
*/
@Override
public int patternIndex() {
return patternIndex;
}

/**
* Gets the syntax element class of this stack element.
*/
public Class<? extends SyntaxElement> getSyntaxElementClass() {
return syntaxElementInfo.getElementClass();
}

/**
* Gets the pattern that was matched for this stack element.
*/
public String getPattern() {
return syntaxElementInfo.getPatterns()[patternIndex];
}

/**
* Gets all patterns registered with the syntax element
* of this stack element.
*/
public String[] getPatterns() {
return syntaxElementInfo.getPatterns();
}

}

}

0 comments on commit e1565dc

Please sign in to comment.