java-commons/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java

189 lines
5.2 KiB
Java
Raw Normal View History

package io.gitlab.jfronny.muscript.compiler;
// Heavily inspired by starscript
public class Lexer {
/** The type of the token */
public Token token;
/** The string representation of the token */
public String lexeme;
public int line = 1, character = -1;
public char ch;
private final String source;
private int start, current;
public Lexer(String source) {
this.source = source;
}
/** Scans for the next token storing it in {@link Lexer#token} and {@link Lexer#lexeme}. Produces {@link Token#EOF} if the end of source code has been reached and {@link Token#Error} if there has been an error */
public void next() {
start = current;
if (isAtEnd()) {
createToken(Token.EOF);
return;
}
// Scan expression
skipWhitespace();
if (isAtEnd()) {
createToken(Token.EOF);
return;
}
char c = advance();
if (isDigit(c) || (c == '-' && isDigit(peek()))) number();
else if (isAlpha(c)) identifier();
else {
switch (c) {
case '\'', '"' -> string();
case '=' -> {if (match('=')) createToken(Token.EqualEqual); else unexpected();}
case '!' -> createToken(match('=') ? Token.BangEqual : Token.Bang);
case '+' -> createToken(Token.Plus);
case '-' -> createToken(Token.Minus);
case '*' -> createToken(Token.Star);
case '/' -> createToken(Token.Slash);
case '%' -> createToken(Token.Percentage);
case '>' -> createToken(match('=') ? Token.GreaterEqual : Token.Greater);
case '<' -> createToken(match('=') ? Token.LessEqual : Token.Less);
case '&' -> createToken(Token.And);
case '|' -> createToken(match('|') ? Token.Concat : Token.Or);
case '^' -> createToken(Token.UpArrow);
case '.' -> createToken(Token.Dot);
case ',' -> createToken(Token.Comma);
case '?' -> createToken(Token.QuestionMark);
case ':' -> createToken(Token.Colon);
case '(' -> createToken(Token.LeftParen);
case ')' -> createToken(Token.RightParen);
case '[' -> createToken(Token.LeftBracket);
case ']' -> createToken(Token.RightBracket);
default -> unexpected();
}
}
}
private void string() {
while (!isAtEnd() && peek() != '"' && peek() != '\'') {
if (peek() == '\n') line++;
advance();
}
if (isAtEnd()) {
createToken(Token.Error, "Unterminated expression.");
}
else {
advance();
createToken(Token.String, source.substring(start + 1, current - 1));
}
}
private void number() {
while (isDigit(peek())) advance();
if (peek() == '.' && isDigit(peekNext())) {
advance();
while (isDigit(peek())) advance();
}
createToken(Token.Number);
}
private void identifier() {
while (!isAtEnd()) {
char c = peek();
if (isAlpha(c) || isDigit(c)) {
advance();
} else if (c == ':' && peekNext() == ':') {
advance();
advance();
} else break;
}
createToken(Token.Identifier);
switch (lexeme) {
case "null" -> token = Token.Null;
case "true" -> token = Token.True;
case "false" -> token = Token.False;
}
}
private void skipWhitespace() {
while (true) {
if (isAtEnd()) return;
char c = peek();
switch (c) {
case ' ', '\r', '\t' -> advance();
case '\n' -> {
line++;
advance();
}
default -> {
start = current;
return;
}
}
}
}
// Helpers
private void unexpected() {
createToken(Token.Error, "Unexpected character.");
}
private void createToken(Token token, String lexeme) {
this.token = token;
this.lexeme = lexeme;
}
private void createToken(Token token) {
createToken(token, source.substring(start, current));
}
private boolean match(char expected) {
if (isAtEnd()) return false;
if (source.charAt(current) != expected) return false;
advance();
return true;
}
private char advance() {
character++;
return ch = source.charAt(current++);
}
private char peek() {
if (isAtEnd()) return '\0';
return source.charAt(current);
}
private char peekNext() {
if (current + 1 >= source.length()) return '\0';
return source.charAt(current + 1);
}
private boolean isAtEnd() {
return current >= source.length();
}
private boolean isDigit(char c) {
return c >= '0' && c <= '9';
}
private boolean isAlpha(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || c == '$';
}
}