///|
// Direct expression children, without assigning lexical-scope meaning to them.
// Scope-sensitive validators handle lambda/comprehension boundaries themselves.
fn expression_children(expr : Expr) -> Array[Expr] {
let out : Array[Expr] = []
match expr {
Expr::Literal(_) | Expr::Name(_) => ()
Expr::FString(text) => out.append(fstring_expression_children(text))
Expr::WithSpan(expr~, ..)
| Expr::NamedExpr(value=expr, ..)
| Expr::YieldFrom(expr)
| Expr::Await(expr)
| Expr::Unary(expr~, ..)
| Expr::Attribute(value=expr, ..)
| Expr::Keyword(value=expr, ..)
| Expr::Starred(value=expr)
| Expr::DoubleStarred(value=expr) => out.push(expr)
Expr::Yield(expr) => if expr is Some(expr) { out.push(expr) }
Expr::Tuple(items)
| Expr::List(items)
| Expr::Set(items)
| Expr::BoolOp(values=items, ..) => out.append(items)
Expr::Dict(items) =>
for item in items {
out.push(item.0)
out.push(item.1)
}
Expr::ListComp(elt~, clauses~, filters~)
| Expr::SetComp(elt~, clauses~, filters~)
| Expr::GenExp(elt~, clauses~, filters~) => {
out.push(elt)
for clause in clauses {
out.push(clause.iter)
out.append(target_expressions(clause.target))
}
out.append(filters)
}
Expr::DictComp(key~, value~, clauses~, filters~) => {
out.push(key)
out.push(value)
for clause in clauses {
out.push(clause.iter)
out.append(target_expressions(clause.target))
}
out.append(filters)
}
Expr::Lambda(defaults~, body~, ..) => {
out.append(defaults)
out.push(body)
}
Expr::IfExpr(condition~, then_expr~, else_expr~) => {
out.push(condition)
out.push(then_expr)
out.push(else_expr)
}
Expr::Slice(start~, end~, step~) =>
for expr in [start, end, step] {
if expr is Some(expr) {
out.push(expr)
}
}
Expr::Subscript(value~, index~) => {
out.push(value)
out.push(index)
}
Expr::Call(callee~, args~) => {
out.push(callee)
out.append(args)
}
Expr::Binary(left~, right~, ..) => {
out.push(left)
out.push(right)
}
Expr::Compare(left~, comparators~, ..) => {
out.push(left)
out.append(comparators)
}
}
out
}
///|
fn target_expressions(target : Target) -> Array[Expr] {
match target {
Target::Name(_) => []
Target::Attribute(value~, ..) => [value]
Target::Subscript(value~, index~) => [value, index]
Target::Starred(target) => target_expressions(target)
Target::Tuple(items) | Target::List(items) => {
let out : Array[Expr] = []
for item in items {
out.append(target_expressions(item))
}
out
}
}
}
///|
// F-strings retain their source in the AST. Traverse replacement expressions and
// nested format fields using the same scanners as evaluation.
fn fstring_expression_children(text : String) -> Array[Expr] {
let out : Array[Expr] = []
let chars = text.to_array()
let mut i = 0
while i < chars.length() {
if (chars[i] == '{' || chars[i] == '}') &&
i + 1 < chars.length() &&
chars[i + 1] == chars[i] {
i += 2
continue
}
if chars[i] != '{' {
i += 1
continue
}
let close = match
find_fstring_expr_close(substring(text, i + 1, text.length())) {
Some(close) => i + 1 + close
None => break
}
let inner = substring(text, i + 1, close)
let (expr_and_conv, format) = match find_top_level_format_colon(inner) {
Some(pos) =>
(
substring(inner, 0, pos),
Some(substring(inner, pos + 1, inner.length())),
)
None => (inner, None)
}
let raw = match find_top_level_fstring_conversion_bang(expr_and_conv) {
Some(pos) => substring(expr_and_conv, 0, pos)
None => expr_and_conv
}
let expression = match find_top_level_fstring_debug_equal(raw) {
Some(pos) => substring(raw, 0, pos)
None => raw
}
if parse_expr(expression.trim(chars=" \t").to_owned()) is Ok(expr) {
out.push(expr)
}
if format is Some(format) {
out.append(fstring_expression_children(format))
}
i = close + 1
}
out
}