2013-10-22 21:16:39 +02:00
|
|
|
#include <stdio.h>
|
2013-10-22 22:25:39 +02:00
|
|
|
#include <stdlib.h>
|
2013-10-22 21:16:39 +02:00
|
|
|
#include <stdarg.h>
|
2013-10-22 22:25:39 +02:00
|
|
|
#include <string.h>
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
#include "compiler.h"
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
TOKEN_LEFT_PAREN,
|
|
|
|
|
TOKEN_RIGHT_PAREN,
|
|
|
|
|
TOKEN_LEFT_BRACKET,
|
|
|
|
|
TOKEN_RIGHT_BRACKET,
|
|
|
|
|
TOKEN_LEFT_BRACE,
|
|
|
|
|
TOKEN_RIGHT_BRACE,
|
|
|
|
|
TOKEN_COLON,
|
|
|
|
|
TOKEN_DOT,
|
|
|
|
|
TOKEN_COMMA,
|
|
|
|
|
TOKEN_STAR,
|
|
|
|
|
TOKEN_SLASH,
|
|
|
|
|
TOKEN_PERCENT,
|
|
|
|
|
TOKEN_PLUS,
|
|
|
|
|
TOKEN_MINUS,
|
|
|
|
|
TOKEN_PIPE,
|
|
|
|
|
TOKEN_AMP,
|
|
|
|
|
TOKEN_BANG,
|
|
|
|
|
TOKEN_EQ,
|
|
|
|
|
TOKEN_LT,
|
|
|
|
|
TOKEN_GT,
|
|
|
|
|
TOKEN_LTEQ,
|
|
|
|
|
TOKEN_GTEQ,
|
|
|
|
|
TOKEN_EQEQ,
|
|
|
|
|
TOKEN_BANGEQ,
|
|
|
|
|
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
TOKEN_CLASS,
|
|
|
|
|
TOKEN_META,
|
2013-10-22 22:25:39 +02:00
|
|
|
TOKEN_VAR,
|
|
|
|
|
|
|
|
|
|
TOKEN_NAME,
|
|
|
|
|
TOKEN_NUMBER,
|
|
|
|
|
TOKEN_STRING,
|
|
|
|
|
|
|
|
|
|
TOKEN_LINE,
|
|
|
|
|
|
|
|
|
|
TOKEN_ERROR,
|
|
|
|
|
TOKEN_EOF,
|
|
|
|
|
|
|
|
|
|
MAX_TOKEN
|
|
|
|
|
} TokenType;
|
|
|
|
|
|
|
|
|
|
typedef struct Token_s
|
|
|
|
|
{
|
|
|
|
|
TokenType type;
|
|
|
|
|
int start;
|
|
|
|
|
int end;
|
|
|
|
|
} Token;
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
typedef struct
|
|
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
VM* vm;
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
const char* source;
|
|
|
|
|
size_t sourceLength;
|
|
|
|
|
|
|
|
|
|
// The index in source of the beginning of the currently-being-lexed token.
|
|
|
|
|
int tokenStart;
|
|
|
|
|
|
|
|
|
|
// The position of the current character being lexed.
|
|
|
|
|
int currentChar;
|
|
|
|
|
|
|
|
|
|
// The most recently lexed token.
|
|
|
|
|
Token current;
|
|
|
|
|
|
|
|
|
|
// The most recently consumed/advanced token.
|
|
|
|
|
Token previous;
|
2013-10-22 21:16:39 +02:00
|
|
|
|
|
|
|
|
// The block being compiled.
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
ObjBlock* block;
|
2013-10-22 21:16:39 +02:00
|
|
|
int numCodes;
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
// Symbol table for declared local variables in the current block.
|
|
|
|
|
SymbolTable locals;
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
// Non-zero if a compile error has occurred.
|
|
|
|
|
int hasError;
|
|
|
|
|
} Compiler;
|
|
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
// Grammar:
|
|
|
|
|
static void statement(Compiler* compiler);
|
2013-10-22 21:16:39 +02:00
|
|
|
static void expression(Compiler* compiler);
|
2013-10-23 22:51:41 +02:00
|
|
|
static void call(Compiler* compiler);
|
|
|
|
|
static void primary(Compiler* compiler);
|
|
|
|
|
static void number(Compiler* compiler, Token* token);
|
2013-10-22 21:16:39 +02:00
|
|
|
static TokenType peek(Compiler* compiler);
|
2013-10-22 22:25:39 +02:00
|
|
|
static int match(Compiler* compiler, TokenType expected);
|
|
|
|
|
static void consume(Compiler* compiler, TokenType expected);
|
|
|
|
|
static void advance(Compiler* compiler);
|
|
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
// Tokens:
|
2013-10-22 22:25:39 +02:00
|
|
|
static void readNextToken(Compiler* compiler);
|
|
|
|
|
static void readName(Compiler* compiler);
|
|
|
|
|
static void readNumber(Compiler* compiler);
|
|
|
|
|
static void readString(Compiler* compiler);
|
|
|
|
|
static void skipWhitespace(Compiler* compiler);
|
|
|
|
|
static int isKeyword(Compiler* compiler, const char* keyword);
|
|
|
|
|
static int isName(char c);
|
|
|
|
|
static int isDigit(char c);
|
|
|
|
|
static char advanceChar(Compiler* compiler);
|
|
|
|
|
static char peekChar(Compiler* compiler);
|
|
|
|
|
static void makeToken(Compiler* compiler, TokenType type);
|
|
|
|
|
|
|
|
|
|
// Utility:
|
2013-10-24 00:32:59 +02:00
|
|
|
static void emit(Compiler* compiler, Code code);
|
2013-10-22 21:16:39 +02:00
|
|
|
static void error(Compiler* compiler, const char* format, ...);
|
|
|
|
|
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
ObjBlock* compile(VM* vm, const char* source, size_t sourceLength)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
|
|
|
|
Compiler compiler;
|
2013-10-23 22:51:41 +02:00
|
|
|
compiler.vm = vm;
|
2013-10-22 21:16:39 +02:00
|
|
|
compiler.source = source;
|
2013-10-22 22:25:39 +02:00
|
|
|
compiler.sourceLength = sourceLength;
|
2013-10-22 21:16:39 +02:00
|
|
|
compiler.hasError = 0;
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
compiler.tokenStart = 0;
|
|
|
|
|
compiler.currentChar = 0;
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
initSymbolTable(&compiler.locals);
|
|
|
|
|
|
|
|
|
|
// Zero-init the current token. This will get copied to previous when
|
|
|
|
|
// advance() is called below.
|
|
|
|
|
compiler.current.type = TOKEN_EOF;
|
|
|
|
|
compiler.current.start = 0;
|
|
|
|
|
compiler.current.end = 0;
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
// Read the first token.
|
|
|
|
|
advance(&compiler);
|
|
|
|
|
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
compiler.block = makeBlock();
|
2013-10-22 21:16:39 +02:00
|
|
|
// TODO(bob): Hack! make variable sized.
|
|
|
|
|
compiler.block->bytecode = malloc(sizeof(Code) * 1024);
|
|
|
|
|
|
|
|
|
|
// TODO(bob): Hack! make variable sized.
|
|
|
|
|
compiler.block->constants = malloc(sizeof(Value) * 256);
|
|
|
|
|
compiler.block->numConstants = 0;
|
|
|
|
|
|
|
|
|
|
compiler.numCodes = 0;
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
for (;;)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
statement(&compiler);
|
2013-10-22 21:16:39 +02:00
|
|
|
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
consume(&compiler, TOKEN_LINE);
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
if (match(&compiler, TOKEN_EOF)) break;
|
|
|
|
|
|
|
|
|
|
// Discard the result of the previous expression.
|
|
|
|
|
emit(&compiler, CODE_POP);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit(&compiler, CODE_END);
|
|
|
|
|
|
|
|
|
|
compiler.block->numLocals = compiler.locals.count;
|
2013-10-22 21:16:39 +02:00
|
|
|
|
|
|
|
|
return compiler.hasError ? NULL : compiler.block;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
void statement(Compiler* compiler)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
if (match(compiler, TOKEN_CLASS))
|
|
|
|
|
{
|
|
|
|
|
consume(compiler, TOKEN_NAME);
|
|
|
|
|
|
|
|
|
|
// TODO(bob): Copied from below. Unify.
|
|
|
|
|
int local = addSymbol(&compiler->locals,
|
|
|
|
|
compiler->source + compiler->previous.start,
|
|
|
|
|
compiler->previous.end - compiler->previous.start);
|
|
|
|
|
|
|
|
|
|
if (local == -1)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Local variable is already defined.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the empty class.
|
|
|
|
|
emit(compiler, CODE_CLASS);
|
|
|
|
|
|
|
|
|
|
// Store it in its name.
|
|
|
|
|
emit(compiler, CODE_STORE_LOCAL);
|
|
|
|
|
emit(compiler, local);
|
|
|
|
|
|
|
|
|
|
// Compile the method definitions.
|
|
|
|
|
consume(compiler, TOKEN_LEFT_BRACE);
|
|
|
|
|
|
|
|
|
|
// TODO(bob): Definitions!
|
|
|
|
|
|
|
|
|
|
consume(compiler, TOKEN_RIGHT_BRACE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
if (match(compiler, TOKEN_VAR))
|
|
|
|
|
{
|
2013-10-24 00:32:59 +02:00
|
|
|
consume(compiler, TOKEN_NAME);
|
|
|
|
|
int local = addSymbol(&compiler->locals,
|
|
|
|
|
compiler->source + compiler->previous.start,
|
|
|
|
|
compiler->previous.end - compiler->previous.start);
|
|
|
|
|
|
|
|
|
|
if (local == -1)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-24 00:32:59 +02:00
|
|
|
error(compiler, "Local variable is already defined.");
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
// TODO(bob): Allow uninitialized vars?
|
|
|
|
|
consume(compiler, TOKEN_EQ);
|
|
|
|
|
|
|
|
|
|
// Compile the initializer.
|
|
|
|
|
expression(compiler);
|
|
|
|
|
|
|
|
|
|
emit(compiler, CODE_STORE_LOCAL);
|
|
|
|
|
emit(compiler, local);
|
|
|
|
|
return;
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Statement expression.
|
|
|
|
|
expression(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
void expression(Compiler* compiler)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
call(compiler);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
// Method calls like:
|
|
|
|
|
//
|
|
|
|
|
// foo.bar
|
|
|
|
|
// foo.bar(arg, arg)
|
|
|
|
|
// foo.bar { block } other { block }
|
|
|
|
|
void call(Compiler* compiler)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
primary(compiler);
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
if (match(compiler, TOKEN_DOT))
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
consume(compiler, TOKEN_NAME);
|
2013-10-24 00:32:59 +02:00
|
|
|
int symbol = ensureSymbol(&compiler->vm->symbols,
|
|
|
|
|
compiler->source + compiler->previous.start,
|
|
|
|
|
compiler->previous.end - compiler->previous.start);
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
// Compile the method call.
|
2013-10-24 00:32:59 +02:00
|
|
|
emit(compiler, CODE_CALL);
|
|
|
|
|
emit(compiler, symbol);
|
2013-10-23 22:51:41 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void primary(Compiler* compiler)
|
|
|
|
|
{
|
2013-10-24 00:32:59 +02:00
|
|
|
if (match(compiler, TOKEN_NAME))
|
|
|
|
|
{
|
|
|
|
|
int local = findSymbol(&compiler->locals,
|
|
|
|
|
compiler->source + compiler->previous.start,
|
|
|
|
|
compiler->previous.end - compiler->previous.start);
|
|
|
|
|
if (local == -1)
|
|
|
|
|
{
|
|
|
|
|
// TODO(bob): Look for globals or names in outer scopes.
|
|
|
|
|
error(compiler, "Unknown variable.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emit(compiler, CODE_LOAD_LOCAL);
|
|
|
|
|
emit(compiler, local);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
if (match(compiler, TOKEN_NUMBER))
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-23 22:51:41 +02:00
|
|
|
number(compiler, &compiler->previous);
|
2013-10-24 00:32:59 +02:00
|
|
|
return;
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
void number(Compiler* compiler, Token* token)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-22 22:37:53 +02:00
|
|
|
char* end;
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
// TODO(bob): Parse actual double!
|
2013-10-22 22:37:53 +02:00
|
|
|
long value = strtol(compiler->source + token->start, &end, 10);
|
|
|
|
|
// TODO(bob): Check errno == ERANGE here.
|
|
|
|
|
if (end == compiler->source + token->start)
|
|
|
|
|
{
|
|
|
|
|
error(compiler, "Invalid number literal.");
|
|
|
|
|
value = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-22 21:16:39 +02:00
|
|
|
// Define a constant for the literal.
|
|
|
|
|
// TODO(bob): See if constant with same value already exists.
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
Value constant = (Value)makeNum((double)value);
|
2013-10-22 21:16:39 +02:00
|
|
|
|
|
|
|
|
compiler->block->constants[compiler->block->numConstants++] = constant;
|
|
|
|
|
|
|
|
|
|
// Compile the code to load the constant.
|
2013-10-24 00:32:59 +02:00
|
|
|
emit(compiler, CODE_CONSTANT);
|
|
|
|
|
emit(compiler, compiler->block->numConstants - 1);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
TokenType peek(Compiler* compiler)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-22 22:25:39 +02:00
|
|
|
return compiler->current.type;
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
// TODO(bob): Make a bool type?
|
|
|
|
|
int match(Compiler* compiler, TokenType expected)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-22 22:25:39 +02:00
|
|
|
if (peek(compiler) != expected) return 0;
|
2013-10-22 21:16:39 +02:00
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
advance(compiler);
|
|
|
|
|
return 1;
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
void consume(Compiler* compiler, TokenType expected)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
2013-10-22 22:25:39 +02:00
|
|
|
advance(compiler);
|
|
|
|
|
if (compiler->previous.type != expected)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
|
|
|
|
// TODO(bob): Better error.
|
2013-10-22 22:25:39 +02:00
|
|
|
error(compiler, "Expected %d, got %d.\n", expected, compiler->previous.type);
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
void advance(Compiler* compiler)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
|
|
|
|
// TODO(bob): Check for EOF.
|
2013-10-22 22:25:39 +02:00
|
|
|
compiler->previous = compiler->current;
|
|
|
|
|
readNextToken(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void readNextToken(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
while (peekChar(compiler) != '\0')
|
|
|
|
|
{
|
|
|
|
|
compiler->tokenStart = compiler->currentChar;
|
|
|
|
|
|
|
|
|
|
char c = advanceChar(compiler);
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case '(': makeToken(compiler, TOKEN_LEFT_PAREN); return;
|
|
|
|
|
case ')': makeToken(compiler, TOKEN_RIGHT_PAREN); return;
|
|
|
|
|
case '[': makeToken(compiler, TOKEN_LEFT_BRACKET); return;
|
|
|
|
|
case ']': makeToken(compiler, TOKEN_RIGHT_BRACKET); return;
|
|
|
|
|
case '{': makeToken(compiler, TOKEN_LEFT_BRACE); return;
|
|
|
|
|
case '}': makeToken(compiler, TOKEN_RIGHT_BRACE); return;
|
|
|
|
|
case ':': makeToken(compiler, TOKEN_COLON); return;
|
|
|
|
|
case '.': makeToken(compiler, TOKEN_DOT); return;
|
|
|
|
|
case ',': makeToken(compiler, TOKEN_COMMA); return;
|
|
|
|
|
case '*': makeToken(compiler, TOKEN_STAR); return;
|
|
|
|
|
case '/': makeToken(compiler, TOKEN_SLASH); return;
|
|
|
|
|
case '%': makeToken(compiler, TOKEN_PERCENT); return;
|
|
|
|
|
case '+': makeToken(compiler, TOKEN_PLUS); return;
|
2013-10-24 00:32:59 +02:00
|
|
|
case '-':
|
|
|
|
|
if (isDigit(peekChar(compiler)))
|
|
|
|
|
{
|
|
|
|
|
readNumber(compiler);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(compiler, TOKEN_MINUS);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
case '|': makeToken(compiler, TOKEN_PIPE); return;
|
|
|
|
|
case '&': makeToken(compiler, TOKEN_AMP); return;
|
|
|
|
|
case '=':
|
|
|
|
|
if (peekChar(compiler) == '=')
|
|
|
|
|
{
|
|
|
|
|
advanceChar(compiler);
|
|
|
|
|
makeToken(compiler, TOKEN_EQEQ);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(compiler, TOKEN_EQ);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case '<':
|
|
|
|
|
if (peekChar(compiler) == '=')
|
|
|
|
|
{
|
|
|
|
|
advanceChar(compiler);
|
|
|
|
|
makeToken(compiler, TOKEN_LTEQ);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(compiler, TOKEN_LT);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case '>':
|
|
|
|
|
if (peekChar(compiler) == '=')
|
|
|
|
|
{
|
|
|
|
|
advanceChar(compiler);
|
|
|
|
|
makeToken(compiler, TOKEN_GTEQ);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(compiler, TOKEN_GT);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case '!':
|
|
|
|
|
if (peekChar(compiler) == '=')
|
|
|
|
|
{
|
|
|
|
|
advanceChar(compiler);
|
|
|
|
|
makeToken(compiler, TOKEN_BANGEQ);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(compiler, TOKEN_BANG);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
case '\n': makeToken(compiler, TOKEN_LINE); return;
|
|
|
|
|
|
|
|
|
|
case ' ': skipWhitespace(compiler); break;
|
|
|
|
|
case '"': readString(compiler); return;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
if (isName(c))
|
|
|
|
|
{
|
|
|
|
|
readName(compiler);
|
|
|
|
|
}
|
|
|
|
|
else if (isDigit(c))
|
|
|
|
|
{
|
|
|
|
|
readNumber(compiler);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
makeToken(compiler, TOKEN_ERROR);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we get here, we're out of source, so just make EOF tokens.
|
|
|
|
|
compiler->tokenStart = compiler->currentChar;
|
|
|
|
|
makeToken(compiler, TOKEN_EOF);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void readName(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// TODO(bob): Handle digits and EOF.
|
|
|
|
|
while (isName(peekChar(compiler)) || isDigit(peekChar(compiler))) advanceChar(compiler);
|
|
|
|
|
|
|
|
|
|
TokenType type = TOKEN_NAME;
|
|
|
|
|
|
feat: add class parsing and object type system with ObjBlock, ObjClass, and ObjNum structs
Introduce token types TOKEN_CLASS and TOKEN_META for class declaration parsing in the compiler. Refactor the internal object model from a single Obj union to distinct ObjNum, ObjBlock, and ObjClass structs, replacing the old Block type with ObjBlock throughout the compiler, VM, and main entry point. Implement makeClass() and makeBlock() factory functions, update makeNum() to return ObjNum*, and add CODE_CLASS and CODE_DUP bytecodes to support class definition at compile time. Modify the symbol table and method dispatch to use a Method union (primitive or block) stored in ObjClass, and update the VM's callBlock signature and newVM initialization accordingly.
2013-10-24 07:50:04 +02:00
|
|
|
if (isKeyword(compiler, "class")) type = TOKEN_CLASS;
|
|
|
|
|
if (isKeyword(compiler, "meta")) type = TOKEN_META;
|
2013-10-22 22:38:57 +02:00
|
|
|
if (isKeyword(compiler, "var")) type = TOKEN_VAR;
|
2013-10-22 22:25:39 +02:00
|
|
|
|
|
|
|
|
makeToken(compiler, type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int isKeyword(Compiler* compiler, const char* keyword)
|
|
|
|
|
{
|
|
|
|
|
size_t length = compiler->currentChar - compiler->tokenStart;
|
|
|
|
|
size_t keywordLength = strlen(keyword);
|
|
|
|
|
return length == keywordLength &&
|
|
|
|
|
strncmp(compiler->source + compiler->tokenStart, keyword, length) == 0;
|
2013-10-22 21:16:39 +02:00
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
void readNumber(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// TODO(bob): Floating point, hex, scientific, etc.
|
|
|
|
|
while (isDigit(peekChar(compiler))) advanceChar(compiler);
|
|
|
|
|
|
|
|
|
|
makeToken(compiler, TOKEN_NUMBER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void readString(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
// TODO(bob): Escape sequences, EOL, EOF, etc.
|
|
|
|
|
while (advanceChar(compiler) != '"');
|
|
|
|
|
|
|
|
|
|
makeToken(compiler, TOKEN_STRING);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void skipWhitespace(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
while (peekChar(compiler) == ' ') advanceChar(compiler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int isName(char c)
|
|
|
|
|
{
|
|
|
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int isDigit(char c)
|
|
|
|
|
{
|
|
|
|
|
return c >= '0' && c <= '9';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char advanceChar(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
char c = peekChar(compiler);
|
|
|
|
|
compiler->currentChar++;
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char peekChar(Compiler* compiler)
|
|
|
|
|
{
|
|
|
|
|
return compiler->source[compiler->currentChar];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void makeToken(Compiler* compiler, TokenType type)
|
|
|
|
|
{
|
|
|
|
|
compiler->current.type = type;
|
|
|
|
|
compiler->current.start = compiler->tokenStart;
|
|
|
|
|
compiler->current.end = compiler->currentChar;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-24 00:32:59 +02:00
|
|
|
void emit(Compiler* compiler, Code code)
|
|
|
|
|
{
|
|
|
|
|
compiler->block->bytecode[compiler->numCodes++] = code;
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-22 22:25:39 +02:00
|
|
|
void error(Compiler* compiler, const char* format, ...)
|
2013-10-22 21:16:39 +02:00
|
|
|
{
|
|
|
|
|
compiler->hasError = 1;
|
|
|
|
|
printf("Compile error on '");
|
2013-10-22 22:25:39 +02:00
|
|
|
|
2013-10-23 22:51:41 +02:00
|
|
|
for (int i = compiler->previous.start; i < compiler->previous.end; i++)
|
2013-10-22 22:25:39 +02:00
|
|
|
{
|
|
|
|
|
putchar(compiler->source[i]);
|
|
|
|
|
}
|
2013-10-22 21:16:39 +02:00
|
|
|
|
|
|
|
|
printf("': ");
|
|
|
|
|
|
|
|
|
|
va_list args;
|
|
|
|
|
va_start(args, format);
|
|
|
|
|
vprintf(format, args);
|
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
|
|
printf("\n");
|
|
|
|
|
}
|