Completed chapter 21, global variables

This commit is contained in:
K. Lange 2020-12-26 16:53:15 +09:00
parent 417637ef21
commit 928047f1db
10 changed files with 237 additions and 66 deletions

View File

@ -34,8 +34,7 @@ size_t krk_addConstant(KrkChunk * chunk, KrkValue value) {
return chunk->constants.count - 1;
}
size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) {
size_t ind = krk_addConstant(chunk, value);
void krk_emitConstant(KrkChunk * chunk, size_t ind) {
if (ind >= 256) {
krk_writeChunk(chunk, OP_CONSTANT_LONG, 1);
krk_writeChunk(chunk, 0xFF & (ind >> 16), 1);
@ -45,5 +44,10 @@ size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) {
krk_writeChunk(chunk, OP_CONSTANT, 1);
krk_writeChunk(chunk, ind, 1);
}
}
size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line) {
size_t ind = krk_addConstant(chunk, value);
krk_emitConstant(chunk, ind);
return ind;
}

View File

@ -23,9 +23,17 @@ typedef enum {
OP_TRUE,
OP_FALSE,
OP_NOT,
OP_POP,
OP_EQUAL,
OP_GREATER,
OP_LESS,
OP_PRINT,
OP_DEFINE_GLOBAL,
OP_DEFINE_GLOBAL_LONG,
OP_GET_GLOBAL,
OP_GET_GLOBAL_LONG,
OP_SET_GLOBAL,
OP_SET_GLOBAL_LONG,
} KrkOpCode;
/**
@ -43,4 +51,5 @@ extern void krk_initChunk(KrkChunk * chunk);
extern void krk_writeChunk(KrkChunk * chunk, uint8_t byte, size_t line);
extern void krk_freeChunk(KrkChunk * chunk);
extern size_t krk_addConstant(KrkChunk * chunk, KrkValue value);
extern void krk_emitConstant(KrkChunk * chunk, size_t ind);
extern size_t krk_writeConstant(KrkChunk * chunk, KrkValue value, size_t line);

View File

@ -27,7 +27,7 @@ typedef enum {
PREC_PRIMARY
} Precedence;
typedef void (*ParseFn)(void);
typedef void (*ParseFn)(int);
typedef struct {
ParseFn prefix;
@ -42,7 +42,13 @@ static KrkChunk * currentChunk() {
}
static void parsePrecedence(Precedence precedence);
static size_t parseVariable(const char * errorMessage);
static void defineVariable(size_t global);
static size_t identifierConstant(KrkToken * name);
static ParseRule * getRule(KrkTokenType type);
static void expression();
static void statement();
static void declaration();
static void errorAt(KrkToken * token, const char * message) {
if (parser.panicMode) return;
@ -88,6 +94,16 @@ static void consume(KrkTokenType type, const char * message) {
errorAtCurrent(message);
}
static int check(KrkTokenType type) {
return parser.current.type == type;
}
static int match(KrkTokenType type) {
if (!check(type)) return 0;
advance();
return 1;
}
static void emitByte(uint8_t byte) {
krk_writeChunk(currentChunk(), byte, parser.previous.line);
}
@ -105,11 +121,17 @@ static void endCompiler() {
emitReturn();
}
static void endOfLine() {
if (!(match(TOKEN_EOL) || match(TOKEN_EOF))) {
errorAtCurrent("Expected end of line.");
}
}
static void emitConstant(KrkValue value) {
krk_writeConstant(currentChunk(), value, parser.previous.line);
}
static void number() {
static void number(int canAssign) {
const char * start= parser.previous.start;
int base = 10;
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) {
@ -136,7 +158,7 @@ static void number() {
emitConstant(INTEGER_VAL(value));
}
static void binary() {
static void binary(int canAssign) {
KrkTokenType operatorType = parser.previous.type;
ParseRule * rule = getRule(operatorType);
parsePrecedence((Precedence)(rule->precedence + 1));
@ -157,7 +179,7 @@ static void binary() {
}
}
static void literal() {
static void literal(int canAssign) {
switch (parser.previous.type) {
case TOKEN_FALSE: emitByte(OP_FALSE); break;
case TOKEN_NONE: emitByte(OP_NONE); break;
@ -170,12 +192,77 @@ static void expression() {
parsePrecedence(PREC_ASSIGNMENT);
}
static void grouping() {
static void varDeclaration() {
ssize_t global = parseVariable("Expected variable name.");
if (match(TOKEN_EQUAL)) {
expression();
} else {
emitByte(OP_NONE);
}
endOfLine();
defineVariable(global);
}
static void printStatement() {
expression();
endOfLine();
emitByte(OP_PRINT);
}
static void synchronize() {
parser.panicMode = 0;
while (parser.current.type != TOKEN_EOF) {
if (parser.previous.type == TOKEN_EOL) return;
switch (parser.current.type) {
case TOKEN_CLASS:
case TOKEN_DEF:
case TOKEN_LET:
case TOKEN_FOR:
case TOKEN_IF:
case TOKEN_WHILE:
case TOKEN_PRINT:
case TOKEN_RETURN:
return;
default: break;
}
advance();
}
}
static void declaration() {
if (match(TOKEN_LET)) {
varDeclaration();
} else {
statement();
}
if (parser.panicMode) synchronize();
}
static void expressionStatement() {
expression();
endOfLine();
emitByte(OP_POP);
}
static void statement() {
if (match(TOKEN_PRINT)) {
printStatement();
} else {
expressionStatement();
}
}
static void grouping(int canAssign) {
expression();
consume(TOKEN_RIGHT_PAREN, "Expect ')' after expression.");
}
static void unary() {
static void unary(int canAssign) {
KrkTokenType operatorType = parser.previous.type;
parsePrecedence(PREC_UNARY);
@ -193,10 +280,28 @@ static void unary() {
}
}
static void string() {
static void string(int canAssign) {
emitConstant(OBJECT_VAL(copyString(parser.previous.start + 1, parser.previous.length - 2)));
}
#define EMIT_CONSTANT_OP(opc, arg) do { if (arg < 256) { emitBytes(opc, arg); } \
else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0)
static void namedVariable(KrkToken name, int canAssign) {
size_t arg = identifierConstant(&name);
if (canAssign && match(TOKEN_EQUAL)) {
expression();
EMIT_CONSTANT_OP(OP_SET_GLOBAL, arg);
} else {
EMIT_CONSTANT_OP(OP_GET_GLOBAL, arg);
}
}
static void variable(int canAssign) {
namedVariable(parser.previous, canAssign);
}
ParseRule rules[] = {
[TOKEN_LEFT_PAREN] = {grouping, NULL, PREC_NONE},
[TOKEN_RIGHT_PAREN] = {NULL, NULL, PREC_NONE},
@ -220,7 +325,7 @@ ParseRule rules[] = {
[TOKEN_GREATER_EQUAL] = {NULL, binary, PREC_COMPARISON},
[TOKEN_LESS] = {NULL, binary, PREC_COMPARISON},
[TOKEN_LESS_EQUAL] = {NULL, binary, PREC_COMPARISON},
[TOKEN_IDENTIFIER] = {NULL, NULL, PREC_NONE},
[TOKEN_IDENTIFIER] = {variable, NULL, PREC_NONE},
[TOKEN_STRING] = {string, NULL, PREC_NONE},
[TOKEN_NUMBER] = {number, NULL, PREC_NONE},
[TOKEN_CODEPOINT] = {NULL, NULL, PREC_NONE}, /* should be equivalent to number */
@ -256,12 +361,30 @@ static void parsePrecedence(Precedence precedence) {
error("expect expression");
return;
}
prefixRule();
int canAssign = precedence <= PREC_ASSIGNMENT;
prefixRule(canAssign);
while (precedence <= getRule(parser.current.type)->precedence) {
advance();
ParseFn infixRule = getRule(parser.previous.type)->infix;
infixRule();
infixRule(canAssign);
}
if (canAssign && match(TOKEN_EQUAL)) {
error("invalid assignment target");
}
}
static size_t identifierConstant(KrkToken * name) {
return krk_addConstant(currentChunk(), OBJECT_VAL(copyString(name->start, name->length)));
}
static size_t parseVariable(const char * errorMessage) {
consume(TOKEN_IDENTIFIER, errorMessage);
return identifierConstant(&parser.previous);
}
static void defineVariable(size_t global) {
EMIT_CONSTANT_OP(OP_DEFINE_GLOBAL, global);
}
static ParseRule * getRule(KrkTokenType type) {
@ -276,8 +399,10 @@ int krk_compile(const char * src, KrkChunk * chunk) {
parser.panicMode = 0;
advance();
expression();
consume(TOKEN_EOF, "Expected end of expression.");
while (!match(TOKEN_EOF)) {
declaration();
}
endCompiler();
return !parser.hadError;

35
debug.c
View File

@ -11,6 +11,17 @@ void krk_disassembleChunk(KrkChunk * chunk, const char * name) {
}
#define SIMPLE(opc) case opc: fprintf(stderr, "%s\n", #opc); return offset + 1;
#define CONSTANT(opc) case opc: { size_t constant = chunk->code[offset + 1]; \
fprintf(stderr, "%-16s %4d '", #opc, (int)constant); \
krk_printValue(stderr, chunk->constants.values[constant]); \
fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \
return offset + 2; } \
case opc ## _LONG: { size_t constant = (chunk->code[offset + 1] << 16) | \
(chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); \
fprintf(stderr, "%-16s %4d '", #opc "_LONG", (int)constant); \
krk_printValue(stderr, chunk->constants.values[constant]); \
fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \
return offset + 4; }
size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) {
fprintf(stderr, "%04u ", (unsigned int)offset);
@ -35,24 +46,12 @@ size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) {
SIMPLE(OP_EQUAL)
SIMPLE(OP_GREATER)
SIMPLE(OP_LESS)
case OP_CONSTANT:
{
uint8_t constant = chunk->code[offset + 1];
fprintf(stderr, "%-16s %4d '", "OP_CONSTANT", constant);
krk_printValue(stderr, chunk->constants.values[constant]);
fprintf(stderr, "' (type=%s)\n", typeName(chunk->constants.values[constant]));
}
return offset + 2;
case OP_CONSTANT_LONG:
{
size_t constant = (chunk->code[offset + 1] << 16) |
(chunk->code[offset + 2] << 8) |
(chunk->code[offset + 3] << 0);
fprintf(stderr, "%-16s %10d '", "OP_CONSTANT_LONG", (int)constant);
krk_printValue(stderr, chunk->constants.values[constant]);
fprintf(stderr, "' (type=%s)\n", typeName(chunk->constants.values[constant]));
}
return offset + 4;
SIMPLE(OP_PRINT)
SIMPLE(OP_POP)
CONSTANT(OP_DEFINE_GLOBAL)
CONSTANT(OP_CONSTANT)
CONSTANT(OP_GET_GLOBAL)
CONSTANT(OP_SET_GLOBAL)
default:
fprintf(stderr, "Unknown opcode: %02x\n", opcode);
return offset + 1;

View File

@ -8,25 +8,7 @@
int main(int argc, char * argv[]) {
krk_initVM();
krk_interpret("(\"hello\" + \"hellf\" + 1.4) == \"hellohellf1.4\"");
#if 0
KrkChunk chunk;
krk_initChunk(&chunk);
size_t constant = krk_addConstant(&chunk, 42);
krk_writeConstant(&chunk, 42, 1);
krk_writeConstant(&chunk, 86, 1);
krk_writeChunk(&chunk, OP_ADD, 1);
krk_writeConstant(&chunk, 3, 1);
krk_writeChunk(&chunk, OP_DIVIDE, 1);
krk_writeChunk(&chunk, OP_NEGATE, 1);
krk_writeChunk(&chunk, OP_RETURN, 1);
krk_interpret(&chunk);
krk_freeChunk(&chunk);
#endif
krk_interpret("let breakfast = \"beignets\"\nlet beverage = \"café au lait\"\nbreakfast = \"beignets with \" + beverage\nprint breakfast");
krk_freeVM();
return 0;

View File

@ -61,7 +61,7 @@ KrkString * copyString(const char * chars, size_t length) {
void krk_printObject(FILE * f, KrkValue value) {
switch (OBJECT_TYPE(value)) {
case OBJ_STRING:
fprintf(stderr, "%s", AS_CSTRING(value));
fprintf(f, "%s", AS_CSTRING(value));
break;
}
}

View File

@ -72,9 +72,6 @@ static void skipWhitespace() {
case '\t':
advance();
break;
case '\n':
scanner.line++;
scanner.startOfLine = 1;
default:
return;
}
@ -229,6 +226,12 @@ KrkToken krk_scanToken() {
char c = advance();
if (c == '\n') {
scanner.line++;
scanner.startOfLine = 1;
return makeToken(TOKEN_EOL);
}
if (isAlpha(c)) return identifier();
if (isDigit(c)) return number(c);

View File

@ -37,6 +37,7 @@ typedef enum {
TOKEN_INDENTATION,
TOKEN_RETRY,
TOKEN_EOL,
TOKEN_ERROR,
TOKEN_EOF,

69
vm.c
View File

@ -53,10 +53,12 @@ KrkValue krk_peep(int distance) {
void krk_initVM() {
resetStack();
vm.objects = NULL;
krk_initTable(&vm.globals);
krk_initTable(&vm.strings);
}
void krk_freeVM() {
krk_freeTable(&vm.globals);
krk_freeTable(&vm.strings);
krk_freeObjects();
FREE_ARRAY(size_t, vm.stack, vm.stackSize);
@ -158,9 +160,27 @@ static void addObjects() {
#define DEBUG
static KrkValue run() {
#define READ_BYTE() (*vm.ip++)
#define BINARY_OP(op) { KrkValue b = krk_pop(); KrkValue a = krk_pop(); krk_push(op(a,b)); break; }
#define READ_CONSTANT(s) (vm.chunk->constants.values[readBytes(s)])
#define READ_STRING(s) AS_STRING(READ_CONSTANT(s))
static size_t readBytes(int num) {
if (num == 1) return READ_BYTE();
else if (num == 2) {
unsigned int top = READ_BYTE();
unsigned int bot = READ_BYTE();
return (top << 8) | (bot);
} else if (num == 3) {
unsigned int top = READ_BYTE();
unsigned int mid = READ_BYTE();
unsigned int bot = READ_BYTE();
return (top << 16) | (mid << 8) | (bot);
}
return 0;
}
static KrkValue run() {
for (;;) {
#ifdef DEBUG
@ -175,6 +195,11 @@ static KrkValue run() {
#endif
uint8_t opcode;
switch ((opcode = READ_BYTE())) {
case OP_PRINT: {
krk_printValue(stdout, krk_pop());
fprintf(stdout, "\n");
break;
}
case OP_RETURN: {
krk_printValue(stdout, krk_pop());
fprintf(stdout, "\n");
@ -201,17 +226,9 @@ static KrkValue run() {
else { runtimeError("Incompatible operand type for prefix negation."); return NONE_VAL(); }
break;
}
case OP_CONSTANT_LONG:
case OP_CONSTANT: {
size_t index = READ_BYTE();
KrkValue constant = vm.chunk->constants.values[index];
krk_push(constant);
break;
}
case OP_CONSTANT_LONG: {
size_t top = READ_BYTE();
size_t mid = READ_BYTE();
size_t low = READ_BYTE();
size_t index = (top << 16) | (mid << 8) | (low);
size_t index = readBytes(opcode == OP_CONSTANT ? 1 : 3);
KrkValue constant = vm.chunk->constants.values[index];
krk_push(constant);
break;
@ -220,6 +237,36 @@ static KrkValue run() {
case OP_TRUE: krk_push(BOOLEAN_VAL(1)); break;
case OP_FALSE: krk_push(BOOLEAN_VAL(0)); break;
case OP_NOT: krk_push(BOOLEAN_VAL(isFalsey(krk_pop()))); break;
case OP_POP: krk_pop(); break;
case OP_DEFINE_GLOBAL_LONG:
case OP_DEFINE_GLOBAL: {
KrkString * name = READ_STRING((opcode == OP_DEFINE_GLOBAL ? 1 : 3));
krk_tableSet(&vm.globals, OBJECT_VAL(name), krk_peep(0));
krk_pop();
break;
}
case OP_GET_GLOBAL_LONG:
case OP_GET_GLOBAL: {
KrkString * name = READ_STRING((opcode == OP_GET_GLOBAL ? 1 : 3));
KrkValue value;
if (!krk_tableGet(&vm.globals, OBJECT_VAL(name), &value)) {
runtimeError("Undefined variable '%s'.", name->chars);
return NONE_VAL();
}
krk_push(value);
break;
}
case OP_SET_GLOBAL_LONG:
case OP_SET_GLOBAL: {
KrkString * name = READ_STRING((opcode == OP_SET_GLOBAL ? 1 : 3));
if (krk_tableSet(&vm.globals, OBJECT_VAL(name), krk_peep(0))) {
krk_tableDelete(&vm.globals, OBJECT_VAL(name));
/* TODO: This should probably just work as an assignment? */
runtimeError("Undefined variable '%s'.", name->chars);
return NONE_VAL();
}
break;
}
}
}

1
vm.h
View File

@ -11,6 +11,7 @@ typedef struct {
size_t stackSize;
KrkValue * stack;
KrkValue * stackTop;
KrkTable globals;
KrkTable strings;
KrkObj * objects;
} KrkVM;