Finish up chapter 22, local variables; fix up indentation-based scoping
This commit is contained in:
parent
f5d3cd24e0
commit
aab01f01f7
4
chunk.h
4
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;
|
||||
|
||||
/**
|
||||
|
144
compiler.c
144
compiler.c
@ -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 = ¤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;
|
||||
|
9
debug.c
9
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;
|
||||
|
15
kuroko.c
15
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;
|
||||
}
|
||||
|
24
scanner.c
24
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);
|
||||
|
||||
|
@ -36,6 +36,7 @@ typedef enum {
|
||||
|
||||
TOKEN_INDENTATION,
|
||||
|
||||
TOKEN_BLOCK, /* temporary XXX remove me */
|
||||
TOKEN_RETRY,
|
||||
TOKEN_EOL,
|
||||
|
||||
|
16
test.krk
Normal file
16
test.krk
Normal 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
|
2
value.c
2
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);
|
||||
|
12
vm.c
12
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user