Finish up chapter 22, local variables; fix up indentation-based scoping

This commit is contained in:
K. Lange 2020-12-26 18:39:29 +09:00
parent f5d3cd24e0
commit aab01f01f7
9 changed files with 210 additions and 17 deletions

View File

@ -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;
/**

View File

@ -1,5 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 = &current->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 = &current->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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -36,6 +36,7 @@ typedef enum {
TOKEN_INDENTATION,
TOKEN_BLOCK, /* temporary XXX remove me */
TOKEN_RETRY,
TOKEN_EOL,

16
test.krk Normal file
View File

@ -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

View File

@ -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);

12
vm.c
View File

@ -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;
}
}
}