use crate::{ markup_error::MarkResult, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle, }; use super::{attribute::Attributes, common_nodes::new_comma_mn_ast}; use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; use roc_ast::{ ast_error::ASTResult, lang::{ core::{ ast::{ASTNodeId, AST}, def::def2::{Def2, DefId}, expr::{ expr2::{Expr2, ExprId}, record_field::RecordField, }, header::AppHeader, pattern::get_identifier_string, val_def::ValueDef, }, env::Env, }, mem_pool::pool_str::PoolStr, }; use roc_module::symbol::Interns; use roc_utils::{index_of, slice_get}; use std::fmt; use itertools::Itertools; #[derive(Debug)] pub enum MarkupNode { Nested { ast_node_id: ASTNodeId, children_ids: Vec, parent_id_opt: Option, newlines_at_end: usize, }, Text { content: String, ast_node_id: ASTNodeId, syn_high_style: HighlightStyle, attributes: Attributes, parent_id_opt: Option, newlines_at_end: usize, }, Blank { ast_node_id: ASTNodeId, attributes: Attributes, parent_id_opt: Option, newlines_at_end: usize, }, Indent { ast_node_id: ASTNodeId, indent_level: usize, parent_id_opt: Option, }, } impl MarkupNode { pub fn get_ast_node_id(&self) -> ASTNodeId { match self { MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, MarkupNode::Text { ast_node_id, .. } => *ast_node_id, MarkupNode::Blank { ast_node_id, .. } => *ast_node_id, MarkupNode::Indent { ast_node_id, .. } => *ast_node_id, } } pub fn get_parent_id_opt(&self) -> Option { match self { MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt, MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt, MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt, MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt, } } pub fn get_children_ids(&self) -> Vec { match self { MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(), MarkupNode::Text { .. } => vec![], MarkupNode::Blank { .. } => vec![], MarkupNode::Indent { .. } => vec![], } } pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { if let Some(parent_id) = self.get_parent_id_opt() { let parent_node = mark_node_pool.get(parent_id); parent_node.get_children_ids() } else { vec![] } } // return (index of child in list of children, closest ast index of child corresponding to ast node) pub fn get_child_indices( &self, child_id: MarkNodeId, mark_node_pool: &SlowPool, ) -> MarkResult<(usize, usize)> { match self { MarkupNode::Nested { children_ids, .. } => { let mut mark_child_index_opt: Option = None; let mut child_ids_with_ast: Vec = Vec::new(); let self_ast_id = self.get_ast_node_id(); for (indx, &mark_child_id) in children_ids.iter().enumerate() { if mark_child_id == child_id { mark_child_index_opt = Some(indx); } let child_mark_node = mark_node_pool.get(mark_child_id); // a node that points to the same ast_node as the parent is a ',', '[', ']' // those are not "real" ast children if child_mark_node.get_ast_node_id() != self_ast_id { child_ids_with_ast.push(mark_child_id) } } if let Some(child_index) = mark_child_index_opt { if child_index == (children_ids.len() - 1) { let ast_child_index = child_ids_with_ast.len(); Ok((child_index, ast_child_index)) } else { // we want to find the index of the closest ast mark node to child_index let mut indices_in_mark = vec![]; for &c_id in child_ids_with_ast.iter() { indices_in_mark.push(index_of(c_id, children_ids)?); } let mut last_diff = usize::MAX; let mut best_index = 0; for index in indices_in_mark.iter() { let curr_diff = isize::abs((*index as isize) - (child_index as isize)) as usize; if curr_diff >= last_diff { break; } else { last_diff = curr_diff; best_index = *index; } } let closest_ast_child = slice_get(best_index, children_ids)?; let closest_ast_child_index = index_of(*closest_ast_child, &child_ids_with_ast)?; // +1 because we want to insert after ast_child Ok((child_index, closest_ast_child_index + 1)) } } else { NestedNodeMissingChild { node_id: child_id, children_ids: children_ids.clone(), } .fail() } } _ => NestedNodeRequired { node_type: self.node_type_as_string(), } .fail(), } } pub fn get_content(&self) -> String { match self { MarkupNode::Nested { .. } => "".to_owned(), MarkupNode::Text { content, .. } => content.clone(), MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), MarkupNode::Indent { indent_level, .. } => std::iter::repeat(SINGLE_INDENT) .take(*indent_level) .collect(), } } // gets content and adds newline from newline_at_end pub fn get_full_content(&self) -> String { let mut full_content = self.get_content(); for _ in 0..self.get_newlines_at_end() { full_content.push('\n') } full_content } pub fn get_content_mut(&mut self) -> MarkResult<&mut String> { match self { MarkupNode::Text { content, .. } => Ok(content), _ => ExpectedTextNode { function_name: "set_content".to_owned(), node_type: self.node_type_as_string(), } .fail(), } } pub fn is_all_alphanumeric(&self) -> bool { self.get_content() .chars() .all(|chr| chr.is_ascii_alphanumeric()) } pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> MarkResult<()> { if let MarkupNode::Nested { children_ids, .. } = self { children_ids.splice(index..index, vec![child_id]); } else { NestedNodeRequired { node_type: self.node_type_as_string(), } .fail()?; } Ok(()) } pub fn node_type_as_string(&self) -> String { let type_str = match self { MarkupNode::Nested { .. } => "Nested", MarkupNode::Text { .. } => "Text", MarkupNode::Blank { .. } => "Blank", MarkupNode::Indent { .. } => "Indent", }; type_str.to_owned() } pub fn is_blank(&self) -> bool { matches!(self, MarkupNode::Blank { .. }) } pub fn is_nested(&self) -> bool { matches!(self, MarkupNode::Nested { .. }) } pub fn get_newlines_at_end(&self) -> usize { match self { MarkupNode::Nested { newlines_at_end, .. } => *newlines_at_end, MarkupNode::Text { newlines_at_end, .. } => *newlines_at_end, MarkupNode::Blank { newlines_at_end, .. } => *newlines_at_end, MarkupNode::Indent { .. } => 0, } } pub fn add_newline_at_end(&mut self) { match self { MarkupNode::Nested { newlines_at_end, .. } => *newlines_at_end += 1, MarkupNode::Text { newlines_at_end, .. } => *newlines_at_end += 1, MarkupNode::Blank { newlines_at_end, .. } => *newlines_at_end += 1, _ => {} } } } pub fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { pool_str.as_str(env.pool).to_owned() } pub const BLANK_PLACEHOLDER: &str = " "; pub const LEFT_ACCOLADE: &str = "{ "; pub const RIGHT_ACCOLADE: &str = " }"; pub const LEFT_SQUARE_BR: &str = "[ "; pub const RIGHT_SQUARE_BR: &str = " ]"; pub const COLON: &str = ": "; pub const COMMA: &str = ", "; pub const STRING_QUOTES: &str = "\"\""; pub const EQUALS: &str = " = "; pub const ARROW: &str = " -> "; pub const SINGLE_INDENT: &str = " "; // 4 spaces pub fn new_markup_node( text: String, node_id: ASTNodeId, highlight_style: HighlightStyle, mark_node_pool: &mut SlowPool, indent_level: usize, ) -> MarkNodeId { let content_node = MarkupNode::Text { content: text, ast_node_id: node_id, syn_high_style: highlight_style, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; let content_node_id = mark_node_pool.add(content_node); if indent_level > 0 { let indent_node = MarkupNode::Indent { ast_node_id: node_id, indent_level, parent_id_opt: None, }; let indent_node_id = mark_node_pool.add(indent_node); let nested_node = MarkupNode::Nested { ast_node_id: node_id, children_ids: vec![indent_node_id, content_node_id], parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(nested_node) } else { content_node_id } } pub fn def2_to_markup<'a>( env: &mut Env<'a>, def2: &Def2, def2_node_id: DefId, mark_node_pool: &mut SlowPool, interns: &Interns, ) -> ASTResult { let ast_node_id = ASTNodeId::ADefId(def2_node_id); let mark_node_id = match def2 { Def2::ValueDef { identifier_id, expr_id, } => { let expr_mn_id = expr2_to_markup( env, env.pool.get(*expr_id), *expr_id, mark_node_pool, interns, )?; let tld_mn = tld_mark_node( *identifier_id, expr_mn_id, ast_node_id, mark_node_pool, env, interns, )?; mark_node_pool.add(tld_mn) } Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), }; Ok(mark_node_id) } // make Markup Nodes: generate String representation, assign Highlighting Style pub fn expr2_to_markup<'a>( env: &mut Env<'a>, expr2: &Expr2, expr2_node_id: ExprId, mark_node_pool: &mut SlowPool, interns: &Interns, ) -> ASTResult { let ast_node_id = ASTNodeId::AExprId(expr2_node_id); let mark_node_id = match expr2 { Expr2::SmallInt { text, .. } | Expr2::I128 { text, .. } | Expr2::U128 { text, .. } | Expr2::Float { text, .. } => { let num_str = get_string(env, text); new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) } Expr2::Str(text) => new_markup_node( "\"".to_owned() + text.as_str(env.pool) + "\"", ast_node_id, HighlightStyle::String, mark_node_pool, ), Expr2::GlobalTag { name, .. } => new_markup_node( get_string(env, name), ast_node_id, HighlightStyle::Type, mark_node_pool, ), Expr2::Call { expr: expr_id, .. } => { let expr = env.pool.get(*expr_id); expr2_to_markup(env, expr, *expr_id, mark_node_pool, interns)? } Expr2::Var(symbol) => { //TODO make bump_format with arena let text = format!("{:?}", symbol); new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) } Expr2::List { elems, .. } => { let mut children_ids = vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; let indexed_node_ids: Vec<(usize, ExprId)> = elems.iter(env.pool).copied().enumerate().collect(); for (idx, node_id) in indexed_node_ids.iter() { let sub_expr2 = env.pool.get(*node_id); children_ids.push(expr2_to_markup( env, sub_expr2, *node_id, mark_node_pool, interns, )?); if idx + 1 < elems.len() { children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); } } children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); let list_node = MarkupNode::Nested { ast_node_id, children_ids, parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(list_node) } Expr2::EmptyRecord => { let children_ids = vec![ mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), ]; let record_node = MarkupNode::Nested { ast_node_id, children_ids, parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(record_node) } Expr2::Record { fields, .. } => { let mut children_ids = vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; for (idx, field_node_id) in fields.iter_node_ids().enumerate() { let record_field = env.pool.get(field_node_id); let field_name = record_field.get_record_field_pool_str(); children_ids.push(new_markup_node( field_name.as_str(env.pool).to_owned(), ast_node_id, HighlightStyle::RecordField, mark_node_pool, )); match record_field { RecordField::InvalidLabelOnly(_, _) => (), RecordField::LabelOnly(_, _, _) => (), RecordField::LabeledValue(_, _, sub_expr2_node_id) => { children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); let sub_expr2 = env.pool.get(*sub_expr2_node_id); children_ids.push(expr2_to_markup( env, sub_expr2, *sub_expr2_node_id, mark_node_pool, interns, )?); } } if idx + 1 < fields.len() { children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); } } children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); let record_node = MarkupNode::Nested { ast_node_id, children_ids, parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(record_node) } Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), Expr2::LetValue { def_id, body_id: _, body_var: _, } => { let pattern_id = env.pool.get(*def_id).get_pattern_id(); let pattern2 = env.pool.get(pattern_id); let val_name = get_identifier_string(pattern2, interns)?; let val_name_mn = MarkupNode::Text { content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; let val_name_mn_id = mark_node_pool.add(val_name_mn); let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); let value_def = env.pool.get(*def_id); match value_def { ValueDef::NoAnnotation { pattern_id: _, expr_id, expr_var: _, } => { let body_mn_id = expr2_to_markup( env, env.pool.get(*expr_id), *expr_id, mark_node_pool, interns, )?; let body_mn = mark_node_pool.get_mut(body_mn_id); body_mn.add_newline_at_end(); let full_let_node = MarkupNode::Nested { ast_node_id, children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], parent_id_opt: None, newlines_at_end: 1, }; mark_node_pool.add(full_let_node) } other => { unimplemented!( "I don't know how to convert {:?} into a MarkupNode yet.", other ) } } } Expr2::RuntimeError() => new_markup_node( "RunTimeError".to_string(), ast_node_id, HighlightStyle::Blank, mark_node_pool, ), rest => todo!("implement expr2_to_markup for {:?}", rest), }; Ok(mark_node_id) } pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { let node = mark_node_pool.get(markup_node_id); if let MarkupNode::Nested { ast_node_id: _, children_ids, parent_id_opt: _, newlines_at_end: _, } = node { // need to clone because of borrowing issues let children_ids_clone = children_ids.clone(); for child_id in children_ids_clone { set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); } } } pub fn set_parent_for_all_helper( markup_node_id: MarkNodeId, parent_node_id: MarkNodeId, mark_node_pool: &mut SlowPool, ) { let node = mark_node_pool.get_mut(markup_node_id); match node { MarkupNode::Nested { children_ids, parent_id_opt, .. } => { *parent_id_opt = Some(parent_node_id); // need to clone because of borrowing issues let children_ids_clone = children_ids.clone(); for child_id in children_ids_clone { set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); } } MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), } } fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { let mark_node = MarkupNode::Text { content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::PackageRelated, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(mark_node) } fn header_val_mn( content: String, expr_id: ExprId, highlight_style: HighlightStyle, mark_node_pool: &mut SlowPool, ) -> MarkNodeId { let mark_node = MarkupNode::Text { content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: highlight_style, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(mark_node) } pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { let expr_id = app_header.ast_node_id; let ast_node_id = ASTNodeId::AExprId(expr_id); let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); let app_name_node_id = header_val_mn( app_header.app_name.clone(), expr_id, HighlightStyle::String, mark_node_pool, ); let full_app_node = MarkupNode::Nested { ast_node_id, children_ids: vec![app_node_id, app_name_node_id], parent_id_opt: None, newlines_at_end: 1, }; let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); let pack_base_node_id = header_val_mn( "base: ".to_owned(), expr_id, HighlightStyle::RecordField, mark_node_pool, ); let pack_val_node_id = header_val_mn( app_header.packages_base.clone(), expr_id, HighlightStyle::String, mark_node_pool, ); let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); let full_packages_node = MarkupNode::Nested { ast_node_id, children_ids: vec![ packages_node_id, pack_left_acc_node_id, pack_base_node_id, pack_val_node_id, pack_right_acc_node_id, ], parent_id_opt: None, newlines_at_end: 1, }; let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); let mut import_child_ids: Vec = add_header_mn_list( &app_header.imports, expr_id, HighlightStyle::Import, mark_node_pool, ); let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; full_import_children.append(&mut import_child_ids); full_import_children.push(imports_right_square_node_id); let full_import_node = MarkupNode::Nested { ast_node_id, children_ids: full_import_children, parent_id_opt: None, newlines_at_end: 1, }; let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); let mut provides_val_node_ids: Vec = add_header_mn_list( &app_header.provides, expr_id, HighlightStyle::Provides, mark_node_pool, ); let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; full_provides_children.append(&mut provides_val_node_ids); full_provides_children.push(provides_right_square_node_id); full_provides_children.push(provides_end_node_id); let full_provides_node = MarkupNode::Nested { ast_node_id, children_ids: full_provides_children, parent_id_opt: None, newlines_at_end: 1, }; let full_app_node_id = mark_node_pool.add(full_app_node); let full_packages_node = mark_node_pool.add(full_packages_node); let full_import_node_id = mark_node_pool.add(full_import_node); let full_provides_node_id = mark_node_pool.add(full_provides_node); let header_mark_node = MarkupNode::Nested { ast_node_id, children_ids: vec![ full_app_node_id, full_packages_node, full_import_node_id, full_provides_node_id, ], parent_id_opt: None, newlines_at_end: 1, }; let header_mn_id = mark_node_pool.add(header_mark_node); set_parent_for_all(header_mn_id, mark_node_pool); header_mn_id } // Used for provides and imports fn add_header_mn_list( str_vec: &[String], expr_id: ExprId, highlight_style: HighlightStyle, mark_node_pool: &mut SlowPool, ) -> Vec { let nr_of_elts = str_vec.len(); str_vec .iter() .enumerate() .map(|(indx, provide_str)| { let provide_str = header_val_mn( provide_str.to_owned(), expr_id, highlight_style, mark_node_pool, ); if indx != nr_of_elts - 1 { vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] } else { vec![provide_str] } }) .flatten() .collect() } pub fn ast_to_mark_nodes<'a>( env: &mut Env<'a>, ast: &AST, mark_node_pool: &mut SlowPool, interns: &Interns, ) -> ASTResult> { let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; for &def_id in ast.def_ids.iter() { let def2 = env.pool.get(def_id); let def2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?; set_parent_for_all(def2_markup_id, mark_node_pool); all_mark_node_ids.push(def2_markup_id); } Ok(all_mark_node_ids) } impl fmt::Display for MarkupNode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} ({}, {})", self.node_type_as_string(), self.get_content(), self.get_newlines_at_end() ) } } pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { let mut full_string = "\n(mark_node_tree)\n".to_owned(); let node = mark_node_pool.get(root_node_id); full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); full_string } fn tree_as_string_helper( node: &MarkupNode, level: usize, tree_string: &mut String, mark_node_pool: &SlowPool, ) { for child_id in node.get_children_ids() { let mut full_str = std::iter::repeat("|--- ") .take(level) .collect::>() .join("") .to_owned(); let child = mark_node_pool.get(child_id); let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); tree_string.push_str(&full_str); tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); } } // return to the the root parent_id of the current node pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { let mut curr_mark_node_id = mark_node_id; let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); while let Some(curr_parent_id) = curr_parent_id_opt { curr_mark_node_id = curr_parent_id; curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); } curr_mark_node_id } pub fn join_mark_nodes_spaces(mark_nodes_ids: Vec, with_prepend: bool, ast_node_id: ASTNodeId, mark_node_pool: &mut SlowPool) -> Vec { let space_range_max = if with_prepend { mark_nodes_ids.len() } else { mark_nodes_ids.len() - 1 }; let join_nodes: Vec = (0..space_range_max) .map(|_| { let space_node = MarkupNode::Text { content: " ".to_string(), ast_node_id, syn_high_style: HighlightStyle::Blank, attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; mark_node_pool.add(space_node) }) .collect(); if with_prepend { join_nodes.into_iter().interleave(mark_nodes_ids).collect() } else { mark_nodes_ids.into_iter().interleave(join_nodes).collect() } } pub fn join_mark_nodes_commas(mark_nodes: Vec, ast_node_id: ASTNodeId) -> Vec { let join_nodes: Vec = (0..(mark_nodes.len() - 1)) .map(|_| { new_comma_mn_ast(ast_node_id, None) }) .collect(); mark_nodes.into_iter().interleave(join_nodes).collect() }