Add coroutines, async/await functionality, and gen.close()

This commit is contained in:
coolreader18
2019-10-16 20:07:30 -05:00
parent 52f1965c1c
commit 577cea92fe
10 changed files with 255 additions and 26 deletions

View File

@@ -55,6 +55,7 @@ bitflags! {
const HAS_ANNOTATIONS = 0x04;
const NEW_LOCALS = 0x08;
const IS_GENERATOR = 0x10;
const IS_COROUTINE = 0x20;
}
}
@@ -273,6 +274,7 @@ pub enum Instruction {
Reverse {
amount: usize,
},
GetAwaitable,
}
use self::Instruction::*;
@@ -549,6 +551,7 @@ impl Instruction {
FormatValue { spec, .. } => w!(FormatValue, spec), // TODO: write conversion
PopException => w!(PopException),
Reverse { amount } => w!(Reverse, amount),
GetAwaitable => w!(GetAwaitable),
}
}
}

View File

@@ -29,6 +29,7 @@ struct Compiler<O: OutputStream = BasicOutputStream> {
current_qualified_path: Option<String>,
in_loop: bool,
in_function_def: bool,
in_async_func: bool,
optimize: u8,
}
@@ -126,6 +127,7 @@ impl<O: OutputStream> Compiler<O> {
current_qualified_path: None,
in_loop: false,
in_function_def: false,
in_async_func: false,
optimize,
}
}
@@ -451,11 +453,7 @@ impl<O: OutputStream> Compiler<O> {
decorator_list,
returns,
} => {
if *is_async {
unimplemented!("async def");
} else {
self.compile_function_def(name, args, body, decorator_list, returns)?
}
self.compile_function_def(name, args, body, decorator_list, returns, *is_async)?;
}
ClassDef {
name,
@@ -797,11 +795,15 @@ impl<O: OutputStream> Compiler<O> {
body: &[ast::Statement],
decorator_list: &[ast::Expression],
returns: &Option<ast::Expression>, // TODO: use type hint somehow..
is_async: bool,
) -> Result<(), CompileError> {
// Create bytecode for this function:
// remember to restore self.in_loop to the original after the function is compiled
let was_in_loop = self.in_loop;
let was_in_function_def = self.in_function_def;
let was_in_async_func = self.in_async_func;
self.in_async_func = is_async;
self.in_loop = false;
self.in_function_def = true;
@@ -870,6 +872,10 @@ impl<O: OutputStream> Compiler<O> {
});
}
if is_async {
code.flags |= bytecode::CodeFlags::IS_COROUTINE;
}
self.emit(Instruction::LoadConst {
value: bytecode::Constant::Code {
code: Box::new(code),
@@ -891,6 +897,7 @@ impl<O: OutputStream> Compiler<O> {
self.current_qualified_path = old_qualified_path;
self.in_loop = was_in_loop;
self.in_function_def = was_in_function_def;
self.in_async_func = was_in_async_func;
Ok(())
}
@@ -1551,7 +1558,7 @@ impl<O: OutputStream> Compiler<O> {
self.emit(Instruction::BuildSlice { size });
}
Yield { value } => {
if !self.in_function_def {
if !self.in_function_def || self.in_async_func {
return Err(CompileError {
error: CompileErrorType::InvalidYield,
location: self.current_source_location.clone(),
@@ -1566,8 +1573,13 @@ impl<O: OutputStream> Compiler<O> {
};
self.emit(Instruction::YieldValue);
}
Await { .. } => {
unimplemented!("await");
Await { value } => {
self.compile_expression(value)?;
self.emit(Instruction::GetAwaitable);
self.emit(Instruction::LoadConst {
value: bytecode::Constant::None,
});
self.emit(Instruction::YieldFrom);
}
YieldFrom { value } => {
self.mark_generator();
@@ -1612,8 +1624,14 @@ impl<O: OutputStream> Compiler<O> {
self.load_name(name);
}
Lambda { args, body } => {
let was_in_loop = self.in_loop;
let was_in_function_def = self.in_function_def;
let was_in_async_func = self.in_async_func;
self.in_async_func = false;
self.in_loop = false;
self.in_function_def = true;
let name = "<lambda>".to_string();
// no need to worry about the self.loop_depth because there are no loops in lambda expressions
self.enter_function(&name, args)?;
self.compile_expression(body)?;
self.emit(Instruction::ReturnValue);
@@ -1629,6 +1647,10 @@ impl<O: OutputStream> Compiler<O> {
});
// Turn code object into function object:
self.emit(Instruction::MakeFunction);
self.in_loop = was_in_loop;
self.in_function_def = was_in_function_def;
self.in_async_func = was_in_async_func;
}
Comprehension { kind, generators } => {
self.compile_comprehension(kind, generators)?;

View File

@@ -900,6 +900,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) {
"UserWarning" => ctx.exceptions.user_warning.clone(),
"KeyboardInterrupt" => ctx.exceptions.keyboard_interrupt.clone(),
"GeneratorExit" => ctx.exceptions.generator_exit.clone(),
"SystemExit" => ctx.exceptions.system_exit.clone(),
});
}

View File

@@ -271,6 +271,7 @@ pub struct ExceptionZoo {
pub user_warning: PyClassRef,
pub keyboard_interrupt: PyClassRef,
pub generator_exit: PyClassRef,
pub system_exit: PyClassRef,
}
@@ -327,6 +328,7 @@ impl ExceptionZoo {
let user_warning = create_type("UserWarning", &type_type, &warning);
let keyboard_interrupt = create_type("KeyboardInterrupt", &type_type, &base_exception_type);
let generator_exit = create_type("GeneratorExit", &type_type, &base_exception_type);
let system_exit = create_type("SystemExit", &type_type, &base_exception_type);
ExceptionZoo {
@@ -376,6 +378,7 @@ impl ExceptionZoo {
reference_error,
user_warning,
keyboard_interrupt,
generator_exit,
system_exit,
}
}

View File

@@ -8,16 +8,16 @@ use crate::bytecode;
use crate::function::PyFuncArgs;
use crate::obj::objbool;
use crate::obj::objcode::PyCodeRef;
use crate::obj::objcoroutine::PyCoroutine;
use crate::obj::objdict::{PyDict, PyDictRef};
use crate::obj::objgenerator::PyGenerator;
use crate::obj::objiter;
use crate::obj::objlist;
use crate::obj::objslice::PySlice;
use crate::obj::objstr;
use crate::obj::objstr::PyString;
use crate::obj::objstr::{self, PyString};
use crate::obj::objtraceback::{PyTraceback, PyTracebackRef};
use crate::obj::objtuple::PyTuple;
use crate::obj::objtype;
use crate::obj::objtype::PyClassRef;
use crate::obj::objtype::{self, PyClassRef};
use crate::pyobject::{
IdProtocol, ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol,
};
@@ -230,7 +230,7 @@ impl Frame {
exc_type: PyClassRef,
exc_val: PyObjectRef,
exc_tb: PyObjectRef,
) -> PyResult {
) -> PyResult<ExecutionResult> {
if let bytecode::Instruction::YieldFrom = self.code.instructions[self.lasti.get()] {
let coro = self.last_value();
vm.call_method(
@@ -243,7 +243,8 @@ impl Frame {
self.lasti.set(self.lasti.get() + 1);
let val = objiter::stop_iter_value(vm, &err)?;
self._send(coro, val, vm)
})
})?;
Ok(ExecutionResult::Return(vm.get_none()))
} else {
let exception = vm.new_exception_obj(exc_type, vec![exc_val])?;
match self.unwind_blocks(vm, UnwindReason::Raising { exception }) {
@@ -251,7 +252,6 @@ impl Frame {
Ok(Some(result)) => Ok(result),
Err(exception) => Err(exception),
}
.and_then(|res| res.into_result(vm))
}
}
@@ -465,6 +465,24 @@ impl Frame {
self.push_value(iter_obj);
Ok(None)
}
bytecode::Instruction::GetAwaitable => {
let awaited_obj = self.pop_value();
let awaitable = if awaited_obj.payload_is::<crate::obj::objcoroutine::PyCoroutine>()
{
awaited_obj
} else {
let await_method =
vm.get_method_or_type_error(awaited_obj.clone(), "__await__", || {
format!(
"object {} can't be used in 'await' expression",
awaited_obj.class().name,
)
})?;
vm.invoke(&await_method, vec![])?
};
self.push_value(awaitable);
Ok(None)
}
bytecode::Instruction::ForIter { target } => self.execute_for_iter(vm, *target),
bytecode::Instruction::MakeFunction => self.execute_make_function(vm),
bytecode::Instruction::CallFunction { typ } => self.execute_call_function(vm, typ),
@@ -1016,7 +1034,11 @@ impl Frame {
}
fn _send(&self, coro: PyObjectRef, val: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if vm.is_none(&val) {
if let Some(gen) = coro.payload::<PyGenerator>() {
gen.send(val, vm)
} else if let Some(coro) = coro.payload::<PyCoroutine>() {
coro.send(val, vm)
} else if vm.is_none(&val) {
objiter::call_next(vm, &coro)
} else {
vm.call_method(&coro, "send", vec![val])

View File

@@ -8,6 +8,7 @@ pub mod objbytes;
pub mod objclassmethod;
pub mod objcode;
pub mod objcomplex;
pub mod objcoroutine;
pub mod objdict;
pub mod objellipsis;
pub mod objenumerate;

137
vm/src/obj/objcoroutine.rs Normal file
View File

@@ -0,0 +1,137 @@
use super::objtype::{isinstance, issubclass, PyClassRef};
use crate::frame::{ExecutionResult, FrameRef};
use crate::function::OptionalArg;
use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
pub type PyCoroutineRef = PyRef<PyCoroutine>;
#[pyclass(name = "coroutine")]
#[derive(Debug)]
pub struct PyCoroutine {
frame: FrameRef,
}
impl PyValue for PyCoroutine {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.types.coroutine_type.clone()
}
}
#[pyimpl]
impl PyCoroutine {
pub fn new(frame: FrameRef, vm: &VirtualMachine) -> PyCoroutineRef {
PyCoroutine { frame }.into_ref(vm)
}
#[pymethod]
pub(crate) fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self.frame.push_value(value.clone());
vm.run_frame(self.frame.clone())?.into_result(vm)
}
#[pymethod]
fn throw(
&self,
exc_type: PyClassRef,
exc_val: OptionalArg,
exc_tb: OptionalArg,
vm: &VirtualMachine,
) -> PyResult {
// TODO what should we do with the other parameters? CPython normalises them with
// PyErr_NormalizeException, do we want to do the same.
if !issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) {
return Err(vm.new_type_error("Can't throw non exception".to_string()));
}
vm.frames.borrow_mut().push(self.frame.clone());
let result = self
.frame
.gen_throw(
vm,
exc_type,
exc_val.unwrap_or(vm.get_none()),
exc_tb.unwrap_or(vm.get_none()),
)
.and_then(|res| res.into_result(vm));
vm.frames.borrow_mut().pop();
result
}
#[pymethod]
fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
vm.frames.borrow_mut().push(self.frame.clone());
let result = self.frame.gen_throw(
vm,
vm.ctx.exceptions.generator_exit.clone(),
vm.get_none(),
vm.get_none(),
);
vm.frames.borrow_mut().pop();
match result {
Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception(
vm.ctx.exceptions.runtime_error.clone(),
"generator ignored GeneratorExit".to_string(),
)),
Err(e) => {
if isinstance(&e, &vm.ctx.exceptions.generator_exit) {
Ok(())
} else {
Err(e)
}
}
_ => Ok(()),
}
}
#[pymethod(name = "__await__")]
fn r#await(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyCoroutineWrapper {
PyCoroutineWrapper { coro: zelf }
}
}
#[pyclass(name = "coroutine_wrapper")]
#[derive(Debug)]
pub struct PyCoroutineWrapper {
coro: PyCoroutineRef,
}
impl PyValue for PyCoroutineWrapper {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.types.coroutine_wrapper_type.clone()
}
}
#[pyimpl]
impl PyCoroutineWrapper {
#[pymethod(name = "__iter__")]
fn iter(zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyRef<Self> {
zelf
}
#[pymethod(name = "__next__")]
fn next(&self, vm: &VirtualMachine) -> PyResult {
self.coro.send(vm.get_none(), vm)
}
#[pymethod]
fn send(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self.coro.send(val, vm)
}
#[pymethod]
fn throw(
&self,
exc_type: PyClassRef,
exc_val: OptionalArg,
exc_tb: OptionalArg,
vm: &VirtualMachine,
) -> PyResult {
self.coro.throw(exc_type, exc_val, exc_tb, vm)
}
}
pub fn init(ctx: &PyContext) {
PyCoroutine::extend_class(ctx, &ctx.types.coroutine_type);
PyCoroutineWrapper::extend_class(ctx, &ctx.types.coroutine_wrapper_type);
}

View File

@@ -2,8 +2,8 @@
* The mythical generator.
*/
use super::objtype::{issubclass, PyClassRef};
use crate::frame::FrameRef;
use super::objtype::{isinstance, issubclass, PyClassRef};
use crate::frame::{ExecutionResult, FrameRef};
use crate::function::OptionalArg;
use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue};
use crate::vm::VirtualMachine;
@@ -39,7 +39,7 @@ impl PyGenerator {
}
#[pymethod]
fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
pub(crate) fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self.frame.push_value(value.clone());
vm.run_frame(self.frame.clone())?.into_result(vm)
@@ -59,15 +59,44 @@ impl PyGenerator {
return Err(vm.new_type_error("Can't throw non exception".to_string()));
}
vm.frames.borrow_mut().push(self.frame.clone());
let result = self.frame.gen_throw(
vm,
exc_type,
exc_val.unwrap_or(vm.get_none()),
exc_tb.unwrap_or(vm.get_none()),
);
let result = self
.frame
.gen_throw(
vm,
exc_type,
exc_val.unwrap_or(vm.get_none()),
exc_tb.unwrap_or(vm.get_none()),
)
.and_then(|res| res.into_result(vm));
vm.frames.borrow_mut().pop();
result
}
#[pymethod]
fn close(&self, vm: &VirtualMachine) -> PyResult<()> {
vm.frames.borrow_mut().push(self.frame.clone());
let result = self.frame.gen_throw(
vm,
vm.ctx.exceptions.generator_exit.clone(),
vm.get_none(),
vm.get_none(),
);
vm.frames.borrow_mut().pop();
match result {
Ok(ExecutionResult::Yield(_)) => Err(vm.new_exception(
vm.ctx.exceptions.runtime_error.clone(),
"generator ignored GeneratorExit".to_string(),
)),
Err(e) => {
if isinstance(&e, &vm.ctx.exceptions.generator_exit) {
Ok(())
} else {
Err(e)
}
}
_ => Ok(()),
}
}
}
pub fn init(ctx: &PyContext) {

View File

@@ -4,6 +4,7 @@ use crate::obj::objbytes;
use crate::obj::objclassmethod;
use crate::obj::objcode;
use crate::obj::objcomplex;
use crate::obj::objcoroutine;
use crate::obj::objdict;
use crate::obj::objellipsis;
use crate::obj::objenumerate;
@@ -50,6 +51,8 @@ pub struct TypeZoo {
pub bool_type: PyClassRef,
pub classmethod_type: PyClassRef,
pub code_type: PyClassRef,
pub coroutine_type: PyClassRef,
pub coroutine_wrapper_type: PyClassRef,
pub dict_type: PyClassRef,
pub enumerate_type: PyClassRef,
pub filter_type: PyClassRef,
@@ -122,6 +125,8 @@ impl TypeZoo {
let weakref_type = create_type("ref", &type_type, &object_type);
let weakproxy_type = create_type("weakproxy", &type_type, &object_type);
let generator_type = create_type("generator", &type_type, &object_type);
let coroutine_type = create_type("coroutine", &type_type, &object_type);
let coroutine_wrapper_type = create_type("coroutine_wrapper", &type_type, &object_type);
let bound_method_type = create_type("method", &type_type, &object_type);
let str_type = create_type("str", &type_type, &object_type);
let list_type = create_type("list", &type_type, &object_type);
@@ -170,6 +175,8 @@ impl TypeZoo {
bytes_type,
bytesiterator_type,
code_type,
coroutine_type,
coroutine_wrapper_type,
complex_type,
classmethod_type,
int_type,
@@ -291,6 +298,7 @@ pub fn initialize_types(context: &PyContext) {
objstaticmethod::init(&context);
objclassmethod::init(&context);
objgenerator::init(&context);
objcoroutine::init(&context);
objint::init(&context);
objfloat::init(&context);
objcomplex::init(&context);

View File

@@ -26,6 +26,7 @@ use crate::import;
use crate::obj::objbool;
use crate::obj::objbuiltinfunc::PyBuiltinFunction;
use crate::obj::objcode::{PyCode, PyCodeRef};
use crate::obj::objcoroutine::PyCoroutine;
use crate::obj::objdict::PyDictRef;
use crate::obj::objfunction::{PyFunction, PyMethod};
use crate::obj::objgenerator::PyGenerator;
@@ -664,6 +665,8 @@ impl VirtualMachine {
// If we have a generator, create a new generator
if code.flags.contains(bytecode::CodeFlags::IS_GENERATOR) {
Ok(PyGenerator::new(frame, self).into_object())
} else if code.flags.contains(bytecode::CodeFlags::IS_COROUTINE) {
Ok(PyCoroutine::new(frame, self).into_object())
} else {
self.run_frame_full(frame)
}