use std::collections::HashMap;
use std::collections::HashSet;
use kcl_error::SourceRange;
use crate::errors::KclError;
use crate::errors::KclErrorDetails;
use crate::front::find_defined_names;
use crate::frontend::modify::next_free_name_with_padding;
use crate::parsing::ast::types::CodeBlock;
use crate::parsing::ast::types::ItemVisibility;
use crate::parsing::ast::types::LabeledArg;
use crate::parsing::ast::types::VariableDeclarator;
use crate::parsing::ast::types::VariableKind;
use crate::parsing::ast::types::{self as ast};
pub(super) fn insert(program: &mut ast::Node<ast::Program>) -> Result<(), KclError> {
let mut context = Context::default();
migrate_program(&mut context, program)?;
Ok(())
}
#[derive(Debug, Clone)]
enum Ty {
SketchBlock { seg1: String, seg2: Option<String> },
Unknown,
}
#[derive(Debug, Clone, Default)]
struct Context {
env: HashMap<String, Ty>,
new_declarations: Vec<ast::BodyItem>,
defined_names: HashSet<String>,
}
impl Context {
fn typ(&self, name: &str) -> &Ty {
self.env.get(name).unwrap_or(&Ty::Unknown)
}
fn bind(&mut self, name: String, ty: Ty) {
self.env.insert(name, ty);
}
}
fn migrate_program(context: &mut Context, program: &mut ast::Node<ast::Program>) -> Result<(), KclError> {
migrate_block(context, program)?;
Ok(())
}
fn migrate_block<B: ast::CodeBlock>(context: &mut Context, block: &mut B) -> Result<(), KclError> {
let block_defined_names = find_defined_names(block);
let mut new_defined_names = context.defined_names.clone();
new_defined_names.extend(block_defined_names);
let previous_defined_names = std::mem::replace(&mut context.defined_names, new_defined_names);
let mut i = 0;
while let Some(item) = block.body_mut().get_mut(i) {
migrate_body_item(context, item)?;
let num_new_declarations = context.new_declarations.len();
if num_new_declarations > 0 {
let ncm = block.non_code_meta_mut();
let shifted: std::collections::BTreeMap<usize, _> = std::mem::take(&mut ncm.non_code_nodes)
.into_iter()
.map(|(k, v)| if k >= i { (k + num_new_declarations, v) } else { (k, v) })
.collect();
ncm.non_code_nodes = shifted;
block
.body_mut()
.splice(i..i, std::mem::take(&mut context.new_declarations));
}
i += 1 + num_new_declarations;
}
context.defined_names = previous_defined_names;
Ok(())
}
fn migrate_body_item(context: &mut Context, item: &mut ast::BodyItem) -> Result<(), KclError> {
match item {
ast::BodyItem::ImportStatement(_) => {}
ast::BodyItem::ExpressionStatement(node) => {
migrate_expr(context, &mut node.expression)?;
}
ast::BodyItem::VariableDeclaration(node) => {
let ty = migrate_expr(context, &mut node.declaration.init)?;
context.bind(node.name().to_owned(), ty);
}
ast::BodyItem::TypeDeclaration(_) => {}
ast::BodyItem::ReturnStatement(node) => {
migrate_expr(context, &mut node.argument)?;
}
}
Ok(())
}
fn migrate_expr(context: &mut Context, expr: &mut ast::Expr) -> Result<Ty, KclError> {
match expr {
ast::Expr::Literal(_) => Ok(Ty::Unknown),
ast::Expr::Name(_) => Ok(Ty::Unknown),
ast::Expr::TagDeclarator(_) => Ok(Ty::Unknown),
ast::Expr::BinaryExpression(node) => {
migrate_binary_expr(context, &mut node.left)?;
migrate_binary_expr(context, &mut node.right)?;
Ok(Ty::Unknown)
}
ast::Expr::FunctionExpression(node) => {
migrate_block(context, &mut node.body)?;
Ok(Ty::Unknown)
}
ast::Expr::CallExpressionKw(node) => {
migrate_call(context, node)?;
Ok(Ty::Unknown)
}
ast::Expr::PipeExpression(node) => {
let mut ty = Ty::Unknown;
for expr in node.body.iter_mut() {
ty = migrate_expr(context, expr)?;
}
Ok(ty)
}
ast::Expr::PipeSubstitution(_) => Ok(Ty::Unknown),
ast::Expr::ArrayExpression(node) => {
for elem in &mut node.elements {
migrate_expr(context, elem)?;
}
Ok(Ty::Unknown)
}
ast::Expr::ArrayRangeExpression(node) => {
migrate_expr(context, &mut node.start_element)?;
migrate_expr(context, &mut node.end_element)?;
Ok(Ty::Unknown)
}
ast::Expr::ObjectExpression(node) => {
for prop in &mut node.properties {
migrate_expr(context, &mut prop.value)?;
}
Ok(Ty::Unknown)
}
ast::Expr::MemberExpression(node) => {
migrate_expr(context, &mut node.object)?;
migrate_expr(context, &mut node.property)?;
Ok(Ty::Unknown)
}
ast::Expr::UnaryExpression(node) => {
migrate_binary_expr(context, &mut node.argument)?;
Ok(Ty::Unknown)
}
ast::Expr::IfExpression(node) => {
migrate_expr(context, &mut node.cond)?;
migrate_block(context, &mut *node.then_val)?;
for else_if in &mut node.else_ifs {
migrate_expr(context, &mut else_if.cond)?;
migrate_block(context, &mut *else_if.then_val)?;
}
migrate_block(context, &mut *node.final_else)?;
Ok(Ty::Unknown)
}
ast::Expr::LabelledExpression(node) => migrate_expr(context, &mut node.expr),
ast::Expr::AscribedExpression(node) => {
migrate_expr(context, &mut node.expr)?;
Ok(Ty::Unknown)
}
ast::Expr::SketchBlock(sketch_block) => {
let mut vars = Vec::with_capacity(2);
for item in sketch_block.body.body() {
if let ast::BodyItem::VariableDeclaration(var_decl) = item {
vars.push(var_decl.name().to_owned());
if vars.len() == 2 {
break;
}
}
}
let mut vars = vars.into_iter();
if let Some(seg1) = vars.next() {
let seg2 = vars.next();
Ok(Ty::SketchBlock { seg1, seg2 })
} else {
Ok(Ty::Unknown)
}
}
ast::Expr::SketchVar(_) => Ok(Ty::Unknown),
ast::Expr::None(_) => Ok(Ty::Unknown),
}
}
fn is_unlabeled_sketch_block(
context: &Context,
call: &ast::Node<ast::CallExpressionKw>,
) -> Option<(String, String, Option<String>)> {
let Some(ast::Expr::Name(name)) = &call.unlabeled else {
return None;
};
let name = &name.name.name;
if let Ty::SketchBlock { seg1, seg2 } = context.typ(name) {
Some((name.to_owned(), seg1.clone(), seg2.clone()))
} else {
None
}
}
fn migrate_call(context: &mut Context, node: &mut ast::Node<ast::CallExpressionKw>) -> Result<(), KclError> {
let range = SourceRange::from(&*node);
let callee_name = node.callee.name.name.as_ref();
match callee_name {
"extrude" | "revolve" | "sweep" | "loft" => {
if let Some((sketch_name, seg1, seg2)) = is_unlabeled_sketch_block(context, node) {
let region_name = next_free_name("region", &context.defined_names, vec![range])?;
context.defined_names.insert(region_name.clone());
let mut args = vec![name_dot_name_ast(sketch_name.clone(), seg1)];
if let Some(seg2) = seg2 {
args.push(name_dot_name_ast(sketch_name, seg2));
}
let region_call = ast::Expr::CallExpressionKw(Box::new(ast::CallExpressionKw::new(
"region",
None,
vec![LabeledArg {
label: Some(ast::Identifier::new("segments")),
arg: ast::Expr::ArrayExpression(Box::new(ast::ArrayExpression::new(args))),
}],
)));
let var_decl = ast::Node::boxed(
Default::default(),
Default::default(),
Default::default(),
ast::VariableDeclaration::new(
VariableDeclarator::new(®ion_name, region_call),
ItemVisibility::Default,
VariableKind::Const,
),
);
context
.new_declarations
.push(ast::BodyItem::VariableDeclaration(var_decl));
node.unlabeled = Some(ast::Expr::Name(Box::new(ast::Name::new(®ion_name))));
}
}
_ => {}
}
for (_, arg) in &mut node.iter_arguments_mut() {
migrate_expr(context, arg)?;
}
Ok(())
}
fn migrate_binary_expr(context: &mut Context, binary_part: &mut ast::BinaryPart) -> Result<(), KclError> {
match binary_part {
ast::BinaryPart::Literal(_) => Ok(()),
ast::BinaryPart::Name(_) => Ok(()),
ast::BinaryPart::BinaryExpression(node) => {
migrate_binary_expr(context, &mut node.left)?;
migrate_binary_expr(context, &mut node.right)
}
ast::BinaryPart::CallExpressionKw(node) => migrate_call(context, node),
ast::BinaryPart::UnaryExpression(node) => migrate_binary_expr(context, &mut node.argument),
ast::BinaryPart::MemberExpression(node) => {
migrate_expr(context, &mut node.object)?;
migrate_expr(context, &mut node.property)?;
Ok(())
}
ast::BinaryPart::ArrayExpression(node) => {
for elem in &mut node.elements {
migrate_expr(context, elem)?;
}
Ok(())
}
ast::BinaryPart::ArrayRangeExpression(node) => {
migrate_expr(context, &mut node.start_element)?;
migrate_expr(context, &mut node.end_element)?;
Ok(())
}
ast::BinaryPart::ObjectExpression(node) => {
for prop in &mut node.properties {
migrate_expr(context, &mut prop.value)?;
}
Ok(())
}
ast::BinaryPart::IfExpression(node) => {
migrate_expr(context, &mut node.cond)?;
migrate_block(context, &mut *node.then_val)?;
for else_if in &mut node.else_ifs {
migrate_expr(context, &mut else_if.cond)?;
migrate_block(context, &mut *else_if.then_val)?;
}
migrate_block(context, &mut *node.final_else)?;
Ok(())
}
ast::BinaryPart::AscribedExpression(node) => {
migrate_expr(context, &mut node.expr)?;
Ok(())
}
ast::BinaryPart::SketchVar(_) => Ok(()),
}
}
fn next_free_name(
prefix: &str,
taken_names: &HashSet<String>,
source_ranges: Vec<SourceRange>,
) -> Result<String, KclError> {
next_free_name_with_padding(prefix, taken_names).map_err(|e| {
KclError::new_internal(KclErrorDetails::new(
format!("Failed to generate a unique name for {prefix}: {}", e.msg),
source_ranges,
))
})
}
fn name_dot_name_ast<S1: Into<String>, S2: Into<String>>(name: S1, property: S2) -> ast::Expr {
ast::Expr::MemberExpression(ast::Node::boxed(
Default::default(),
Default::default(),
Default::default(),
ast::MemberExpression {
object: ast::Expr::Name(Box::new(ast::Name::new(name))),
property: ast::Expr::Name(Box::new(ast::Name::new(property))),
computed: false,
digest: None,
},
))
}