136 lines
3.9 KiB
C#
136 lines
3.9 KiB
C#
namespace Amogulator.Suslang;
|
|
|
|
public class Lexer {
|
|
private int i = 0;
|
|
private string text;
|
|
private int line = 1;
|
|
private int col = 1;
|
|
|
|
public Lexer(string text) {
|
|
this.text = text;
|
|
}
|
|
|
|
public Token next() {
|
|
if (done()) {
|
|
return token(TokenType.Eof, position());
|
|
} else if (" \t\r\n".Contains(current())) {
|
|
while (!done() && " \t\r\n".Contains(current()))
|
|
step();
|
|
return next();
|
|
} else if ("123456789".Contains(current())) {
|
|
return intToken();
|
|
} else if ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".Contains(current())) {
|
|
return idToken();
|
|
} else {
|
|
return staticToken();
|
|
}
|
|
}
|
|
|
|
private Token intToken() {
|
|
var start = position();
|
|
step();
|
|
while (!done() && "1234567890".Contains(current()))
|
|
step();
|
|
return token(TokenType.Int, start);
|
|
}
|
|
|
|
private Token idToken() {
|
|
var start = position();
|
|
step();
|
|
while (!done() && "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_".Contains(current()))
|
|
step();
|
|
return this.text.Substring(start.index, this.i - start.index) switch {
|
|
"false" => token(TokenType.False, start),
|
|
"true" => token(TokenType.True, start),
|
|
_ => token(TokenType.Id, start),
|
|
};
|
|
}
|
|
|
|
private Token staticToken() {
|
|
var start = position();
|
|
switch (current()) {
|
|
case '0':
|
|
return zeroToken();
|
|
case '.':
|
|
return dotToken();
|
|
case '(':
|
|
step();
|
|
return token(TokenType.LParen, start);
|
|
case ')':
|
|
step();
|
|
return token(TokenType.RParen, start);
|
|
case '+':
|
|
step();
|
|
return token(TokenType.Plus, start);
|
|
case '-':
|
|
step();
|
|
return token(TokenType.Minus, start);
|
|
case '*':
|
|
step();
|
|
return token(TokenType.Asterisk, start);
|
|
case '/':
|
|
step();
|
|
return token(TokenType.Slash, start);
|
|
case '%':
|
|
step();
|
|
return token(TokenType.Percent, start);
|
|
default:
|
|
step();
|
|
return token(TokenType.Invalid, start);
|
|
}
|
|
}
|
|
|
|
private Token zeroToken() {
|
|
var start = position();
|
|
step();
|
|
if (!done() && (current() == 'x' || current() == 'X')) {
|
|
while (!done() && "1234567890abcdefABCDEF".Contains(current()))
|
|
step();
|
|
return token(TokenType.Hex, start);
|
|
} else {
|
|
while (!done() && "1234567890".Contains(current()))
|
|
step();
|
|
return token(TokenType.Int, start);
|
|
}
|
|
}
|
|
|
|
private Token dotToken() {
|
|
var start = position();
|
|
step();
|
|
if (!done() && "1234567890".Contains(current())) {
|
|
while (!done() && "1234567890".Contains(current()))
|
|
step();
|
|
return token(TokenType.Decimal, start);
|
|
} else {
|
|
return token(TokenType.Dot, start);
|
|
}
|
|
}
|
|
|
|
private Token token(TokenType type, Position start) => new Token() {
|
|
type = type,
|
|
position = start,
|
|
length = this.i - start.index,
|
|
};
|
|
|
|
private Position position() => new Position() {
|
|
index = this.i,
|
|
line = this.line,
|
|
col = this.col,
|
|
};
|
|
|
|
private char current() => this.text[this.i];
|
|
private bool done() => this.i >= this.text.Length;
|
|
|
|
private void step() {
|
|
this.i += 1;
|
|
if (!done()) {
|
|
if (current() == '\n') {
|
|
this.line += 1;
|
|
this.col = 1;
|
|
} else {
|
|
this.col += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|