diff --git a/chunk.h b/chunk.h index 63bb62d..a4f768e 100644 --- a/chunk.h +++ b/chunk.h @@ -34,6 +34,10 @@ typedef enum { OP_GET_GLOBAL_LONG, OP_SET_GLOBAL, OP_SET_GLOBAL_LONG, + OP_SET_LOCAL, + OP_SET_LOCAL_LONG, + OP_GET_LOCAL, + OP_GET_LOCAL_LONG, } KrkOpCode; /** diff --git a/compiler.c b/compiler.c index 476a80c..a8dfcc4 100644 --- a/compiler.c +++ b/compiler.c @@ -1,5 +1,6 @@ #include #include +#include #include "kuroko.h" #include "compiler.h" @@ -35,16 +36,36 @@ typedef struct { Precedence precedence; } ParseRule; +typedef struct { + KrkToken name; + ssize_t depth; +} Local; + +typedef struct { + Local locals[256]; + size_t localCount; + size_t scopeDepth; +} Compiler; + Parser parser; +Compiler * current = NULL; + KrkChunk * compilingChunk; static KrkChunk * currentChunk() { return compilingChunk; } +static void initCompiler(Compiler * compiler) { + compiler->localCount = 0; + compiler->scopeDepth = 0; + current = compiler; +} + static void parsePrecedence(Precedence precedence); -static size_t parseVariable(const char * errorMessage); +static ssize_t parseVariable(const char * errorMessage); static void defineVariable(size_t global); -static size_t identifierConstant(KrkToken * name); +static ssize_t identifierConstant(KrkToken * name); +static ssize_t resolveLocal(Compiler * compiler, KrkToken * name); static ParseRule * getRule(KrkTokenType type); static void expression(); static void statement(); @@ -249,9 +270,49 @@ static void expressionStatement() { emitByte(OP_POP); } +static void block() { + consume(TOKEN_COLON, "Blocks must start with a colon; where's your colon?"); + + if (match(TOKEN_EOL)) { + /* Begin actual blocks */ + if (check(TOKEN_INDENTATION)) { + /* TODO: Check if this is correctly indented more than the current block */ + size_t currentIndentation = parser.current.length; + do { + if (parser.current.length != currentIndentation) break; + advance(); + declaration(); + } while (check(TOKEN_INDENTATION)); + } else { + errorAtCurrent("Expected indentation for block"); + } + } else { + errorAtCurrent("Unsupported single-line block"); + } +} + +static void beginScope() { + current->scopeDepth++; +} + +static void endScope() { + current->scopeDepth--; + while (current->localCount > 0 && + current->locals[current->localCount - 1].depth > current->scopeDepth) { + emitByte(OP_POP); + current->localCount--; + } +} + static void statement() { + if (match(TOKEN_EOL)) return; /* Meaningless blank line */ + if (match(TOKEN_PRINT)) { printStatement(); + } else if (match(TOKEN_BLOCK)) { + beginScope(); + block(); + endScope(); } else { expressionStatement(); } @@ -288,13 +349,22 @@ static void string(int canAssign) { 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); + ssize_t arg = resolveLocal(current, &name); + if (arg != -1) { + if (canAssign && match(TOKEN_EQUAL)) { + expression(); + EMIT_CONSTANT_OP(OP_SET_LOCAL, arg); + } else { + EMIT_CONSTANT_OP(OP_GET_LOCAL, arg); + } } else { - EMIT_CONSTANT_OP(OP_GET_GLOBAL, arg); + arg = identifierConstant(&name); + if (canAssign && match(TOKEN_EQUAL)) { + expression(); + EMIT_CONSTANT_OP(OP_SET_GLOBAL, arg); + } else { + EMIT_CONSTANT_OP(OP_GET_GLOBAL, arg); + } } } @@ -374,16 +444,68 @@ static void parsePrecedence(Precedence precedence) { } } -static size_t identifierConstant(KrkToken * name) { +static ssize_t identifierConstant(KrkToken * name) { return krk_addConstant(currentChunk(), OBJECT_VAL(copyString(name->start, name->length))); } -static size_t parseVariable(const char * errorMessage) { +static int identifiersEqual(KrkToken * a, KrkToken * b) { + return (a->length == b->length && memcmp(a->start, b->start, a->length) == 0); +} + +static ssize_t resolveLocal(Compiler * compiler, KrkToken * name) { + for (ssize_t i = compiler->localCount - 1; i >= 0; i--) { + Local * local = &compiler->locals[i]; + if (identifiersEqual(name, &local->name)) { + if (local->depth == -1) { + error("can not initialize value recursively (are you shadowing something?)"); + } + return i; + } + } + return -1; +} + +static void addLocal(KrkToken name) { + if (current->localCount == 256) { + error("too many locals"); + return; + } + Local * local = ¤t->locals[current->localCount++]; + local->name = name; + local->depth = -1; +} + +static void declareVariable() { + if (current->scopeDepth == 0) return; + KrkToken * name = &parser.previous; + /* Detect duplicate definition */ + for (ssize_t i = current->localCount - 1; i >= 0; i--) { + Local * local = ¤t->locals[i]; + if (local->depth != -1 && local->depth < current->scopeDepth) break; + if (identifiersEqual(name, &local->name)) error("Duplicate definition"); + } + addLocal(*name); +} + +static ssize_t parseVariable(const char * errorMessage) { consume(TOKEN_IDENTIFIER, errorMessage); + + declareVariable(); + if (current->scopeDepth > 0) return 0; + return identifierConstant(&parser.previous); } +static void markInitialized() { + current->locals[current->localCount - 1].depth = current->scopeDepth; +} + static void defineVariable(size_t global) { + if (current->scopeDepth > 0) { + markInitialized(); + return; + } + EMIT_CONSTANT_OP(OP_DEFINE_GLOBAL, global); } @@ -393,6 +515,8 @@ static ParseRule * getRule(KrkTokenType type) { int krk_compile(const char * src, KrkChunk * chunk) { krk_initScanner(src); + Compiler compiler; + initCompiler(&compiler); compilingChunk = chunk; parser.hadError = 0; diff --git a/debug.c b/debug.c index 21ed09c..c9b9420 100644 --- a/debug.c +++ b/debug.c @@ -22,6 +22,13 @@ void krk_disassembleChunk(KrkChunk * chunk, const char * name) { krk_printValue(stderr, chunk->constants.values[constant]); \ fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \ return offset + 4; } +#define OPERAND(opc) case opc: { uint32_t operand = chunk->code[offset + 1]; \ + fprintf(stderr, "%-16s %4d\n", #opc, (int)operand); \ + return offset + 2; } \ + case opc ## _LONG: { uint32_t operand = (chunk->code[offset + 1] << 16) | \ + (chunk->code[offset + 2] << 8) | (chunk->code[offset + 3]); \ + fprintf(stderr, "%-16s %4d\n", #opc "_LONG", (int)operand); \ + return offset + 4; } size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) { fprintf(stderr, "%04u ", (unsigned int)offset); @@ -52,6 +59,8 @@ size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) { CONSTANT(OP_CONSTANT) CONSTANT(OP_GET_GLOBAL) CONSTANT(OP_SET_GLOBAL) + OPERAND(OP_SET_LOCAL) + OPERAND(OP_GET_LOCAL) default: fprintf(stderr, "Unknown opcode: %02x\n", opcode); return offset + 1; diff --git a/kuroko.c b/kuroko.c index 01463b5..82c8106 100644 --- a/kuroko.c +++ b/kuroko.c @@ -8,8 +8,21 @@ int main(int argc, char * argv[]) { krk_initVM(); - krk_interpret("let breakfast = \"beignets\"\nlet beverage = \"cafĂ© au lait\"\nbreakfast = \"beignets with \" + beverage\nprint breakfast"); + FILE * f = fopen("test.krk","r"); + if (!f) return 1; + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + fseek(f, 0, SEEK_SET); + + char * buf = malloc(size+1); + fread(buf, 1, size, f); + fclose(f); + buf[size] = '\0'; + + krk_interpret(buf); krk_freeVM(); + + free(buf); return 0; } diff --git a/scanner.c b/scanner.c index a4bc401..d1a52c5 100644 --- a/scanner.c +++ b/scanner.c @@ -81,7 +81,13 @@ static void skipWhitespace() { static KrkToken makeIndentation() { while (!isAtEnd() && peek() == ' ') advance(); if (peek() == '\n') { - return errorToken("Empty indentation line is invalid."); + /* Pretend we didn't see this line */ + return makeToken(TOKEN_INDENTATION); + } + if (peek() == '#') { + KrkToken out = makeToken(TOKEN_INDENTATION); + while (!isAtEnd() && peek() != '\n') advance(); + return out; } return makeToken(TOKEN_INDENTATION); } @@ -175,6 +181,7 @@ static KrkTokenType identifierType() { switch (*scanner.start) { case 'a': return checkKeyword(1, "nd", TOKEN_AND); case 'c': return checkKeyword(1, "lass", TOKEN_CLASS); + case 'b': return checkKeyword(1, "lock", TOKEN_BLOCK); case 'd': return checkKeyword(1, "ef", TOKEN_DEF); case 'e': return checkKeyword(1, "lse", TOKEN_ELSE); case 'f': return checkKeyword(1, "or", TOKEN_FOR); @@ -210,9 +217,8 @@ KrkToken krk_scanToken() { /* If at start of line, do thing */ if (scanner.startOfLine && peek() == ' ') { scanner.start = scanner.cur; - return makeIndentation(); - } else { scanner.startOfLine = 0; + return makeIndentation(); } /* Eat whitespace */ @@ -228,10 +234,18 @@ KrkToken krk_scanToken() { if (c == '\n') { scanner.line++; - scanner.startOfLine = 1; - return makeToken(TOKEN_EOL); + if (scanner.startOfLine) { + /* Ignore completely blank lines */ + return makeToken(TOKEN_RETRY); + } else { + scanner.startOfLine = 1; + return makeToken(TOKEN_EOL); + } } + /* Not indentation, not a linefeed on an empty line, must be not be start of line any more */ + scanner.startOfLine = 0; + if (isAlpha(c)) return identifier(); if (isDigit(c)) return number(c); diff --git a/scanner.h b/scanner.h index 039e33e..9f5b647 100644 --- a/scanner.h +++ b/scanner.h @@ -36,6 +36,7 @@ typedef enum { TOKEN_INDENTATION, + TOKEN_BLOCK, /* temporary XXX remove me */ TOKEN_RETRY, TOKEN_EOL, diff --git a/test.krk b/test.krk new file mode 100644 index 0000000..c8f750a --- /dev/null +++ b/test.krk @@ -0,0 +1,16 @@ +print "Hello, world" +let a = "Hello," +let b = "world" +print a + " " + b +block: + let a = "This is a new variable" + print "I'm in a block" + # This should work + print a + + print b + + print "I am still in the block" + +print a +print b diff --git a/value.c b/value.c index 8fe9266..63bb674 100644 --- a/value.c +++ b/value.c @@ -10,7 +10,7 @@ void krk_initValueArray(KrkValueArray * array) { } void krk_writeValueArray(KrkValueArray * array, KrkValue value) { - if (array->capacity < array->count - 1) { + if (array->capacity < array->count + 1) { int old = array->capacity; array->capacity = GROW_CAPACITY(old); array->values = GROW_ARRAY(KrkValue, array->values, old, array->capacity); diff --git a/vm.c b/vm.c index 021ef38..e638d9e 100644 --- a/vm.c +++ b/vm.c @@ -267,6 +267,18 @@ static KrkValue run() { } break; } + case OP_GET_LOCAL_LONG: + case OP_GET_LOCAL: { + uint32_t slot = readBytes((opcode == OP_GET_LOCAL ? 1 : 3)); + krk_push(vm.stack[slot]); + break; + } + case OP_SET_LOCAL_LONG: + case OP_SET_LOCAL: { + uint32_t slot = readBytes((opcode == OP_SET_LOCAL ? 1 : 3)); + vm.stack[slot] = krk_peep(0); + break; + } } }