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:
parent
bb6b4d09f5
commit
52427a5147
39
README.md
39
README.md
@ -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.
|
||||
|
53
compiler.c
53
compiler.c
@ -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 = ¤t->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();
|
||||
|
4
rline.c
4
rline.c
@ -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
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ typedef enum {
|
||||
TOKEN_AS,
|
||||
TOKEN_FROM,
|
||||
TOKEN_LAMBDA,
|
||||
TOKEN_WITH,
|
||||
|
||||
TOKEN_ERROR,
|
||||
TOKEN_EOF,
|
||||
|
12
src/fileio.c
12
src/fileio.c
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user