147 lines
3.4 KiB
D
147 lines
3.4 KiB
D
module jlox.scanner;
|
|
|
|
import std.ascii;
|
|
import std.conv;
|
|
|
|
import jlox.token;
|
|
import jlox.tokentype;
|
|
import jlox.main;
|
|
import common.util;
|
|
|
|
class Scanner{
|
|
private string source;
|
|
private Token[] tokens;
|
|
private uint start = 0;
|
|
private uint current = 0;
|
|
private uint line = 1;
|
|
|
|
this(string source){
|
|
this.source = source;
|
|
}
|
|
private bool isAtEnd(uint n = 0) const => current + n >= source.length;
|
|
private char advance() => source[current++];
|
|
private char peek(uint n = 0) const => isAtEnd(n) ? '\0' : source[current + n];
|
|
private char peekNext() const => peek(1);
|
|
private bool match(char expected){
|
|
if(peek() != expected)
|
|
return false;
|
|
current++;
|
|
return true;
|
|
}
|
|
private void addToken(TokenType type, LoxValue literal = new LoxNil()){
|
|
string text = source[start .. current];
|
|
tokens ~= new Token(type, text, literal, line);
|
|
}
|
|
Token[] scanTokens(){
|
|
while(!isAtEnd){
|
|
start = current;
|
|
scanToken();
|
|
}
|
|
addToken(TokenType.EOF);
|
|
return tokens;
|
|
}
|
|
private void stringLiteral(){
|
|
while(peek() != '"' && !isAtEnd){
|
|
if(peek() == '\n')
|
|
line++;
|
|
advance();
|
|
}
|
|
if(isAtEnd){
|
|
Lox.error(line, "Unterminated string.");
|
|
return;
|
|
}
|
|
advance();
|
|
string value = source[start + 1 .. current -1];
|
|
addToken(TokenType.STRING, new LoxStr(value));
|
|
}
|
|
private void number(){
|
|
while(peek().isDigit)
|
|
advance();
|
|
if(peek() == '.' && peekNext().isDigit){
|
|
advance(); // Eat the .
|
|
while(peek().isDigit)
|
|
advance();
|
|
}
|
|
addToken(TokenType.NUMBER, new LoxNum(source[start .. current].to!double));
|
|
}
|
|
private TokenType keywords(string word){
|
|
with(TokenType) switch(word){
|
|
case "and": return AND;
|
|
case "class": return CLASS;
|
|
case "else": return ELSE;
|
|
case "false": return FALSE;
|
|
case "for": return FOR;
|
|
case "fun": return FUN;
|
|
case "if": return IF;
|
|
case "nil": return NIL;
|
|
case "or": return OR;
|
|
case "print": return PRINT;
|
|
case "return": return RETURN;
|
|
case "super": return SUPER;
|
|
case "this": return THIS;
|
|
case "true": return TRUE;
|
|
case "var": return VAR;
|
|
case "while": return WHILE;
|
|
default: return IDENTIFIER;
|
|
}
|
|
}
|
|
private void identifier(){
|
|
while(peek().isAlphaNum_)
|
|
advance();
|
|
addToken(keywords(source[start .. current]));
|
|
}
|
|
private void scanToken() {
|
|
char c = advance();
|
|
with(TokenType) switch(c){
|
|
case '(': addToken(LEFT_PAREN); break;
|
|
case ')': addToken(RIGHT_PAREN); break;
|
|
case '{': addToken(LEFT_BRACE); break;
|
|
case '}': addToken(RIGHT_BRACE); break;
|
|
case ',': addToken(COMMA); break;
|
|
case '.': addToken(DOT); break;
|
|
case '-': addToken(MINUS); break;
|
|
case '+': addToken(PLUS); break;
|
|
case ';': addToken(SEMICOLON); break;
|
|
case '*': addToken(STAR); break;
|
|
case '!':
|
|
addToken(match('=') ? BANG_EQUAL : BANG);
|
|
break;
|
|
case '=':
|
|
addToken(match('=') ? EQUAL_EQUAL : EQUAL);
|
|
break;
|
|
case '<':
|
|
addToken(match('=') ? LESS_EQUAL : LESS);
|
|
break;
|
|
case '>':
|
|
addToken(match('=') ? GREATER_EQUAL : GREATER);
|
|
break;
|
|
case '/':
|
|
if(match('/')){
|
|
while(peek() != '\n' && !isAtEnd)
|
|
advance();
|
|
} else {
|
|
addToken(SLASH);
|
|
}
|
|
break;
|
|
case ' ', '\r', '\t':
|
|
break;
|
|
case '\n':
|
|
line++;
|
|
break;
|
|
case '"':
|
|
stringLiteral();
|
|
break;
|
|
case '0': .. case '9':
|
|
number();
|
|
break;
|
|
case 'a': .. case 'z':
|
|
case 'A': .. case 'Z':
|
|
case '_':
|
|
identifier();
|
|
break;
|
|
default:
|
|
Lox.error(line, "Unexpected character");
|
|
}
|
|
}
|
|
}
|
|
|