/// <summary> /// Skip through a block comment. /// </summary> private void SkipBlockComment() { // Stop when (Peek == * && PeekNext == /) ---> (Peek != * || PeekNext != /) // Keep moving through the comment until it reaches */ while ((Peek() != '*' || PeekNext() != '/') && IsAtEnd() == false) { // Allow multi-line block comments if (Peek() == '\n') { line++; } Advance(); } // Unterminated comment if (IsAtEnd()) { Min.Error(line, "Unterminated block comment."); return; } // Move past the closing comment */ Advance(); Advance(); }
/// <summary> /// Read a full string between quotes " and add it as a token. /// </summary> private void ReadString() { // Keep moving through the string until it reaches " while (Peek() != '"' && IsAtEnd() == false) { // Allow multi-line strings if (Peek() == '\n') { line++; } Advance(); } // Unterminated string if (IsAtEnd()) { Min.Error(line, "Unterminated string."); return; } // Move past the closing quote (") Advance(); // Trim the surrounding quotes string value = source.Substring(start + 1, current - 2 - start); // TODO: Support escape sequences (unescape them here) AddToken(TokenType.STRING, value); }
/// <summary> /// Report a Parsing Error at a specific token. /// </summary> /// <param name="token">The token that encountered an error.</param> /// <param name="message">The message of the error.</param> /// <returns>The Parsing Error created as a result.</returns> private ParseError Error(Token token, string message) { Min.Error(token, message); return(new ParseError()); }
/// <summary> /// Move through the characters to scan one token and add it to the list of tokens. /// </summary> private void ScanToken() { char c = Advance(); switch (c) { case '(': AddToken(TokenType.LEFT_PAREN); break; case ')': AddToken(TokenType.RIGHT_PAREN); break; case '{': AddToken(TokenType.LEFT_BRACE); break; case '}': AddToken(TokenType.RIGHT_BRACE); break; case ',': AddToken(TokenType.COMMA); break; case '.': AddToken(TokenType.DOT); break; case ';': AddToken(TokenType.SEMICOLON); break; case '+': AddToken(TokenType.PLUS); break; case '-': AddToken(TokenType.MINUS); break; case '*': AddToken(TokenType.STAR); break; case '?': AddToken(TokenType.QUESTION); break; case ':': AddToken(TokenType.COLON); break; case '!': AddToken(Match('=') ? TokenType.BANG_EQUAL : TokenType.BANG); break; case '=': AddToken(Match('=') ? TokenType.EQUAL_EQUAL : TokenType.EQUAL); break; case '<': AddToken(Match('=') ? TokenType.LESS_EQUAL : TokenType.LESS); break; case '>': AddToken(Match('=') ? TokenType.GREATER_EQUAL : TokenType.GREATER); break; case '&': if (Match('&')) { AddToken(TokenType.AND); } else { // TODO: Bitwise "&" does not exist yet. Min.Error(line, $"Unexpected character \"{c}\""); } break; case '|': if (Match('|')) { AddToken(TokenType.OR); } else { // TODO: Bitwise "|" does not exist yet. Min.Error(line, $"Unexpected character \"{c}\""); } break; case '/': // Comment // if (Match('/')) { // A comment goes until the end of the line, keep consuming characters until then. while (Peek() != '\n' && IsAtEnd() == false) { Advance(); } } // Block comment /* else if (Match('*')) { SkipBlockComment(); } // Any other slash / else { AddToken(TokenType.SLASH); } break; case '"': ReadString(); break; case ' ': case '\r': case '\t': // Ignore whitespace break; case '\n': line++; break; default: // Have to go through default because bools/methods don't work in switch/case if (IsDigit(c)) { ReadNumber(); } else if (IsAlpha(c)) { ReadIdentifier(); } else { Min.Error(line, $"Unexpected character \"{c}\""); } break; } }