From 87326a2f3b2b61422df2eebf15958a95ace5452e Mon Sep 17 00:00:00 2001 From: Adam Obuchowicz Date: Wed, 24 Jun 2020 09:35:10 +0200 Subject: [PATCH] Expression id in span tree (https://github.com/enso-org/ide/pull/599) The optional Expression Id was added to SpanTree, to allow reading type information by views. Original commit: https://github.com/enso-org/ide/commit/b85eeba5aad7d704391117d052c240d326ee3c9a --- gui/build/run.js | 2 +- gui/src/rust/ide/ast/impl/src/lib.rs | 9 +- gui/src/rust/ide/ast/impl/src/opr.rs | 44 +++-- gui/src/rust/ide/ast/impl/src/prefix.rs | 62 +++++-- gui/src/rust/ide/parser/src/lib.rs | 9 +- gui/src/rust/ide/span-tree/src/action.rs | 5 +- gui/src/rust/ide/span-tree/src/builder.rs | 18 ++- gui/src/rust/ide/span-tree/src/generate.rs | 178 ++++++++++++++------- gui/src/rust/ide/span-tree/src/lib.rs | 9 +- gui/src/rust/ide/span-tree/src/node.rs | 17 +- 10 files changed, 242 insertions(+), 111 deletions(-) diff --git a/gui/build/run.js b/gui/build/run.js index 261dbb5c69f..3592b156af9 100755 --- a/gui/build/run.js +++ b/gui/build/run.js @@ -127,7 +127,7 @@ commands.build.rust = async function(argv) { console.log('Checking the resulting WASM size.') let stats = fss.statSync(paths.dist.wasm.mainOptGz) - let limit = 3.32 + let limit = 3.33 let size = Math.round(100 * stats.size / 1024 / 1024) / 100 if (size > limit) { throw(`Output file size exceeds the limit (${size}MB > ${limit}MB).`) diff --git a/gui/src/rust/ide/ast/impl/src/lib.rs b/gui/src/rust/ide/ast/impl/src/lib.rs index 2375ec5cf42..341121ebb5b 100644 --- a/gui/src/rust/ide/ast/impl/src/lib.rs +++ b/gui/src/rust/ide/ast/impl/src/lib.rs @@ -75,14 +75,17 @@ impl IdMap { IdMap {vec} } /// Assigns Span to given ID. - pub fn insert(&mut self, span:Span, id:Id) { - self.vec.push((span,id)); + pub fn insert(&mut self, span:impl Into, id:Id) { + self.vec.push((span.into(),id)); + } + /// Generate random Uuid for span. + pub fn generate(&mut self, span:impl Into) { + self.vec.push((span.into(),Uuid::new_v4())); } } - // ============== // === Errors === // ============== diff --git a/gui/src/rust/ide/ast/impl/src/opr.rs b/gui/src/rust/ide/ast/impl/src/opr.rs index b6f79c0fc95..cbcf5f2146c 100644 --- a/gui/src/rust/ide/ast/impl/src/opr.rs +++ b/gui/src/rust/ide/ast/impl/src/opr.rs @@ -2,6 +2,7 @@ use crate::prelude::*; +use crate::Id; use crate::Infix; use crate::SectionLeft; use crate::SectionRight; @@ -129,6 +130,8 @@ pub struct GeneralizedInfix { pub opr : Operator, /// Right operand, if present. pub right : Operand, + /// Infix id. + pub id : Option, } /// A structure used for GeneralizedInfix construction which marks operands as _target_ and @@ -144,23 +147,28 @@ impl GeneralizedInfix { /// Tries interpret given AST node as GeneralizedInfix. Returns None, if Ast is not any kind of /// application on infix operator. pub fn try_new(ast:&Ast) -> Option { + let id = ast.id; match ast.shape().clone() { Shape::Infix(infix) => Some(GeneralizedInfix{ + id, left : make_operand (infix.larg,infix.loff), opr : make_operator(&infix.opr)?, right : make_operand (infix.rarg,infix.roff), }), Shape::SectionLeft(left) => Some(GeneralizedInfix{ + id, left : make_operand (left.arg,left.off), opr : make_operator(&left.opr)?, right : None, }), Shape::SectionRight(right) => Some(GeneralizedInfix{ + id, left : None, opr : make_operator(&right.opr)?, right : make_operand (right.arg,right.off), }), Shape::SectionSides(sides) => Some(GeneralizedInfix{ + id, left : None, opr : make_operator(&sides.opr)?, right : None, @@ -170,13 +178,13 @@ impl GeneralizedInfix { } /// Constructor with operands marked as target and argument. - pub fn new_from_operands(operands:MarkedOperands, opr:Operator) -> Self { + pub fn new_from_operands(operands:MarkedOperands, opr:Operator, id:Option) -> Self { match assoc(&opr) { - Assoc::Left => GeneralizedInfix {opr, + Assoc::Left => GeneralizedInfix {opr,id, left : operands.target, right : operands.argument, }, - Assoc::Right => GeneralizedInfix {opr, + Assoc::Right => GeneralizedInfix {opr,id, left : operands.argument, right : operands.target, }, @@ -185,7 +193,7 @@ impl GeneralizedInfix { /// Convert to AST node. pub fn into_ast(self) -> Ast { - match (self.left,self.right) { + let ast:Ast = match (self.left,self.right) { (Some(left),Some(right)) => Infix{ larg : left.arg, loff : left.offset, @@ -206,6 +214,11 @@ impl GeneralizedInfix { (None,None) => SectionSides { opr : self.opr.into() }.into() + }; + if let Some(id) = self.id{ + ast.with_id(id) + } else { + ast } } @@ -247,6 +260,7 @@ impl GeneralizedInfix { let rest = ChainElement {offset, operator : self.opr.clone(), operand : self.argument_operand(), + infix_id : self.id, }; let target_subtree_infix = target.clone().and_then(|arg| { @@ -341,15 +355,16 @@ impl Chain { /// Indexing does not skip `None` operands. Function panics, if get index greater than operands /// count. pub fn insert_operand(&mut self, at_index:usize, operand:ArgWithOffset) { - let offset = operand.offset; - let mut operand = Some(operand); - let operator = self.operator.clone_ref(); - let before_target = at_index == 0; + let offset = operand.offset; + let mut operand = Some(operand); + let operator = self.operator.clone_ref(); + let before_target = at_index == 0; + let infix_id:Option = None; if before_target { std::mem::swap(&mut operand, &mut self.target); - self.args.insert(0,ChainElement{operator,operand,offset}) + self.args.insert(0,ChainElement{operator,operand,offset,infix_id}) } else { - self.args.insert(at_index-1,ChainElement{operator,operand,offset}) + self.args.insert(at_index-1,ChainElement{operator,operand,offset,infix_id}) } } @@ -380,7 +395,8 @@ impl Chain { let operator = element.operator; let argument = element.operand; let operands = MarkedOperands{target,argument}; - let new_infix = GeneralizedInfix::new_from_operands(operands,operator); + let id = element.infix_id; + let new_infix = GeneralizedInfix::new_from_operands(operands,operator,id); let new_with_offset = ArgWithOffset { arg : new_infix.into_ast(), offset : element.offset, @@ -398,8 +414,8 @@ impl Chain { self.fold_arg() } // TODO[ao] the only case when target is none is when chain have None target and empty - // arguments list. Such Chain cannot be generated from Ast, but someone could think that - // this is still a valid chain. To consider returning error here. + // arguments list. Such Chain cannot be generated from Ast, but someone could think that + // this is still a valid chain. To consider returning error here. self.target.unwrap().arg } @@ -429,6 +445,8 @@ pub struct ChainElement { pub operand : Operand, /// Offset between this operand and the next operator. pub offset : usize, + /// Id of infix AST which applies this operand. + pub infix_id : Option, } impl ChainElement { diff --git a/gui/src/rust/ide/ast/impl/src/prefix.rs b/gui/src/rust/ide/ast/impl/src/prefix.rs index f3641d1a207..5fb0cfdbc2d 100644 --- a/gui/src/rust/ide/ast/impl/src/prefix.rs +++ b/gui/src/rust/ide/ast/impl/src/prefix.rs @@ -2,22 +2,49 @@ use crate::prelude::*; -use crate::Ast; +use crate::{Ast, TokenConsumer}; +use crate::Id; use crate::crumbs::Located; use crate::crumbs::PrefixCrumb; +use crate::HasTokens; use crate::known; use crate::Prefix; use crate::Shifted; use utils::vec::VecExt; + + +// ================ +// === Argument === +// ================ + +/// Struct representing an element of a Prefix Chain: an argument applied over the function. #[derive(Clone,Debug)] +pub struct Argument { + /// An argument ast with offset between it and previous arg or function. + pub sast : Shifted, + /// The id of Prefix AST of this argument application. + pub prefix_id : Option, +} + +impl HasTokens for Argument { + fn feed_to(&self, consumer: &mut impl TokenConsumer) { + self.sast.feed_to(consumer) + } +} + +// ==================== +// === Prefix Chain === +// ==================== + /// Result of flattening a sequence of prefix applications. +#[derive(Clone,Debug)] pub struct Chain { /// The function (initial application target) pub func : Ast, /// Subsequent arguments applied over the function. - pub args : Vec>, + pub args : Vec, } impl Chain { @@ -25,12 +52,14 @@ impl Chain { /// App(App(a,b),c) into flat list where first element is the function and /// then arguments are placed: `{func:a, args:[b,c]}`. pub fn new(ast:&known::Prefix) -> Chain { - fn run(ast:&known::Prefix, mut acc: &mut Vec>) -> Ast { + fn run(ast:&known::Prefix, mut acc: &mut Vec) -> Ast { let func = match known::Prefix::try_from(&ast.func) { Ok(lhs_app) => run(&lhs_app, &mut acc), _ => ast.func.clone(), }; - acc.push(Shifted{wrapped:ast.arg.clone(),off:ast.off}); + let sast = Shifted{wrapped:ast.arg.clone(),off:ast.off}; + let prefix_id = ast.id(); + acc.push(Argument{sast,prefix_id}); func } @@ -52,9 +81,11 @@ impl Chain { Self::new(prefix) } else if let Ok(ref section) = known::SectionRight::try_from(ast) { // Case like `+ a b` - let func = section.opr.clone(); + let func = section.opr.clone(); let right_chain = Chain::new_non_strict(§ion.arg); - let mut args = vec![Shifted{wrapped:right_chain.func, off:section.off}]; + let sast = Shifted{wrapped:right_chain.func, off:section.off}; + let prefix_id = section.id(); + let mut args = vec![Argument{sast,prefix_id}]; args.extend(right_chain.args); Chain {func,args} } else { @@ -87,7 +118,7 @@ impl Chain { let mut i = 0; self.args.iter().map(move |arg| { i += 1; - Located::new(&func_crumbs[i..],&arg.wrapped) + Located::new(&func_crumbs[i..],&arg.sast.wrapped) }) } @@ -96,11 +127,11 @@ impl Chain { pub fn fold_arg(&mut self) { if let Some(arg) = self.args.pop_front() { let new_prefix = Prefix{ - arg : arg.wrapped, + arg : arg.sast.wrapped, func : self.func.clone_ref(), - off : arg.off, + off : arg.sast.off, }; - self.func = new_prefix.into(); + self.func = Ast::new(new_prefix,arg.prefix_id); } } @@ -120,6 +151,7 @@ mod tests { use super::*; use utils::test::ExpectTuple; + use uuid::Uuid; #[test] fn prefix_chain() { @@ -127,13 +159,15 @@ mod tests { let b = Ast::var("b"); let c = Ast::var("c"); - let a_b = Ast::prefix(a.clone(),b.clone()); - let a_b_c = Ast::prefix(a_b.clone(),c.clone()); + let a_b = Ast::prefix(a.clone(),b.clone()).with_id(Uuid::new_v4()); + let a_b_c = Ast::prefix(a_b.clone(),c.clone()).with_id(Uuid::new_v4()); let chain = Chain::try_new(&a_b_c).unwrap(); assert_eq!(chain.func, a); - assert_eq!(chain.args[0].wrapped, b); - assert_eq!(chain.args[1].wrapped, c); + assert_eq!(chain.args[0].sast.wrapped, b); + assert_eq!(chain.args[1].sast.wrapped, c); + assert_eq!(chain.args[0].prefix_id, a_b.id); + assert_eq!(chain.args[1].prefix_id, a_b_c.id); let (arg1,arg2) = chain.enumerate_args().expect_tuple(); assert_eq!(arg1.item, &b); diff --git a/gui/src/rust/ide/parser/src/lib.rs b/gui/src/rust/ide/parser/src/lib.rs index fb8eeaa9d10..0dd37a9ba2f 100644 --- a/gui/src/rust/ide/parser/src/lib.rs +++ b/gui/src/rust/ide/parser/src/lib.rs @@ -97,9 +97,14 @@ impl Parser { } /// Program is expected to be single non-empty line module. The line's AST is - /// returned. Panics otherwise. + /// returned. Panics otherwise. The program is parsed with empty IdMap. pub fn parse_line(&self, program:impl Str) -> FallibleResult { - let module = self.parse_module(program,default())?; + self.parse_line_with_id_map(program,default()) + } + /// Program is expected to be single non-empty line module. The line's AST is returned. Panics + /// otherwise. + pub fn parse_line_with_id_map(&self, program:impl Str, id_map:IdMap) -> FallibleResult { + let module = self.parse_module(program,id_map)?; let mut lines = module.lines.clone().into_iter().filter_map(|line| { line.elem diff --git a/gui/src/rust/ide/span-tree/src/action.rs b/gui/src/rust/ide/span-tree/src/action.rs index 6407fee0f08..9803291b9d7 100644 --- a/gui/src/rust/ide/span-tree/src/action.rs +++ b/gui/src/rust/ide/span-tree/src/action.rs @@ -130,7 +130,10 @@ impl<'a> Implementation for node::Ref<'a> { infix.into_ast() } else { let mut prefix = ast::prefix::Chain::new_non_strict(ast); - let item = Shifted{wrapped:new, off:DEFAULT_OFFSET}; + let item = ast::prefix::Argument{ + sast : Shifted{wrapped:new, off:DEFAULT_OFFSET}, + prefix_id : None, + }; match ins_type { BeforeTarget => prefix.args.insert(0,item), AfterTarget => prefix.args.insert(1,item), diff --git a/gui/src/rust/ide/span-tree/src/builder.rs b/gui/src/rust/ide/span-tree/src/builder.rs index 9748bfcfcd5..d8548882377 100644 --- a/gui/src/rust/ide/span-tree/src/builder.rs +++ b/gui/src/rust/ide/span-tree/src/builder.rs @@ -22,8 +22,9 @@ pub trait Builder : Sized { fn add_child (self, offset:usize, len:usize, kind:node::Kind, crumbs:impl IntoCrumbs) -> ChildBuilder { let node = Node {kind, - size: Size::new(len), - children : vec![] + size : Size::new(len), + children : vec![], + expression_id : None, }; let child = node::Child { node, offset : Size::new(offset), @@ -50,6 +51,12 @@ pub trait Builder : Sized { self.node_being_built().children.push(child); self } + + /// Set expression id for this node. + fn set_expression_id(mut self, id:ast::Id) -> Self { + self.node_being_built().expression_id = Some(id); + self + } } @@ -71,9 +78,10 @@ impl TreeBuilder { pub fn new(len:usize) -> Self { TreeBuilder { built : Node { - kind : node::Kind::Root, - size : Size::new(len), - children : vec![], + kind : node::Kind::Root, + size : Size::new(len), + children : vec![], + expression_id : None, } } } diff --git a/gui/src/rust/ide/span-tree/src/generate.rs b/gui/src/rust/ide/span-tree/src/generate.rs index 35290581427..b385f7dbf5d 100644 --- a/gui/src/rust/ide/span-tree/src/generate.rs +++ b/gui/src/rust/ide/span-tree/src/generate.rs @@ -110,14 +110,15 @@ impl SpanTreeGenerator for Ast { ast::prefix::Chain::try_new(self).unwrap().generate_node(kind), // Lambdas should fall in _ case, because we don't want to create subports for // them - ast::Shape::Match(ast) if ast::macros::as_lambda_match(self).is_none() => - ast.generate_node(kind), - ast::Shape::Ambiguous(ast) => - ast.generate_node(kind), + ast::Shape::Match(_) if ast::macros::as_lambda_match(self).is_none() => + ast::known::Match::try_new(self.clone_ref()).unwrap().generate_node(kind), + ast::Shape::Ambiguous(_) => + ast::known::Ambiguous::try_new(self.clone_ref()).unwrap().generate_node(kind), _ => { - let size = Size::new(self.len()); - let children = default(); - Ok(Node {kind,size,children}) + let size = Size::new(self.len()); + let children = default(); + let expression_id = self.id; + Ok(Node {kind,size,children,expression_id}) }, } } @@ -174,9 +175,10 @@ impl SpanTreeGenerator for ast::opr::Chain { } Ok((Node { - kind : if is_last {kind} else {node::Kind::Chained}, - size : gen.current_offset, - children : gen.children, + kind : if is_last {kind} else {node::Kind::Chained}, + size : gen.current_offset, + children : gen.children, + expression_id : elem.infix_id, }, elem.offset)) })?; Ok(node) @@ -201,16 +203,17 @@ impl SpanTreeGenerator for ast::prefix::Chain { let mut gen = ChildGenerator::default(); gen.add_node(vec![Func.into()],node); - gen.spacing(arg.off); + gen.spacing(arg.sast.off); if let node::Kind::Target {..} = arg_kind { gen.generate_empty_node(InsertType::BeforeTarget); } - gen.generate_ast_node(Located::new(Arg,arg.wrapped.clone_ref()), arg_kind)?; + gen.generate_ast_node(Located::new(Arg,arg.sast.wrapped.clone_ref()), arg_kind)?; gen.generate_empty_node(InsertType::Append); Ok(Node { - kind : if is_last {kind} else {node::Kind::Chained}, - size : gen.current_offset, - children : gen.children, + kind : if is_last {kind} else {node::Kind::Chained}, + size : gen.current_offset, + children : gen.children, + expression_id : arg.prefix_id, }) }) } @@ -219,7 +222,7 @@ impl SpanTreeGenerator for ast::prefix::Chain { // === Match === -impl SpanTreeGenerator for ast::Match { +impl SpanTreeGenerator for ast::known::Match { fn generate_node(&self, kind:node::Kind) -> FallibleResult { let is_removable = false; let children_kind = node::Kind::Argument {is_removable}; @@ -239,8 +242,9 @@ impl SpanTreeGenerator for ast::Match { generate_children_from_segment(&mut gen,index+1,&segment.wrapped)?; } Ok(Node {kind, - size : gen.current_offset, - children : gen.children, + size : gen.current_offset, + children : gen.children, + expression_id : self.id(), }) } } @@ -263,7 +267,7 @@ fn generate_children_from_segment // === Ambiguous == -impl SpanTreeGenerator for ast::Ambiguous { +impl SpanTreeGenerator for ast::known::Ambiguous { fn generate_node(&self, kind:node::Kind) -> FallibleResult { let mut gen = ChildGenerator::default(); let first_segment_index = 0; @@ -273,8 +277,9 @@ impl SpanTreeGenerator for ast::Ambiguous { generate_children_from_abiguous_segment(&mut gen, index+1, &segment.wrapped)?; } Ok(Node{kind, - size : gen.current_offset, - children : gen.children, + size : gen.current_offset, + children : gen.children, + expression_id : self.id(), }) } } @@ -317,17 +322,46 @@ mod test { use parser::Parser; use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::wasm_bindgen_test_configure; + use ast::IdMap; wasm_bindgen_test_configure!(run_in_browser); + /// A helper function which removes information about expression id from thw tree rooted at + /// `node`. + /// + /// It is used in tests. Because parser can assign id as he pleases, therefore to keep tests + /// cleaner the expression ids are removed before comparing trees. + fn clear_expression_ids(node:&mut Node) { + node.expression_id = None; + for child in &mut node.children { + clear_expression_ids(&mut child.node); + } + } + #[wasm_bindgen_test] fn generating_span_tree() { - let parser = Parser::new_or_panic(); - let ast = parser.parse_line("2 + foo bar - 3").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = false; + let parser = Parser::new_or_panic(); + let mut id_map = IdMap::default(); + id_map.generate(0..15); + id_map.generate(0..11); + id_map.generate(12..13); + id_map.generate(14..15); + id_map.generate(4..11); + let ast = parser.parse_line_with_id_map("2 + foo bar - 3",id_map.clone()).unwrap(); + let mut tree = ast.generate_tree().unwrap(); - let expected = TreeBuilder::new(15) + // Check the expression ids we defined: + for id_map_entry in id_map.vec { + let (span,id) = id_map_entry; + let node = tree.root_ref().find_by_span(&span); + assert!(node.is_some(), "Node with span {} not found", span); + assert_eq!(node.unwrap().node.expression_id, Some(id)); + } + + // Check the other fields: + clear_expression_ids(&mut tree.root); + let is_removable = false; + let expected = TreeBuilder::new(15) .add_empty_child(0,BeforeTarget) .add_child(0,11,Target{is_removable},InfixCrumb::LeftOperand) .add_empty_child(0,BeforeTarget) @@ -353,12 +387,13 @@ mod test { #[wasm_bindgen_test] fn generate_span_tree_with_chains() { - let parser = Parser::new_or_panic(); - let ast = parser.parse_line("2 + 3 + foo bar baz 13 + 5").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = true; + let parser = Parser::new_or_panic(); + let ast = parser.parse_line("2 + 3 + foo bar baz 13 + 5").unwrap(); + let mut tree = ast.generate_tree().unwrap(); + clear_expression_ids(&mut tree.root); - let expected = TreeBuilder::new(26) + let is_removable = true; + let expected = TreeBuilder::new(26) .add_child(0,22,Chained,InfixCrumb::LeftOperand) .add_child(0,5,Chained,InfixCrumb::LeftOperand) .add_empty_child(0,BeforeTarget) @@ -395,12 +430,13 @@ mod test { #[wasm_bindgen_test] fn generating_span_tree_from_right_assoc_operator() { - let parser = Parser::new_or_panic(); - let ast = parser.parse_line("1,2,3").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = true; + let parser = Parser::new_or_panic(); + let ast = parser.parse_line("1,2,3").unwrap(); + let mut tree = ast.generate_tree().unwrap(); + clear_expression_ids(&mut tree.root); - let expected = TreeBuilder::new(5) + let is_removable = true; + let expected = TreeBuilder::new(5) .add_empty_child(0,Append) .add_leaf (0,1,Argument{is_removable},InfixCrumb::LeftOperand) .add_leaf (1,1,Operation,InfixCrumb::Operator) @@ -422,11 +458,12 @@ mod test { let parser = Parser::new_or_panic(); // The star makes `SectionSides` ast being one of the parameters of + chain. First + makes // SectionRight, and last + makes SectionLeft. - let ast = parser.parse_line("+ * + + 2 +").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = true; + let ast = parser.parse_line("+ * + + 2 +").unwrap(); + let mut tree = ast.generate_tree().unwrap(); + clear_expression_ids(&mut tree.root); - let expected = TreeBuilder::new(11) + let is_removable = true; + let expected = TreeBuilder::new(11) .add_child(0,9,Chained,SectionLeftCrumb::Arg) .add_child(0,5,Chained,InfixCrumb::LeftOperand) .add_child(0,3,Chained,SectionLeftCrumb::Arg) @@ -455,12 +492,13 @@ mod test { #[wasm_bindgen_test] fn generating_span_tree_from_right_assoc_section() { - let parser = Parser::new_or_panic(); - let ast = parser.parse_line(",2,").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = true; + let parser = Parser::new_or_panic(); + let ast = parser.parse_line(",2,").unwrap(); + let mut tree = ast.generate_tree().unwrap(); + clear_expression_ids(&mut tree.root); - let expected = TreeBuilder::new(3) + let is_removable = true; + let expected = TreeBuilder::new(3) .add_empty_child(0,Append) .add_leaf (0,1,Operation,SectionRightCrumb::Opr) .add_child(1,2,Chained ,SectionRightCrumb::Arg) @@ -478,13 +516,22 @@ mod test { fn generating_span_tree_from_matched_macros() { use PatternMatchCrumb::*; - let parser = Parser::new_or_panic(); - let ast = parser.parse_line("if foo then (a + b) x else ()").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = false; + let parser = Parser::new_or_panic(); + let mut id_map = IdMap::default(); + id_map.generate(0..29); + let expression = "if foo then (a + b) x else ()"; + let ast = parser.parse_line_with_id_map(expression,id_map.clone()).unwrap(); + let mut tree = ast.generate_tree().unwrap(); - let if_then_else_cr = vec![Seq { right: false }, Or, Build]; - let parens_cr = vec![Seq { right: false }, Or, Or, Build]; + // Check if expression id is set + let (_,expected_id) = id_map.vec.first().unwrap(); + assert_eq!(tree.root_ref().expression_id,Some(*expected_id)); + + // Check the other fields + clear_expression_ids(&mut tree.root); + let is_removable = false; + let if_then_else_cr = vec![Seq { right: false }, Or, Build]; + let parens_cr = vec![Seq { right: false }, Or, Or, Build]; let segment_body_crumbs = |index:usize, pattern_crumb:&Vec| { let val = ast::crumbs::SegmentMatchCrumb::Body {val:pattern_crumb.clone()}; ast::crumbs::MatchCrumb::Segs {val,index} @@ -515,13 +562,21 @@ mod test { #[wasm_bindgen_test] fn generating_span_tree_from_ambiguous_macros() { - let parser = Parser::new_or_panic(); - let ast = parser.parse_line("(4").unwrap(); - let tree = ast.generate_tree().unwrap(); + let parser = Parser::new_or_panic(); + let mut id_map = IdMap::default(); + id_map.generate(0..2); + let ast = parser.parse_line_with_id_map("(4",id_map.clone()).unwrap(); + let mut tree = ast.generate_tree().unwrap(); + + // Check the expression id: + let (_,expected_id) = id_map.vec.first().unwrap(); + assert_eq!(tree.root_ref().expression_id,Some(*expected_id)); + + // Check the other fields: + clear_expression_ids(&mut tree.root); let is_removable = false; let crumb = AmbiguousCrumb{index:0, field:AmbiguousSegmentCrumb::Body}; - - let expected = TreeBuilder::new(2) + let expected = TreeBuilder::new(2) .add_leaf(1,1,Argument {is_removable},crumb) .build(); @@ -530,12 +585,13 @@ mod test { #[wasm_bindgen_test] fn generating_span_tree_for_lambda() { - let parser = Parser::new_or_panic(); - let ast = parser.parse_line("foo a-> b + c").unwrap(); - let tree = ast.generate_tree().unwrap(); - let is_removable = false; + let parser = Parser::new_or_panic(); + let ast = parser.parse_line("foo a-> b + c").unwrap(); + let mut tree = ast.generate_tree().unwrap(); + clear_expression_ids(&mut tree.root); - let expected = TreeBuilder::new(13) + let is_removable = false; + let expected = TreeBuilder::new(13) .add_leaf(0,3,Operation,PrefixCrumb::Func) .add_empty_child(4,BeforeTarget) .add_leaf(4,9,Target{is_removable},PrefixCrumb::Arg) diff --git a/gui/src/rust/ide/span-tree/src/lib.rs b/gui/src/rust/ide/span-tree/src/lib.rs index 25cd083b961..52ddeda17dc 100644 --- a/gui/src/rust/ide/span-tree/src/lib.rs +++ b/gui/src/rust/ide/span-tree/src/lib.rs @@ -86,10 +86,11 @@ impl SpanTree { impl Default for SpanTree { fn default() -> Self { - let kind = node::Kind::Root; - let size = default(); - let children = default(); - let root = Node {kind,size,children}; + let expression_id = None; + let kind = node::Kind::Root; + let size = default(); + let children = default(); + let root = Node {kind,size,children,expression_id}; Self {root} } } diff --git a/gui/src/rust/ide/span-tree/src/node.rs b/gui/src/rust/ide/span-tree/src/node.rs index 4b42cab2dcc..fbd00b2e691 100644 --- a/gui/src/rust/ide/span-tree/src/node.rs +++ b/gui/src/rust/ide/span-tree/src/node.rs @@ -59,7 +59,8 @@ pub enum InsertType {BeforeTarget,AfterTarget,Append} // === Errors === #[allow(missing_docs)] -#[fail(display = "The crumb `{}` is invalid, only {} children present. Traversed crumbs: {:?}.", crumb,count,context)] +#[fail(display = "The crumb `{}` is invalid, only {} children present. Traversed crumbs: {:?}.", + crumb,count,context)] #[derive(Debug,Fail,Clone)] pub struct InvalidCrumb { /// Crumb that was attempted. @@ -90,18 +91,20 @@ pub fn parent_crumbs(crumbs:&[Crumb]) -> Option<&[Crumb]> { #[derive(Clone,Debug,Eq,PartialEq)] #[allow(missing_docs)] pub struct Node { - pub kind : Kind, - pub size : Size, - pub children : Vec, + pub kind : Kind, + pub size : Size, + pub children : Vec, + pub expression_id : Option, } impl Node { /// Create Empty node. pub fn new_empty(insert_type:InsertType) -> Self { Node { - kind : Kind::Empty(insert_type), - size : Size::new(0), - children : Vec::new(), + kind : Kind::Empty(insert_type), + size : Size::new(0), + children : Vec::new(), + expression_id : None, } }