Initial commit
This commit is contained in:
commit
b2edca2cb4
25 changed files with 3590 additions and 0 deletions
274
scanner.c
Normal file
274
scanner.c
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "scanner.h"
|
||||
|
||||
typedef struct {
|
||||
const char* start;
|
||||
const char* current;
|
||||
int line;
|
||||
} Scanner;
|
||||
|
||||
Scanner scanner;
|
||||
|
||||
void initScanner(const char* source)
|
||||
{
|
||||
scanner.start = source;
|
||||
scanner.current = source;
|
||||
scanner.line = 1;
|
||||
}
|
||||
|
||||
static bool isAlpha(char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
||||
}
|
||||
|
||||
static bool isDigit(char c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
static bool isAtEnd()
|
||||
{
|
||||
return *scanner.current == '\0';
|
||||
}
|
||||
|
||||
static char advance()
|
||||
{
|
||||
scanner.current++;
|
||||
return scanner.current[-1];
|
||||
}
|
||||
|
||||
static char peek()
|
||||
{
|
||||
return *scanner.current;
|
||||
}
|
||||
|
||||
static char peekNext()
|
||||
{
|
||||
if (isAtEnd()) {
|
||||
return '\0';
|
||||
}
|
||||
return scanner.current[1];
|
||||
}
|
||||
|
||||
static bool match(char expected)
|
||||
{
|
||||
if (isAtEnd()) {
|
||||
return false;
|
||||
}
|
||||
if (*scanner.current != expected) {
|
||||
return false;
|
||||
}
|
||||
scanner.current++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static Token makeToken(TokenType type)
|
||||
{
|
||||
return (Token){
|
||||
.type = type,
|
||||
.start = scanner.start,
|
||||
.length = (int)(scanner.current - scanner.start),
|
||||
.line = scanner.line,
|
||||
};
|
||||
}
|
||||
|
||||
static Token errorToken(const char* message)
|
||||
{
|
||||
return (Token){
|
||||
.type = TOKEN_ERROR,
|
||||
.start = message,
|
||||
.length = (int)strlen(message),
|
||||
.line = scanner.line,
|
||||
};
|
||||
}
|
||||
|
||||
static void skipWhitespace()
|
||||
{
|
||||
for (;;) {
|
||||
auto c = peek();
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\r':
|
||||
case '\t':
|
||||
advance();
|
||||
break;
|
||||
case '\n':
|
||||
scanner.line++;
|
||||
advance();
|
||||
break;
|
||||
case '/':
|
||||
if (peekNext() == '/') {
|
||||
while (peek() != '\n' && !isAtEnd()) {
|
||||
advance();
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TokenType checkKeyword(int start, int length, const char* rest, TokenType type)
|
||||
{
|
||||
if (scanner.current - scanner.start == start + length && memcmp(scanner.start + start, rest, length) == 0) {
|
||||
return type;
|
||||
}
|
||||
return TOKEN_IDENTIFIER;
|
||||
}
|
||||
|
||||
static TokenType identifierType()
|
||||
{
|
||||
switch (scanner.start[0]) {
|
||||
case 'a':
|
||||
return checkKeyword(1, 2, "nd", TOKEN_AND);
|
||||
case 'c':
|
||||
return checkKeyword(1, 4, "lass", TOKEN_CLASS);
|
||||
case 'e':
|
||||
return checkKeyword(1, 3, "lse", TOKEN_ELSE);
|
||||
case 'f':
|
||||
if (scanner.current - scanner.start > 1) {
|
||||
switch (scanner.start[1]) {
|
||||
case 'a':
|
||||
return checkKeyword(2, 3, "lse", TOKEN_FALSE);
|
||||
case 'o':
|
||||
return checkKeyword(2, 1, "r", TOKEN_FOR);
|
||||
case 'u':
|
||||
return checkKeyword(2, 1, "n", TOKEN_FUN);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
return checkKeyword(1, 1, "f", TOKEN_IF);
|
||||
case 'n':
|
||||
return checkKeyword(1, 2, "il", TOKEN_NIL);
|
||||
case 'o':
|
||||
return checkKeyword(1, 1, "r", TOKEN_OR);
|
||||
case 'p':
|
||||
return checkKeyword(1, 4, "rint", TOKEN_PRINT);
|
||||
case 'r':
|
||||
return checkKeyword(1, 5, "eturn", TOKEN_RETURN);
|
||||
case 's':
|
||||
return checkKeyword(1, 4, "uper", TOKEN_SUPER);
|
||||
case 't':
|
||||
if (scanner.current - scanner.start > 1) {
|
||||
switch (scanner.start[1]) {
|
||||
case 'h':
|
||||
return checkKeyword(2, 2, "is", TOKEN_THIS);
|
||||
case 'r':
|
||||
return checkKeyword(2, 2, "ue", TOKEN_TRUE);
|
||||
}
|
||||
}
|
||||
case 'v':
|
||||
return checkKeyword(1, 2, "ar", TOKEN_VAR);
|
||||
case 'w':
|
||||
return checkKeyword(1, 4, "hile", TOKEN_WHILE);
|
||||
}
|
||||
|
||||
return TOKEN_IDENTIFIER;
|
||||
}
|
||||
|
||||
static Token identifier()
|
||||
{
|
||||
while (isAlpha(peek()) || isDigit(peek())) {
|
||||
advance();
|
||||
}
|
||||
return makeToken(identifierType());
|
||||
}
|
||||
|
||||
static Token number()
|
||||
{
|
||||
while (isDigit(peek())) {
|
||||
advance();
|
||||
}
|
||||
|
||||
if (peek() == '.' && isDigit(peekNext())) {
|
||||
advance();
|
||||
|
||||
while (isDigit(peek())) {
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
return makeToken(TOKEN_NUMBER);
|
||||
}
|
||||
|
||||
static Token string()
|
||||
{
|
||||
while (peek() != '"' && !isAtEnd()) {
|
||||
if (peek() == '\n') {
|
||||
scanner.line++;
|
||||
}
|
||||
advance();
|
||||
}
|
||||
|
||||
if (isAtEnd()) {
|
||||
return errorToken("Unterminated string.");
|
||||
}
|
||||
|
||||
advance();
|
||||
|
||||
return makeToken(TOKEN_STRING);
|
||||
}
|
||||
|
||||
Token scanToken()
|
||||
{
|
||||
skipWhitespace();
|
||||
|
||||
scanner.start = scanner.current;
|
||||
if (isAtEnd()) {
|
||||
return makeToken(TOKEN_EOF);
|
||||
}
|
||||
|
||||
auto c = advance();
|
||||
|
||||
if (isAlpha(c)) {
|
||||
return identifier();
|
||||
}
|
||||
if (isDigit(c)) {
|
||||
return number();
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case '(':
|
||||
return makeToken(TOKEN_LEFT_PAREN);
|
||||
case ')':
|
||||
return makeToken(TOKEN_RIGHT_PAREN);
|
||||
case '{':
|
||||
return makeToken(TOKEN_LEFT_BRACE);
|
||||
case '}':
|
||||
return makeToken(TOKEN_RIGHT_BRACE);
|
||||
case ';':
|
||||
return makeToken(TOKEN_SEMICOLON);
|
||||
case ',':
|
||||
return makeToken(TOKEN_COMMA);
|
||||
case '.':
|
||||
return makeToken(TOKEN_DOT);
|
||||
case '-':
|
||||
return makeToken(TOKEN_MINUS);
|
||||
case '+':
|
||||
return makeToken(TOKEN_PLUS);
|
||||
case '/':
|
||||
return makeToken(TOKEN_SLASH);
|
||||
case '*':
|
||||
return makeToken(TOKEN_STAR);
|
||||
case '!':
|
||||
return makeToken(match('=') ? TOKEN_BANG_EQUAL : TOKEN_BANG);
|
||||
case '=':
|
||||
return makeToken(match('=') ? TOKEN_EQUAL_EQUAL : TOKEN_EQUAL);
|
||||
case '<':
|
||||
return makeToken(match('=') ? TOKEN_LESS_EQUAL : TOKEN_LESS);
|
||||
case '>':
|
||||
return makeToken(match('=') ? TOKEN_GREATER_EQUAL : TOKEN_GREATER);
|
||||
case '"':
|
||||
return string();
|
||||
}
|
||||
|
||||
return errorToken("Unexpected character.");
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue