hytale-server/com/hypixel/hytale/server/npc/util/expression/compile/Lexer.java

165 lines
6.1 KiB
Java

package com.hypixel.hytale.server.npc.util.expression.compile;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.text.ParseException;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class Lexer<Token extends Supplier<String>> {
public static final String UNTERMINATED_STRING = "Unterminated string";
public static final String INVALID_NUMBER_FORMAT = "Invalid number format";
public static final String INVALID_CHARACTER_IN_EXPRESSION = "Invalid character in expression :";
private final Token tokenEnd;
private final Token tokenIdent;
private final Token tokenString;
private final Token tokenNumber;
private final Lexer.CharacterSequenceMatcher<Token> characterSequenceMatcher;
public Lexer(Token tokenEnd, Token tokenIdent, Token tokenString, Token tokenNumber, @Nonnull Stream<Token> operators) {
this.tokenEnd = tokenEnd;
this.tokenIdent = tokenIdent;
this.tokenString = tokenString;
this.tokenNumber = tokenNumber;
this.characterSequenceMatcher = new Lexer.CharacterSequenceMatcher<>();
operators.forEach(token -> this.characterSequenceMatcher.addToken((Token)token, token.get()));
}
public Token nextToken(@Nonnull LexerContext<Token> context) throws ParseException {
context.resetToken();
if (!context.eatWhiteSpace()) {
return context.setToken(this.tokenEnd);
} else {
char ch = context.currentChar();
if (Character.isLetter(ch) || ch == '_') {
context.parseIdent(ch);
return context.setToken(this.tokenIdent);
} else if (context.isNumber(ch)) {
context.parseNumber(ch);
return context.setToken(this.tokenNumber);
} else if (ch != '"' && ch != '\'') {
Lexer.CharacterSequenceMatcher<Token> lastTerminal = null;
Lexer.CharacterSequenceMatcher<Token> matcher = this.characterSequenceMatcher.matchLetter(ch);
int lastValidPosition;
for (lastValidPosition = context.getPosition(); matcher != null; matcher = matcher.matchLetter(ch)) {
if (matcher.token != null) {
lastValidPosition = context.getPosition();
lastTerminal = matcher;
}
ch = context.addTokenCharacter(ch);
if (!context.haveChar()) {
break;
}
}
if (lastTerminal != null) {
context.adjustPosition(lastValidPosition + 1);
return context.setToken(lastTerminal.token);
} else {
throw new ParseException("Invalid character in expression :" + ch, context.getTokenPosition());
}
} else {
context.parseString(ch);
return context.setToken(this.tokenString);
}
}
}
protected static class CharacterSequenceMatcher<Token> {
@Nullable
public Token token = (Token)null;
public char letter;
@Nullable
public List<Lexer.CharacterSequenceMatcher<Token>> children;
public CharacterSequenceMatcher() {
this.letter = 0;
this.children = null;
}
public CharacterSequenceMatcher(char letter) {
this.letter = letter;
this.children = null;
}
protected void addToken(Token token, int depth, @Nonnull String text, int maxDepth) {
char ch = text.charAt(depth);
if (this.children == null) {
this.children = new ObjectArrayList();
this.append(token, depth, text, maxDepth, ch);
} else {
int index = 0;
int size = this.children.size();
while (index < size && this.children.get(index).letter < ch) {
index++;
}
if (index == size) {
this.append(token, depth, text, maxDepth, ch);
} else {
Lexer.CharacterSequenceMatcher<Token> child = this.children.get(index);
if (child.letter == ch) {
if (depth == maxDepth) {
if (child.token != null) {
throw new RuntimeException("Duplicate operator " + text);
}
child.token = token;
} else {
child.addToken(token, depth + 1, text, maxDepth);
}
} else {
Lexer.CharacterSequenceMatcher<Token> lookup = new Lexer.CharacterSequenceMatcher<>(ch);
this.children.add(index, lookup);
this.addTail(token, depth, text, maxDepth, lookup);
}
}
}
}
protected void addToken(Token token, @Nonnull String text) {
this.addToken(token, 0, text, text.length() - 1);
}
private void append(Token token, int depth, @Nonnull String text, int maxDepth, char ch) {
Lexer.CharacterSequenceMatcher<Token> lookup = new Lexer.CharacterSequenceMatcher<>(ch);
this.children.add(lookup);
this.addTail(token, depth, text, maxDepth, lookup);
}
private void addTail(Token token, int depth, @Nonnull String text, int maxDepth, @Nonnull Lexer.CharacterSequenceMatcher<Token> lookup) {
if (depth == maxDepth) {
lookup.token = token;
} else {
lookup.addToken(token, depth + 1, text, maxDepth);
}
}
@Nullable
protected Lexer.CharacterSequenceMatcher<Token> matchLetter(char ch) {
if (this.children != null) {
int index = 0;
for (int size = this.children.size(); index < size; index++) {
Lexer.CharacterSequenceMatcher<Token> characterSequenceMatcher = this.children.get(index);
char letter = characterSequenceMatcher.letter;
if (letter == ch) {
return characterSequenceMatcher;
}
if (letter > ch) {
return null;
}
}
}
return null;
}
}
}