use std::collections::HashSet;
use crate::front::Error as ApiError;
use crate::front::Result as ApiResult;
use crate::parsing::ast::types::BinaryPart;
use crate::parsing::ast::types::BodyItem;
use crate::parsing::ast::types::CodeBlock;
use crate::parsing::ast::types::Expr;
use crate::parsing::ast::types::ImportSelector;
pub(crate) fn find_defined_names<B: CodeBlock>(block: &B) -> HashSet<String> {
let mut defined_names = HashSet::new();
for item in block.body() {
match item {
BodyItem::ImportStatement(import) => {
match &import.selector {
ImportSelector::List { items } => {
for import_item in items {
defined_names.insert(import_item.identifier().to_owned());
}
}
ImportSelector::Glob(_) => {}
ImportSelector::None { .. } => {}
}
if let Some(module_name) = import.module_name() {
defined_names.insert(module_name);
}
}
BodyItem::ExpressionStatement(expr_stmt) => {
find_defined_names_expr(&expr_stmt.expression, &mut defined_names);
}
BodyItem::VariableDeclaration(var_decl) => {
find_defined_names_expr(&var_decl.declaration.init, &mut defined_names);
let decl = &var_decl.declaration;
defined_names.insert(decl.id.name.clone());
}
BodyItem::TypeDeclaration(type_decl) => {
defined_names.insert(type_decl.name.name.clone());
}
BodyItem::ReturnStatement(ret_stmt) => {
find_defined_names_expr(&ret_stmt.argument, &mut defined_names);
}
}
}
defined_names
}
fn find_defined_names_expr(expr: &Expr, defined_names: &mut HashSet<String>) {
match expr {
Expr::CallExpressionKw(call) => {
for (_, arg) in call.iter_arguments() {
find_defined_names_expr(arg, defined_names);
}
}
Expr::PipeExpression(pipe) => {
for expr in &pipe.body {
find_defined_names_expr(expr, defined_names);
}
}
Expr::LabelledExpression(labeled) => {
find_defined_names_expr(&labeled.expr, defined_names);
defined_names.insert(labeled.label.name.clone());
}
Expr::Literal(_) => {}
Expr::Name(_) => {}
Expr::TagDeclarator(tag_decl) => {
defined_names.insert(tag_decl.name.clone());
}
Expr::BinaryExpression(bin_expr) => {
find_defined_names_binary_part(&bin_expr.left, defined_names);
find_defined_names_binary_part(&bin_expr.right, defined_names);
}
Expr::FunctionExpression(func) => {
if let Some(name) = &func.name {
defined_names.insert(name.name.to_owned());
}
}
Expr::PipeSubstitution(_) => {}
Expr::ArrayExpression(array) => {
for element in &array.elements {
find_defined_names_expr(element, defined_names);
}
}
Expr::ArrayRangeExpression(range) => {
find_defined_names_expr(&range.start_element, defined_names);
find_defined_names_expr(&range.end_element, defined_names);
}
Expr::ObjectExpression(obj) => {
for property in &obj.properties {
find_defined_names_expr(&property.value, defined_names);
}
}
Expr::MemberExpression(member) => {
find_defined_names_expr(&member.object, defined_names);
find_defined_names_expr(&member.property, defined_names);
}
Expr::UnaryExpression(unary_expr) => {
find_defined_names_binary_part(&unary_expr.argument, defined_names);
}
Expr::IfExpression(if_expr) => {
find_defined_names_expr(&if_expr.cond, defined_names);
for else_if in &if_expr.else_ifs {
find_defined_names_expr(&else_if.cond, defined_names);
}
}
Expr::AscribedExpression(expr) => {
find_defined_names_expr(&expr.expr, defined_names);
}
Expr::SketchBlock(sketch_block) => {
for labeled_arg in &sketch_block.arguments {
find_defined_names_expr(&labeled_arg.arg, defined_names);
}
}
Expr::SketchVar(_) => {}
Expr::None(_) => {}
}
}
fn find_defined_names_binary_part(expr: &BinaryPart, defined_names: &mut HashSet<String>) {
match expr {
BinaryPart::Literal(_) => {}
BinaryPart::Name(_) => {}
BinaryPart::BinaryExpression(binary_expr) => {
find_defined_names_binary_part(&binary_expr.left, defined_names);
find_defined_names_binary_part(&binary_expr.right, defined_names);
}
BinaryPart::CallExpressionKw(call) => {
for (_, arg) in call.iter_arguments() {
find_defined_names_expr(arg, defined_names);
}
}
BinaryPart::UnaryExpression(unary_expr) => {
find_defined_names_binary_part(&unary_expr.argument, defined_names);
}
BinaryPart::MemberExpression(member) => {
find_defined_names_expr(&member.object, defined_names);
find_defined_names_expr(&member.property, defined_names);
}
BinaryPart::ArrayExpression(array) => {
for element in &array.elements {
find_defined_names_expr(element, defined_names);
}
}
BinaryPart::ArrayRangeExpression(range) => {
find_defined_names_expr(&range.start_element, defined_names);
find_defined_names_expr(&range.end_element, defined_names);
}
BinaryPart::ObjectExpression(obj) => {
for property in &obj.properties {
find_defined_names_expr(&property.value, defined_names);
}
}
BinaryPart::IfExpression(if_expr) => {
find_defined_names_expr(&if_expr.cond, defined_names);
for else_if in &if_expr.else_ifs {
find_defined_names_expr(&else_if.cond, defined_names);
}
}
BinaryPart::AscribedExpression(expr) => {
find_defined_names_expr(&expr.expr, defined_names);
}
BinaryPart::SketchVar(_) => {}
}
}
pub(super) fn next_free_name(prefix: &str, taken_names: &HashSet<String>) -> ApiResult<String> {
next_free_name_using_padding(prefix, taken_names, 0)
}
pub(crate) fn next_free_name_with_padding(prefix: &str, taken_names: &HashSet<String>) -> ApiResult<String> {
next_free_name_using_max_and_padding(prefix, taken_names, 999, 3)
}
pub(super) fn next_free_name_using_padding(
prefix: &str,
taken_names: &HashSet<String>,
padding: usize,
) -> ApiResult<String> {
next_free_name_using_max_and_padding(prefix, taken_names, 999, padding)
}
pub(crate) fn next_free_name_using_max(prefix: &str, taken_names: &HashSet<String>, max: u16) -> ApiResult<String> {
next_free_name_using_max_and_padding(prefix, taken_names, max, 0)
}
fn next_free_name_using_max_and_padding(
prefix: &str,
taken_names: &HashSet<String>,
mut max: u16,
padding: usize,
) -> ApiResult<String> {
if max == u16::MAX {
max = u16::MAX - 1;
}
let mut index = 1;
while index <= max {
let candidate = if padding > 0 {
format!("{prefix}{index:0padding$}", padding = padding)
} else {
format!("{prefix}{index}")
};
if !taken_names.contains(&candidate) {
return Ok(candidate);
}
index += 1;
}
Err(ApiError {
msg: format!("Could not find a free name with prefix '{prefix}' after maximum tries."),
})
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::next_free_name;
use super::next_free_name_using_max;
use super::next_free_name_using_padding;
#[test]
fn next_free_name_defaults_to_no_padding() {
let taken = HashSet::new();
assert_eq!(next_free_name("sketch", &taken).unwrap(), "sketch1");
assert_eq!(next_free_name("line", &taken).unwrap(), "line1");
}
#[test]
fn next_free_name_with_padding_uses_padding() {
let mut taken = HashSet::new();
taken.insert("sketch001".to_owned());
assert_eq!(next_free_name_using_padding("sketch", &taken, 3).unwrap(), "sketch002");
assert_eq!(next_free_name_using_padding("line", &taken, 2).unwrap(), "line01");
for i in 2..10 {
taken.insert(format!("sketch00{i}").to_owned());
}
taken.insert("line01".to_owned());
assert_eq!(next_free_name_using_padding("sketch", &taken, 3).unwrap(), "sketch010");
assert_eq!(next_free_name_using_padding("line", &taken, 2).unwrap(), "line02");
for i in 2..10 {
taken.insert(format!("line0{i}").to_owned());
}
assert_eq!(next_free_name_using_padding("line", &taken, 2).unwrap(), "line10");
taken.insert("line10".to_owned());
assert_eq!(next_free_name_using_padding("line", &taken, 2).unwrap(), "line11");
}
#[test]
fn next_free_name_using_max_remains_unpadded() {
let taken = HashSet::new();
assert_eq!(next_free_name_using_max("line", &taken, 999).unwrap(), "line1");
}
}