aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBad Diode <bd@badd10de.dev>2021-10-27 13:59:25 +0200
committerBad Diode <bd@badd10de.dev>2021-10-27 13:59:25 +0200
commitad659dca44892a5f2ba4e088603a819af58a5819 (patch)
tree9743e49b7be6a93bf718a6dbe4a3e7faf4c54d09
parentf0cd7a3cab56a6f8d7b4520aaa168a271a94d6d4 (diff)
downloadbdl-ad659dca44892a5f2ba4e088603a819af58a5819.tar.gz
bdl-ad659dca44892a5f2ba4e088603a819af58a5819.zip
Add support for lexically scoped local variables
-rw-r--r--src/bytecode/chunk.c6
-rwxr-xr-xsrc/bytecode/chunk.h8
-rwxr-xr-xsrc/bytecode/compiler.h109
-rwxr-xr-xsrc/bytecode/debug.h6
-rwxr-xr-xsrc/bytecode/vm.h16
5 files changed, 104 insertions, 41 deletions
diff --git a/src/bytecode/chunk.c b/src/bytecode/chunk.c
index 71562fa..8ff6acf 100644
--- a/src/bytecode/chunk.c
+++ b/src/bytecode/chunk.c
@@ -9,8 +9,8 @@ chunk_init(StringView name) {
9 array_init(chunk->lines, 0); 9 array_init(chunk->lines, 0);
10 array_init(chunk->name, name.n); 10 array_init(chunk->name, name.n);
11 array_insert(chunk->name, name.start, name.n); 11 array_insert(chunk->name, name.start, name.n);
12 array_init(chunk->params, 0); 12 chunk->n_params = 0;
13 array_init(chunk->locals, 0); 13 chunk->n_locals = 0;
14 return chunk; 14 return chunk;
15} 15}
16 16
@@ -24,8 +24,6 @@ chunk_free(Chunk *chunk) {
24 array_free(chunk->constants); 24 array_free(chunk->constants);
25 array_free(chunk->lines); 25 array_free(chunk->lines);
26 array_free(chunk->name); 26 array_free(chunk->name);
27 array_free(chunk->params);
28 array_free(chunk->locals);
29 free(chunk); 27 free(chunk);
30} 28}
31 29
diff --git a/src/bytecode/chunk.h b/src/bytecode/chunk.h
index c584d4a..9457fa9 100755
--- a/src/bytecode/chunk.h
+++ b/src/bytecode/chunk.h
@@ -20,10 +20,10 @@ typedef struct Chunk {
20 LineInfo *lines; 20 LineInfo *lines;
21 // Chunk name. 21 // Chunk name.
22 char *name; 22 char *name;
23 // Parameters 23
24 StringView *params; 24 // Number of locals and parameters.
25 // Locals. 25 size_t n_params;
26 StringView *locals; 26 size_t n_locals;
27} Chunk; 27} Chunk;
28 28
29#define NEW_CHUNK(NAME) chunk_init((StringView){(NAME), sizeof(NAME) - 1}) 29#define NEW_CHUNK(NAME) chunk_init((StringView){(NAME), sizeof(NAME) - 1})
diff --git a/src/bytecode/compiler.h b/src/bytecode/compiler.h
index 7497ea7..f9e1da1 100755
--- a/src/bytecode/compiler.h
+++ b/src/bytecode/compiler.h
@@ -4,16 +4,49 @@
4#include "chunk.h" 4#include "chunk.h"
5#include "lexer.h" 5#include "lexer.h"
6 6
7#define MAX_DEPTH 1024
8
9typedef struct Scope {
10 StringView *params;
11 StringView *locals;
12} Scope;
13
7typedef struct Compiler { 14typedef struct Compiler {
8 Token *tokens; 15 Token *tokens;
9 size_t current; 16 size_t current;
17 size_t scope_depth;
18 Scope scopes[MAX_DEPTH];
10} Compiler; 19} Compiler;
11 20
21
12// Mimics the functionality in the Scanner functions, but for entire tokens. 22// Mimics the functionality in the Scanner functions, but for entire tokens.
13Token next_token(Compiler *compiler); 23Token next_token(Compiler *compiler);
14Token peek_token(const Compiler *compiler); 24Token peek_token(const Compiler *compiler);
15bool has_next_token(const Compiler *compiler); 25bool has_next_token(const Compiler *compiler);
16 26
27// Scope initialization/exit.
28void enter_scope(Compiler *compiler);
29void exit_scope(Compiler *compiler);
30
31void
32enter_scope(Compiler *compiler) {
33 Scope *scope = &compiler->scopes[compiler->scope_depth++];
34 array_init(scope->params, 0);
35 array_init(scope->locals, 0);
36}
37
38void
39exit_scope(Compiler *compiler) {
40 Scope *scope = &compiler->scopes[--compiler->scope_depth];
41 array_free(scope->params);
42 array_free(scope->locals);
43}
44
45Scope *
46get_current_scope(Compiler *compiler) {
47 return &compiler->scopes[compiler->scope_depth - 1];
48}
49
17Chunk * compile(Token *tokens); 50Chunk * compile(Token *tokens);
18 51
19Token 52Token
@@ -32,10 +65,9 @@ has_next_token(const Compiler *compiler) {
32} 65}
33 66
34ssize_t 67ssize_t
35find_local_index(Chunk *chunk, Token tok) { 68find_local_index(Scope *scope, Token tok) {
36 // NOTE: This is dumb and potentially slow. 69 for (size_t i = 0; i < array_size(scope->locals); i++) {
37 for (size_t i = 0; i < array_size(chunk->locals); i++) { 70 if (sv_equal(&tok.value, &scope->locals[i])) {
38 if (sv_equal(&tok.value, &chunk->locals[i])) {
39 return i; 71 return i;
40 } 72 }
41 } 73 }
@@ -217,29 +249,49 @@ compile_declare_op(Chunk *chunk, Compiler *compiler, Token start, Ops op) {
217 }); 249 });
218 return; 250 return;
219 } 251 }
220 // TODO: If we are inside a function and we are using OP_DEF, we just 252
221 // declare the local variable directly in the stack. No need for symbols! 253 if (compiler->scope_depth <= 1) {
222 // TODO: If we are inside a function and we are using OP_SET, check if the
223 // local variable has been defined before, if not try to read from the
224 // globals for setting.
225 if (sv_equal(&STRING(""), &STR_ARRAY(chunk->name))) {
226 Object obj = make_symbol(name.value); 254 Object obj = make_symbol(name.value);
227 emit_constant(chunk, name, obj); 255 emit_constant(chunk, name, obj);
228 } else { 256 } else {
229 // TODO: only do this if we are defining! not setting.
230 // Check if we already have the local
231 ssize_t idx = find_local_index(chunk, name);
232 if (idx < 0) {
233 array_push(chunk->locals, name.value);
234 idx = array_size(chunk->locals) - 1;
235 }
236 if (op == OP_DEF) { 257 if (op == OP_DEF) {
237 op = OP_DEF_LOCAL; 258 op = OP_DEF_LOCAL;
259 // Check if the local is already registered.
260 Scope *scope = get_current_scope(compiler);
261 ssize_t idx = find_local_index(scope, name);
262 if (idx < 0) {
263 array_push(scope->locals, name.value);
264 idx = chunk->n_locals++;
265 }
266 emit_constant(chunk, name, FIXNUM_VAL(idx));
238 } else if (op == OP_SET) { 267 } else if (op == OP_SET) {
239 op = OP_SET_LOCAL; 268 size_t depth = compiler->scope_depth - 1;
269 ssize_t idx = -1;
270 // Check if name is local in this or any previous scope.
271 do {
272 Scope *scope = &compiler->scopes[depth];
273 idx = find_local_index(scope, name);
274 if (idx >= 0) {
275 break;
276 }
277 depth--;
278 } while (depth > 0);
279
280 if (idx >= 0) {
281 // If the value is found emit OP_SET_LOCAL with tree parameters:
282 // The new value, the scope depth, and the scope index.
283 op = OP_SET_LOCAL;
284 emit_constant(chunk, name, FIXNUM_VAL(depth));
285 emit_constant(chunk, name, FIXNUM_VAL(idx));
286 } else {
287 // If not found at all, emit set for the global scope.
288 Object obj = make_symbol(name.value);
289 emit_constant(chunk, name, obj);
290 }
240 } 291 }
241 emit_constant(chunk, name, FIXNUM_VAL(idx));
242 } 292 }
293 // NOTE: We can have compiler support for preemptively finding if globals
294 // exist or not.
243 295
244 Token tok = peek_token(compiler); 296 Token tok = peek_token(compiler);
245 if (name.type == TOKEN_EOF || tok.type == TOKEN_EOF) { 297 if (name.type == TOKEN_EOF || tok.type == TOKEN_EOF) {
@@ -276,6 +328,7 @@ compile_declare_op(Chunk *chunk, Compiler *compiler, Token start, Ops op) {
276 328
277void 329void
278compile_lambda(Chunk *chunk, Compiler *compiler, Token start, StringView name) { 330compile_lambda(Chunk *chunk, Compiler *compiler, Token start, StringView name) {
331 enter_scope(compiler);
279 Object fun = make_lambda(name); 332 Object fun = make_lambda(name);
280 333
281 // Prepeare parameters. 334 // Prepeare parameters.
@@ -305,7 +358,8 @@ compile_lambda(Chunk *chunk, Compiler *compiler, Token start, StringView name) {
305 return; 358 return;
306 } 359 }
307 // Check if parameters name already exists. 360 // Check if parameters name already exists.
308 ssize_t idx = find_local_index(fun.chunk, tok); 361 Scope *scope = get_current_scope(compiler);
362 ssize_t idx = find_local_index(scope, tok);
309 if (idx >= 0) { 363 if (idx >= 0) {
310 error_push((Error){ 364 error_push((Error){
311 .type = ERR_TYPE_COMPILER, 365 .type = ERR_TYPE_COMPILER,
@@ -315,8 +369,10 @@ compile_lambda(Chunk *chunk, Compiler *compiler, Token start, StringView name) {
315 }); 369 });
316 return; 370 return;
317 } 371 }
318 array_push(fun.chunk->params, tok.value); 372 array_push(scope->params, tok.value);
319 array_push(fun.chunk->locals, tok.value); 373 array_push(scope->locals, tok.value);
374 fun.chunk->n_params++;
375 fun.chunk->n_locals++;
320 } 376 }
321 } else if (tok.type != TOKEN_NIL) { 377 } else if (tok.type != TOKEN_NIL) {
322 error_push((Error){ 378 error_push((Error){
@@ -348,6 +404,7 @@ compile_lambda(Chunk *chunk, Compiler *compiler, Token start, StringView name) {
348 } 404 }
349 add_code(fun.chunk, OP_RETURN, start.line, start.column); 405 add_code(fun.chunk, OP_RETURN, start.line, start.column);
350 emit_constant(chunk, start, fun); 406 emit_constant(chunk, start, fun);
407 exit_scope(compiler);
351} 408}
352 409
353void 410void
@@ -551,7 +608,8 @@ parse_tree(Chunk *chunk, Compiler *compiler) {
551 return; 608 return;
552 } break; 609 } break;
553 case TOKEN_SYMBOL: { 610 case TOKEN_SYMBOL: {
554 ssize_t idx = find_local_index(chunk, tok); 611 Scope *scope = get_current_scope(compiler);
612 ssize_t idx = find_local_index(scope, tok);
555 if (idx < 0) { 613 if (idx < 0) {
556 Object obj = make_symbol(tok.value); 614 Object obj = make_symbol(tok.value);
557 emit_constant(chunk, tok, obj); 615 emit_constant(chunk, tok, obj);
@@ -583,11 +641,13 @@ parse_tree(Chunk *chunk, Compiler *compiler) {
583Chunk * 641Chunk *
584compile(Token *tokens) { 642compile(Token *tokens) {
585 Chunk *chunk = NULL; 643 Chunk *chunk = NULL;
586 chunk = NEW_CHUNK(""); 644 chunk = NEW_CHUNK("main");
587 Compiler compiler = (Compiler){ 645 Compiler compiler = (Compiler){
588 .tokens = tokens, 646 .tokens = tokens,
589 .current = 0, 647 .current = 0,
648 .scope_depth = 0,
590 }; 649 };
650 enter_scope(&compiler);
591 Token main_start = peek_token(&compiler); 651 Token main_start = peek_token(&compiler);
592 while (has_next_token(&compiler)) { 652 while (has_next_token(&compiler)) {
593 Token start = peek_token(&compiler); 653 Token start = peek_token(&compiler);
@@ -598,6 +658,7 @@ compile(Token *tokens) {
598 add_code(chunk, OP_DROP, start.line, start.column); 658 add_code(chunk, OP_DROP, start.line, start.column);
599 } 659 }
600 add_code(chunk, OP_RETURN, main_start.line, main_start.column); 660 add_code(chunk, OP_RETURN, main_start.line, main_start.column);
661 exit_scope(&compiler);
601 return chunk; 662 return chunk;
602} 663}
603 664
diff --git a/src/bytecode/debug.h b/src/bytecode/debug.h
index 54d2cdb..e2e3756 100755
--- a/src/bytecode/debug.h
+++ b/src/bytecode/debug.h
@@ -48,11 +48,7 @@ static const char* ops_str[] = {
48 48
49void 49void
50disassemble_chunk(Chunk *chunk) { 50disassemble_chunk(Chunk *chunk) {
51 if (array_size(chunk->name) < 1) { 51 printf("===== %.*s =====\n", (int)array_size(chunk->name), chunk->name);
52 printf("===== main =====\n");
53 } else {
54 printf("===== %.*s =====\n", (int)array_size(chunk->name), chunk->name);
55 }
56 printf("code:\n"); 52 printf("code:\n");
57 size_t offset = 0; 53 size_t offset = 0;
58 while (offset < array_size(chunk->code)) { 54 while (offset < array_size(chunk->code)) {
diff --git a/src/bytecode/vm.h b/src/bytecode/vm.h
index 84d2432..287c83c 100755
--- a/src/bytecode/vm.h
+++ b/src/bytecode/vm.h
@@ -188,6 +188,13 @@ vm_interpret(VM *vm) {
188 ssize_t idx = AS_FIXNUM(array_pop(vm->stack)); 188 ssize_t idx = AS_FIXNUM(array_pop(vm->stack));
189 vm->stack[frame->stack_offset + idx] = value; 189 vm->stack[frame->stack_offset + idx] = value;
190 } break; 190 } break;
191 case OP_SET_LOCAL: {
192 Object value = array_pop(vm->stack);
193 ssize_t idx = AS_FIXNUM(array_pop(vm->stack));
194 ssize_t depth = AS_FIXNUM(array_pop(vm->stack));
195 CallFrame frame = vm->frames[depth];
196 vm->stack[frame.stack_offset + idx] = value;
197 } break;
191 case OP_DEF: { 198 case OP_DEF: {
192 Object value = array_pop(vm->stack); 199 Object value = array_pop(vm->stack);
193 Object name = array_pop(vm->stack); 200 Object name = array_pop(vm->stack);
@@ -280,13 +287,13 @@ vm_interpret(VM *vm) {
280 // Check the number of arguments is correct. 287 // Check the number of arguments is correct.
281 // NOTE: This is probably better handled at compilation, but for 288 // NOTE: This is probably better handled at compilation, but for
282 // now this is simpler to implement. 289 // now this is simpler to implement.
283 ssize_t n_params = array_size(proc.chunk->params); 290 ssize_t n_params = proc.chunk->n_params;
291 ssize_t n_locals = proc.chunk->n_locals;
284 if (n_args < n_params) { 292 if (n_args < n_params) {
285 RUNTIME_ERROR(ERR_NOT_ENOUGH_ARGS); 293 RUNTIME_ERROR(ERR_NOT_ENOUGH_ARGS);
286 } else if (n_args > n_params) { 294 } else if (n_args > n_params) {
287 RUNTIME_ERROR(ERR_TOO_MANY_ARGS); 295 RUNTIME_ERROR(ERR_TOO_MANY_ARGS);
288 } 296 }
289 ssize_t n_locals = array_size(proc.chunk->locals) - n_params;
290 297
291#ifdef DEBUG 298#ifdef DEBUG
292 disassemble_chunk(proc.chunk); 299 disassemble_chunk(proc.chunk);
@@ -298,7 +305,7 @@ vm_interpret(VM *vm) {
298 CallFrame new_frame = (CallFrame){ 305 CallFrame new_frame = (CallFrame){
299 .chunk = proc.chunk, 306 .chunk = proc.chunk,
300 .rp = vm->pc, 307 .rp = vm->pc,
301 .stack_offset = array_size(vm->stack) - array_size(proc.chunk->params), 308 .stack_offset = array_size(vm->stack) - n_params,
302 }; 309 };
303 array_push(vm->frames, new_frame); 310 array_push(vm->frames, new_frame);
304 frame = &vm->frames[array_size(vm->frames) - 1]; 311 frame = &vm->frames[array_size(vm->frames) - 1];
@@ -315,7 +322,8 @@ vm_interpret(VM *vm) {
315 } 322 }
316 323
317 // Reset stack size. 324 // Reset stack size.
318 array_head(vm->stack)->size = frame->stack_offset + n_params + n_locals; 325 size_t offset = frame->stack_offset + n_params + n_locals;
326 array_head(vm->stack)->size = offset;
319 } 327 }
320 vm->pc = frame->chunk->code; 328 vm->pc = frame->chunk->code;
321 } break; 329 } break;