///|
/// Scope and parameter analysis helpers.
///|
fn append_unique_name(names : Array[String], name : String) -> Unit {
for existing in names {
if existing == name {
return
}
}
names.push(name)
}
///|
fn collect_scope_decl_names(
body : Array[Stmt],
) -> (Array[String], Array[String]) {
let globals : Array[String] = []
let nonlocals : Array[String] = []
fn walk(stmt : Stmt) -> Unit {
match stmt {
Stmt::WithSpan(span=_, stmt=inner) => walk(inner)
Stmt::Global(ns) =>
for n in ns {
append_unique_name(globals, n)
}
Stmt::Nonlocal(ns) =>
for n in ns {
append_unique_name(nonlocals, n)
}
Stmt::If(condition~, body~, else_body~) => {
let _ = condition
for s in body {
walk(s)
}
for s in else_body {
walk(s)
}
}
Stmt::While(condition~, body~, else_body~) => {
let _ = condition
for s in body {
walk(s)
}
for s in else_body {
walk(s)
}
}
Stmt::For(target~, iter~, body~, else_body~) => {
let _ = target
let _ = iter
for s in body {
walk(s)
}
for s in else_body {
walk(s)
}
}
Stmt::AsyncFor(target~, iter~, body~, else_body~) => {
let _ = target
let _ = iter
for s in body {
walk(s)
}
for s in else_body {
walk(s)
}
}
Stmt::With(context~, target~, body~) => {
let _ = context
let _ = target
for s in body {
walk(s)
}
}
Stmt::AsyncWith(context~, target~, body~) => {
let _ = context
let _ = target
for s in body {
walk(s)
}
}
Stmt::Try(body~, handlers~, else_body~, finally_body~) => {
for s in body {
walk(s)
}
for handler in handlers {
let _ = handler.exc
let _ = handler.name
for s in handler.body {
walk(s)
}
}
for s in else_body {
walk(s)
}
for s in finally_body {
walk(s)
}
}
Stmt::Match(subject~, cases~) => {
let _ = subject
for case in cases {
let _ = case.0
let _ = case.1
for s in case.2 {
walk(s)
}
}
}
Stmt::Function(..) | Stmt::Class(..) | Stmt::TypeAlias(..) => ()
_ => ()
}
}
for stmt in body {
walk(stmt)
}
(globals, nonlocals)
}
///|
fn name_in_list(names : Array[String], name : String) -> Bool {
for n in names {
if n == name {
return true
}
}
false
}
///|
fn param_has_default_marker(param : String) -> Bool {
if param == "*" || param == "/" {
return false
}
if param.has_prefix("*") {
return false
}
param.has_suffix("=")
}
///|
fn normalize_param_name(param : String) -> String? {
if param == "*" || param == "/" {
return None
}
let mut name = param
if name.has_suffix("=") {
name = substring(name, 0, name.length() - 1)
}
if name.has_prefix("**") {
name = substring(name, 2, name.length())
} else if name.has_prefix("*") {
name = substring(name, 1, name.length())
}
if name.length() == 0 {
None
} else {
Some(name)
}
}
///|
fn param_list_has_name(params : Array[String], name : String) -> Bool {
for param in params {
match normalize_param_name(param) {
Some(param_name) => if param_name == name { return true }
None => ()
}
}
false
}
///|
fn param_is_kw_only_marker(param : String) -> Bool {
param == "*"
}
///|
fn param_is_posonly_marker(param : String) -> Bool {
param == "/"
}
///|
fn param_is_vararg(param : String) -> Bool {
param.has_prefix("*") && !param.has_prefix("**") && param != "*"
}
///|
fn param_is_kwarg(param : String) -> Bool {
param.has_prefix("**")
}
///|
let globals_capture_name = "$__mpython_globals__"
///|
fn resolve_function_globals(
func : FunctionValue,
fallback : Array[(String, Value)],
) -> Array[(String, Value)] {
for pair in func.closure {
if pair.0 == globals_capture_name {
match pair.1 {
Value::Instance(inst) => return inst.dict
_ => ()
}
}
}
fallback
}
///|
fn check_scope_decl_conflicts(
params : Array[String],
global_names : Array[String],
nonlocal_names : Array[String],
) -> Result[Unit, RuntimeError] {
for name in global_names {
if name_in_list(nonlocal_names, name) {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: name '" + name + "' is nonlocal and global",
),
)
}
if param_list_has_name(params, name) {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: name '" + name + "' is parameter and global",
),
)
}
}
for name in nonlocal_names {
if param_list_has_name(params, name) {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"SyntaxError: name '" + name + "' is parameter and nonlocal",
),
)
}
}
Ok(())
}
///|
fn collect_function_local_names(
body : Array[Stmt],
params : Array[String],
global_names : Array[String],
nonlocal_names : Array[String],
include_reads~ : Bool = false,
) -> Array[String] {
let locals : Array[String] = []
fn add_local(name : String) -> Unit {
if name_in_list(global_names, name) || name_in_list(nonlocal_names, name) {
return
}
append_unique_name(locals, name)
}
fn add_target(target : Target) -> Unit {
match target {
Target::Name(name) => add_local(name)
Target::Tuple(items) | Target::List(items) =>
for item in items {
add_target(item)
}
Target::Starred(inner) => add_target(inner)
_ => ()
}
}
fn add_pattern(pat : Pattern) -> Unit {
match pat {
Pattern::Name(name) => add_local(name)
Pattern::Tuple(items) =>
for item in items {
add_pattern(item)
}
_ => ()
}
}
fn walk_expr(expr : Expr) -> Unit {
match expr {
Expr::WithSpan(expr~, ..) => walk_expr(expr)
Expr::Name(name) => if include_reads { add_local(name) }
Expr::NamedExpr(name~, value~) => {
add_local(name)
walk_expr(value)
}
Expr::Lambda(params~, defaults~, body~) => {
let _ = params
for d in defaults {
walk_expr(d)
}
let _ = body
}
Expr::List(items) | Expr::Tuple(items) | Expr::Set(items) =>
for item in items {
walk_expr(item)
}
Expr::ListComp(elt~, clauses~, filters~)
| Expr::GenExp(elt~, clauses~, filters~)
| Expr::SetComp(elt~, clauses~, filters~) => {
if include_reads {
// Only the outer iterator is evaluated in the enclosing scope.
if clauses.length() > 0 {
walk_expr(clauses[0].iter)
}
return
}
for clause in clauses {
let _ = clause.targets
walk_expr(clause.iter)
}
for filter in filters {
walk_expr(filter)
}
walk_expr(elt)
}
Expr::Dict(items) =>
for pair in items {
walk_expr(pair.0)
walk_expr(pair.1)
}
Expr::DictComp(key~, value~, clauses~, filters~) => {
if include_reads {
if clauses.length() > 0 {
walk_expr(clauses[0].iter)
}
return
}
for clause in clauses {
let _ = clause.targets
walk_expr(clause.iter)
}
for filter in filters {
walk_expr(filter)
}
walk_expr(key)
walk_expr(value)
}
Expr::IfExpr(condition~, then_expr~, else_expr~) => {
walk_expr(condition)
walk_expr(then_expr)
walk_expr(else_expr)
}
Expr::Yield(value) =>
match value {
Some(v) => walk_expr(v)
None => ()
}
Expr::YieldFrom(value) | Expr::Await(value) => walk_expr(value)
Expr::Slice(start~, end~, step~) => {
match start {
Some(v) => walk_expr(v)
None => ()
}
match end {
Some(v) => walk_expr(v)
None => ()
}
match step {
Some(v) => walk_expr(v)
None => ()
}
}
Expr::Attribute(value~, attr~) => {
let _ = attr
walk_expr(value)
}
Expr::Subscript(value~, index~) => {
walk_expr(value)
walk_expr(index)
}
Expr::Call(callee~, args~) => {
walk_expr(callee)
for arg in args {
walk_expr(arg)
}
}
Expr::Keyword(name~, value~) => {
let _ = name
walk_expr(value)
}
Expr::Starred(value~) | Expr::DoubleStarred(value~) => walk_expr(value)
Expr::Unary(op~, expr~) => {
let _ = op
walk_expr(expr)
}
Expr::Binary(op~, left~, right~) => {
let _ = op
walk_expr(left)
walk_expr(right)
}
Expr::BoolOp(op~, values~) => {
let _ = op
for v in values {
walk_expr(v)
}
}
Expr::Compare(left~, ops~, comparators~) => {
let _ = ops
walk_expr(left)
for v in comparators {
walk_expr(v)
}
}
_ => ()
}
}
fn walk_stmt(stmt : Stmt) -> Unit {
match stmt {
Stmt::WithSpan(span=_, stmt=inner) => walk_stmt(inner)
Stmt::Assign(target~, value~) => {
add_target(target)
walk_expr(value)
}
Stmt::AnnAssign(target~, annotation~, value~) => {
add_target(target)
walk_expr(annotation)
match value {
Some(v) => walk_expr(v)
None => ()
}
}
Stmt::AugAssign(target~, op~, value~) => {
let _ = op
add_target(target)
walk_expr(value)
}
Stmt::TypeAlias(name~, type_params~, value~) => {
let _ = type_params
add_local(name)
walk_expr(value)
}
Stmt::Del(targets) =>
for t in targets {
add_target(t)
}
Stmt::ExprStmt(expr) => walk_expr(expr)
Stmt::Return(expr) =>
match expr {
Some(v) => walk_expr(v)
None => ()
}
Stmt::If(condition~, body~, else_body~) => {
walk_expr(condition)
for s in body {
walk_stmt(s)
}
for s in else_body {
walk_stmt(s)
}
}
Stmt::While(condition~, body~, else_body~) => {
walk_expr(condition)
for s in body {
walk_stmt(s)
}
for s in else_body {
walk_stmt(s)
}
}
Stmt::For(target~, iter~, body~, else_body~) => {
add_target(target)
walk_expr(iter)
for s in body {
walk_stmt(s)
}
for s in else_body {
walk_stmt(s)
}
}
Stmt::AsyncFor(target~, iter~, body~, else_body~) => {
add_target(target)
walk_expr(iter)
for s in body {
walk_stmt(s)
}
for s in else_body {
walk_stmt(s)
}
}
Stmt::With(context~, target~, body~)
| Stmt::AsyncWith(context~, target~, body~) => {
walk_expr(context)
match target {
Some(name) => add_local(name)
None => ()
}
for s in body {
walk_stmt(s)
}
}
Stmt::Try(body~, handlers~, else_body~, finally_body~) => {
for s in body {
walk_stmt(s)
}
for handler in handlers {
let _ = handler.exc
match handler.name {
Some(name) => add_local(name)
None => ()
}
for s in handler.body {
walk_stmt(s)
}
}
for s in else_body {
walk_stmt(s)
}
for s in finally_body {
walk_stmt(s)
}
}
Stmt::Match(subject~, cases~) => {
walk_expr(subject)
for case in cases {
add_pattern(case.0)
match case.1 {
Some(v) => walk_expr(v)
None => ()
}
for s in case.2 {
walk_stmt(s)
}
}
}
Stmt::Import(module_name~, alias_name~) => {
let binding = match alias_name {
Some(name) => name
None => module_name
}
add_local(binding)
}
Stmt::FromImport(module_name~, names~) => {
let _ = module_name
for item in names {
let name = item.0
let alias_opt = item.1
if name != "*" {
add_local(
match alias_opt {
Some(alias_name) => alias_name
None => name
},
)
}
}
}
Stmt::Function(
name~,
decorators~,
type_params~,
params~,
defaults~,
annotations~,
return_annotation~,
body~,
is_generator~,
is_async~
) => {
add_local(name)
for dec in decorators {
walk_expr(dec)
}
for d in defaults {
walk_expr(d)
}
for ann in annotations {
let _ = ann.0
walk_expr(ann.1)
}
match return_annotation {
Some(expr) => walk_expr(expr)
None => ()
}
let _ = type_params
let _ = params
let _ = body
let _ = is_generator
let _ = is_async
}
Stmt::Class(name~, decorators~, type_params~, bases~, keywords~, body~) => {
add_local(name)
for dec in decorators {
walk_expr(dec)
}
for b in bases {
walk_expr(b)
}
for kw in keywords {
let _ = kw.0
walk_expr(kw.1)
}
let _ = type_params
let _ = body
}
_ => ()
}
}
for p in params {
match normalize_param_name(p) {
Some(name) => add_local(name)
None => ()
}
}
for stmt in body {
walk_stmt(stmt)
}
locals
}