165 lines
6.1 KiB
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;
|
|
}
|
|
}
|
|
}
|