///|
/// AST -> bytecode compiler.
///
/// This starts as a minimal compiler and is extended feature-by-feature until
/// it covers the full surface area that the old AST-walking evaluator supports.
///|
fn literal_to_value(lit : Literal) -> Value {
match lit {
Literal::None => Value::None
Literal::Bool(v) => Value::Bool(v)
Literal::Int(v) => Value::Int(v)
Literal::Float(v) => Value::Float(v)
Literal::Complex(r, i) => Value::Complex(r, i)
Literal::Str(v) => Value::Str(v)
Literal::Bytes(v) => Value::Bytes(v)
}
}
///|
fn emit_pep695_typing_import(b : BcBuilder, span : Span?) -> Unit {
let mod_idx = b.add_const(Value::Str("typing"))
let _ = b.emit(BcOp::ImportName(mod_idx, -1), span)
let _ = b.emit(BcOp::PopTop, span)
}
///|
priv struct LoopCtx {
has_iter : Bool
break_jumps : Array[Int32]
continue_jumps : Array[Int32]
finally_depth : Int32
}
///|
fn compile_expr(
expr : Expr,
b : BcBuilder,
span : Span?,
) -> Result[Unit, RuntimeError] {
match expr {
Expr::WithSpan(span=new_span, expr=inner) =>
// Prefer the expression's own span when available.
compile_expr(inner, b, Some(new_span))
Expr::Literal(lit) => {
let idx = b.add_const(literal_to_value(lit))
let _ = b.emit(BcOp::LoadConst(idx), span)
Ok(())
}
Expr::Name(name) => {
let idx = b.intern_name(name)
let _ = b.emit(BcOp::LoadName(idx), span)
Ok(())
}
Expr::FString(text) => {
let idx = b.add_const(Value::Str(text))
let _ = b.emit(BcOp::LoadConst(idx), span)
let _ = b.emit(BcOp::EvalFString, span)
Ok(())
}
Expr::NamedExpr(name~, value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::DupTop, span)
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::StoreName(name_idx), span)
Ok(())
}
Expr::IfExpr(condition~, then_expr~, else_expr~) => {
match compile_expr(condition, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let j_else = b.emit(BcOp::JumpIfFalse(-1), span)
match compile_expr(then_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let j_end = b.emit(BcOp::Jump(-1), span)
let else_pc = b.code.ops.length()
bc_patch_jump(b.code, j_else, else_pc)
match compile_expr(else_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let end_pc = b.code.ops.length()
bc_patch_jump(b.code, j_end, end_pc)
Ok(())
}
Expr::Unary(op~, expr~) => {
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::Unary(op), span)
Ok(())
}
Expr::Binary(op~, left~, right~) => {
match compile_expr(left, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(right, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::Binary(op), span)
Ok(())
}
Expr::BoolOp(op~, values~) => {
if values.length() == 0 {
let idx = b.add_const(Value::Bool(true))
let _ = b.emit(BcOp::LoadConst(idx), span)
return Ok(())
}
match compile_expr(values[0], b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let jumps : Array[Int32] = []
for i = 1; i < values.length(); i = i + 1 {
let j = match op {
BoolOp::And => b.emit(BcOp::JumpIfFalseOrPop(-1), span)
BoolOp::Or => b.emit(BcOp::JumpIfTrueOrPop(-1), span)
}
jumps.push(j)
match compile_expr(values[i], b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let end_pc = b.code.ops.length()
for pc in jumps {
bc_patch_jump(b.code, pc, end_pc)
}
Ok(())
}
Expr::Lambda(params~, defaults~, body~) => {
// Keep evaluation order aligned with CPython: defaults are evaluated left-to-right
// at lambda creation time.
for def_expr in defaults {
match compile_expr(def_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let is_generator = match body {
Expr::Yield(_) | Expr::YieldFrom(_) => true
_ => false
}
let func_body : Array[Stmt] = if is_generator {
[Stmt::ExprStmt(body)]
} else {
[Stmt::Return(Some(body))]
}
let template = FunctionValue::{
name: "".to_string(),
params,
defaults: [],
body: func_body,
is_generator,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(
BcOp::MakeFunction(template_idx, defaults.length(), 0),
span,
)
Ok(())
}
Expr::Compare(left~, ops~, comparators~) => {
if ops.length() != comparators.length() {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"bytecode compiler: invalid compare node".to_string(),
),
)
}
if ops.length() == 0 {
let idx = b.add_const(Value::Bool(true))
let _ = b.emit(BcOp::LoadConst(idx), span)
return Ok(())
}
match compile_expr(left, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(comparators[0], b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
if ops.length() == 1 {
let _ = b.emit(BcOp::Compare(ops[0]), span)
return Ok(())
}
let fail_jumps : Array[Int32] = []
for i = 0; i < ops.length(); i = i + 1 {
let op = ops[i]
if i < ops.length() - 1 {
// Stack: [prev, cur]
// -> duplicate cur and rotate so we can compare prev/cur while keeping cur:
// [prev, cur] -> [prev, cur, cur] -> [cur, prev, cur]
let _ = b.emit(BcOp::DupTop, span)
let _ = b.emit(BcOp::RotThree, span)
let _ = b.emit(BcOp::Compare(op), span)
// On failure: JumpIfFalse consumes the bool and jumps, leaving [cur] on the stack.
let j_fail = b.emit(BcOp::JumpIfFalse(-1), span)
fail_jumps.push(j_fail)
match compile_expr(comparators[i + 1], b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
} else {
let _ = b.emit(BcOp::Compare(op), span)
}
}
let j_done = b.emit(BcOp::Jump(-1), span)
let fail_pc = b.code.ops.length()
for pc in fail_jumps {
bc_patch_jump(b.code, pc, fail_pc)
}
let _ = b.emit(BcOp::PopTop, span)
let false_idx = b.add_const(Value::Bool(false))
let _ = b.emit(BcOp::LoadConst(false_idx), span)
let done_pc = b.code.ops.length()
bc_patch_jump(b.code, j_done, done_pc)
Ok(())
}
Expr::Call(callee~, args~) => {
let mut has_star = false
for arg in args {
match arg {
Expr::Starred(_) | Expr::DoubleStarred(_) => {
has_star = true
break
}
_ => ()
}
}
if !has_star {
let pos_args : Array[Expr] = []
let kw_names : Array[String] = []
let kw_values : Array[Expr] = []
for arg in args {
match arg {
Expr::Keyword(name~, value~) => {
kw_names.push(name)
kw_values.push(value)
}
Expr::Starred(_) | Expr::DoubleStarred(_) => ()
_ => pos_args.push(arg)
}
}
match compile_expr(callee, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
for arg in pos_args {
match compile_expr(arg, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
for value in kw_values {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
if kw_names.length() == 0 {
let _ = b.emit(BcOp::CallFunction(pos_args.length()), span)
} else {
let name_values : Array[Value] = []
for name in kw_names {
name_values.push(Value::Str(name))
}
let kw_tuple_idx = b.add_const(Value::Tuple(name_values))
let _ = b.emit(BcOp::LoadConst(kw_tuple_idx), span)
let _ = b.emit(BcOp::CallFunctionKw(pos_args.length()), span)
}
Ok(())
} else {
// Fallback to MicroPython-style "CALL_FUNCTION_EX"-like path:
// build expanded positional list + keyword-pair list, then call.
match compile_expr(callee, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::BuildList(0), span) // positional
let _ = b.emit(BcOp::BuildList(0), span) // keyword pairs (name,value tuples)
for arg in args {
match arg {
Expr::Keyword(name~, value~) => {
let name_idx = b.add_const(Value::Str(name))
let _ = b.emit(BcOp::LoadConst(name_idx), span)
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::BuildTuple(2), span)
let _ = b.emit(BcOp::ListAppend, span)
}
Expr::DoubleStarred(value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::KwListExtendFromDict, span)
}
Expr::Starred(value~) => {
// Swap keyword list / positional list to make positional list the target.
let _ = b.emit(BcOp::RotTwo, span)
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ListExtend, span)
let _ = b.emit(BcOp::RotTwo, span)
}
_ => {
let _ = b.emit(BcOp::RotTwo, span)
match compile_expr(arg, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ListAppend, span)
let _ = b.emit(BcOp::RotTwo, span)
}
}
}
let _ = b.emit(BcOp::CallFunctionVar, span)
Ok(())
}
}
Expr::Tuple(items) => {
let mut has_star = false
for item in items {
match item {
Expr::Starred(_) => {
has_star = true
break
}
_ => ()
}
}
if !has_star {
for item in items {
match compile_expr(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::BuildTuple(items.length()), span)
Ok(())
} else {
let _ = b.emit(BcOp::BuildList(0), span)
for item in items {
match item {
Expr::Starred(value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ListExtend, span)
}
_ => {
match compile_expr(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ListAppend, span)
}
}
}
let _ = b.emit(BcOp::ListToTuple, span)
Ok(())
}
}
Expr::ListComp(elt~, clauses~, filters~) => {
let filters = unowned_comprehension_filters(clauses, filters)
let mut has_async = false
for clause in clauses {
if clause.is_async {
has_async = true
break
}
}
if has_async {
let _ = b.emit(
BcOp::CheckInCoroutineScope(
"SyntaxError: 'async for' outside async function".to_string(),
),
span,
)
}
let res_name = "__mpy_comp_res$" + b.code.ops.length().to_string()
let res_init = Stmt::Assign(
target=Target::Name(res_name),
value=Expr::List([]),
)
let first_iter_param : String? = if clauses.length() > 0 {
Some("__mpy_comp_iter$" + b.code.ops.length().to_string())
} else {
None
}
let comp_clauses : Array[CompClause] = []
match first_iter_param {
Some(name) => {
comp_clauses.push(CompClause::{
is_async: clauses[0].is_async,
targets: clauses[0].targets,
target: clauses[0].target,
filters: clauses[0].filters,
iter: Expr::Name(name),
})
for i = 1; i < clauses.length(); i = i + 1 {
comp_clauses.push(clauses[i])
}
}
None => ()
}
let body_clauses = if clauses.length() > 0 {
comp_clauses
} else {
clauses
}
// Build the innermost statement: res.append(elt)
let append_stmt = Stmt::ExprStmt(
Expr::Call(
callee=Expr::Attribute(value=Expr::Name(res_name), attr="append"),
args=[elt],
),
)
let mut inner : Array[Stmt] = [append_stmt]
// Filters apply at the innermost level.
for i = filters.length() - 1; i >= 0; i = i - 1 {
inner = [Stmt::If(condition=filters[i], body=inner, else_body=[])]
if i == 0 {
break
}
}
// Wrap nested `for` clauses from inside-out.
for i = body_clauses.length() - 1; i >= 0; i = i - 1 {
let clause = body_clauses[i]
let loop_target = clause.target
let mut body = inner
for j = clause.filters.length() - 1; j >= 0; j = j - 1 {
body = [Stmt::If(condition=clause.filters[j], body~, else_body=[])]
}
inner = [
if clause.is_async {
Stmt::AsyncFor(target=loop_target, iter=clause.iter, body~, else_body=[])
} else {
Stmt::For(target=loop_target, iter=clause.iter, body~, else_body=[])
},
]
if i == 0 {
break
}
}
let func_body : Array[Stmt] = [res_init]
for s in inner {
func_body.push(s)
}
func_body.push(Stmt::Return(Some(Expr::Name(res_name))))
let template = FunctionValue::{
name: "".to_string(),
params: match first_iter_param {
Some(name) => [name]
None => []
},
defaults: [],
body: func_body,
is_generator: false,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(BcOp::MakeFunction(template_idx, 0, 0), span)
match first_iter_param {
Some(_) => {
match compile_expr(clauses[0].iter, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::CallFunction(1), span)
}
None => {
let _ = b.emit(BcOp::CallFunction(0), span)
}
}
Ok(())
}
Expr::GenExp(elt~, clauses~, filters~) => {
fn contains_await(expr : Expr) -> Bool {
expr_has_await(expr)
}
let mut has_async_clause = false
for clause in clauses {
if clause.is_async {
has_async_clause = true
break
}
}
let mut has_await = contains_await(elt)
if !has_await {
for clause in clauses {
if contains_await(clause.iter) {
has_await = true
break
}
}
}
if !has_await {
for f in filters {
if contains_await(f) {
has_await = true
break
}
}
}
let is_async_genexp = has_async_clause || has_await
if is_async_genexp && clauses.length() == 0 {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: invalid async generator expression".to_string(),
),
)
}
let target_names : Array[Value] = []
for clause in clauses {
for name in clause.targets {
target_names.push(Value::Str(name))
}
}
let target_names_idx = b.add_const(Value::Tuple(target_names))
if clauses.length() == 0 {
// Synchronous genexp with no clauses is effectively a one-shot generator.
let body = genexp_to_generator_body(elt, clauses, filters)
let template = FunctionValue::{
name: "".to_string(),
params: [],
defaults: [],
body,
is_generator: true,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(
BcOp::GenExpNew(template_idx, -1, target_names_idx),
span,
)
Ok(())
} else {
// Evaluate the first iterator at generator-expression creation time.
match compile_expr(clauses[0].iter, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
if clauses[0].is_async {
let aiter_idx = b.intern_name("__aiter__")
let _ = b.emit(BcOp::LoadAttr(aiter_idx), span)
let _ = b.emit(BcOp::CallFunction(0), span)
} else {
let _ = b.emit(BcOp::GetIter, span)
}
let iter_name = "__mpy_genexp_iter$" + b.code.ops.length().to_string()
let iter_name_idx = b.intern_name(iter_name)
let adjusted : Array[CompClause] = []
adjusted.push(CompClause::{
is_async: clauses[0].is_async,
targets: clauses[0].targets,
target: clauses[0].target,
filters: clauses[0].filters,
iter: Expr::Name(iter_name),
})
for i = 1; i < clauses.length(); i = i + 1 {
adjusted.push(clauses[i])
}
if is_async_genexp {
let spec_idx = b.add_genexp(GenExpSpec::{
elt,
clauses: adjusted,
filters,
})
let _ = b.emit(
BcOp::AsyncGenExpNew(spec_idx, iter_name_idx, target_names_idx),
span,
)
} else {
let body = genexp_to_generator_body(elt, adjusted, filters)
let template = FunctionValue::{
name: "".to_string(),
params: [],
defaults: [],
body,
is_generator: true,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(
BcOp::GenExpNew(template_idx, iter_name_idx, target_names_idx),
span,
)
}
Ok(())
}
}
Expr::SetComp(elt~, clauses~, filters~) => {
let filters = unowned_comprehension_filters(clauses, filters)
let mut has_async = false
for clause in clauses {
if clause.is_async {
has_async = true
break
}
}
if has_async {
let _ = b.emit(
BcOp::CheckInCoroutineScope(
"SyntaxError: 'async for' outside async function".to_string(),
),
span,
)
}
let res_name = "__mpy_comp_res$" + b.code.ops.length().to_string()
// set comprehensions use a dedicated set object, independent of any `set` binding.
// MoonPython's runtime does not have a literal empty-set node, so use `set()`.
let res_init = Stmt::Assign(
target=Target::Name(res_name),
value=Expr::Call(callee=Expr::Name("set"), args=[]),
)
let first_iter_param : String? = if clauses.length() > 0 {
Some("__mpy_comp_iter$" + b.code.ops.length().to_string())
} else {
None
}
let comp_clauses : Array[CompClause] = []
match first_iter_param {
Some(name) => {
comp_clauses.push(CompClause::{
is_async: clauses[0].is_async,
targets: clauses[0].targets,
target: clauses[0].target,
filters: clauses[0].filters,
iter: Expr::Name(name),
})
for i = 1; i < clauses.length(); i = i + 1 {
comp_clauses.push(clauses[i])
}
}
None => ()
}
let body_clauses = if clauses.length() > 0 {
comp_clauses
} else {
clauses
}
let add_stmt = Stmt::ExprStmt(
Expr::Call(
callee=Expr::Attribute(value=Expr::Name(res_name), attr="add"),
args=[elt],
),
)
let mut inner : Array[Stmt] = [add_stmt]
for i = filters.length() - 1; i >= 0; i = i - 1 {
inner = [Stmt::If(condition=filters[i], body=inner, else_body=[])]
if i == 0 {
break
}
}
for i = body_clauses.length() - 1; i >= 0; i = i - 1 {
let clause = body_clauses[i]
let loop_target = clause.target
let mut body = inner
for j = clause.filters.length() - 1; j >= 0; j = j - 1 {
body = [Stmt::If(condition=clause.filters[j], body~, else_body=[])]
}
inner = [
if clause.is_async {
Stmt::AsyncFor(target=loop_target, iter=clause.iter, body~, else_body=[])
} else {
Stmt::For(target=loop_target, iter=clause.iter, body~, else_body=[])
},
]
if i == 0 {
break
}
}
let func_body : Array[Stmt] = [res_init]
for s in inner {
func_body.push(s)
}
func_body.push(Stmt::Return(Some(Expr::Name(res_name))))
let template = FunctionValue::{
name: "".to_string(),
params: match first_iter_param {
Some(name) => [name]
None => []
},
defaults: [],
body: func_body,
is_generator: false,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(BcOp::MakeFunction(template_idx, 0, 0), span)
match first_iter_param {
Some(_) => {
match compile_expr(clauses[0].iter, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::CallFunction(1), span)
}
None => {
let _ = b.emit(BcOp::CallFunction(0), span)
}
}
Ok(())
}
Expr::DictComp(key~, value~, clauses~, filters~) => {
let filters = unowned_comprehension_filters(clauses, filters)
let mut has_async = false
for clause in clauses {
if clause.is_async {
has_async = true
break
}
}
if has_async {
let _ = b.emit(
BcOp::CheckInCoroutineScope(
"SyntaxError: 'async for' outside async function".to_string(),
),
span,
)
}
let res_name = "__mpy_comp_res$" + b.code.ops.length().to_string()
let res_init = Stmt::Assign(
target=Target::Name(res_name),
value=Expr::Dict([]),
)
let first_iter_param : String? = if clauses.length() > 0 {
Some("__mpy_comp_iter$" + b.code.ops.length().to_string())
} else {
None
}
let comp_clauses : Array[CompClause] = []
match first_iter_param {
Some(name) => {
comp_clauses.push(CompClause::{
is_async: clauses[0].is_async,
targets: clauses[0].targets,
target: clauses[0].target,
filters: clauses[0].filters,
iter: Expr::Name(name),
})
for i = 1; i < clauses.length(); i = i + 1 {
comp_clauses.push(clauses[i])
}
}
None => ()
}
let body_clauses = if clauses.length() > 0 {
comp_clauses
} else {
clauses
}
let set_stmt = Stmt::Assign(
target=Target::Subscript(value=Expr::Name(res_name), index=key),
value~,
)
let mut inner : Array[Stmt] = [set_stmt]
for i = filters.length() - 1; i >= 0; i = i - 1 {
inner = [Stmt::If(condition=filters[i], body=inner, else_body=[])]
if i == 0 {
break
}
}
for i = body_clauses.length() - 1; i >= 0; i = i - 1 {
let clause = body_clauses[i]
let loop_target = clause.target
let mut body = inner
for j = clause.filters.length() - 1; j >= 0; j = j - 1 {
body = [Stmt::If(condition=clause.filters[j], body~, else_body=[])]
}
inner = [
if clause.is_async {
Stmt::AsyncFor(target=loop_target, iter=clause.iter, body~, else_body=[])
} else {
Stmt::For(target=loop_target, iter=clause.iter, body~, else_body=[])
},
]
if i == 0 {
break
}
}
let func_body : Array[Stmt] = [res_init]
for s in inner {
func_body.push(s)
}
func_body.push(Stmt::Return(Some(Expr::Name(res_name))))
let template = FunctionValue::{
name: "".to_string(),
params: match first_iter_param {
Some(name) => [name]
None => []
},
defaults: [],
body: func_body,
is_generator: false,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(BcOp::MakeFunction(template_idx, 0, 0), span)
match first_iter_param {
Some(_) => {
match compile_expr(clauses[0].iter, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::CallFunction(1), span)
}
None => {
let _ = b.emit(BcOp::CallFunction(0), span)
}
}
Ok(())
}
Expr::List(items) => {
let mut has_star = false
for item in items {
match item {
Expr::Starred(_) => {
has_star = true
break
}
_ => ()
}
}
if !has_star {
for item in items {
match compile_expr(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::BuildList(items.length()), span)
Ok(())
} else {
let _ = b.emit(BcOp::BuildList(0), span)
for item in items {
match item {
Expr::Starred(value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ListExtend, span)
}
_ => {
match compile_expr(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ListAppend, span)
}
}
}
Ok(())
}
}
Expr::Dict(items) => {
let mut has_double_star = false
for pair in items {
match pair.0 {
Expr::DoubleStarred(_) => {
has_double_star = true
break
}
_ => ()
}
}
if !has_double_star {
for pair in items {
match compile_expr(pair.0, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(pair.1, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::BuildMap(items.length()), span)
Ok(())
} else {
// Build and update a dict to support `{**m, k: v}`.
let _ = b.emit(BcOp::BuildMap(0), span)
for pair in items {
match pair.0 {
Expr::DoubleStarred(value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::DictUpdate, span)
}
_ => {
match compile_expr(pair.0, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(pair.1, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::DictSetItem, span)
}
}
}
Ok(())
}
}
Expr::Set(items) => {
let mut has_star = false
for item in items {
match item {
Expr::Starred(_) => {
has_star = true
break
}
_ => ()
}
}
if !has_star {
for item in items {
match compile_expr(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::BuildSet(items.length()), span)
Ok(())
} else {
let _ = b.emit(BcOp::BuildSet(0), span)
for item in items {
match item {
Expr::Starred(value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::SetUpdate, span)
}
_ => {
match compile_expr(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::SetAdd, span)
}
}
}
Ok(())
}
}
Expr::Attribute(value~, attr~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let idx = b.intern_name(attr)
let _ = b.emit(BcOp::LoadAttr(idx), span)
Ok(())
}
Expr::Subscript(value~, index~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(index, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::LoadSubscr, span)
Ok(())
}
Expr::Await(expr) => {
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::Await, span)
Ok(())
}
Expr::Slice(start~, end~, step~) => {
match start {
Some(expr) =>
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
None => {
let idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadConst(idx), span)
}
}
match end {
Some(expr) =>
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
None => {
let idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadConst(idx), span)
}
}
match step {
Some(expr) =>
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
None => {
let idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadConst(idx), span)
}
}
let _ = b.emit(BcOp::BuildSlice, span)
Ok(())
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: expr not implemented".to_string(),
),
)
}
}
///|
fn compile_target_store(
target : Target,
b : BcBuilder,
span : Span?,
) -> Result[Unit, RuntimeError] {
match target {
Target::Name(name) => {
let idx = b.intern_name(name)
let _ = b.emit(BcOp::StoreName(idx), span)
Ok(())
}
Target::Attribute(value~, attr~) => {
// Assignment target where the assigned value is already on the stack.
// Emit:
// -> -> STORE_ATTR
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::RotTwo, span)
let idx = b.intern_name(attr)
let _ = b.emit(BcOp::StoreAttr(idx), span)
Ok(())
}
Target::Subscript(value~, index~) => {
// Assignment target where the assigned value is already on the stack.
// Emit:
// -> -> STORE_SUBSCR
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(index, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::RotThree, span)
let _ = b.emit(BcOp::RotThree, span)
let _ = b.emit(BcOp::StoreSubscr, span)
Ok(())
}
Target::Tuple(items) | Target::List(items) => {
let mut star_idx : Int32? = None
for i = 0; i < items.length(); i = i + 1 {
match items[i] {
Target::Starred(_) =>
match star_idx {
Some(_) =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: two starred expressions in assignment".to_string(),
),
)
None => star_idx = Some(i)
}
_ => ()
}
}
match star_idx {
Some(si) => {
let before = si
let after = items.length() - si - 1
let _ = b.emit(BcOp::UnpackEx(before, after), span)
for item in items {
match item {
Target::Starred(inner) =>
match compile_target_store(inner, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
_ =>
match compile_target_store(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
}
Ok(())
}
None => {
let _ = b.emit(BcOp::UnpackSequence(items.length()), span)
for item in items {
match compile_target_store(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
}
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: complex assignment target not implemented".to_string(),
),
)
}
}
///|
fn compile_target_delete(
target : Target,
b : BcBuilder,
span : Span?,
) -> Result[Unit, RuntimeError] {
match target {
Target::Name(name) => {
let idx = b.intern_name(name)
let _ = b.emit(BcOp::DeleteName(idx), span)
Ok(())
}
Target::Starred(_) =>
Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: can't use starred expression here".to_string(),
),
)
Target::Tuple(items) | Target::List(items) => {
for item in items {
match compile_target_delete(item, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
Target::Attribute(value~, attr~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let idx = b.intern_name(attr)
let _ = b.emit(BcOp::DeleteAttr(idx), span)
Ok(())
}
Target::Subscript(value~, index~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(index, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::DeleteSubscr, span)
Ok(())
}
}
}
///|
fn compile_try_except_else(
body : Array[Stmt],
handlers : Array[ExceptHandler],
else_body : Array[Stmt],
b : BcBuilder,
span : Span?,
loop_stack : Ref[Array[LoopCtx]],
finally_depth : Int32,
future_annotations : Bool,
) -> Result[Unit, RuntimeError] {
fn caretless_span(span : Span?) -> Span? {
match span {
Some(s) =>
// Suppress PEP 657 caret rendering for internal exception-dispatch spans.
// We still keep the correct line/column, but force end_column == 0 by
// making the span width exceed the caret cutoff.
Some(Span::{
start: s.start,
end: s.start + 1024,
line: s.line,
column: s.column,
})
None => None
}
}
fn validate_except_star_body(
body : Array[Stmt],
loop_depth : Int32,
in_nested_def : Bool,
) -> Result[Unit, RuntimeError] {
for stmt in body {
match stmt {
Stmt::WithSpan(span=_, stmt=inner) =>
match validate_except_star_body([inner], loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
Stmt::Break | Stmt::Continue =>
if !in_nested_def && loop_depth == 0 {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: 'break', 'continue' and 'return' cannot appear in an except* block".to_string(),
),
)
}
Stmt::Return(_) =>
if !in_nested_def {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: 'break', 'continue' and 'return' cannot appear in an except* block".to_string(),
),
)
}
Stmt::Function(
name=_,
decorators=_,
type_params=_,
params=_,
defaults=_,
annotations=_,
return_annotation=_,
body=nested_body,
is_generator=_,
is_async=_
) =>
// Nested function bodies are allowed to use return/break/continue.
match validate_except_star_body(nested_body, 0, true) {
Ok(_) => ()
Err(err) => return Err(err)
}
Stmt::Class(
name=_,
decorators=_,
type_params=_,
bases=_,
keywords=_,
body=nested_body
) =>
// Nested class bodies are allowed to contain nested defs/loops.
match validate_except_star_body(nested_body, 0, true) {
Ok(_) => ()
Err(err) => return Err(err)
}
Stmt::If(condition=_, body=if_body, else_body~) => {
match validate_except_star_body(if_body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
match
validate_except_star_body(else_body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Stmt::While(condition=_, body=while_body, else_body=while_else) => {
match
validate_except_star_body(while_body, loop_depth + 1, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
match
validate_except_star_body(while_else, loop_depth + 1, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Stmt::For(target=_, iter=_, body=for_body, else_body=for_else) => {
match
validate_except_star_body(for_body, loop_depth + 1, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
match
validate_except_star_body(for_else, loop_depth + 1, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Stmt::AsyncFor(target=_, iter=_, body=for_body, else_body=for_else) => {
match
validate_except_star_body(for_body, loop_depth + 1, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
match
validate_except_star_body(for_else, loop_depth + 1, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Stmt::With(context=_, target=_, body=with_body) =>
match
validate_except_star_body(with_body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
Stmt::AsyncWith(context=_, target=_, body=with_body) =>
match
validate_except_star_body(with_body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
Stmt::Try(
body=try_body,
handlers=try_handlers,
else_body=try_else,
finally_body=try_finally
) => {
match validate_except_star_body(try_body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
for handler in try_handlers {
match
validate_except_star_body(handler.body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
match validate_except_star_body(try_else, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
match
validate_except_star_body(try_finally, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Stmt::Match(subject=_, cases=match_cases) =>
for case in match_cases {
let case_body = case.2
match
validate_except_star_body(case_body, loop_depth, in_nested_def) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
_ => ()
}
}
Ok(())
}
let mut has_star = false
let mut first_star_span : Span? = None
for handler in handlers {
if handler.is_star {
has_star = true
if first_star_span is None {
first_star_span = Some(handler.span)
}
}
}
if has_star {
// PEP 654: a try block cannot mix `except` and `except*` clauses.
for handler in handlers {
if !handler.is_star {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: cannot have both 'except' and 'except*' on the same 'try'".to_string(),
),
)
}
// `except*:` is invalid: every handler must have a type expression.
match handler.exc {
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: invalid except* clause".to_string(),
),
)
Some(_) => ()
}
match validate_except_star_body(handler.body, 0, false) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
}
let setup_pc = b.emit(BcOp::SetupExcept(-1), span)
for s in body {
match
compile_stmt(s, b, span, loop_stack, finally_depth, future_annotations) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::PopExcept, span)
for s in else_body {
match
compile_stmt(s, b, span, loop_stack, finally_depth, future_annotations) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let j_end = b.emit(BcOp::Jump(-1), span)
let handlers_pc = b.code.ops.length()
bc_patch_jump(b.code, setup_pc, handlers_pc)
if has_star {
let star_span = match first_star_span {
Some(s) => Some(s)
None => span
}
let star_span = caretless_span(star_span)
let _ = b.emit(BcOp::EnsureExceptionGroup, star_span)
let _ = b.emit(BcOp::ExceptStarInit, star_span)
}
let handler_done_jumps : Array[Int32] = []
if has_star {
let star_done_jumps : Array[Int32] = []
for i = 0; i < handlers.length(); i = i + 1 {
let handler = handlers[i]
let handler_span = caretless_span(Some(handler.span))
// Handler type expression is guaranteed to exist (validated above).
let exc_expr = match handler.exc {
Some(v) => v
None =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: invalid except* clause".to_string(),
),
)
}
match compile_expr(exc_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::ExceptStarMatch, handler_span)
let next_handler_jump = b.emit(BcOp::JumpIfFalse(-1), handler_span)
match handler.name {
Some(name) => {
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::LoadException, handler_span)
let _ = b.emit(BcOp::StoreName(name_idx), handler_span)
()
}
None => ()
}
// See non-star handler comment: clear pending unwind exception once a
// handler starts executing.
let _ = b.emit(BcOp::ClearPendingException, handler_span)
for s in handler.body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::ExceptStarEndHandler, handler_span)
// If no remaining exceptions exist, the exception is fully handled.
star_done_jumps.push(b.emit(BcOp::JumpIfFalse(-1), handler_span))
let next_pc = b.code.ops.length()
bc_patch_jump(b.code, next_handler_jump, next_pc)
}
let _ = b.emit(BcOp::ExceptStarRaiseRemaining, span)
let handled_pc = b.code.ops.length()
for pc in star_done_jumps {
bc_patch_jump(b.code, pc, handled_pc)
}
let _ = b.emit(BcOp::EndExcept, span)
let handled_jump = b.emit(BcOp::Jump(-1), span)
let end_pc = b.code.ops.length()
bc_patch_jump(b.code, j_end, end_pc)
bc_patch_jump(b.code, handled_jump, end_pc)
Ok(())
} else {
for i = 0; i < handlers.length(); i = i + 1 {
let handler = handlers[i]
let next_handler_jump : Int32? = match handler.exc {
None => (None : Int32?)
Some(exc_expr) => {
match compile_expr(exc_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::CheckExceptionMatch, span)
Some(b.emit(BcOp::JumpIfFalse(-1), span))
}
}
match handler.name {
Some(name) => {
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::LoadException, span)
let _ = b.emit(BcOp::StoreName(name_idx), span)
()
}
None => ()
}
// Once we're executing a handler, the exception is no longer "pending"
// for unwind through nested finally/with blocks. Keep it available via
// `active_exception_stack` (for sys.exc_info / bare raise), but clear the
// VM's unwind flag so normal finally blocks don't re-raise it.
let _ = b.emit(BcOp::ClearPendingException, span)
for s in handler.body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::EndExcept, span)
handler_done_jumps.push(b.emit(BcOp::Jump(-1), span))
match next_handler_jump {
Some(pc) => {
let next_pc = b.code.ops.length()
bc_patch_jump(b.code, pc, next_pc)
()
}
None => ()
}
}
let _ = b.emit(BcOp::Reraise, span)
let end_pc = b.code.ops.length()
bc_patch_jump(b.code, j_end, end_pc)
for pc in handler_done_jumps {
bc_patch_jump(b.code, pc, end_pc)
}
Ok(())
}
}
///|
fn compile_stmt(
stmt : Stmt,
b : BcBuilder,
span : Span?,
loop_stack : Ref[Array[LoopCtx]],
finally_depth : Int32,
future_annotations : Bool,
) -> Result[Unit, RuntimeError] {
match stmt {
Stmt::WithSpan(span~, stmt~) =>
compile_stmt(
stmt,
b,
Some(span),
loop_stack,
finally_depth,
future_annotations,
)
Stmt::Pass => {
// Preserve the source location of an explicit pass for code introspection.
let _ = b.emit(BcOp::Nop, span)
Ok(())
}
Stmt::ExprStmt(expr) => {
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::PopTop, span)
Ok(())
}
Stmt::Assign(target~, value~) => {
// Match CPython/MicroPython evaluation order:
// - Evaluate the RHS first, then assign into the target from left to
// right. `compile_target_store` consumes the already-stacked value.
let rhs = value
match target {
Target::Name(_) => {
match compile_expr(rhs, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
compile_target_store(target, b, span)
}
Target::Tuple(_) | Target::List(_) => {
match compile_expr(rhs, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
compile_target_store(target, b, span)
}
Target::Attribute(_) | Target::Subscript(_) => {
match compile_expr(rhs, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
compile_target_store(target, b, span)
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: complex assignment target not implemented".to_string(),
),
)
}
}
Stmt::AnnAssign(target~, annotation~, value~) => {
match value {
Some(expr) =>
match
compile_stmt(
Stmt::Assign(target~, value=expr),
b,
span,
loop_stack,
finally_depth,
future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
None =>
for expr in target_expressions(target) {
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::PopTop, span)
}
}
match target {
Target::Name(name) => {
match compile_annotation(annotation, b, span, future_annotations) {
Ok(_) => ()
Err(err) => return Err(err)
}
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::StoreAnnotation(name_idx), span)
}
_ =>
if !future_annotations {
match compile_expr(annotation, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::PopTop, span)
}
}
Ok(())
}
Stmt::TypeAlias(name~, type_params~, value~) => {
let _ = type_params
let template = FunctionValue::{
name: "",
params: [],
defaults: [],
body: [Stmt::Return(Some(value))],
is_generator: false,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(BcOp::MakeFunction(template_idx, 0, 0), span)
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::MakeTypeAlias(name_idx), span)
let _ = b.emit(BcOp::StoreName(name_idx), span)
Ok(())
}
Stmt::AugAssign(target~, op~, value~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match target {
Target::Name(name) => {
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::AugAssignName(name_idx, op), span)
Ok(())
}
Target::Attribute(value~, attr~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let attr_idx = b.intern_name(attr)
let _ = b.emit(BcOp::AugAssignAttr(attr_idx, op), span)
Ok(())
}
Target::Subscript(value~, index~) => {
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match compile_expr(index, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::AugAssignSubscr(op), span)
Ok(())
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: augmented assignment target not implemented".to_string(),
),
)
}
}
Stmt::If(condition~, body~, else_body~) => {
match compile_expr(condition, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let j_else = b.emit(BcOp::JumpIfFalse(-1), span)
for s in body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let j_end = b.emit(BcOp::Jump(-1), span)
let else_pc = b.code.ops.length()
bc_patch_jump(b.code, j_else, else_pc)
for s in else_body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let end_pc = b.code.ops.length()
bc_patch_jump(b.code, j_end, end_pc)
Ok(())
}
Stmt::While(condition~, body~, else_body~) => {
let loop_start = b.code.ops.length()
match compile_expr(condition, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
// If the condition is false, jump to the `else` suite (or loop end when absent).
let j_else = b.emit(BcOp::JumpIfFalse(-1), span)
loop_stack.val.push(LoopCtx::{
has_iter: false,
break_jumps: [],
continue_jumps: [],
finally_depth,
})
for s in body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let ctx_idx = loop_stack.val.length() - 1
let ctx = loop_stack.val[ctx_idx]
let _ = loop_stack.val.remove(ctx_idx)
let _ = b.emit(BcOp::Jump(loop_start), span)
let else_pc = b.code.ops.length()
bc_patch_jump(b.code, j_else, else_pc)
for pc in ctx.continue_jumps {
bc_patch_jump(b.code, pc, loop_start)
}
// `while ... else`: run else_body only when loop terminates normally (i.e. not broken).
for s in else_body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let end_pc = b.code.ops.length()
for pc in ctx.break_jumps {
bc_patch_jump(b.code, pc, end_pc)
}
Ok(())
}
Stmt::For(target~, iter~, body~, else_body~) => {
match target {
Target::Name(_)
| Target::Tuple(_)
| Target::List(_)
| Target::Attribute(..)
| Target::Subscript(..) => ()
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: for target not implemented".to_string(),
),
)
}
match compile_expr(iter, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::GetIter, span)
let loop_start = b.code.ops.length()
let for_iter_pc = b.emit(BcOp::ForIter(-1), span)
loop_stack.val.push(LoopCtx::{
has_iter: true,
break_jumps: [],
continue_jumps: [],
finally_depth,
})
// Assign iteration value (TOS) to target; iterator stays below it.
match compile_target_store(target, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
for s in body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let ctx_idx = loop_stack.val.length() - 1
let ctx = loop_stack.val[ctx_idx]
let _ = loop_stack.val.remove(ctx_idx)
let _ = b.emit(BcOp::Jump(loop_start), span)
let else_pc = b.code.ops.length()
bc_patch_jump(b.code, for_iter_pc, else_pc)
for pc in ctx.continue_jumps {
bc_patch_jump(b.code, pc, loop_start)
}
// `for ... else`: run else_body only when loop is exhausted (i.e. not broken).
for s in else_body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let end_pc = b.code.ops.length()
for pc in ctx.break_jumps {
bc_patch_jump(b.code, pc, end_pc)
}
Ok(())
}
Stmt::AsyncFor(target~, iter~, body~, else_body~) => {
match target {
Target::Name(_)
| Target::Tuple(_)
| Target::List(_)
| Target::Attribute(..)
| Target::Subscript(..) => ()
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: async for target not implemented".to_string(),
),
)
}
let _ = b.emit(
BcOp::CheckInCoroutineScope(
"SyntaxError: 'async for' outside async function".to_string(),
),
span,
)
// it = (ITER).__aiter__()
match compile_expr(iter, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let aiter_idx = b.intern_name("__aiter__")
let _ = b.emit(BcOp::LoadAttr(aiter_idx), span)
let _ = b.emit(BcOp::CallFunction(0), span)
let it_name = "__mpy_asyncfor_it$" + b.code.ops.length().to_string()
let it_name_idx = b.intern_name(it_name)
let _ = b.emit(BcOp::StoreName(it_name_idx), span)
let loop_start = b.code.ops.length()
// try:
// value = await it.__anext__()
// except StopAsyncIteration:
// break
let setup_pc = b.emit(BcOp::SetupExcept(-1), span)
let anext_idx = b.intern_name("__anext__")
let _ = b.emit(BcOp::LoadName(it_name_idx), span)
let _ = b.emit(BcOp::LoadAttr(anext_idx), span)
let _ = b.emit(BcOp::CallFunction(0), span)
let _ = b.emit(BcOp::Await, span)
let _ = b.emit(BcOp::PopExcept, span)
loop_stack.val.push(LoopCtx::{
has_iter: false,
break_jumps: [],
continue_jumps: [],
finally_depth,
})
match compile_target_store(target, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
for s in body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let ctx_idx = loop_stack.val.length() - 1
let ctx = loop_stack.val[ctx_idx]
let _ = loop_stack.val.remove(ctx_idx)
let _ = b.emit(BcOp::Jump(loop_start), span)
// Handler block (catch StopAsyncIteration; otherwise reraise).
let handler_pc = b.code.ops.length()
bc_patch_jump(b.code, setup_pc, handler_pc)
let stop_idx = b.intern_name("StopAsyncIteration")
let _ = b.emit(BcOp::LoadName(stop_idx), span)
let _ = b.emit(BcOp::CheckExceptionMatch, span)
let j_not_stop = b.emit(BcOp::JumpIfFalse(-1), span)
let _ = b.emit(BcOp::EndExcept, span)
let j_to_else = b.emit(BcOp::Jump(-1), span)
let reraise_pc = b.code.ops.length()
bc_patch_jump(b.code, j_not_stop, reraise_pc)
let _ = b.emit(BcOp::Reraise, span)
// Loop exhausted => else suite.
let else_pc = b.code.ops.length()
bc_patch_jump(b.code, j_to_else, else_pc)
for pc in ctx.continue_jumps {
bc_patch_jump(b.code, pc, loop_start)
}
for s in else_body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let end_pc = b.code.ops.length()
for pc in ctx.break_jumps {
bc_patch_jump(b.code, pc, end_pc)
}
let _ = b.emit(BcOp::DeleteName(it_name_idx), span)
Ok(())
}
Stmt::Break => {
if loop_stack.val.length() == 0 {
return Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: break not in loop".to_string(),
),
)
}
let idx = loop_stack.val.length() - 1
let ctx = loop_stack.val[idx]
if ctx.has_iter {
let _ = b.emit(BcOp::PopTop, span)
// pop iterator
}
let unwind = finally_depth - ctx.finally_depth
let j = if unwind > 0 {
b.emit(BcOp::UnwindJump(-1, unwind), span)
} else {
b.emit(BcOp::Jump(-1), span)
}
ctx.break_jumps.push(j)
loop_stack.val[idx] = ctx
Ok(())
}
Stmt::Continue => {
if loop_stack.val.length() == 0 {
return Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: continue not in loop".to_string(),
),
)
}
let idx = loop_stack.val.length() - 1
let ctx = loop_stack.val[idx]
let unwind = finally_depth - ctx.finally_depth
let j = if unwind > 0 {
b.emit(BcOp::UnwindJump(-1, unwind), span)
} else {
b.emit(BcOp::Jump(-1), span)
}
ctx.continue_jumps.push(j)
loop_stack.val[idx] = ctx
Ok(())
}
Stmt::Match(subject~, cases~) => {
match compile_expr(subject, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let subj_name = "__mpy_match_subj$" + b.code.ops.length().to_string()
let subj_idx = b.intern_name(subj_name)
let _ = b.emit(BcOp::StoreName(subj_idx), span)
let end_jumps : Array[Int32] = []
for case in cases {
let pattern = case.0
let guard_opt = case.1
let case_body = case.2
let _ = b.emit(BcOp::LoadName(subj_idx), span)
let pat_idx = b.add_pattern(pattern)
let _ = b.emit(BcOp::MatchPattern(pat_idx), span)
// If match_pattern returns None, skip this case.
let _ = b.emit(BcOp::DupTop, span)
let none_idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::Compare(CompareOp::Eq), span)
let j_no_match = b.emit(BcOp::JumpIfTrue(-1), span)
// Match: bind names and evaluate optional guard.
let _ = b.emit(BcOp::MatchBind, span)
let mut j_guard_fail : Int32? = None
match guard_opt {
Some(guard_expr) => {
match compile_expr(guard_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
j_guard_fail = Some(b.emit(BcOp::JumpIfFalse(-1), span))
}
None => ()
}
for s in case_body {
match
compile_stmt(
s, b, span, loop_stack, finally_depth, future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
// Matched: drop restore token and finish.
let _ = b.emit(BcOp::PopTop, span)
end_jumps.push(b.emit(BcOp::Jump(-1), span))
// Guard failed: restore bindings and continue.
match j_guard_fail {
Some(j) => {
let guard_fail_pc = b.code.ops.length()
bc_patch_jump(b.code, j, guard_fail_pc)
let _ = b.emit(BcOp::MatchRestore, span)
let j_to_next = b.emit(BcOp::Jump(-1), span)
// No-match label: pop the None result and continue to next case.
let no_match_pc = b.code.ops.length()
bc_patch_jump(b.code, j_no_match, no_match_pc)
let _ = b.emit(BcOp::PopTop, span)
let next_case_pc = b.code.ops.length()
bc_patch_jump(b.code, j_to_next, next_case_pc)
}
None => {
let no_match_pc = b.code.ops.length()
bc_patch_jump(b.code, j_no_match, no_match_pc)
let _ = b.emit(BcOp::PopTop, span)
}
}
}
let end_pc = b.code.ops.length()
for pc in end_jumps {
bc_patch_jump(b.code, pc, end_pc)
}
let _ = b.emit(BcOp::DeleteName(subj_idx), span)
Ok(())
}
Stmt::Global(_) => Ok(())
Stmt::Nonlocal(_) => {
let _ = b.emit(
BcOp::CheckInFunctionScope(
"SyntaxError: nonlocal declaration not allowed at module level".to_string(),
),
span,
)
Ok(())
}
Stmt::Assert(condition~, message~) => {
let assert_span : Span? = match expr_span(condition) {
Some(s) => Some(s)
None => span
}
match compile_expr(condition, b, assert_span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let j_ok = b.emit(BcOp::JumpIfTrue(-1), assert_span)
match message {
Some(expr) => {
let msg_span : Span? = match expr_span(expr) {
Some(s) => Some(s)
None => span
}
match compile_expr(expr, b, msg_span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::AssertFail, assert_span)
}
None => {
let _ = b.emit(BcOp::AssertFailNone, assert_span)
}
}
let ok_pc = b.code.ops.length()
bc_patch_jump(b.code, j_ok, ok_pc)
Ok(())
}
Stmt::Return(expr_opt) => {
match expr_opt {
Some(expr) =>
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
None => {
let idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadConst(idx), span)
}
}
let _ = b.emit(BcOp::ReturnValue, span)
Ok(())
}
Stmt::Raise(exc~, cause~) =>
match exc {
None => {
let _ = b.emit(BcOp::Reraise, span)
Ok(())
}
Some(expr) => {
match compile_expr(expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
match cause {
None => {
let _ = b.emit(BcOp::Raise, span)
Ok(())
}
Some(cause_expr) => {
match compile_expr(cause_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::RaiseFrom, span)
Ok(())
}
}
}
}
Stmt::Del(targets) => {
for target in targets {
match compile_target_delete(target, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
Ok(())
}
Stmt::Import(module_name~, alias_name~) => {
let mod_idx = b.add_const(Value::Str(module_name))
let alias_idx = match alias_name {
Some(name) => b.intern_name(name)
None => -1
}
let _ = b.emit(BcOp::ImportName(mod_idx, alias_idx), span)
Ok(())
}
Stmt::FromImport(module_name~, names~) => {
let mod_idx = b.add_const(Value::Str(module_name))
let import_names : Array[Value] = []
let import_aliases : Array[Value] = []
for item in names {
import_names.push(Value::Str(item.0))
match item.1 {
Some(alias_name) => import_aliases.push(Value::Str(alias_name))
None => import_aliases.push(Value::None)
}
}
let names_idx = b.add_const(Value::Tuple(import_names))
let aliases_idx = b.add_const(Value::Tuple(import_aliases))
let _ = b.emit(BcOp::FromImport(mod_idx, names_idx, aliases_idx), span)
Ok(())
}
Stmt::Class(name~, decorators~, type_params~, bases~, keywords~, body~) => {
// Keep evaluation order aligned with CPython:
// decorator expressions first, then base/keyword expressions, then class creation.
for dec_expr in decorators {
match compile_expr(dec_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
if type_params.length() > 0 {
emit_pep695_typing_import(b, span)
}
for base in bases {
match base {
Expr::Starred(value~) => {
let flag_idx = b.add_const(Value::Bool(true))
let _ = b.emit(BcOp::LoadConst(flag_idx), span)
match compile_expr(value, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::BuildTuple(2), span)
}
_ => {
let flag_idx = b.add_const(Value::Bool(false))
let _ = b.emit(BcOp::LoadConst(flag_idx), span)
match compile_expr(base, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::BuildTuple(2), span)
}
}
}
let _ = b.emit(BcOp::BuildList(bases.length()), span)
for kw in keywords {
let key_idx = b.add_const(Value::Str(kw.0))
let _ = b.emit(BcOp::LoadConst(key_idx), span)
match compile_expr(kw.1, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let _ = b.emit(BcOp::BuildTuple(2), span)
}
let _ = b.emit(BcOp::BuildList(keywords.length()), span)
let template = FunctionValue::{
name: "".to_string(),
params: [],
defaults: [],
body,
is_generator: false,
is_async: false,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let name_idx = b.intern_name(name)
// Decorator application happens inside `MakeClass`. Use the outermost
// decorator's span so failures report the correct `@decorator` line.
let apply_span = if decorators.length() > 0 {
match decorators[0] {
Expr::WithSpan(span=dec_span, expr=_) => Some(dec_span)
_ => span
}
} else {
span
}
let _ = b.emit(
BcOp::MakeClass(template_idx, name_idx, decorators.length()),
apply_span,
)
let _ = b.emit(BcOp::StoreName(name_idx), span)
Ok(())
}
Stmt::Try(body~, handlers~, else_body~, finally_body~) =>
if finally_body.length() == 0 {
compile_try_except_else(
body, handlers, else_body, b, span, loop_stack, finally_depth, future_annotations,
)
} else {
let setup_pc = b.emit(BcOp::SetupFinally(-1), span)
match
compile_try_except_else(
body,
handlers,
else_body,
b,
span,
loop_stack,
finally_depth + 1,
future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
let enter_pc = b.emit(BcOp::EnterFinally(-1), span)
let finally_pc = b.code.ops.length()
bc_patch_jump(b.code, setup_pc, finally_pc)
bc_patch_jump(b.code, enter_pc, finally_pc)
for s in finally_body {
match
compile_stmt(
s,
b,
span,
loop_stack,
finally_depth + 1,
future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let _ = b.emit(BcOp::EndFinally, span)
Ok(())
}
Stmt::Function(
name~,
decorators~,
type_params~,
params~,
defaults~,
annotations~,
return_annotation~,
body~,
is_generator~,
is_async~
) => {
// PEP 695 type parameters introduce implicit bindings that are visible to
// annotations. MoonPython does not yet model those runtime bindings, so
// avoid eagerly evaluating annotations for generic functions (typing.py
// uses `def override[F](...) -> F:` patterns).
// Keep evaluation order aligned with CPython:
// decorator expressions first, then default expressions, then function creation.
for dec_expr in decorators {
match compile_expr(dec_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
for def_expr in defaults {
match compile_expr(def_expr, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let mut ann_tmp_idx : Int32? = None
let has_annotations = annotations.length() > 0 ||
return_annotation is Some(_)
let eval_annotations = has_annotations &&
(future_annotations || type_params.length() == 0)
if eval_annotations {
let tmp_name = "$__mpy_fn_annotations$" +
b.code.ops.length().to_string()
let tmp_idx = b.intern_name(tmp_name)
ann_tmp_idx = Some(tmp_idx)
let mut pairs = 0
for pair in annotations {
let key = pair.0
let expr = pair.1
let key_idx = b.add_const(Value::Str(key))
let _ = b.emit(BcOp::LoadConst(key_idx), span)
let ann_span : Span? = match expr_span(expr) {
Some(s) => Some(s)
None => span
}
match compile_annotation(expr, b, ann_span, future_annotations) {
Ok(_) => ()
Err(err) => return Err(err)
}
pairs = pairs + 1
}
match return_annotation {
Some(expr) => {
let key_idx = b.add_const(Value::Str("return".to_string()))
let _ = b.emit(BcOp::LoadConst(key_idx), span)
let ann_span : Span? = match expr_span(expr) {
Some(s) => Some(s)
None => span
}
match compile_annotation(expr, b, ann_span, future_annotations) {
Ok(_) => ()
Err(err) => return Err(err)
}
pairs = pairs + 1
}
None => ()
}
let _ = b.emit(BcOp::BuildMap(pairs), span)
let _ = b.emit(BcOp::StoreName(tmp_idx), span)
}
let template = FunctionValue::{
name,
params,
defaults: [],
body,
is_generator,
is_async,
closure: [],
}
let template_idx = b.add_const(Value::Function(template))
let _ = b.emit(
BcOp::MakeFunction(template_idx, defaults.length(), decorators.length()),
// Decorator application happens inside `MakeFunction`. Use the outermost
// decorator's span so failures report the correct `@decorator` line.
if decorators.length() > 0 {
match decorators[0] {
Expr::WithSpan(span=dec_span, expr=_) => Some(dec_span)
_ => span
}
} else {
span
},
)
match ann_tmp_idx {
Some(tmp_idx) => {
let ann_attr_idx = b.intern_name("__annotations__")
let _ = b.emit(BcOp::DupTop, span)
let _ = b.emit(BcOp::LoadName(tmp_idx), span)
let _ = b.emit(BcOp::StoreAttr(ann_attr_idx), span)
let _ = b.emit(BcOp::DeleteName(tmp_idx), span)
}
None => ()
}
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::StoreName(name_idx), span)
Ok(())
}
Stmt::With(context~, target~, body~) => {
match compile_expr(context, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let exit_idx = b.intern_name("__exit__")
let enter_idx = b.intern_name("__enter__")
let exit_name = "__mpy_with_exit$" + b.code.ops.length().to_string()
let exit_name_idx = b.intern_name(exit_name)
let _ = b.emit(BcOp::DupTop, span)
let _ = b.emit(BcOp::LoadAttr(exit_idx), span)
let _ = b.emit(BcOp::StoreName(exit_name_idx), span)
let _ = b.emit(BcOp::LoadAttr(enter_idx), span)
let _ = b.emit(BcOp::CallFunction(0), span)
match target {
Some(name) => {
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::StoreName(name_idx), span)
()
}
None => {
let _ = b.emit(BcOp::PopTop, span)
()
}
}
let setup_pc = b.emit(BcOp::SetupFinally(-1), span)
for s in body {
match
compile_stmt(
s,
b,
span,
loop_stack,
finally_depth + 1,
future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let enter_pc = b.emit(BcOp::EnterFinally(-1), span)
// Handler (runs on normal exit, return, and exception unwind).
let handler_pc = b.code.ops.length()
bc_patch_jump(b.code, setup_pc, handler_pc)
bc_patch_jump(b.code, enter_pc, handler_pc)
let _ = b.emit(BcOp::HasPendingException, span)
let j_normal = b.emit(BcOp::JumpIfFalse(-1), span)
// Exception path: __exit__(exc_type, exc, tb); if truthy -> suppress.
let _ = b.emit(BcOp::LoadName(exit_name_idx), span)
let _ = b.emit(BcOp::LoadExceptionType, span)
let _ = b.emit(BcOp::LoadException, span)
let _ = b.emit(BcOp::LoadExceptionTraceback, span)
let _ = b.emit(BcOp::CallFunction(3), span)
let j_no_suppress = b.emit(BcOp::JumpIfFalse(-1), span)
let _ = b.emit(BcOp::ClearPendingException, span)
let j_tail = b.emit(BcOp::Jump(-1), span)
// Normal/return path: __exit__(None, None, None)
let normal_pc = b.code.ops.length()
bc_patch_jump(b.code, j_normal, normal_pc)
let none_idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadName(exit_name_idx), span)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::CallFunction(3), span)
let _ = b.emit(BcOp::PopTop, span)
// Tail: delete hidden exit and let EndFinally continue unwind.
let tail_pc = b.code.ops.length()
bc_patch_jump(b.code, j_no_suppress, tail_pc)
bc_patch_jump(b.code, j_tail, tail_pc)
let _ = b.emit(BcOp::DeleteName(exit_name_idx), span)
let _ = b.emit(BcOp::EndFinally, span)
Ok(())
}
Stmt::AsyncWith(context~, target~, body~) => {
match compile_expr(context, b, span) {
Ok(_) => ()
Err(err) => return Err(err)
}
let aexit_idx = b.intern_name("__aexit__")
let aenter_idx = b.intern_name("__aenter__")
let exit_name = "__mpy_async_with_exit$" + b.code.ops.length().to_string()
let exit_name_idx = b.intern_name(exit_name)
let _ = b.emit(BcOp::DupTop, span)
let _ = b.emit(BcOp::LoadAttr(aexit_idx), span)
let _ = b.emit(BcOp::StoreName(exit_name_idx), span)
let _ = b.emit(BcOp::LoadAttr(aenter_idx), span)
let _ = b.emit(BcOp::CallFunction(0), span)
let _ = b.emit(BcOp::Await, span)
match target {
Some(name) => {
let name_idx = b.intern_name(name)
let _ = b.emit(BcOp::StoreName(name_idx), span)
()
}
None => {
let _ = b.emit(BcOp::PopTop, span)
()
}
}
let setup_pc = b.emit(BcOp::SetupFinally(-1), span)
for s in body {
match
compile_stmt(
s,
b,
span,
loop_stack,
finally_depth + 1,
future_annotations,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let enter_pc = b.emit(BcOp::EnterFinally(-1), span)
// Handler (runs on normal exit, return, and exception unwind).
let handler_pc = b.code.ops.length()
bc_patch_jump(b.code, setup_pc, handler_pc)
bc_patch_jump(b.code, enter_pc, handler_pc)
let _ = b.emit(BcOp::HasPendingException, span)
let j_normal = b.emit(BcOp::JumpIfFalse(-1), span)
// Exception path: await __aexit__(exc_type_name, exc, tb); if truthy -> suppress.
let _ = b.emit(BcOp::LoadName(exit_name_idx), span)
let _ = b.emit(BcOp::LoadExceptionTypeName, span)
let _ = b.emit(BcOp::LoadException, span)
let _ = b.emit(BcOp::LoadExceptionTraceback, span)
let _ = b.emit(BcOp::CallFunction(3), span)
let _ = b.emit(BcOp::Await, span)
let j_no_suppress = b.emit(BcOp::JumpIfFalse(-1), span)
let _ = b.emit(BcOp::ClearPendingException, span)
let j_tail = b.emit(BcOp::Jump(-1), span)
// Normal/return path: await __aexit__(None, None, None)
let normal_pc = b.code.ops.length()
bc_patch_jump(b.code, j_normal, normal_pc)
let none_idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadName(exit_name_idx), span)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::LoadConst(none_idx), span)
let _ = b.emit(BcOp::CallFunction(3), span)
let _ = b.emit(BcOp::Await, span)
let _ = b.emit(BcOp::PopTop, span)
// Tail: delete hidden exit and let EndFinally continue unwind.
let tail_pc = b.code.ops.length()
bc_patch_jump(b.code, j_no_suppress, tail_pc)
bc_patch_jump(b.code, j_tail, tail_pc)
let _ = b.emit(BcOp::DeleteName(exit_name_idx), span)
let _ = b.emit(BcOp::EndFinally, span)
Ok(())
}
_ =>
Err(
make_runtime_error(
RuntimeErrorKind::NotImplemented,
"bytecode compiler: stmt not implemented".to_string(),
),
)
}
}
///|
pub fn compile_module_to_bc(
program : Module,
filename : String,
) -> Result[BcCode, RuntimeError] {
compile_stmts_to_bc(program.body, "".to_string(), filename)
}
///|
fn stmts_have_future_annotations(body : Array[Stmt]) -> Bool {
for stmt in body {
match stmt {
Stmt::WithSpan(span=_, stmt=inner) =>
if stmts_have_future_annotations([inner]) {
return true
}
Stmt::FromImport(module_name~, names~) =>
if module_name == "__future__" {
for item in names {
if item.0 == "annotations" {
return true
}
}
}
_ => ()
}
}
false
}
///|
pub fn compile_stmts_to_bc_with_future(
body : Array[Stmt],
name : String,
filename : String,
future_annotations : Bool,
) -> Result[BcCode, RuntimeError] {
if future_annotations {
if future_annotation_error_line(body, 1) is Some(line) {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"line " + line.to_string() + ":1 invalid syntax",
),
)
}
}
let b = BcBuilder::new(name, filename)
let loop_stack : Ref[Array[LoopCtx]] = { val: [], }
for stmt in body {
match compile_stmt(stmt, b, None, loop_stack, 0, future_annotations) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
// Falling off the end returns None.
let none_idx = b.add_const(Value::None)
let _ = b.emit(BcOp::LoadConst(none_idx), None)
let _ = b.emit(BcOp::ReturnValue, None)
Ok(b.code)
}
///|
pub fn compile_stmts_to_bc(
body : Array[Stmt],
name : String,
filename : String,
) -> Result[BcCode, RuntimeError] {
let future = stmts_have_future_annotations(body)
compile_stmts_to_bc_with_future(body, name, filename, future)
}