From 2f2928f79288c9b0189164b7ed010a8deb095476 Mon Sep 17 00:00:00 2001 From: Liz3 Date: Sun, 17 Nov 2024 00:58:28 +0100 Subject: [PATCH 1/8] feat: allow custom var name for with and nesting of files. This refactors the logic in the compiler to allow files to have custom variable names and also allow the nesting of multiple files within each other. Both of which should be possible. The latter is a bit questionable in terms of code quality but needed anyways. --- docs/docs/files.md | 8 +++++++ src/vm/compiler.c | 52 ++++++++++++++++++++++++++++++------------- src/vm/compiler.h | 4 +++- src/vm/vm.c | 1 - tests/files/read.du | 24 ++++++++++++++++++++ tests/files/read2.txt | 1 + tests/files/write.du | 16 +++++++++++++ 7 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 tests/files/read2.txt diff --git a/docs/docs/files.md b/docs/docs/files.md index b01689fb..2661bb1c 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -41,6 +41,14 @@ with("test.txt", "r") { // file is out of scope, and will the file will be closed for you ``` +### Custom name +It is possible to append `as ` before the opening `{` in order to customise the Name of the variable within the block. +```cs +with("test.txt", "r") as myfile { + // file var is passed in here as myfile, NOT file. +} +``` + ### Writing to files There are two methods available when writing to a file: `write()` and `writeLine()`. `write()` simply writes strings to a file, `writeLine()` is exactly the same, except it appends a newline to the passed in string. Both functions return the amount of characters wrote to the file. diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 08d0caba..51e16731 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -162,10 +162,11 @@ static void initCompiler(Parser *parser, Compiler *compiler, Compiler *parent, F compiler->function = NULL; compiler->class = NULL; compiler->loop = NULL; - compiler->withBlock = false; + compiler->withBlock = -1; compiler->classAnnotations = NULL; compiler->methodAnnotations = NULL; compiler->fieldAnnotations = NULL; + memset(compiler->locals, 0, sizeof(Local) * UINT8_COUNT); if (parent != NULL) { compiler->class = parent->class; @@ -312,6 +313,22 @@ static int resolveLocal(Compiler *compiler, LangToken *name, bool inFunction) { return -1; } +static int resolveFileHandles(Compiler *compiler, bool inFunction, int* out) { + // Look it up in the local scopes. Look in reverse order so that the + // most nested variable is found first and shadows outer ones. + int count = 0; + for (int i = compiler->localCount - 1; i >= 0; i--) { + Local *local = &compiler->locals[i]; + if (local->isFile) { + if (!inFunction && local->depth == -1) { + error(compiler->parser, "Cannot read local variable in its own initializer."); + } + out[count++] = i; + } + } + + return count; +} // Adds an upvalue to [compiler]'s function with the given properties. // Does not add one if an upvalue for that variable is already in the @@ -2519,38 +2536,41 @@ static void switchStatement(Compiler *compiler) { } static void withStatement(Compiler *compiler) { - compiler->withBlock = true; consume(compiler, TOKEN_LEFT_PAREN, "Expect '(' after 'with'."); expression(compiler); consume(compiler, TOKEN_COMMA, "Expect comma"); expression(compiler); consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after 'with'."); - consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' before with body."); - - beginScope(compiler); int fileIndex = compiler->localCount; Local *local = &compiler->locals[compiler->localCount++]; + + if(match(compiler, TOKEN_AS)) { + consume(compiler, TOKEN_IDENTIFIER, "Expect indentifier."); + local->name = compiler->parser->previous; + } else { + local->name = syntheticToken("file"); + } + consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' before with body."); + + beginScope(compiler); local->depth = compiler->scopeDepth; local->isUpvalue = false; - local->name = syntheticToken("file"); + local->isFile = true; local->constant = true; emitByte(compiler, OP_OPEN_FILE); block(compiler); emitBytes(compiler, OP_CLOSE_FILE, fileIndex); endScope(compiler); - compiler->withBlock = false; } -static void checkForFileHandle(Compiler *compiler) { - if (compiler->withBlock) { - LangToken token = syntheticToken("file"); - int local = resolveLocal(compiler, &token, true); +static void closeFileHandles(Compiler *compiler) { + int ids[UINT8_COUNT]; + int count = resolveFileHandles(compiler, true, ids); - if (local != -1) { - emitBytes(compiler, OP_CLOSE_FILE, local); - } + for (int i = 0; i < count; i++) { + emitBytes(compiler, OP_CLOSE_FILE, ids[i]); } } @@ -2560,7 +2580,7 @@ static void returnStatement(Compiler *compiler) { } if (match(compiler, TOKEN_SEMICOLON)) { - checkForFileHandle(compiler); + closeFileHandles(compiler); emitReturn(compiler); } else { if (compiler->type == TYPE_INITIALIZER) { @@ -2570,7 +2590,7 @@ static void returnStatement(Compiler *compiler) { expression(compiler); consume(compiler, TOKEN_SEMICOLON, "Expect ';' after return value."); - checkForFileHandle(compiler); + closeFileHandles(compiler); emitByte(compiler, OP_RETURN); } } diff --git a/src/vm/compiler.h b/src/vm/compiler.h index 13ff5735..44743fc6 100644 --- a/src/vm/compiler.h +++ b/src/vm/compiler.h @@ -40,6 +40,8 @@ typedef struct { // True if it's a constant value. bool constant; + + bool isFile; } Local; typedef struct { @@ -100,7 +102,7 @@ typedef struct Compiler { Upvalue upvalues[UINT8_COUNT]; int scopeDepth; - bool withBlock; + int withBlock; ObjDict *classAnnotations; ObjDict *methodAnnotations; ObjDict *fieldAnnotations; diff --git a/src/vm/vm.c b/src/vm/vm.c index d11ae39c..93eec1f2 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -2430,7 +2430,6 @@ static DictuInterpretResult runWithBreakFrame(DictuVM *vm, int breakFrame) { Value file = frame->slots[slot]; ObjFile *fileObject = AS_FILE(file); fclose(fileObject->file); - DISPATCH(); } } diff --git a/tests/files/read.du b/tests/files/read.du index 1821f49f..522b565c 100644 --- a/tests/files/read.du +++ b/tests/files/read.du @@ -18,6 +18,7 @@ class TestFileReading < UnitTest { "Dictu is great!\n" + "Dictu is great!\n" + "Dictu is great!"; + const EXPECTED2 = "This is another file"; testFileRead() { var contents; @@ -29,6 +30,29 @@ class TestFileReading < UnitTest { this.assertType(contents, "string"); this.assertEquals(contents, TestFileReading.EXPECTED); } + testFileReadCustomName() { + var contents; + + with("tests/files/read.txt", "r") as f { + contents = f.read(); + } + + this.assertType(contents, "string"); + this.assertEquals(contents, TestFileReading.EXPECTED); + } + testFileReadNested() { + var contents; + + with("tests/files/read.txt", "r") { + contents = file.read(); + with("tests/files/read2.txt", "r") as otherFile { + contents += otherFile.read(); + } + } + + this.assertType(contents, "string"); + this.assertEquals(contents, TestFileReading.EXPECTED + TestFileReading.EXPECTED2); + } } TestFileReading().run(); \ No newline at end of file diff --git a/tests/files/read2.txt b/tests/files/read2.txt new file mode 100644 index 00000000..8e2b5c59 --- /dev/null +++ b/tests/files/read2.txt @@ -0,0 +1 @@ +This is another file \ No newline at end of file diff --git a/tests/files/write.du b/tests/files/write.du index b3d1b6b4..711fb977 100644 --- a/tests/files/write.du +++ b/tests/files/write.du @@ -38,6 +38,22 @@ class TestFileWrite < UnitTest { this.assertEquals(file.read(), "Dictu is great!Dictu is great!Dictu is great!"); } } + testFileWriteCustomName() { + with("tests/files/read.txt", "w") as f { + // Save contents to reset the file + var count = f.write("Dictu is great!"); + this.assertEquals(count, 15); + count = f.write("Dictu is great!"); + this.assertEquals(count, 15); + count = f.write("Dictu is great!"); + this.assertEquals(count, 15); + } + + with("tests/files/read.txt", "r") as rf { + // Save contents to reset the file + this.assertEquals(rf.read(), "Dictu is great!Dictu is great!Dictu is great!"); + } + } } TestFileWrite().run(); \ No newline at end of file From 5f7d8743622e3fc63bdd55fb66f60be347ede677 Mon Sep 17 00:00:00 2001 From: Liz3 Date: Sun, 17 Nov 2024 01:13:29 +0100 Subject: [PATCH 2/8] add shadow test --- tests/files/read.du | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/files/read.du b/tests/files/read.du index 522b565c..dd3017a1 100644 --- a/tests/files/read.du +++ b/tests/files/read.du @@ -53,6 +53,22 @@ class TestFileReading < UnitTest { this.assertType(contents, "string"); this.assertEquals(contents, TestFileReading.EXPECTED + TestFileReading.EXPECTED2); } + testFileReadNestedShadow() { + var contents; + + with("tests/files/read.txt", "r") { + with("tests/files/read2.txt", "r") { + contents = file.read(); + } + this.assertType(contents, "string"); + this.assertEquals(contents, TestFileReading.EXPECTED2); + contents += file.read(); + } + + this.assertType(contents, "string"); + this.assertEquals(contents, TestFileReading.EXPECTED2 + TestFileReading.EXPECTED); + } + } TestFileReading().run(); \ No newline at end of file From 4314e24934f44436f35cee5d63b76be5fda7f0a8 Mon Sep 17 00:00:00 2001 From: Liz3 Date: Sun, 17 Nov 2024 01:16:29 +0100 Subject: [PATCH 3/8] readd this --- src/vm/vm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vm/vm.c b/src/vm/vm.c index 93eec1f2..d11ae39c 100644 --- a/src/vm/vm.c +++ b/src/vm/vm.c @@ -2430,6 +2430,7 @@ static DictuInterpretResult runWithBreakFrame(DictuVM *vm, int breakFrame) { Value file = frame->slots[slot]; ObjFile *fileObject = AS_FILE(file); fclose(fileObject->file); + DISPATCH(); } } From 7165008be344d9fb74a21d39cd13bbdce83244cb Mon Sep 17 00:00:00 2001 From: Liz3 Date: Sun, 17 Nov 2024 01:19:53 +0100 Subject: [PATCH 4/8] fix --- ops/checkTests.du | 1 + 1 file changed, 1 insertion(+) diff --git a/ops/checkTests.du b/ops/checkTests.du index d9288ec3..4fee3dc2 100644 --- a/ops/checkTests.du +++ b/ops/checkTests.du @@ -14,6 +14,7 @@ const ignored = { ], 'files': [ 'read.txt', + 'read2.txt', ], 'strings': [ 'test', From 945cd2610a3d76a539395906b8bebaecc442abd3 Mon Sep 17 00:00:00 2001 From: Liz3 Date: Sun, 17 Nov 2024 01:37:58 +0100 Subject: [PATCH 5/8] remove this --- src/vm/compiler.c | 1 - src/vm/compiler.h | 1 - 2 files changed, 2 deletions(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 51e16731..b7a23cd0 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -162,7 +162,6 @@ static void initCompiler(Parser *parser, Compiler *compiler, Compiler *parent, F compiler->function = NULL; compiler->class = NULL; compiler->loop = NULL; - compiler->withBlock = -1; compiler->classAnnotations = NULL; compiler->methodAnnotations = NULL; compiler->fieldAnnotations = NULL; diff --git a/src/vm/compiler.h b/src/vm/compiler.h index 44743fc6..6272992c 100644 --- a/src/vm/compiler.h +++ b/src/vm/compiler.h @@ -102,7 +102,6 @@ typedef struct Compiler { Upvalue upvalues[UINT8_COUNT]; int scopeDepth; - int withBlock; ObjDict *classAnnotations; ObjDict *methodAnnotations; ObjDict *fieldAnnotations; From fc3413ef265330428b04002f97c24637c235ba23 Mon Sep 17 00:00:00 2001 From: Liz Date: Sun, 17 Nov 2024 18:33:42 +0100 Subject: [PATCH 6/8] Update src/vm/compiler.c Co-authored-by: Jason_000 --- src/vm/compiler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/compiler.c b/src/vm/compiler.c index b7a23cd0..1df3c60e 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -2545,7 +2545,7 @@ static void withStatement(Compiler *compiler) { Local *local = &compiler->locals[compiler->localCount++]; if(match(compiler, TOKEN_AS)) { - consume(compiler, TOKEN_IDENTIFIER, "Expect indentifier."); + consume(compiler, TOKEN_IDENTIFIER, "Expect identifier."); local->name = compiler->parser->previous; } else { local->name = syntheticToken("file"); From f2f5089019d0591791be83fa7676dffc7d922a44 Mon Sep 17 00:00:00 2001 From: Liz Date: Sun, 17 Nov 2024 18:33:50 +0100 Subject: [PATCH 7/8] Update docs/docs/files.md Co-authored-by: Jason_000 --- docs/docs/files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/files.md b/docs/docs/files.md index 2661bb1c..bb30d713 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -45,7 +45,7 @@ with("test.txt", "r") { It is possible to append `as ` before the opening `{` in order to customise the Name of the variable within the block. ```cs with("test.txt", "r") as myfile { - // file var is passed in here as myfile, NOT file. + // file constant is passed in here as myfile, NOT file. } ``` From d5c65288d96ebf102ba0fff3e05d3ddfd63085d3 Mon Sep 17 00:00:00 2001 From: Liz Date: Sun, 17 Nov 2024 18:33:55 +0100 Subject: [PATCH 8/8] Update docs/docs/files.md Co-authored-by: Jason_000 --- docs/docs/files.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/files.md b/docs/docs/files.md index bb30d713..faea7c10 100644 --- a/docs/docs/files.md +++ b/docs/docs/files.md @@ -42,7 +42,7 @@ with("test.txt", "r") { ``` ### Custom name -It is possible to append `as ` before the opening `{` in order to customise the Name of the variable within the block. +It is possible to append `as ` before the opening `{` in order to customise the name of the constant within the block. ```cs with("test.txt", "r") as myfile { // file constant is passed in here as myfile, NOT file.