diff --git a/docs/docs/files.md b/docs/docs/files.md index b01689fb..faea7c10 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 constant within the block. +```cs +with("test.txt", "r") as myfile { + // file constant 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/ops/checkTests.du b/ops/checkTests.du index d7f102a3..724962da 100644 --- a/ops/checkTests.du +++ b/ops/checkTests.du @@ -14,6 +14,7 @@ const ignored = { ], 'files': [ 'read.txt', + 'read2.txt', ], 'strings': [ 'test', diff --git a/src/vm/compiler.c b/src/vm/compiler.c index 08d0caba..1df3c60e 100644 --- a/src/vm/compiler.c +++ b/src/vm/compiler.c @@ -162,10 +162,10 @@ static void initCompiler(Parser *parser, Compiler *compiler, Compiler *parent, F compiler->function = NULL; compiler->class = NULL; compiler->loop = NULL; - compiler->withBlock = false; 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 +312,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 +2535,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 identifier."); + 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 +2579,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 +2589,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..6272992c 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,6 @@ typedef struct Compiler { Upvalue upvalues[UINT8_COUNT]; int scopeDepth; - bool withBlock; ObjDict *classAnnotations; ObjDict *methodAnnotations; ObjDict *fieldAnnotations; diff --git a/tests/files/read.du b/tests/files/read.du index 1821f49f..dd3017a1 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,45 @@ 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); + } + 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 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