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_GET_GLOBAL_LONG,
|
||||||
OP_SET_GLOBAL,
|
OP_SET_GLOBAL,
|
||||||
OP_SET_GLOBAL_LONG,
|
OP_SET_GLOBAL_LONG,
|
||||||
|
OP_SET_LOCAL,
|
||||||
|
OP_SET_LOCAL_LONG,
|
||||||
|
OP_GET_LOCAL,
|
||||||
|
OP_GET_LOCAL_LONG,
|
||||||
} KrkOpCode;
|
} KrkOpCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
144
compiler.c
144
compiler.c
@ -1,5 +1,6 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#include "kuroko.h"
|
#include "kuroko.h"
|
||||||
#include "compiler.h"
|
#include "compiler.h"
|
||||||
@ -35,16 +36,36 @@ typedef struct {
|
|||||||
Precedence precedence;
|
Precedence precedence;
|
||||||
} ParseRule;
|
} ParseRule;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
KrkToken name;
|
||||||
|
ssize_t depth;
|
||||||
|
} Local;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Local locals[256];
|
||||||
|
size_t localCount;
|
||||||
|
size_t scopeDepth;
|
||||||
|
} Compiler;
|
||||||
|
|
||||||
Parser parser;
|
Parser parser;
|
||||||
|
Compiler * current = NULL;
|
||||||
|
|
||||||
KrkChunk * compilingChunk;
|
KrkChunk * compilingChunk;
|
||||||
static KrkChunk * currentChunk() {
|
static KrkChunk * currentChunk() {
|
||||||
return compilingChunk;
|
return compilingChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void initCompiler(Compiler * compiler) {
|
||||||
|
compiler->localCount = 0;
|
||||||
|
compiler->scopeDepth = 0;
|
||||||
|
current = compiler;
|
||||||
|
}
|
||||||
|
|
||||||
static void parsePrecedence(Precedence precedence);
|
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 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 ParseRule * getRule(KrkTokenType type);
|
||||||
static void expression();
|
static void expression();
|
||||||
static void statement();
|
static void statement();
|
||||||
@ -249,9 +270,49 @@ static void expressionStatement() {
|
|||||||
emitByte(OP_POP);
|
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() {
|
static void statement() {
|
||||||
|
if (match(TOKEN_EOL)) return; /* Meaningless blank line */
|
||||||
|
|
||||||
if (match(TOKEN_PRINT)) {
|
if (match(TOKEN_PRINT)) {
|
||||||
printStatement();
|
printStatement();
|
||||||
|
} else if (match(TOKEN_BLOCK)) {
|
||||||
|
beginScope();
|
||||||
|
block();
|
||||||
|
endScope();
|
||||||
} else {
|
} else {
|
||||||
expressionStatement();
|
expressionStatement();
|
||||||
}
|
}
|
||||||
@ -288,13 +349,22 @@ static void string(int canAssign) {
|
|||||||
else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0)
|
else { emitBytes(opc ## _LONG, arg >> 16); emitBytes(arg >> 8, arg); } } while (0)
|
||||||
|
|
||||||
static void namedVariable(KrkToken name, int canAssign) {
|
static void namedVariable(KrkToken name, int canAssign) {
|
||||||
size_t arg = identifierConstant(&name);
|
ssize_t arg = resolveLocal(current, &name);
|
||||||
|
if (arg != -1) {
|
||||||
if (canAssign && match(TOKEN_EQUAL)) {
|
if (canAssign && match(TOKEN_EQUAL)) {
|
||||||
expression();
|
expression();
|
||||||
EMIT_CONSTANT_OP(OP_SET_GLOBAL, arg);
|
EMIT_CONSTANT_OP(OP_SET_LOCAL, arg);
|
||||||
|
} else {
|
||||||
|
EMIT_CONSTANT_OP(OP_GET_LOCAL, arg);
|
||||||
|
}
|
||||||
} else {
|
} 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)));
|
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);
|
consume(TOKEN_IDENTIFIER, errorMessage);
|
||||||
|
|
||||||
|
declareVariable();
|
||||||
|
if (current->scopeDepth > 0) return 0;
|
||||||
|
|
||||||
return identifierConstant(&parser.previous);
|
return identifierConstant(&parser.previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void markInitialized() {
|
||||||
|
current->locals[current->localCount - 1].depth = current->scopeDepth;
|
||||||
|
}
|
||||||
|
|
||||||
static void defineVariable(size_t global) {
|
static void defineVariable(size_t global) {
|
||||||
|
if (current->scopeDepth > 0) {
|
||||||
|
markInitialized();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EMIT_CONSTANT_OP(OP_DEFINE_GLOBAL, global);
|
EMIT_CONSTANT_OP(OP_DEFINE_GLOBAL, global);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,6 +515,8 @@ static ParseRule * getRule(KrkTokenType type) {
|
|||||||
|
|
||||||
int krk_compile(const char * src, KrkChunk * chunk) {
|
int krk_compile(const char * src, KrkChunk * chunk) {
|
||||||
krk_initScanner(src);
|
krk_initScanner(src);
|
||||||
|
Compiler compiler;
|
||||||
|
initCompiler(&compiler);
|
||||||
compilingChunk = chunk;
|
compilingChunk = chunk;
|
||||||
|
|
||||||
parser.hadError = 0;
|
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]); \
|
krk_printValue(stderr, chunk->constants.values[constant]); \
|
||||||
fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \
|
fprintf(stderr,"' (type=%s)\n", typeName(chunk->constants.values[constant])); \
|
||||||
return offset + 4; }
|
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) {
|
size_t krk_disassembleInstruction(KrkChunk * chunk, size_t offset) {
|
||||||
fprintf(stderr, "%04u ", (unsigned int)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_CONSTANT)
|
||||||
CONSTANT(OP_GET_GLOBAL)
|
CONSTANT(OP_GET_GLOBAL)
|
||||||
CONSTANT(OP_SET_GLOBAL)
|
CONSTANT(OP_SET_GLOBAL)
|
||||||
|
OPERAND(OP_SET_LOCAL)
|
||||||
|
OPERAND(OP_GET_LOCAL)
|
||||||
default:
|
default:
|
||||||
fprintf(stderr, "Unknown opcode: %02x\n", opcode);
|
fprintf(stderr, "Unknown opcode: %02x\n", opcode);
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
|
15
kuroko.c
15
kuroko.c
@ -8,8 +8,21 @@
|
|||||||
int main(int argc, char * argv[]) {
|
int main(int argc, char * argv[]) {
|
||||||
krk_initVM();
|
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();
|
krk_freeVM();
|
||||||
|
|
||||||
|
free(buf);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
24
scanner.c
24
scanner.c
@ -81,7 +81,13 @@ static void skipWhitespace() {
|
|||||||
static KrkToken makeIndentation() {
|
static KrkToken makeIndentation() {
|
||||||
while (!isAtEnd() && peek() == ' ') advance();
|
while (!isAtEnd() && peek() == ' ') advance();
|
||||||
if (peek() == '\n') {
|
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);
|
return makeToken(TOKEN_INDENTATION);
|
||||||
}
|
}
|
||||||
@ -175,6 +181,7 @@ static KrkTokenType identifierType() {
|
|||||||
switch (*scanner.start) {
|
switch (*scanner.start) {
|
||||||
case 'a': return checkKeyword(1, "nd", TOKEN_AND);
|
case 'a': return checkKeyword(1, "nd", TOKEN_AND);
|
||||||
case 'c': return checkKeyword(1, "lass", TOKEN_CLASS);
|
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 'd': return checkKeyword(1, "ef", TOKEN_DEF);
|
||||||
case 'e': return checkKeyword(1, "lse", TOKEN_ELSE);
|
case 'e': return checkKeyword(1, "lse", TOKEN_ELSE);
|
||||||
case 'f': return checkKeyword(1, "or", TOKEN_FOR);
|
case 'f': return checkKeyword(1, "or", TOKEN_FOR);
|
||||||
@ -210,9 +217,8 @@ KrkToken krk_scanToken() {
|
|||||||
/* If at start of line, do thing */
|
/* If at start of line, do thing */
|
||||||
if (scanner.startOfLine && peek() == ' ') {
|
if (scanner.startOfLine && peek() == ' ') {
|
||||||
scanner.start = scanner.cur;
|
scanner.start = scanner.cur;
|
||||||
return makeIndentation();
|
|
||||||
} else {
|
|
||||||
scanner.startOfLine = 0;
|
scanner.startOfLine = 0;
|
||||||
|
return makeIndentation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Eat whitespace */
|
/* Eat whitespace */
|
||||||
@ -228,10 +234,18 @@ KrkToken krk_scanToken() {
|
|||||||
|
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
scanner.line++;
|
scanner.line++;
|
||||||
scanner.startOfLine = 1;
|
if (scanner.startOfLine) {
|
||||||
return makeToken(TOKEN_EOL);
|
/* 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 (isAlpha(c)) return identifier();
|
||||||
if (isDigit(c)) return number(c);
|
if (isDigit(c)) return number(c);
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ typedef enum {
|
|||||||
|
|
||||||
TOKEN_INDENTATION,
|
TOKEN_INDENTATION,
|
||||||
|
|
||||||
|
TOKEN_BLOCK, /* temporary XXX remove me */
|
||||||
TOKEN_RETRY,
|
TOKEN_RETRY,
|
||||||
TOKEN_EOL,
|
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) {
|
void krk_writeValueArray(KrkValueArray * array, KrkValue value) {
|
||||||
if (array->capacity < array->count - 1) {
|
if (array->capacity < array->count + 1) {
|
||||||
int old = array->capacity;
|
int old = array->capacity;
|
||||||
array->capacity = GROW_CAPACITY(old);
|
array->capacity = GROW_CAPACITY(old);
|
||||||
array->values = GROW_ARRAY(KrkValue, array->values, old, array->capacity);
|
array->values = GROW_ARRAY(KrkValue, array->values, old, array->capacity);
|
||||||
|
12
vm.c
12
vm.c
@ -267,6 +267,18 @@ static KrkValue run() {
|
|||||||
}
|
}
|
||||||
break;
|
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