105 lines
3.7 KiB
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);
|
|
}
|
|
}
|