Add a basic 'with' statement

This is incomplete; it's missing the necessary plumbing to ensure __exit__
still gets called if the inner block returns or raises an exception. TODO
This commit is contained in:
K. Lange 2021-01-09 11:01:18 +09:00
parent bb6b4d09f5
commit 52427a5147
6 changed files with 111 additions and 3 deletions

View File

@ -777,6 +777,45 @@ print(foo.__doc__)
# → This is a function that does things.
```
### `with` Blocks
A `with` statement introduces a context manager:
```py
with expr:
...
```
The value of `expr` must be an object with an `__enter__` and `__exit__` method, such as a `fileio.File`. The `__enter__` method will be called upon entry and the `__exit__` method will be called upon exit from the block.
The result of `expr` can also be assigned a name for use within the block. Note that as with other control flow structures in Kuroko, this name is only valid within the block and can not be referenced outside of it, and the same is true of any names defined within the block. If you need to output values from within the block, such as in the typical case of opening a file and loading its contents, be sure to declare any necessary variables before the `with` statement:
```py
from fileio import open
let lines
with open('README.md') as f:
lines = [l.strip() for l in f.readlines()]
print(lines)
# → ["![logo]...
```
Note that you can declare a variable for the object used with `__enter__` and `__exit__` before the `with` statement:
```py
from fileio import open
let f = open('README.md')
print(f)
# → <open file 'README.md' ...>
let lines
with f:
lines = [l.strip() for l in f.readlines()]
print(f)
# → <closed file 'README.md' ...>
```
_**Note:** The implementation of `with` blocks is incomplete; exceptions raised from within a `with` that are not caught within the block will cause `__exit__` to not be called. The same is also true of `return` statements within a `with` block._
## About the REPL
Kuroko's repl provides an interactive environment for executing code and seeing results.

View File

@ -1048,6 +1048,57 @@ static void emitLoop(int loopStart) {
/* Patch break statements */
}
static void withStatement() {
/* TODO: Multiple items, I'm feeling lazy. */
/* We only need this for block() */
size_t blockWidth = (parser.previous.type == TOKEN_INDENTATION) ? parser.previous.length : 0;
/* Collect the with token that started this statement */
advance();
beginScope();
expression();
if (match(TOKEN_AS)) {
consume(TOKEN_IDENTIFIER, "Expected variable name after 'as'");
size_t ind = identifierConstant(&parser.previous);
declareVariable();
defineVariable(ind);
} else {
/* Otherwise we want an unnamed local; TODO: Wait, can't we do this for iterable counts? */
Local * local = &current->locals[current->localCount++];
local->depth = current->scopeDepth;
local->isCaptured = 0;
local->name.start = "";
local->name.length = 0;
}
consume(TOKEN_COLON, "Expected ':' after with statement");
/* Call enter */
emitBytes(OP_DUP, 0);
KrkToken _enter = syntheticToken("__enter__");
ssize_t ind = identifierConstant(&_enter);
EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind);
emitBytes(OP_CALL, 0);
emitByte(OP_POP); /* We don't care about the result */
beginScope();
block(blockWidth,"with");
endScope();
emitBytes(OP_DUP, 0);
KrkToken _exit = syntheticToken("__exit__");
ind = identifierConstant(&_exit);
EMIT_CONSTANT_OP(OP_GET_PROPERTY, ind);
emitBytes(OP_CALL, 0);
emitByte(OP_POP); /* We don't care about the result */
/* Scope exit pops context manager */
endScope();
}
static void ifStatement() {
/* Figure out what block level contains us so we can match our partner else */
size_t blockWidth = (parser.previous.type == TOKEN_INDENTATION) ? parser.previous.length : 0;
@ -1387,6 +1438,8 @@ static void statement() {
forStatement();
} else if (check(TOKEN_TRY)) {
tryStatement();
} else if (check(TOKEN_WITH)) {
withStatement();
} else {
if (match(TOKEN_RAISE)) {
raiseStatement();

View File

@ -521,7 +521,7 @@ void paint_krk_string(struct syntax_state * state, int type) {
char * syn_krk_keywords[] = {
"and","class","def","else","for","if","in","import",
"let","not","or","return","while","try","except","raise",
"continue","break","as","from","elif","lambda",
"continue","break","as","from","elif","lambda","with",
NULL
};
@ -531,7 +531,7 @@ char * syn_krk_types[] = {
"len", "str", "int", "float", "dir", "repr", /* global functions from __builtins__ */
"list","dict","range", /* builtin classes */
"object","exception","isinstance","type",
"print",
"print","set","any","all",
NULL
};

View File

@ -236,7 +236,10 @@ static KrkTokenType identifierType() {
} break;
case 't': return checkKeyword(1, "ry", TOKEN_TRY);
case 'T': return checkKeyword(1, "rue", TOKEN_TRUE);
case 'w': return checkKeyword(1, "hile", TOKEN_WHILE);
case 'w': if (MORE(1)) switch(scanner.start[1]) {
case 'h': return checkKeyword(2, "ile", TOKEN_WHILE);
case 'i': return checkKeyword(2, "th", TOKEN_WITH);
} break;
}
return TOKEN_IDENTIFIER;
}

View File

@ -66,6 +66,7 @@ typedef enum {
TOKEN_AS,
TOKEN_FROM,
TOKEN_LAMBDA,
TOKEN_WITH,
TOKEN_ERROR,
TOKEN_EOF,

View File

@ -247,6 +247,16 @@ static KrkValue krk_file_reject_init(int argc, KrkValue argv[]) {
return NONE_VAL();
}
static KrkValue krk_file_enter(int argc, KrkValue argv[]) {
/* Does nothing. */
return NONE_VAL();
}
static KrkValue krk_file_exit(int argc, KrkValue argv[]) {
/* Just an alias to close that triggers when a context manager is exited */
return krk_file_close(argc,argv);
}
static void makeFileInstance(KrkInstance * module, const char name[], FILE * file) {
KrkInstance * fileObject = krk_newInstance(FileClass);
krk_push(OBJECT_VAL(fileObject));
@ -290,6 +300,8 @@ KrkValue krk_module_onload_fileio(void) {
krk_defineNative(&FileClass->methods, ".__str__", krk_file_str);
krk_defineNative(&FileClass->methods, ".__repr__", krk_file_str);
krk_defineNative(&FileClass->methods, ".__init__", krk_file_reject_init);
krk_defineNative(&FileClass->methods, ".__enter__", krk_file_enter);
krk_defineNative(&FileClass->methods, ".__exit__", krk_file_exit);
/* Make an instance for stdout, stderr, and stdin */
makeFileInstance(module, "stdin",stdin);