gombaila/src/main/java/ch/fritteli/gombaila/domain/lexer/Lexer.java

105 lines
3.7 KiB
Java

package ch.fritteli.gombaila.domain.lexer;
import ch.fritteli.gombaila.domain.common.Token;
import ch.fritteli.gombaila.domain.common.TokenType;
import io.vavr.Predicates;
import io.vavr.collection.Stream;
import io.vavr.control.Option;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class Lexer {
@NotNull
private final StringWalker content;
@NotNull
private Stream<Token> tokens = Stream.empty();
public Lexer(@NotNull final String input) {
this.content = new StringWalker(input);
}
@NonNls
public Stream<Token> lex() {
Option<Character> next;
while ((next = this.content.peekNext()).isDefined()) {
final char c = next.get();
if (Character.isAlphabetic(c) || c == '_') {
this.handleAlphabeticOrUnderscore();
} else if (Character.isDigit(c)) {
this.handleDigit();
} else if (Character.isWhitespace(c)) {
this.handleWhitespace();
} else if (c == ';') {
this.handleSimple(TokenType.SEMI);
} else if (c == '(') {
this.handleSimple(TokenType.OPEN_PAREN);
} else if (c == ')') {
this.handleSimple(TokenType.CLOSE_PAREN);
} else if (c == '=') {
this.handleSimple(TokenType.EQUALS);
} else if (c == '+') {
this.handleSimple(TokenType.PLUS);
} else if (c == '-') {
this.handleSimple(TokenType.MINUS);
} else if (c == '*') {
this.handleSimple(TokenType.MULT);
} else if (c == '/') {
this.handleSimple(TokenType.DIV);
} else if (c == '%') {
this.handleSimple(TokenType.MOD);
} else if (c == '^') {
this.handleSimple(TokenType.EXP);
} else {
throw this.error(c);
}
}
return this.tokens;
}
private LexerException error(final char c) {
return new LexerException("Error parsing input: Unexpected character '%c'.".formatted(c), -1, -1);
}
private void handleAlphabeticOrUnderscore() {
final StringBuilder s = new StringBuilder();
while (this.content.peekNext().exists(Predicates.<Character>anyOf(
Character::isAlphabetic,
Character::isDigit,
c -> c == '_'
))) {
s.append(this.content.next());
}
switch (s.toString()) {
case "exit" -> this.appendToken(new Token(TokenType.EXIT));
case "let" -> this.appendToken(new Token(TokenType.LET));
case "print" -> this.appendToken(new Token(TokenType.PRINT));
case final String value -> this.appendToken(new Token(TokenType.IDENTIFIER, Option.of(value)));
}
}
private void handleDigit() {
final StringBuilder s = new StringBuilder();
while (this.content.peekNext().exists(Character::isDigit)) {
s.append(this.content.next());
}
this.appendToken(new Token(TokenType.INT_LIT, Option.of(Long.parseLong(s.toString()))));
}
private void handleWhitespace() {
final StringBuilder s = new StringBuilder();
while (this.content.peekNext().exists(Character::isWhitespace)) {
s.append(this.content.next());
}
this.appendToken(new Token(TokenType.WHITESPACE, Option.of(s.toString())));
}
private void handleSimple(@NotNull final TokenType tokenType) {
this.content.next();
this.appendToken(new Token(tokenType));
}
private void appendToken(@NotNull final Token token) {
this.tokens = this.tokens.append(token);
}
}