use super::support::{
assign_item_slot_and_rank, assign_meta_slot_and_rank, assign_stmt_slot_and_rank, expect_field,
expect_item, expect_match_arm, expect_param, expect_stmt, expect_variant,
require_insert_fragment, slot_ref_label,
};
use crate::error::{patch_error, PatchError};
use crate::model::{PatchNode, RankedDest, SlotOwner};
use crate::schema::{
expr_kind, invalid_ranked_slot_message, item_kind, ranked_slot_spec, NodeKind,
};
use draxl_ast::{Block, Expr, File, Item, LowerLanguage, Stmt};
pub(super) fn apply_insert(
language: LowerLanguage,
file: &mut File,
dest: RankedDest,
node: PatchNode,
) -> Result<(), PatchError> {
require_insert_fragment(&node)?;
let mut node = Some(node);
let found = match &dest.slot.owner {
SlotOwner::File => {
insert_into_file_slot(
language,
&mut file.items,
&dest.slot.slot,
&dest.rank,
&mut node,
)?;
true
}
SlotOwner::Node(id) => {
let mut found = false;
for item in &mut file.items {
if insert_into_item(language, item, id, &dest.slot.slot, &dest.rank, &mut node)? {
found = true;
break;
}
}
found
}
};
if !found {
return Err(patch_error(&format!(
"insert destination `{}` was not found",
slot_ref_label(&dest.slot)
)));
}
Ok(())
}
fn insert_into_file_slot(
language: LowerLanguage,
items: &mut Vec<Item>,
public_slot: &str,
rank: &str,
node: &mut Option<PatchNode>,
) -> Result<(), PatchError> {
let spec = ranked_slot_spec(language, NodeKind::File, public_slot)
.ok_or_else(|| patch_error(&invalid_ranked_slot_message(language, "file", public_slot)))?;
let mut item = expect_item(node.take(), spec.public_name)?;
assign_item_slot_and_rank(&mut item, spec.meta_slot_name, Some(rank), true)?;
items.push(item);
Ok(())
}
fn insert_into_item(
language: LowerLanguage,
item: &mut Item,
owner_id: &str,
public_slot: &str,
rank: &str,
node: &mut Option<PatchNode>,
) -> Result<bool, PatchError> {
if item.meta().id == owner_id {
let spec = ranked_slot_spec(language, item_kind(language, item), public_slot).ok_or_else(
|| {
patch_error(&invalid_ranked_slot_message(
language,
&format!("@{owner_id}"),
public_slot,
))
},
)?;
match item {
Item::Mod(module) => {
let mut child = expect_item(node.take(), spec.public_name)?;
assign_item_slot_and_rank(&mut child, spec.meta_slot_name, Some(rank), true)?;
module.items.push(child);
return Ok(true);
}
Item::Struct(strukt) => {
let mut field = expect_field(node.take(), spec.public_name)?;
assign_meta_slot_and_rank(&mut field.meta, spec.meta_slot_name, Some(rank), true);
strukt.fields.push(field);
return Ok(true);
}
Item::Enum(enm) => {
let mut variant = expect_variant(node.take(), spec.public_name)?;
assign_meta_slot_and_rank(&mut variant.meta, spec.meta_slot_name, Some(rank), true);
enm.variants.push(variant);
return Ok(true);
}
Item::Fn(function) => match spec.fragment_kind {
crate::schema::FragmentKind::Param => {
let mut param = expect_param(node.take(), spec.public_name)?;
assign_meta_slot_and_rank(
&mut param.meta,
spec.meta_slot_name,
Some(rank),
true,
);
function.params.push(param);
return Ok(true);
}
crate::schema::FragmentKind::Stmt => {
let mut stmt = expect_stmt(node.take(), spec.public_name)?;
assign_stmt_slot_and_rank(&mut stmt, spec.meta_slot_name, Some(rank), true)?;
function.body.stmts.push(stmt);
return Ok(true);
}
_ => {
return Err(patch_error(
"ranked function slot expected parameter or statement",
))
}
},
Item::Use(_) | Item::Doc(_) | Item::Comment(_) => unreachable!(),
}
}
match item {
Item::Mod(module) => {
for child in &mut module.items {
if insert_into_item(language, child, owner_id, public_slot, rank, node)? {
return Ok(true);
}
}
}
Item::Fn(function) => {
if insert_into_block(
language,
&mut function.body,
owner_id,
public_slot,
rank,
node,
)? {
return Ok(true);
}
}
Item::Use(_) | Item::Struct(_) | Item::Enum(_) | Item::Doc(_) | Item::Comment(_) => {}
}
Ok(false)
}
fn insert_into_block(
language: LowerLanguage,
block: &mut Block,
owner_id: &str,
public_slot: &str,
rank: &str,
node: &mut Option<PatchNode>,
) -> Result<bool, PatchError> {
if let Some(meta) = &block.meta {
if meta.id == owner_id {
let spec =
ranked_slot_spec(language, NodeKind::ExprBlock, public_slot).ok_or_else(|| {
patch_error(&invalid_ranked_slot_message(
language,
&format!("@{owner_id}"),
public_slot,
))
})?;
let mut stmt = expect_stmt(node.take(), spec.public_name)?;
assign_stmt_slot_and_rank(&mut stmt, spec.meta_slot_name, Some(rank), true)?;
block.stmts.push(stmt);
return Ok(true);
}
}
for stmt in &mut block.stmts {
if insert_into_stmt(language, stmt, owner_id, public_slot, rank, node)? {
return Ok(true);
}
}
Ok(false)
}
fn insert_into_stmt(
language: LowerLanguage,
stmt: &mut Stmt,
owner_id: &str,
public_slot: &str,
rank: &str,
node: &mut Option<PatchNode>,
) -> Result<bool, PatchError> {
match stmt {
Stmt::Let(let_stmt) => insert_into_expr(
language,
&mut let_stmt.value,
owner_id,
public_slot,
rank,
node,
),
Stmt::Expr(expr_stmt) => insert_into_expr(
language,
&mut expr_stmt.expr,
owner_id,
public_slot,
rank,
node,
),
Stmt::Item(item) => insert_into_item(language, item, owner_id, public_slot, rank, node),
Stmt::Doc(_) | Stmt::Comment(_) => Ok(false),
}
}
fn insert_into_expr(
language: LowerLanguage,
expr: &mut Expr,
owner_id: &str,
public_slot: &str,
rank: &str,
node: &mut Option<PatchNode>,
) -> Result<bool, PatchError> {
if let Some(meta) = expr.meta() {
if meta.id == owner_id {
let spec = ranked_slot_spec(language, expr_kind(language, expr), public_slot)
.ok_or_else(|| {
patch_error(&invalid_ranked_slot_message(
language,
&format!("@{owner_id}"),
public_slot,
))
})?;
match expr {
Expr::Match(match_expr) => {
let mut arm = expect_match_arm(node.take(), spec.public_name)?;
assign_meta_slot_and_rank(&mut arm.meta, spec.meta_slot_name, Some(rank), true);
match_expr.arms.push(arm);
return Ok(true);
}
Expr::Block(block) => {
let mut stmt = expect_stmt(node.take(), spec.public_name)?;
assign_stmt_slot_and_rank(&mut stmt, spec.meta_slot_name, Some(rank), true)?;
block.stmts.push(stmt);
return Ok(true);
}
Expr::Path(_)
| Expr::Lit(_)
| Expr::Group(_)
| Expr::Binary(_)
| Expr::Unary(_)
| Expr::Call(_) => unreachable!(),
}
}
}
match expr {
Expr::Group(group) => {
insert_into_expr(language, &mut group.expr, owner_id, public_slot, rank, node)
}
Expr::Binary(binary) => {
if insert_into_expr(language, &mut binary.lhs, owner_id, public_slot, rank, node)? {
return Ok(true);
}
insert_into_expr(language, &mut binary.rhs, owner_id, public_slot, rank, node)
}
Expr::Unary(unary) => {
insert_into_expr(language, &mut unary.expr, owner_id, public_slot, rank, node)
}
Expr::Call(call) => {
if insert_into_expr(
language,
&mut call.callee,
owner_id,
public_slot,
rank,
node,
)? {
return Ok(true);
}
for arg in &mut call.args {
if insert_into_expr(language, arg, owner_id, public_slot, rank, node)? {
return Ok(true);
}
}
Ok(false)
}
Expr::Match(match_expr) => {
if insert_into_expr(
language,
&mut match_expr.scrutinee,
owner_id,
public_slot,
rank,
node,
)? {
return Ok(true);
}
for arm in &mut match_expr.arms {
if let Some(guard) = &mut arm.guard {
if insert_into_expr(language, guard, owner_id, public_slot, rank, node)? {
return Ok(true);
}
}
if insert_into_expr(language, &mut arm.body, owner_id, public_slot, rank, node)? {
return Ok(true);
}
}
Ok(false)
}
Expr::Block(block) => insert_into_block(language, block, owner_id, public_slot, rank, node),
Expr::Path(_) | Expr::Lit(_) => Ok(false),
}
}