Skip to content

Commit

Permalink
Merge pull request #759 from liz3/liz3/file-custom-name
Browse files Browse the repository at this point in the history
feat: allow custom var name for with statement and nesting of files.
  • Loading branch information
Jason2605 authored Nov 17, 2024
2 parents a2554ab + d5c6528 commit bce42e8
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 17 deletions.
8 changes: 8 additions & 0 deletions docs/docs/files.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>` 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.
Expand Down
1 change: 1 addition & 0 deletions ops/checkTests.du
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ignored = {
],
'files': [
'read.txt',
'read2.txt',
],
'strings': [
'test',
Expand Down
51 changes: 35 additions & 16 deletions src/vm/compiler.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]);
}
}

Expand All @@ -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) {
Expand All @@ -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);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/vm/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ typedef struct {

// True if it's a constant value.
bool constant;

bool isFile;
} Local;

typedef struct {
Expand Down Expand Up @@ -100,7 +102,6 @@ typedef struct Compiler {
Upvalue upvalues[UINT8_COUNT];

int scopeDepth;
bool withBlock;
ObjDict *classAnnotations;
ObjDict *methodAnnotations;
ObjDict *fieldAnnotations;
Expand Down
40 changes: 40 additions & 0 deletions tests/files/read.du
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
1 change: 1 addition & 0 deletions tests/files/read2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is another file
16 changes: 16 additions & 0 deletions tests/files/write.du
Original file line number Diff line number Diff line change
Expand Up @@ -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();

0 comments on commit bce42e8

Please sign in to comment.