module jlox.scanner; import std.ascii; import std.conv; import jlox.token; import jlox.tokentype; import jlox.main; private bool isAlpha_(dchar c) => c.isAlpha || c == '_'; private bool isAlphaNum_(dchar c) => c.isAlphaNum || c == '_'; 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, TValue literal = TValue.nil(tvalueNil)){ 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, TValue.str(value)); } private void number(){ while(peek().isDigit) advance(); if(peek() == '.' && peekNext().isDigit){ advance(); // Eat the . while(peek().isDigit) advance(); } addToken(TokenType.NUMBER, TValue.dbl(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"); } } }