Completed chapter 21, global variables
This commit is contained in:
parent
417637ef21
commit
928047f1db
8
chunk.c
8
chunk.c
@ -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;
|
||||
}
|
||||
|
9
chunk.h
9
chunk.h
@ -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);
|
||||
|
149
compiler.c
149
compiler.c
@ -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
35
debug.c
@ -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;
|
||||
|
20
kuroko.c
20
kuroko.c
@ -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;
|
||||
|
2
object.c
2
object.c
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -37,6 +37,7 @@ typedef enum {
|
||||
TOKEN_INDENTATION,
|
||||
|
||||
TOKEN_RETRY,
|
||||
TOKEN_EOL,
|
||||
|
||||
TOKEN_ERROR,
|
||||
TOKEN_EOF,
|
||||
|
69
vm.c
69
vm.c
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user