///|
/// Generator runtime (minimal, resumable).
///|
priv enum GenFrame {
Block(body~ : Array[Stmt], index~ : Int32, loop_depth~ : Int32)
// Marker pushed when yielding to the caller. Needed so `throw()` can raise
// into the suspended generator at the yield point (contextlib.contextmanager).
AfterYield
AfterAssign(target~ : Target, value~ : Value)
For(
target~ : Target,
iterator~ : Value,
body~ : Array[Stmt],
else_body~ : Array[Stmt],
broke~ : Bool,
done~ : Bool,
loop_depth~ : Int32
)
While(
condition~ : Expr,
body~ : Array[Stmt],
else_body~ : Array[Stmt],
broke~ : Bool,
done~ : Bool,
loop_depth~ : Int32
)
YieldFrom(iterator~ : Value)
WithExit(exit~ : Value)
Try(
handlers~ : Array[ExceptHandler],
else_body~ : Array[Stmt],
finally_body~ : Array[Stmt],
phase~ : Int32,
pending~ : RuntimeError?,
handler_active~ : Bool,
loop_depth~ : Int32
)
}
///|
priv struct GeneratorState {
mut frames : Array[GenFrame]
locals : Array[(String, Value)]
globals : Array[(String, Value)]
builtins : Array[(String, Value)]
io : MockIO
global_names : Array[String]
nonlocal_names : Array[String]
closure : Array[(String, Value)]
name : String
filename : String
is_async : Bool
mut started : Bool
mut done : Bool
}
///|
let generator_registry : Ref[Array[Ref[GeneratorState]]] = { val: [], }
///|
fn make_generator_instance(id : Int32) -> Value {
Value::Instance(InstanceValue::{
class: ClassValue::{ name: "generator", bases: [], dict: [], },
dict: [("id", Value::Int(@bigint.BigInt::from_int(id)))],
})
}
///|
fn make_async_generator_instance(id : Int32) -> Value {
Value::Instance(InstanceValue::{
class: ClassValue::{ name: "async_generator", bases: [], dict: [], },
dict: [("gen_id", Value::Int(@bigint.BigInt::from_int(id)))],
})
}
///|
fn generator_id_from_instance(
inst : InstanceValue,
) -> Result[Int32?, RuntimeError] {
match get_named_value(inst.dict, "id") {
Some(Value::Int(v)) =>
match bigint_to_int_checked(v) {
Ok(i) => Ok(Some(i))
Err(err) => Err(err)
}
Some(Value::Bool(v)) => Ok(Some(if v { 1 } else { 0 }))
_ => Ok(None)
}
}
///|
fn async_generator_id_from_instance(
inst : InstanceValue,
) -> Result[Int32?, RuntimeError] {
match get_named_value(inst.dict, "gen_id") {
Some(Value::Int(v)) =>
match bigint_to_int_checked(v) {
Ok(i) => Ok(Some(i))
Err(err) => Err(err)
}
Some(Value::Bool(v)) => Ok(Some(if v { 1 } else { 0 }))
_ => Ok(None)
}
}
///|
fn generator_new(
body : Array[Stmt],
locals : Array[(String, Value)],
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
global_names : Array[String],
nonlocal_names : Array[String],
closure : Array[(String, Value)],
name : String,
filename : String,
) -> Value {
let frames : Array[GenFrame] = [GenFrame::Block(body~, index=0, loop_depth=0)]
let state : Ref[GeneratorState] = {
val: GeneratorState::{
frames,
locals,
globals,
builtins,
io,
global_names,
nonlocal_names,
closure,
name,
filename,
is_async: false,
started: false,
done: false,
},
}
let id = generator_registry.val.length()
generator_registry.val.push(state)
make_generator_instance(id)
}
///|
fn async_generator_new(
body : Array[Stmt],
locals : Array[(String, Value)],
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
global_names : Array[String],
nonlocal_names : Array[String],
closure : Array[(String, Value)],
name : String,
filename : String,
) -> Value {
let frames : Array[GenFrame] = [GenFrame::Block(body~, index=0, loop_depth=0)]
let state : Ref[GeneratorState] = {
val: GeneratorState::{
frames,
locals,
globals,
builtins,
io,
global_names,
nonlocal_names,
closure,
name,
filename,
is_async: true,
started: false,
done: false,
},
}
let id = generator_registry.val.length()
generator_registry.val.push(state)
make_async_generator_instance(id)
}
///|
fn is_stop_iteration(err : RuntimeError) -> Bool {
err.exc_type == "StopIteration"
}
///|
fn generator_stop_iteration() -> RuntimeError {
make_runtime_error(RuntimeErrorKind::Runtime, "StopIteration".to_string())
}
///|
fn generator_stop_iteration_with_value(value : Value) -> RuntimeError {
let err = generator_stop_iteration()
RuntimeError::{
kind: err.kind,
message: err.message,
span: err.span,
traceback: err.traceback,
traceback_envs: err.traceback_envs,
exc_type: err.exc_type,
exc_args: err.exc_args,
exc_value: Some(value),
exc_cause: err.exc_cause,
exc_context: err.exc_context,
exc_suppress_context: err.exc_suppress_context,
}
}
///|
fn generator_state_from_value(
generator_value : Value,
) -> Result[Ref[GeneratorState], RuntimeError] {
match generator_value {
Value::Instance(inst) =>
if inst.class.name != "generator" {
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"'" + inst.class.name + "' object is not a generator",
),
)
} else {
let id_opt = match generator_id_from_instance(inst) {
Ok(v) => v
Err(err) => return Err(err)
}
let id = match id_opt {
Some(id) => id
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"RuntimeError: invalid generator".to_string(),
),
)
}
if id < 0 || id >= generator_registry.val.length() {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"RuntimeError: invalid generator id".to_string(),
),
)
}
Ok(generator_registry.val[id])
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"object is not a generator".to_string(),
),
)
}
}
///|
fn async_generator_state_from_value(
generator_value : Value,
) -> Result[Ref[GeneratorState], RuntimeError] {
match generator_value {
Value::Instance(inst) =>
if inst.class.name != "async_generator" {
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"'" + inst.class.name + "' object is not an async generator",
),
)
} else {
let id_opt = match async_generator_id_from_instance(inst) {
Ok(v) => v
Err(err) => return Err(err)
}
let id = match id_opt {
Some(id) => id
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"RuntimeError: invalid async generator".to_string(),
),
)
}
if id < 0 || id >= generator_registry.val.length() {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"RuntimeError: invalid async generator id".to_string(),
),
)
}
Ok(generator_registry.val[id])
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::Type,
"object is not an async generator".to_string(),
),
)
}
}
///|
fn generator_unwind_to_loop(
frames : Array[GenFrame],
is_break : Bool,
) -> Result[Unit, RuntimeError] {
let mut i = frames.length()
while i > 0 {
i = i - 1
match frames[i] {
GenFrame::For(
target~,
iterator~,
body~,
else_body~,
broke~,
done~,
loop_depth~
) => {
let _ = broke
let _ = done
// Drop everything above the loop frame.
while frames.length() > i + 1 {
let _ = frames.pop()
}
if is_break {
frames[i] = GenFrame::For(
target~,
iterator~,
body~,
else_body~,
broke=true,
done=true,
loop_depth~,
)
}
// For `continue`, no special state: execution resumes at loop header.
return Ok(())
}
GenFrame::While(condition~, body~, else_body~, broke~, done~, loop_depth~) => {
let _ = broke
let _ = done
while frames.length() > i + 1 {
let _ = frames.pop()
}
if is_break {
frames[i] = GenFrame::While(
condition~,
body~,
else_body~,
broke=true,
done=true,
loop_depth~,
)
}
return Ok(())
}
_ => ()
}
}
Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
if is_break {
"SyntaxError: 'break' outside loop".to_string()
} else {
"SyntaxError: 'continue' not properly in loop".to_string()
},
),
)
}
///|
fn generator_step_statement(
stmt : Stmt,
state : Ref[GeneratorState],
loop_depth : Int32,
) -> Result[Value?, RuntimeError] {
match stmt {
Stmt::WithSpan(span~, stmt~) => {
push_active_span(span)
let result = generator_step_statement(stmt, state, loop_depth)
pop_active_span()
result
}
Stmt::ExprStmt(expr) => {
match expr {
Expr::YieldFrom(inner) => {
let iter_value = match
eval_expr_with_env(
inner,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iterator = match
iter_value_to_iterator(
iter_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
state.val.frames.push(GenFrame::YieldFrom(iterator~))
return Ok(None)
}
Expr::Yield(expr_opt) => {
let value = match expr_opt {
Some(inner) =>
match
eval_expr_with_env(
inner,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
None => Value::None
}
state.val.frames.push(GenFrame::AfterYield)
return Ok(Some(value))
}
_ => ()
}
let _ = match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
Ok(None)
}
Stmt::Assign(target~, value~) => {
match value {
Expr::YieldFrom(inner) => {
let iter_value = match
eval_expr_with_env(
inner,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iterator = match
iter_value_to_iterator(
iter_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
state.val.frames.push(
GenFrame::AfterAssign(target~, value=Value::None),
)
state.val.frames.push(GenFrame::YieldFrom(iterator~))
return Ok(None)
}
Expr::Yield(expr_opt) => {
let yield_value = match expr_opt {
Some(inner) =>
match
eval_expr_with_env(
inner,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
None => Value::None
}
state.val.frames.push(
GenFrame::AfterAssign(target~, value=Value::None),
)
return Ok(Some(yield_value))
}
_ => ()
}
let eval_value = match
eval_expr_with_env(
value,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let _ = match
assign_target_with_env(
target,
eval_value,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
Ok(None)
}
Stmt::AnnAssign(target~, annotation~, value~) => {
match value {
Some(value_expr) => {
match value_expr {
Expr::YieldFrom(inner) => {
let iter_value = match
eval_expr_with_env(
inner,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iterator = match
iter_value_to_iterator(
iter_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
state.val.frames.push(
GenFrame::AfterAssign(target~, value=Value::None),
)
state.val.frames.push(GenFrame::YieldFrom(iterator~))
return Ok(None)
}
Expr::Yield(expr_opt) => {
let yield_value = match expr_opt {
Some(inner) =>
match
eval_expr_with_env(
inner,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
None => Value::None
}
state.val.frames.push(
GenFrame::AfterAssign(target~, value=Value::None),
)
return Ok(Some(yield_value))
}
_ => ()
}
let eval_value = match
eval_expr_with_env(
value_expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let _ = match
assign_target_with_env(
target,
eval_value,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
()
}
None => ()
}
match target {
Target::Name(name) => {
// Only simple-name annotations contribute to __annotations__. For
// attribute/subscript targets, ignore the annotation expression.
let ann_value = match
eval_expr_with_env(
annotation,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let pairs = match
get_scoped_value(
state.val.locals,
state.val.globals,
"__annotations__",
) {
Some(Value::Dict(pairs)) => pairs
Some(_) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"__annotations__ must be a dict".to_string(),
),
)
None => {
let pairs : Array[(Value, Value)] = []
set_scoped_value(
state.val.locals,
state.val.globals,
"__annotations__",
Value::Dict(pairs),
)
pairs
}
}
let _ = match dict_set_item(pairs, Value::Str(name), ann_value) {
Ok(v) => v
Err(err) => return Err(err)
}
}
_ => ()
}
Ok(None)
}
Stmt::AugAssign(target~, op~, value~) => {
let right_val = match
eval_expr_with_env(
value,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
match target {
Target::Name(name) => {
let current = match
lookup_name_value(
name,
state.val.locals,
state.val.globals,
state.val.builtins,
) {
Ok(v) => v
Err(err) => return Err(err)
}
let next = match
eval_augassign_op_values(
op,
current,
right_val,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
set_scoped_value(state.val.locals, state.val.globals, name, next)
Ok(None)
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"augmented assignment target not supported in generator".to_string(),
),
)
}
}
Stmt::Return(expr_opt) => {
let value = match expr_opt {
Some(expr) =>
match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
None => Value::None
}
Err(generator_stop_iteration_with_value(value))
}
Stmt::Yield(expr_opt) => {
let value = match expr_opt {
Some(expr) =>
match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
None => Value::None
}
state.val.frames.push(GenFrame::AfterYield)
Ok(Some(value))
}
Stmt::YieldFrom(expr) => {
let iter_value = match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iterator = match
iter_value_to_iterator(
iter_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
state.val.frames.push(GenFrame::YieldFrom(iterator~))
Ok(None)
}
Stmt::If(condition~, body~, else_body~) => {
let cond_value = match
eval_expr_with_env(
condition,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let ok = match
truthy_from_value_with_env(
cond_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
if ok {
state.val.frames.push(GenFrame::Block(body~, index=0, loop_depth~))
} else if else_body.length() > 0 {
state.val.frames.push(
GenFrame::Block(body=else_body, index=0, loop_depth~),
)
}
Ok(None)
}
Stmt::While(condition~, body~, else_body~) => {
state.val.frames.push(
GenFrame::While(
condition~,
body~,
else_body~,
broke=false,
done=false,
loop_depth~,
),
)
Ok(None)
}
Stmt::For(target~, iter~, body~, else_body~) => {
let iter_value = match
eval_expr_with_env(
iter,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iterator = match
iter_value_to_iterator(
iter_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
state.val.frames.push(
GenFrame::For(
target~,
iterator~,
body~,
else_body~,
broke=false,
done=false,
loop_depth~,
),
)
Ok(None)
}
Stmt::Break =>
if loop_depth > 0 {
let _ = match generator_unwind_to_loop(state.val.frames, true) {
Ok(v) => v
Err(err) => return Err(err)
}
Ok(None)
} else {
Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: 'break' outside loop".to_string(),
),
)
}
Stmt::Continue =>
if loop_depth > 0 {
let _ = match generator_unwind_to_loop(state.val.frames, false) {
Ok(v) => v
Err(err) => return Err(err)
}
Ok(None)
} else {
Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: 'continue' not properly in loop".to_string(),
),
)
}
Stmt::Import(module_name~, alias_name~) => {
let module_value = match
import_module(
module_name,
state.val.globals,
state.val.builtins,
state.val.io,
current_config(),
) {
Ok(v) => v
Err(err) => return Err(err)
}
let binding = match alias_name {
Some(name) => name
None => module_top_name(module_name)
}
let binding_value = match alias_name {
Some(_) => module_value
None =>
if module_name.contains(".") {
match module_cache_get(binding) {
Some(value) => value
None => module_value
}
} else {
module_value
}
}
set_scoped_value(
state.val.locals,
state.val.globals,
binding,
binding_value,
)
Ok(None)
}
Stmt::FromImport(module_name~, names~) => {
let resolved_name = match
resolve_relative_module_name(module_name, state.val.globals) {
Ok(v) => v
Err(err) => return Err(err)
}
let module_value = match
import_module(
resolved_name,
state.val.globals,
state.val.builtins,
state.val.io,
current_config(),
) {
Ok(v) => v
Err(err) => return Err(err)
}
for item in names {
let name = item.0
let alias_opt = item.1
if name == "*" {
match module_value {
Value::Instance(inst) =>
for pair in inst.dict {
if pair.0 != "__name__" && !pair.0.has_prefix("_") {
set_scoped_value(
state.val.locals,
state.val.globals,
pair.0,
pair.1,
)
}
}
_ => ()
}
continue
}
let imported = match
get_attr_from_value(
module_value,
name,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) =>
match err.kind {
RuntimeErrorKind::Attribute => {
let submodule = resolved_name + "." + name
match
import_module(
submodule,
state.val.globals,
state.val.builtins,
state.val.io,
current_config(),
) {
Ok(value) => value
Err(_) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: cannot import name '" +
name +
"' from '" +
resolved_name +
"'",
),
)
}
}
_ => return Err(err)
}
}
let binding = match alias_opt {
Some(alias_name) => alias_name
None => name
}
set_scoped_value(state.val.locals, state.val.globals, binding, imported)
}
Ok(None)
}
Stmt::Pass | Stmt::Global(_) | Stmt::Nonlocal(_) => Ok(None)
Stmt::With(context~, target~, body~) => {
let ctx_value = match
eval_expr_with_env(
context,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
let enter_value = match
get_attr_from_value(
ctx_value,
"__enter__",
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
let entered = match
call_callable_with_env(
enter_value,
[],
[],
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
match target {
Some(name) =>
set_scoped_value(state.val.locals, state.val.globals, name, entered)
None => ()
}
let exit_value = match
get_attr_from_value(
ctx_value,
"__exit__",
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
state.val.frames.push(GenFrame::WithExit(exit=exit_value))
state.val.frames.push(GenFrame::Block(body~, index=0, loop_depth~))
Ok(None)
}
Stmt::Assert(condition~, message~) => {
let value = match
eval_expr_with_env(
condition,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let ok = match
truthy_from_value_with_env(
value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
if !ok {
let text = match message {
Some(expr) => {
let msg_value = match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
"AssertionError: " + value_to_string(msg_value)
}
None => "AssertionError: ".to_string()
}
return Err(make_runtime_error(RuntimeErrorKind::Runtime, text))
}
Ok(None)
}
Stmt::Raise(exc~, cause~) =>
match exc {
None =>
match active_exception() {
Some(err) => Err(err)
None =>
Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"RuntimeError: No active exception to reraise".to_string(),
),
)
}
Some(expr) => {
let context_err_opt = active_exception()
let raised_value = match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let inst_value = match
ensure_exception_instance_from_value(
raised_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
let err0 = match inst_value {
Value::Instance(inst) => runtime_error_from_exception_instance(inst)
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"RuntimeError: invalid exception".to_string(),
),
)
}
let err1 = match (context_err_opt, err0.exc_context) {
(Some(ctx_err), None) => {
let ctx_value = exception_value_from_runtime_error(
ctx_err,
state.val.globals,
state.val.builtins,
state.val.io,
)
RuntimeError::{
kind: err0.kind,
message: err0.message,
span: err0.span,
traceback: err0.traceback,
traceback_envs: err0.traceback_envs,
exc_type: err0.exc_type,
exc_args: err0.exc_args,
exc_value: err0.exc_value,
exc_cause: err0.exc_cause,
exc_context: Some(ctx_value),
exc_suppress_context: err0.exc_suppress_context,
}
}
_ => err0
}
let err2 = match cause {
None => err1
Some(cause_expr) => {
let cause_value = match
eval_expr_with_env(
cause_expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(e) => return Err(e)
}
let cause_inst = match cause_value {
Value::None => Value::None
_ =>
match
ensure_exception_instance_from_value(
cause_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(e) => return Err(e)
}
}
RuntimeError::{
kind: err1.kind,
message: err1.message,
span: err1.span,
traceback: err1.traceback,
traceback_envs: err1.traceback_envs,
exc_type: err1.exc_type,
exc_args: err1.exc_args,
exc_value: err1.exc_value,
exc_cause: Some(cause_inst),
exc_context: err1.exc_context,
exc_suppress_context: true,
}
}
}
Err(err2)
}
}
Stmt::Match(subject~, cases~) => {
let subject_value = match
eval_expr_with_env(
subject,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for case in cases {
let pattern = case.0
let guard_expr_opt : Expr? = case.1
let case_body = case.2
match match_pattern(subject_value, pattern) {
Some(bindings) => {
let previous : Array[(String, Value?)] = []
for binding in bindings {
previous.push(
(
binding.0,
get_scoped_value(
state.val.locals,
state.val.globals,
binding.0,
),
),
)
set_scoped_value(
state.val.locals,
state.val.globals,
binding.0,
binding.1,
)
}
let mut guard_ok = true
if guard_expr_opt is Some(guard_expr) {
let guard_value = match
eval_expr_with_env(
guard_expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => return Err(err)
}
guard_ok = match
truthy_from_value_with_env(
guard_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
}
if guard_ok {
state.val.frames.push(
GenFrame::Block(body=case_body, index=0, loop_depth~),
)
return Ok(None)
}
// Guard failed: rollback bindings before trying the next case.
for prev in previous {
if prev.1 is Some(value) {
set_scoped_value(
state.val.locals,
state.val.globals,
prev.0,
value,
)
} else {
let _ = delete_scoped_value(
state.val.locals,
state.val.globals,
prev.0,
)
}
}
}
None => ()
}
}
Ok(None)
}
Stmt::Try(body~, handlers~, else_body~, finally_body~) => {
// except* (PEP 654) is not supported in the generator interpreter yet.
for h in handlers {
if h.is_star {
return Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"except* not implemented in generator".to_string(),
),
)
}
}
state.val.frames.push(
GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase=0,
pending=None,
handler_active=false,
loop_depth~,
),
)
state.val.frames.push(GenFrame::Block(body~, index=0, loop_depth~))
Ok(None)
}
Stmt::Del(targets) => {
for target in targets {
let _ = match
delete_target_with_env(
target,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
}
Ok(None)
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"statement not implemented in generator".to_string(),
),
)
}
}
///|
fn generator_try_handler_matches(
err : RuntimeError,
handler : ExceptHandler,
state : Ref[GeneratorState],
) -> Result[Bool, RuntimeError] {
if handler.is_star {
return Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"except* not implemented in generator".to_string(),
),
)
}
match handler.exc {
None => Ok(true)
Some(expr) => {
let exc_value = match
eval_expr_with_env(
expr,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(e) => return Err(e)
}
match exc_value {
Value::Class(klass) =>
exception_matches_handler(err, klass, state.val.builtins)
Value::Tuple(items) => {
for item in items {
match item {
Value::Class(klass) =>
match
exception_matches_handler(err, klass, state.val.builtins) {
Ok(true) => return Ok(true)
Ok(false) => ()
Err(e) => return Err(e)
}
_ => ()
}
}
Ok(false)
}
_ => Ok(false)
}
}
}
}
///|
fn generator_dispatch_error(
state : Ref[GeneratorState],
err : RuntimeError,
) -> Result[Bool, RuntimeError] {
let current = err
while true {
// Find the innermost cleanup frame (`with` exit or `try`).
let mut cleanup_index : Int32? = None
let mut i = state.val.frames.length()
while i > 0 {
i = i - 1
match state.val.frames[i] {
GenFrame::WithExit(..) | GenFrame::Try(..) => {
cleanup_index = Some(i)
break
}
_ => ()
}
}
match cleanup_index {
None => return Ok(false)
Some(idx) => {
// Unwind everything above the cleanup frame.
while state.val.frames.length() > idx + 1 {
let _ = state.val.frames.pop()
}
match state.val.frames[idx] {
GenFrame::WithExit(exit~) => {
// Ensure the exit hook only runs once.
let _ = state.val.frames.pop()
let exc_value = exception_value_from_runtime_error(
current,
state.val.globals,
state.val.builtins,
state.val.io,
)
let exc_type_value = match exc_value {
Value::Instance(inst) => Value::Class(inst.class)
_ => Value::Str(current.exc_type)
}
let tb_value = make_traceback_value(
current.traceback,
Value::Dict([]),
current.span,
)
let exit_result = match
call_callable_with_env(
exit,
[exc_type_value, exc_value, tb_value],
[],
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(e) => return Err(e)
}
let suppress = match
truthy_from_value_with_env(
exit_result,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(e) => return Err(e)
}
if suppress {
// Exception suppressed.
return Ok(true)
} else {
// Continue unwinding outward with the same exception.
continue
}
}
GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase~,
pending~,
handler_active~,
loop_depth~
) => {
let _ = else_body
let _ = pending
// If we're leaving an except handler, clear the active exception.
if handler_active && active_exception_stack.val.length() > 0 {
let _ = active_exception_stack.val.pop()
}
// `phase` tracks which block is currently active: 0=body, 1=else,
// 2=handler, 3=finally.
if phase != 2 && phase != 3 {
let mut matched : ExceptHandler? = None
for h in handlers {
let ok = match
generator_try_handler_matches(current, h, state) {
Ok(v) => v
Err(e) => return Err(e)
}
if ok {
matched = Some(h)
break
}
}
match matched {
Some(h) => {
match h.name {
Some(name) =>
set_scoped_value(
state.val.locals,
state.val.globals,
name,
exception_value_from_runtime_error(
current,
state.val.globals,
state.val.builtins,
state.val.io,
),
)
None => ()
}
active_exception_stack.val.push(current)
state.val.frames[idx] = GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase=2,
pending=None,
handler_active=true,
loop_depth~,
)
state.val.frames.push(
GenFrame::Block(body=h.body, index=0, loop_depth~),
)
return Ok(true)
}
None => ()
}
}
if finally_body.length() > 0 {
state.val.frames[idx] = GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase=3,
pending=Some(current),
handler_active=false,
loop_depth~,
)
state.val.frames.push(
GenFrame::Block(body=finally_body, index=0, loop_depth~),
)
return Ok(true)
} else {
// No finally: drop the try frame and keep searching outward.
let _ = state.val.frames.pop()
continue
}
}
_ => return Ok(false)
}
}
}
}
Ok(false)
}
///|
fn generator_resume_inner(
state : Ref[GeneratorState],
sent_value_in : Value,
thrown_in : RuntimeError?,
) -> Result[Value, RuntimeError] {
if state.val.done {
return Err(generator_stop_iteration())
}
if !state.val.started {
match sent_value_in {
Value::None => ()
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"can't send non-None value to a just-started generator".to_string(),
),
)
}
state.val.started = true
}
let mut sent_value = sent_value_in
let mut thrown = thrown_in
// Resume points only care about the sent value when the previous suspension
// left a marker on the frame stack (yield in assignment, or yield from).
// For plain `yield` statements, the sent value is ignored.
if state.val.frames.length() == 0 {
sent_value = Value::None
} else {
let top = state.val.frames[state.val.frames.length() - 1]
match top {
GenFrame::AfterAssign(..) | GenFrame::YieldFrom(..) => ()
_ => sent_value = Value::None
}
}
while true {
if state.val.frames.length() == 0 {
state.val.done = true
return Err(generator_stop_iteration_with_value(Value::None))
}
let top_index = state.val.frames.length() - 1
match state.val.frames[top_index] {
GenFrame::AfterYield => {
let _ = state.val.frames.pop()
match thrown {
Some(err) => {
thrown = None
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
None => continue
}
}
GenFrame::AfterAssign(target~, value~) => {
let _ = value
let value_to_assign = sent_value
sent_value = Value::None
let _ = match
assign_target_with_env(
target,
value_to_assign,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
}
let _ = state.val.frames.pop()
continue
}
GenFrame::YieldFrom(iterator~) =>
match
iterator_send(
iterator,
sent_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => return Ok(value)
Err(err) =>
if is_stop_iteration(err) {
let _ = state.val.frames.pop()
let stop_value = match err.exc_value {
Some(value) => value
None => Value::None
}
if state.val.frames.length() > 0 {
let next_top = state.val.frames[state.val.frames.length() - 1]
match next_top {
GenFrame::AfterAssign(..) => sent_value = stop_value
_ => sent_value = Value::None
}
} else {
sent_value = Value::None
}
continue
} else {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
}
GenFrame::Block(body~, index~, loop_depth~) => {
if index >= body.length() {
let _ = state.val.frames.pop()
continue
}
state.val.frames[top_index] = GenFrame::Block(
body~,
index=index + 1,
loop_depth~,
)
let stmt = body[index]
match generator_step_statement(stmt, state, loop_depth) {
Ok(Some(yielded)) => return Ok(yielded)
Ok(None) => continue
Err(err) => {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
if is_stop_iteration(err) {
state.val.done = true
state.val.frames = []
}
return Err(err)
}
}
}
GenFrame::For(
target~,
iterator~,
body~,
else_body~,
broke~,
done~,
loop_depth~
) => {
if done {
let _ = state.val.frames.pop()
continue
}
match
iterator_next(
iterator,
None,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(item) => {
let _ = match
assign_target_with_env(
target,
item,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
}
state.val.frames.push(
GenFrame::Block(body~, index=0, loop_depth=loop_depth + 1),
)
continue
}
Err(err) =>
if is_stop_iteration(err) {
state.val.frames[top_index] = GenFrame::For(
target~,
iterator~,
body~,
else_body~,
broke~,
done=true,
loop_depth~,
)
if !broke && else_body.length() > 0 {
state.val.frames.push(
GenFrame::Block(body=else_body, index=0, loop_depth~),
)
}
continue
} else {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
}
}
GenFrame::While(condition~, body~, else_body~, broke~, done~, loop_depth~) => {
if done {
let _ = state.val.frames.pop()
continue
}
let cond_value = match
eval_expr_with_env(
condition,
state.val.locals,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(value) => value
Err(err) => {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
return Err(err)
}
}
let ok = match
truthy_from_value_with_env(
cond_value,
state.val.globals,
state.val.builtins,
state.val.io,
) {
Ok(v) => v
Err(err) => return Err(err)
}
if ok {
state.val.frames.push(
GenFrame::Block(body~, index=0, loop_depth=loop_depth + 1),
)
continue
}
state.val.frames[top_index] = GenFrame::While(
condition~,
body~,
else_body~,
broke~,
done=true,
loop_depth~,
)
if !broke && else_body.length() > 0 {
state.val.frames.push(
GenFrame::Block(body=else_body, index=0, loop_depth~),
)
}
continue
}
GenFrame::WithExit(exit~) => {
let _ = state.val.frames.pop()
let call_result = call_callable_with_env(
exit,
[Value::None, Value::None, Value::None],
[],
state.val.globals,
state.val.builtins,
state.val.io,
)
match call_result {
Ok(_) => continue
Err(err) => {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
}
}
GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase~,
pending~,
handler_active~,
loop_depth~
) => {
let _ = handlers
let _ = loop_depth
// Phase: 0=body done, 1=else done, 2=handler done, 3=finally done.
if phase == 0 {
if else_body.length() > 0 {
state.val.frames[top_index] = GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase=1,
pending~,
handler_active~,
loop_depth~,
)
state.val.frames.push(
GenFrame::Block(body=else_body, index=0, loop_depth~),
)
continue
}
if finally_body.length() > 0 {
state.val.frames[top_index] = GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase=3,
pending~,
handler_active~,
loop_depth~,
)
state.val.frames.push(
GenFrame::Block(body=finally_body, index=0, loop_depth~),
)
continue
}
let _ = state.val.frames.pop()
continue
}
if phase == 1 || phase == 2 {
if handler_active && active_exception_stack.val.length() > 0 {
let _ = active_exception_stack.val.pop()
}
if finally_body.length() > 0 {
state.val.frames[top_index] = GenFrame::Try(
handlers~,
else_body~,
finally_body~,
phase=3,
pending~,
handler_active=false,
loop_depth~,
)
state.val.frames.push(
GenFrame::Block(body=finally_body, index=0, loop_depth~),
)
continue
}
let _ = state.val.frames.pop()
continue
}
if phase == 3 {
let pending_err = pending
let _ = state.val.frames.pop()
match pending_err {
Some(err) => {
let handled = match generator_dispatch_error(state, err) {
Ok(v) => v
Err(e) => return Err(e)
}
if handled {
continue
}
state.val.done = true
return Err(err)
}
None => continue
}
}
// Unknown phase: drop the frame.
let _ = state.val.frames.pop()
continue
}
}
}
Err(generator_stop_iteration())
}
///|
fn generator_resume(
state : Ref[GeneratorState],
sent_value_in : Value,
thrown_in : RuntimeError?,
) -> Result[Value, RuntimeError] {
let _ = match ensure_recursion_available() {
Ok(_) => ()
Err(err) => return Err(err)
}
push_traceback_frame(state.val.name, state.val.filename)
push_closure_env(state.val.closure)
push_scope_decls_with_nonlocals(
state.val.global_names,
state.val.nonlocal_names,
)
if state.val.is_async {
coroutine_enter_context()
}
let result = generator_resume_inner(state, sent_value_in, thrown_in)
if state.val.is_async {
coroutine_leave_context()
}
pop_scope_decls()
pop_closure_env()
pop_traceback_frame()
result
}
///|
fn generator_next(
generator_value : Value,
default_value : Value?,
) -> Result[Value, RuntimeError] {
let state = match generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
match generator_resume(state, Value::None, None) {
Ok(value) => Ok(value)
Err(err) =>
if is_stop_iteration(err) {
match default_value {
Some(v) => Ok(v)
None => Err(err)
}
} else {
Err(err)
}
}
}
///|
fn generator_send(
generator_value : Value,
value : Value,
) -> Result[Value, RuntimeError] {
let state = match generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
generator_resume(state, value, None)
}
///|
fn generator_close(generator_value : Value) -> Result[Value, RuntimeError] {
let state = match generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
state.val.done = true
state.val.frames = []
Ok(Value::None)
}
///|
fn generator_throw(
generator_value : Value,
exc : Value,
) -> Result[Value, RuntimeError] {
let state = match generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
let err = match exc {
Value::Instance(inst) => runtime_error_from_exception_instance(inst)
Value::Class(klass) =>
make_runtime_error(RuntimeErrorKind::Runtime, klass.name)
Value::Str(text) => make_runtime_error(RuntimeErrorKind::Runtime, text)
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"exceptions must derive from BaseException".to_string(),
),
)
}
generator_resume(state, Value::None, Some(err))
}
///|
fn async_generator_stop_iteration() -> RuntimeError {
make_runtime_error(
RuntimeErrorKind::Runtime,
"StopAsyncIteration".to_string(),
)
}
///|
fn async_generator_next(generator_value : Value) -> Result[Value, RuntimeError] {
let state = match async_generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
match generator_resume(state, Value::None, None) {
Ok(value) => Ok(value)
Err(err) =>
if is_stop_iteration(err) {
Err(async_generator_stop_iteration())
} else {
Err(err)
}
}
}
///|
fn async_generator_send(
generator_value : Value,
value : Value,
) -> Result[Value, RuntimeError] {
let state = match async_generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
match generator_resume(state, value, None) {
Ok(value) => Ok(value)
Err(err) =>
if is_stop_iteration(err) {
Err(async_generator_stop_iteration())
} else {
Err(err)
}
}
}
///|
fn async_generator_close(
generator_value : Value,
) -> Result[Value, RuntimeError] {
let state = match async_generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
state.val.done = true
state.val.frames = []
Ok(Value::None)
}
///|
fn async_generator_throw(
generator_value : Value,
exc : Value,
) -> Result[Value, RuntimeError] {
let state = match async_generator_state_from_value(generator_value) {
Ok(v) => v
Err(err) => return Err(err)
}
let err = match exc {
Value::Instance(inst) => runtime_error_from_exception_instance(inst)
Value::Class(klass) =>
make_runtime_error(RuntimeErrorKind::Runtime, klass.name)
Value::Str(text) => make_runtime_error(RuntimeErrorKind::Runtime, text)
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Type,
"exceptions must derive from BaseException".to_string(),
),
)
}
generator_resume(state, Value::None, Some(err))
}
///|
fn async_generator_make_anext_coroutine(
generator_value : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Value {
let body : Array[Stmt] = [
Stmt::Return(
Some(
Expr::Call(callee=Expr::Name("__mpython_asyncgen_next"), args=[
Expr::Name("self"),
]),
),
),
]
let locals : Array[(String, Value)] = [("self", generator_value)]
let filename = current_traceback_filename()
coroutine_new(
body,
locals,
globals,
builtins,
io,
[],
[],
[],
"".to_string(),
filename,
Config::default(),
)
}
///|
fn async_generator_make_asend_coroutine(
generator_value : Value,
send_value : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Value {
let body : Array[Stmt] = [
Stmt::Return(
Some(
Expr::Call(callee=Expr::Name("__mpython_asyncgen_send"), args=[
Expr::Name("self"),
Expr::Name("value"),
]),
),
),
]
let locals : Array[(String, Value)] = [
("self", generator_value),
("value", send_value),
]
let filename = current_traceback_filename()
coroutine_new(
body,
locals,
globals,
builtins,
io,
[],
[],
[],
"".to_string(),
filename,
Config::default(),
)
}
///|
fn async_generator_make_athrow_coroutine(
generator_value : Value,
exc : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Value {
let body : Array[Stmt] = [
Stmt::Return(
Some(
Expr::Call(callee=Expr::Name("__mpython_asyncgen_throw"), args=[
Expr::Name("self"),
Expr::Name("exc"),
]),
),
),
]
let locals : Array[(String, Value)] = [
("self", generator_value),
("exc", exc),
]
let filename = current_traceback_filename()
coroutine_new(
body,
locals,
globals,
builtins,
io,
[],
[],
[],
"".to_string(),
filename,
Config::default(),
)
}
///|
fn async_generator_make_aclose_coroutine(
generator_value : Value,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
) -> Value {
let body : Array[Stmt] = [
Stmt::Return(
Some(
Expr::Call(callee=Expr::Name("__mpython_asyncgen_close"), args=[
Expr::Name("self"),
]),
),
),
]
let locals : Array[(String, Value)] = [("self", generator_value)]
let filename = current_traceback_filename()
coroutine_new(
body,
locals,
globals,
builtins,
io,
[],
[],
[],
"".to_string(),
filename,
Config::default(),
)
}