Skip to content

Commit e561559

Browse files
Merge pull request RustPython#139 from RustPython/with
With statement implementation
2 parents 8a28d1f + 0139645 commit e561559

File tree

7 files changed

+150
-6
lines changed

7 files changed

+150
-6
lines changed

parser/src/ast.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub enum Statement {
7474
orelse: Option<Vec<LocatedStatement>>,
7575
},
7676
With {
77-
items: Expression,
77+
items: Vec<WithItem>,
7878
body: Vec<LocatedStatement>,
7979
},
8080
For {
@@ -106,6 +106,12 @@ pub enum Statement {
106106
},
107107
}
108108

109+
#[derive(Debug, PartialEq)]
110+
pub struct WithItem {
111+
pub context_expr: Expression,
112+
pub optional_vars: Option<Expression>,
113+
}
114+
109115
#[derive(Debug, PartialEq, Clone)]
110116
pub enum Expression {
111117
BoolOp {

parser/src/python.lalrpop

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,14 +299,28 @@ ExceptClause: ast::ExceptHandler = {
299299
};
300300

301301
WithStatement: ast::LocatedStatement = {
302-
<loc:@L> "with" <t:Test> "as" <_e:Expression> ":" <s:Suite> => {
302+
<loc:@L> "with" <i1:WithItem> <i2:("," WithItem)*> ":" <s:Suite> => {
303+
let mut items = vec![i1];
304+
for item in i2 {
305+
items.push(item.1);
306+
}
303307
ast::LocatedStatement {
304308
location: loc,
305-
node: ast::Statement::With { items: t, body: s },
309+
node: ast::Statement::With { items: items, body: s },
306310
}
307311
},
308312
};
309313

314+
WithItem: ast::WithItem = {
315+
<t:Test> <n:("as" Expression)?> => {
316+
let optional_vars = match n {
317+
Some(val) => Some(val.1),
318+
None => None,
319+
};
320+
ast::WithItem { context_expr: t, optional_vars }
321+
},
322+
};
323+
310324
FuncDef: ast::LocatedStatement = {
311325
<loc:@L> "def" <i:Identifier> <a:Parameters> ":" <s:Suite> => {
312326
ast::LocatedStatement {

tests/snippets/with.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
2+
3+
class ContextManager:
4+
def __enter__(self):
5+
print('Entrada')
6+
ls.append(1)
7+
return self
8+
9+
def __exit__(self, exc_type, exc_val, exc_tb):
10+
ls.append(2)
11+
print('Wiedersehen')
12+
13+
def __str__(self):
14+
ls.append(3)
15+
return "c'est moi!"
16+
17+
ls = []
18+
with ContextManager() as c:
19+
print(c)
20+
assert ls == [1, 3, 2]
21+
22+
class ContextManager2:
23+
def __enter__(self):
24+
print('Ni hau')
25+
ls.append(4)
26+
return ls
27+
28+
def __exit__(self, exc_type, exc_val, exc_tb):
29+
ls.append(5)
30+
print('Ajuus')
31+
32+
ls = []
33+
with ContextManager2() as c:
34+
print(c)
35+
assert c == [4]
36+
assert ls == [4, 5]
37+
38+
ls = []
39+
with ContextManager() as c1, ContextManager2() as c2:
40+
print(c1)
41+
assert c2 == [1, 4, 3]
42+
assert ls == [1, 4, 3, 5, 2]

vm/src/bytecode.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ pub enum Instruction {
118118
SetupExcept {
119119
handler: Label,
120120
},
121+
SetupWith {
122+
end: Label,
123+
},
124+
CleanupWith {
125+
end: Label,
126+
},
121127
PopBlock,
122128
Raise {
123129
argc: usize,

vm/src/compile.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,26 @@ impl Compiler {
199199
});
200200
self.set_label(end_label);
201201
}
202-
ast::Statement::With { items: _, body: _ } => {
203-
// TODO
202+
ast::Statement::With { items, body } => {
203+
let end_label = self.new_label();
204+
for item in items {
205+
self.compile_expression(&item.context_expr);
206+
self.emit(Instruction::SetupWith { end: end_label });
207+
match &item.optional_vars {
208+
Some(var) => {
209+
self.compile_store(var);
210+
}
211+
None => {
212+
self.emit(Instruction::Pop);
213+
}
214+
}
215+
}
216+
217+
self.compile_statements(body);
218+
for _ in 0..items.len() {
219+
self.emit(Instruction::CleanupWith { end: end_label });
220+
}
221+
self.set_label(end_label);
204222
}
205223
ast::Statement::For {
206224
target,

vm/src/frame.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ pub enum Block {
1616
#[allow(dead_code)]
1717
// TODO: Implement try/except blocks
1818
TryExcept { handler: bytecode::Label },
19+
With {
20+
end: bytecode::Label,
21+
context_manager: PyObjectRef,
22+
},
1923
}
2024

2125
pub struct Frame {

vm/src/vm.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,24 @@ impl VirtualMachine {
175175
self.current_frame().last_block()
176176
}
177177

178+
fn with_exit(&mut self) {
179+
// Assume top of stack is __exit__ method:
180+
// TODO: do we want to put the exit call on the stack?
181+
let exit_method = self.pop_value();
182+
let args = PyFuncArgs::default();
183+
// TODO: what happens when we got an error during handling exception?
184+
self.invoke(exit_method, args).unwrap();
185+
}
186+
178187
fn unwind_loop(&mut self) -> Block {
179188
loop {
180189
let block = self.pop_block();
181190
match block {
182191
Some(Block::Loop { start: _, end: __ }) => break block.unwrap(),
183192
Some(Block::TryExcept { .. }) => {}
193+
Some(Block::With { .. }) => {
194+
self.with_exit();
195+
}
184196
None => panic!("No block to break / continue"),
185197
}
186198
}
@@ -196,7 +208,10 @@ impl VirtualMachine {
196208
self.jump(handler);
197209
return None;
198210
}
199-
Some(_) => {}
211+
Some(Block::With { .. }) => {
212+
self.with_exit();
213+
}
214+
Some(Block::Loop { .. }) => {}
200215
None => break,
201216
}
202217
}
@@ -823,6 +838,45 @@ impl VirtualMachine {
823838
self.push_block(Block::TryExcept { handler: *handler });
824839
None
825840
}
841+
bytecode::Instruction::SetupWith { end } => {
842+
let context_manager = self.pop_value();
843+
// Call enter:
844+
match self.call_method(context_manager.clone(), "__enter__", vec![]) {
845+
Ok(obj) => {
846+
self.push_block(Block::With {
847+
end: *end,
848+
context_manager: context_manager.clone(),
849+
});
850+
self.push_value(obj);
851+
None
852+
}
853+
Err(err) => Some(Err(err)),
854+
}
855+
}
856+
bytecode::Instruction::CleanupWith { end: end1 } => {
857+
let block = self.pop_block().unwrap();
858+
if let Block::With {
859+
end: end2,
860+
context_manager,
861+
} = &block
862+
{
863+
assert!(end1 == end2);
864+
865+
// call exit now:
866+
// TODO: improve exception handling in context manager.
867+
let exc_type = self.ctx.none();
868+
let exc_val = self.ctx.none();
869+
let exc_tb = self.ctx.none();
870+
self.call_method(
871+
context_manager.clone(),
872+
"__exit__",
873+
vec![exc_type, exc_val, exc_tb],
874+
).unwrap();
875+
None
876+
} else {
877+
panic!("Block stack is incorrect, expected a with block");
878+
}
879+
}
826880
bytecode::Instruction::PopBlock => {
827881
self.pop_block();
828882
None

0 commit comments

Comments
 (0)