From a2906941ecd7d203b792f948c7e84d0463695434 Mon Sep 17 00:00:00 2001 From: AyushAgrawal-A2 Date: Mon, 16 Dec 2024 06:22:49 +0530 Subject: [PATCH] finish merge todo!() --- quadratic-core/src/a1/a1_selection/mod.rs | 2 +- .../pending_transaction.rs | 8 +- .../execution/control_transaction.rs | 8 +- .../execute_operation/execute_code.rs | 2 +- .../src/controller/execution/run_code/mod.rs | 3 +- .../execution/run_code/run_ai_researcher.rs | 320 +++++++++--------- .../src/controller/operations/clipboard.rs | 10 +- quadratic-core/src/formulas/ast.rs | 2 +- quadratic-core/src/formulas/ctx.rs | 17 +- quadratic-core/src/formulas/functions/ai.rs | 7 +- .../src/formulas/functions/lookup.rs | 2 +- quadratic-core/src/grid/cells_accessed.rs | 36 +- 12 files changed, 235 insertions(+), 182 deletions(-) diff --git a/quadratic-core/src/a1/a1_selection/mod.rs b/quadratic-core/src/a1/a1_selection/mod.rs index 179399b43a..d86b132fee 100644 --- a/quadratic-core/src/a1/a1_selection/mod.rs +++ b/quadratic-core/src/a1/a1_selection/mod.rs @@ -146,7 +146,7 @@ impl A1Selection { }); Self::from_ranges(ranges, sheet) } - #[cfg(test)] + pub fn from_ranges(ranges: impl Iterator, sheet: SheetId) -> Self { let ranges = ranges.collect::>(); let last_range = ranges.last().expect("empty selection is invalid"); diff --git a/quadratic-core/src/controller/active_transactions/pending_transaction.rs b/quadratic-core/src/controller/active_transactions/pending_transaction.rs index 1c8423898f..b5192e5104 100644 --- a/quadratic-core/src/controller/active_transactions/pending_transaction.rs +++ b/quadratic-core/src/controller/active_transactions/pending_transaction.rs @@ -98,10 +98,10 @@ pub struct PendingTransaction { pub offsets_modified: HashMap, // ai researcher requests - pub pending_ai_researcher: HashSet, + pub pending_ai_researchers: HashSet, // ai researcher requests that are currently running - pub running_ai_researcher: HashSet, + pub running_ai_researchers: HashSet, } impl Default for PendingTransaction { @@ -131,8 +131,8 @@ impl Default for PendingTransaction { fill_cells: HashSet::new(), sheet_info: HashSet::new(), offsets_modified: HashMap::new(), - pending_ai_researcher: HashSet::new(), - running_ai_researcher: HashSet::new(), + pending_ai_researchers: HashSet::new(), + running_ai_researchers: HashSet::new(), } } } diff --git a/quadratic-core/src/controller/execution/control_transaction.rs b/quadratic-core/src/controller/execution/control_transaction.rs index 938d3e47cd..3d31ce0a5e 100644 --- a/quadratic-core/src/controller/execution/control_transaction.rs +++ b/quadratic-core/src/controller/execution/control_transaction.rs @@ -28,7 +28,7 @@ impl GridController { loop { if transaction.operations.is_empty() - && transaction.pending_ai_researcher.is_empty() + && transaction.pending_ai_researchers.is_empty() && transaction.resize_rows.is_empty() { transaction.complete = true; @@ -43,12 +43,12 @@ impl GridController { // try running pending ai researchers in parallel, only if there are no other operations if transaction.operations.is_empty() - && !transaction.pending_ai_researcher.is_empty() - && transaction.has_async == transaction.running_ai_researcher.len() as i64 + && !transaction.pending_ai_researchers.is_empty() + && transaction.has_async == transaction.running_ai_researchers.len() as i64 { self.run_ai_researcher_parallel(transaction); // if there are still running ai researchers, break and wait for them to complete - if !transaction.running_ai_researcher.is_empty() { + if !transaction.running_ai_researchers.is_empty() { self.transactions.update_async_transaction(transaction); break; } diff --git a/quadratic-core/src/controller/execution/execute_operation/execute_code.rs b/quadratic-core/src/controller/execution/execute_operation/execute_code.rs index f4ef0375ea..dbf758e9a2 100644 --- a/quadratic-core/src/controller/execution/execute_operation/execute_code.rs +++ b/quadratic-core/src/controller/execution/execute_operation/execute_code.rs @@ -157,7 +157,7 @@ impl GridController { } CodeCellLanguage::AIResearcher => { // collect the sheet_pos for the AI researcher, run them in parallel at the end of the transaction - transaction.pending_ai_researcher.insert(sheet_pos); + transaction.pending_ai_researchers.insert(sheet_pos); } } } diff --git a/quadratic-core/src/controller/execution/run_code/mod.rs b/quadratic-core/src/controller/execution/run_code/mod.rs index a1be8d6d5f..d6bdef3c23 100644 --- a/quadratic-core/src/controller/execution/run_code/mod.rs +++ b/quadratic-core/src/controller/execution/run_code/mod.rs @@ -334,7 +334,7 @@ mod test { use super::*; use crate::grid::CodeCellValue; - use crate::wasm_bindings::js::expect_js_call_count; + use crate::wasm_bindings::js::{clear_js_calls, expect_js_call_count}; #[test] #[parallel] @@ -433,6 +433,7 @@ mod test { #[test] #[serial] fn code_run_image() { + clear_js_calls(); let mut gc = GridController::test(); let sheet_id = gc.sheet_ids()[0]; let sheet_pos = SheetPos { diff --git a/quadratic-core/src/controller/execution/run_code/run_ai_researcher.rs b/quadratic-core/src/controller/execution/run_code/run_ai_researcher.rs index 8ab0f9e9fa..7b7bc2feca 100644 --- a/quadratic-core/src/controller/execution/run_code/run_ai_researcher.rs +++ b/quadratic-core/src/controller/execution/run_code/run_ai_researcher.rs @@ -61,6 +61,22 @@ impl GridController { if let Value::Tuple(vec) = parsed.eval(&mut ctx, Some(bounds)).inner { if let Some((query, values_array)) = vec.into_iter().next_tuple() { if let Ok(query) = query.into_cell_value() { + if query == CellValue::Blank { + return Err(RunError { + span: None, + msg: RunErrorMsg::InvalidArgument, + }); + } + + if let CellValue::Text(text) = query.as_ref() { + if text.is_empty() { + return Err(RunError { + span: None, + msg: RunErrorMsg::InvalidArgument, + }); + } + } + let mut referenced_cell_has_error = false; let ref_cell_values = values_array @@ -118,125 +134,129 @@ impl GridController { } pub(crate) fn run_ai_researcher_parallel(&mut self, transaction: &mut PendingTransaction) { - if transaction.pending_ai_researcher.is_empty() { + if transaction.pending_ai_researchers.is_empty() { return; } // all ai researcher requests that are currently pending or running - let mut all_ai_researcher = HashSet::new(); - all_ai_researcher.extend(transaction.pending_ai_researcher.clone()); - all_ai_researcher.extend(transaction.running_ai_researcher.clone()); + let mut all_ai_researchers = HashSet::new(); + all_ai_researchers.extend(transaction.pending_ai_researchers.clone()); + all_ai_researchers.extend(transaction.running_ai_researchers.clone()); // ai researcher requests that are dependent on other ai researcher request currently pending - let pending_ai_researcher = transaction.pending_ai_researcher.clone(); + let pending_ai_researchers = transaction.pending_ai_researchers.clone(); - let mut dependent_ai_researcher: HashSet = HashSet::new(); - for sheet_pos in pending_ai_researcher.iter() { - match self.parse_ai_researcher_code(sheet_pos.to_owned()) { + // check and build new ai researcher requests that are dependent on other ai researcher request currently pending + let mut new_pending_ai_researchers: HashSet = HashSet::new(); + + for pending_ai_researcher in pending_ai_researchers.iter() { + match self.parse_ai_researcher_code(pending_ai_researcher.to_owned()) { Ok(ParsedAIResearcherCode { query, ref_cell_values, cells_accessed, }) => { - let mut referenced_cell_has_error = false; + // circular reference to itself let mut has_circular_reference = false; - let mut is_dependent = false; - - let mut seen_code_cells = HashSet::new(); - seen_code_cells.insert(sheet_pos.to_owned()); - - todo!(); - // let mut cells_accessed_to_check = cells_accessed.iter().collect::>(); - - // // check if this ai researcher code cell is dependent on - // // 1. any other code cell that has an error - // // 2. another ai researcher request - // // 3. has a circular reference - // while let Some(cell_accessed) = cells_accessed_to_check.pop() { - // if referenced_cell_has_error || has_circular_reference || is_dependent { - // break; - // } - - // // circular reference to itself - // has_circular_reference |= cell_accessed - // .intersects(SheetRect::single_sheet_pos(sheet_pos.to_owned())); - - // // dependent on another ai researcher request - // is_dependent |= all_ai_researcher.iter().any(|ai_researcher| { - // cell_accessed - // .intersects(SheetRect::single_sheet_pos(ai_researcher.to_owned())) - // }); - - // let sheet_id = cell_accessed.sheet_id; - // if let Some(sheet) = self.try_sheet(sheet_id) { - // for (output_rect, code_run) in - // sheet.iter_code_output_in_rect(cell_accessed.to_owned().into()) - // { - // let code_cell_pos = output_rect.min; - // if !seen_code_cells.insert(code_cell_pos.to_sheet_pos(sheet_id)) { - // continue; - // } - - // if code_run.spill_error || code_run.result.as_std_ref().is_err() { - // referenced_cell_has_error = true; - // break; - // } - - // // let new_cells_accessed = code_run - // // .cells_accessed - // // .difference(&cells_accessed) - // // .collect::>(); - // // cells_accessed_to_check.extend(new_cells_accessed); - // } - // } - // } - - // if referenced_cell_has_error { - // transaction.pending_ai_researcher.remove(sheet_pos); - // let run_error = RunError { - // span: None, - // msg: RunErrorMsg::CodeRunError("Error in referenced cell(s)".into()), - // }; - // transaction.current_sheet_pos = Some(sheet_pos.to_owned()); - // let _ = self.code_cell_sheet_error(transaction, &run_error); - // } else if has_circular_reference { - // transaction.pending_ai_researcher.remove(sheet_pos); - // let run_error = RunError { - // span: None, - // msg: RunErrorMsg::CircularReference, - // }; - // transaction.current_sheet_pos = Some(sheet_pos.to_owned()); - // let _ = self.code_cell_sheet_error(transaction, &run_error); - // } else if is_dependent { - // dependent_ai_researcher.insert(sheet_pos.to_owned()); - // } else { - // self.request_ai_researcher_result( - // transaction, - // sheet_pos.to_owned(), - // query, - // ref_cell_values, - // ); - // } + + // check if referenced cell has error + let mut referenced_cell_has_error = false; + + let mut seen_code_cells = HashSet::from([pending_ai_researcher.to_owned()]); + let mut cells_accessed_to_check = vec![cells_accessed.clone()]; + 'outer: while let Some(cells_accessed) = cells_accessed_to_check.pop() { + if has_circular_reference || referenced_cell_has_error { + break; + } + + if cells_accessed.contains(pending_ai_researcher.to_owned()) { + has_circular_reference = true; + break; + } + + for selection in cells_accessed.to_selections() { + let sheet_id = selection.sheet_id; + if let Some(sheet) = self.try_sheet(sheet_id) { + for rect in sheet.selection_to_rects(&selection) { + for (output_rect, code_run) in + sheet.iter_code_output_in_rect(rect) + { + let code_cell_pos = output_rect.min; + if !seen_code_cells + .insert(code_cell_pos.to_sheet_pos(sheet_id)) + { + continue; + } + + if code_run.spill_error + || code_run.result.as_std_ref().is_err() + { + referenced_cell_has_error = true; + break 'outer; + } + + cells_accessed_to_check + .push(code_run.cells_accessed.to_owned()); + } + } + } + } + } + + if referenced_cell_has_error || has_circular_reference { + transaction + .pending_ai_researchers + .remove(pending_ai_researcher); + + let msg = if has_circular_reference { + RunErrorMsg::CircularReference + } else { + RunErrorMsg::CodeRunError("Error in referenced cell(s)".into()) + }; + let run_error = RunError { span: None, msg }; + + transaction.current_sheet_pos = Some(pending_ai_researcher.to_owned()); + let _ = self.code_cell_sheet_error(transaction, &run_error); + continue; + } + + // dependent on another ai researcher request + let is_dependent = all_ai_researchers + .iter() + .any(|ai_researcher| cells_accessed.contains(ai_researcher.to_owned())); + if is_dependent { + new_pending_ai_researchers.insert(pending_ai_researcher.to_owned()); + continue; + } + + self.request_ai_researcher_result( + transaction, + pending_ai_researcher.to_owned(), + query, + ref_cell_values, + ); } Err(error) => { - transaction.pending_ai_researcher.remove(sheet_pos); - transaction.current_sheet_pos = Some(sheet_pos.to_owned()); + transaction + .pending_ai_researchers + .remove(pending_ai_researcher); + transaction.current_sheet_pos = Some(pending_ai_researcher.to_owned()); let _ = self.code_cell_sheet_error(transaction, &error); } } } // circular reference - // the ai researcher cells are dependent on other ai researcher requests - if pending_ai_researcher == dependent_ai_researcher - && transaction.running_ai_researcher.is_empty() + // the ai researcher cells are indirectly dependent on other ai researcher requests + if pending_ai_researchers == new_pending_ai_researchers + && transaction.running_ai_researchers.is_empty() { let run_error = RunError { span: None, msg: RunErrorMsg::CircularReference, }; - for sheet_pos in pending_ai_researcher.iter() { - transaction.pending_ai_researcher.remove(sheet_pos); + for sheet_pos in pending_ai_researchers.iter() { + transaction.pending_ai_researchers.remove(sheet_pos); transaction.current_sheet_pos = Some(sheet_pos.to_owned()); let _ = self.code_cell_sheet_error(transaction, &run_error); } @@ -247,7 +267,7 @@ impl GridController { // for tests, process all running ai researcher requests with a dummy result if cfg!(test) { transaction - .running_ai_researcher + .running_ai_researchers .iter() .for_each(|sheet_pos| { let _ = self.receive_ai_researcher_result( @@ -279,8 +299,8 @@ impl GridController { query, ref_cell_values.join(", "), ); - transaction.pending_ai_researcher.remove(&sheet_pos); - transaction.running_ai_researcher.insert(sheet_pos); + transaction.pending_ai_researchers.remove(&sheet_pos); + transaction.running_ai_researchers.insert(sheet_pos); transaction.waiting_for_async = Some(CodeCellLanguage::AIResearcher); self.transactions.add_async_transaction(transaction); } @@ -303,8 +323,8 @@ impl GridController { transaction.cells_accessed = cells_accessed; } - transaction.running_ai_researcher.remove(&sheet_pos); - if transaction.running_ai_researcher.is_empty() { + transaction.running_ai_researchers.remove(&sheet_pos); + if transaction.running_ai_researchers.is_empty() { transaction.waiting_for_async = None; } @@ -349,7 +369,7 @@ impl GridController { } let current_code_run = transaction - .running_ai_researcher + .running_ai_researchers .iter() .map(|sheet_pos| JsCodeRun { transaction_id: transaction.id.to_string(), @@ -362,7 +382,7 @@ impl GridController { .collect::>(); let awaiting_execution = transaction - .pending_ai_researcher + .pending_ai_researchers .iter() .map(|sheet_pos| JsCodeRun { transaction_id: transaction.id.to_string(), @@ -389,13 +409,13 @@ impl GridController { mod test { use serial_test::{parallel, serial}; - // use super::ParsedAIResearcherCode; + use super::ParsedAIResearcherCode; use crate::{ controller::GridController, - grid::{js_types::JsCodeRun, CodeCellLanguage, CodeRunResult, SheetId}, + grid::{js_types::JsCodeRun, CellsAccessed, CodeCellLanguage, CodeRunResult, SheetId}, wasm_bindings::js::{clear_js_calls, expect_js_call}, - CellValue, RunError, RunErrorMsg, Span, + CellValue, RunError, RunErrorMsg, SheetRect, }; fn assert_no_pending_async_transaction(gc: &GridController) { @@ -412,39 +432,35 @@ mod test { let sheet_id = gc.sheet_ids()[0]; gc.set_cell_values( - pos![B1].to_sheet_pos(sheet_id), + pos![A1].to_sheet_pos(sheet_id), vec![vec!["1"], vec!["2"], vec!["3"]], None, ); gc.set_code_cell( - pos![B4].to_sheet_pos(sheet_id), + pos![B1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', B1:B3)".to_string(), + "AI('query', A1:A3)".to_string(), None, ); assert_eq!( - gc.sheet(sheet_id).get_code_cell_value((1, 4).into()), + gc.sheet(sheet_id).get_code_cell_value(pos![B1]), Some(CellValue::Text("result".to_string())) ); - todo!(); - - // let parsed_ai_researcher_code = - // gc.parse_ai_researcher_code(pos![B4].to_sheet_pos(sheet_id)); - // assert_eq!( - // parsed_ai_researcher_code, - // Ok(ParsedAIResearcherCode { - // query: "query".to_string(), - // ref_cell_values: vec!["1".to_string(), "2".to_string(), "3".to_string()], - // cells_accessed: HashSet::from([ - // SheetRect::new(1, 1, 1, 1, sheet_id), - // SheetRect::new(1, 2, 1, 2, sheet_id), - // SheetRect::new(1, 3, 1, 3, sheet_id), - // ]), - // }) - // ); + let parsed_ai_researcher_code = + gc.parse_ai_researcher_code(pos![B1].to_sheet_pos(sheet_id)); + let mut cells_accessed = CellsAccessed::new(); + cells_accessed.add_sheet_rect(SheetRect::from_numbers(1, 1, 1, 3, sheet_id)); + assert_eq!( + parsed_ai_researcher_code, + Ok(ParsedAIResearcherCode { + query: "query".to_string(), + ref_cell_values: vec!["1".to_string(), "2".to_string(), "3".to_string()], + cells_accessed, + }) + ); assert_no_pending_async_transaction(&gc); // incorrect argument @@ -487,7 +503,7 @@ mod test { gc.set_code_cell( pos![H7].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI(query, B1:B3)".to_string(), + "AI(query, A1:A3)".to_string(), None, ); let parsed_ai_researcher_code = @@ -495,11 +511,8 @@ mod test { assert_eq!( parsed_ai_researcher_code, Err(RunError { - span: Some(Span { start: 3, end: 4 }), - msg: RunErrorMsg::Expected { - expected: "right paren or expression or nothing".into(), - got: None, - }, + span: None, + msg: RunErrorMsg::InvalidArgument, }) ); assert_no_pending_async_transaction(&gc); @@ -513,16 +526,7 @@ mod test { ); let parsed_ai_researcher_code = gc.parse_ai_researcher_code(pos![I8].to_sheet_pos(sheet_id)); - assert_eq!( - parsed_ai_researcher_code, - Err(RunError { - span: Some(Span { start: 3, end: 4 }), - msg: RunErrorMsg::Expected { - expected: "right paren or expression or nothing".into(), - got: None, - }, - }) - ); + assert!(parsed_ai_researcher_code.is_err()); assert_no_pending_async_transaction(&gc); // self circular reference @@ -592,15 +596,15 @@ mod test { // circular reference to itself gc.set_code_cell( - pos![B1].to_sheet_pos(sheet_id), + pos![A1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', B1)".to_string(), + "AI('query', A1)".to_string(), None, ); let sheet = gc.sheet(sheet_id); let code_run = sheet.code_run((1, 1).into()); - assert_eq!(code_run.is_some(), true); + assert!(code_run.is_some()); assert_eq!( code_run.unwrap().result, CodeRunResult::Err(RunError { @@ -619,27 +623,27 @@ mod test { let sheet_id = gc.sheet_ids()[0]; gc.set_code_cell( - pos![D1].to_sheet_pos(sheet_id), + pos![C1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', C1)".to_string(), + "AI('query', B1)".to_string(), None, ); gc.set_code_cell( - pos![C1].to_sheet_pos(sheet_id), + pos![B1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', B1)".to_string(), + "AI('query', A1)".to_string(), None, ); gc.set_code_cell( - pos![B1].to_sheet_pos(sheet_id), + pos![A1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', D1)".to_string(), + "AI('query', C1)".to_string(), None, ); let sheet = gc.sheet(sheet_id); let code_run = sheet.code_run((1, 1).into()); - assert_eq!(code_run.is_some(), true); + assert!(code_run.is_some()); assert_eq!( code_run.unwrap().result, CodeRunResult::Err(RunError { @@ -658,27 +662,27 @@ mod test { let sheet_id = gc.sheet_ids()[0]; gc.set_code_cell( - pos![D1].to_sheet_pos(sheet_id), + pos![C1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', C1)".to_string(), + "AI('query', B1)".to_string(), None, ); gc.set_code_cell( - pos![C1].to_sheet_pos(sheet_id), + pos![B1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "B1".to_string(), + "A1".to_string(), None, ); gc.set_code_cell( - pos![B1].to_sheet_pos(sheet_id), + pos![A1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', D1)".to_string(), + "AI('query', C1)".to_string(), None, ); let sheet = gc.sheet(sheet_id); let code_run = sheet.code_run((1, 1).into()); - assert_eq!(code_run.is_some(), true); + assert!(code_run.is_some()); assert_eq!( code_run.unwrap().result, CodeRunResult::Err(RunError { @@ -710,8 +714,8 @@ mod test { ); let sheet = gc.sheet(sheet_id); - let code_run = sheet.code_run((1, 1).into()); - assert_eq!(code_run.is_some(), true); + let code_run = sheet.code_run((2, 1).into()); + assert!(code_run.is_some()); assert_eq!( code_run.unwrap().result, CodeRunResult::Err(RunError { diff --git a/quadratic-core/src/controller/operations/clipboard.rs b/quadratic-core/src/controller/operations/clipboard.rs index d7ef6997c2..6321c9b316 100644 --- a/quadratic-core/src/controller/operations/clipboard.rs +++ b/quadratic-core/src/controller/operations/clipboard.rs @@ -659,20 +659,20 @@ mod test { let sheet_id = gc.sheet_ids()[0]; gc.set_cell_values( - pos![B1].to_sheet_pos(sheet_id), + pos![A1].to_sheet_pos(sheet_id), vec![vec!["1"], vec!["2"], vec!["3"]], None, ); gc.set_code_cell( - pos![B4].to_sheet_pos(sheet_id), + pos![B1].to_sheet_pos(sheet_id), CodeCellLanguage::Formula, - "AI('query', B1:B3)".to_string(), + "AI('query', A1:A3)".to_string(), None, ); assert_eq!( - gc.sheet(sheet_id).get_code_cell_value((1, 4).into()), + gc.sheet(sheet_id).get_code_cell_value(pos![B1]), Some(CellValue::Text("result".to_string())) ); @@ -688,7 +688,7 @@ mod test { ); assert_eq!( - gc.sheet(sheet_id).get_code_cell_value((5, 8).into()), + gc.sheet(sheet_id).get_code_cell_value(pos![F5]), Some(CellValue::Text("result".to_string())) ); } diff --git a/quadratic-core/src/formulas/ast.rs b/quadratic-core/src/formulas/ast.rs index 4c9d491a95..a4327a7d84 100644 --- a/quadratic-core/src/formulas/ast.rs +++ b/quadratic-core/src/formulas/ast.rs @@ -144,7 +144,7 @@ impl AstNode { // Single cell references return 1x1 arrays for Excel compatibility. AstNodeContents::CellRef(cell_ref) => { let pos = ctx.resolve_ref(cell_ref, self.span)?.inner; - Array::from(ctx.get_cell(pos, self.span).inner).into() + Array::from(ctx.get_cell(pos, self.span, true).inner).into() } AstNodeContents::String(s) => Value::from(s.to_string()), diff --git a/quadratic-core/src/formulas/ctx.rs b/quadratic-core/src/formulas/ctx.rs index 673d2db2d4..85302804a2 100644 --- a/quadratic-core/src/formulas/ctx.rs +++ b/quadratic-core/src/formulas/ctx.rs @@ -91,7 +91,12 @@ impl<'ctx> Ctx<'ctx> { /// Fetches the contents of the cell at `pos` evaluated at `self.sheet_pos`, /// or returns an error in the case of a circular reference. - pub fn get_cell(&mut self, pos: SheetPos, span: Span) -> Spanned { + pub fn get_cell( + &mut self, + pos: SheetPos, + span: Span, + add_to_cells_accessed: bool, + ) -> Spanned { if self.skip_computation { let value = CellValue::Blank; return Spanned { span, inner: value }; @@ -109,7 +114,9 @@ impl<'ctx> Ctx<'ctx> { return error_value(RunErrorMsg::CircularReference); } - self.cells_accessed.add_sheet_pos(pos); + if add_to_cells_accessed { + self.cells_accessed.add_sheet_pos(pos); + } let value = sheet.get_cell_for_formula(pos.into()); Spanned { inner: value, span } @@ -149,8 +156,10 @@ impl<'ctx> Ctx<'ctx> { // clone `sheet_name.` for y in bounded_rect.y_range() { for x in bounded_rect.x_range() { - // TODO: record array dependency instead of many individual cell dependencies - flat_array.push(self.get_cell(SheetPos { x, y, sheet_id }, span).inner); + flat_array.push( + self.get_cell(SheetPos { x, y, sheet_id }, span, false) + .inner, + ); } } diff --git a/quadratic-core/src/formulas/functions/ai.rs b/quadratic-core/src/formulas/functions/ai.rs index 26a5a29409..2003bf5417 100644 --- a/quadratic-core/src/formulas/functions/ai.rs +++ b/quadratic-core/src/formulas/functions/ai.rs @@ -18,7 +18,12 @@ fn get_functions() -> Vec { "AI(\"What is the last year's GDP?\", \"A1\")" )] fn AI(query: String, ref_cell: Array) { - Value::Tuple(vec![CellValue::Text(query).into(), ref_cell]) + let query_value = if query.is_empty() { + CellValue::Blank + } else { + CellValue::Text(query) + }; + Value::Tuple(vec![query_value.into(), ref_cell]) } )] } diff --git a/quadratic-core/src/formulas/functions/lookup.rs b/quadratic-core/src/formulas/functions/lookup.rs index e6040206b9..5e4ff749ac 100644 --- a/quadratic-core/src/formulas/functions/lookup.rs +++ b/quadratic-core/src/formulas/functions/lookup.rs @@ -26,7 +26,7 @@ fn get_functions() -> Vec { let cell_ref = CellRef::parse_a1(&cellref_string.inner, ctx.sheet_pos.into()) .ok_or(RunErrorMsg::BadCellReference.with_span(span))?; let pos = ctx.resolve_ref(&cell_ref, span)?.inner; - ctx.get_cell(pos, span).inner + ctx.get_cell(pos, span, true).inner } ), formula_fn!( diff --git a/quadratic-core/src/grid/cells_accessed.rs b/quadratic-core/src/grid/cells_accessed.rs index 07ac98fdb7..9d8c6bd202 100644 --- a/quadratic-core/src/grid/cells_accessed.rs +++ b/quadratic-core/src/grid/cells_accessed.rs @@ -6,7 +6,7 @@ use std::{ use ts_rs::TS; use super::SheetId; -use crate::{CellRefRange, Rect, SheetPos, SheetRect}; +use crate::{A1Selection, CellRefRange, Rect, SheetPos, SheetRect}; #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, TS)] #[serde(rename_all = "camelCase")] @@ -148,6 +148,15 @@ impl CellsAccessed { pub fn sheet_iter(&self, sheet_id: SheetId) -> impl Iterator { self.cells.get(&sheet_id).into_iter().flatten() } + + pub fn to_selections(&self) -> Vec { + self.cells + .iter() + .map(|(sheet_id, ranges)| { + A1Selection::from_ranges(ranges.iter().copied(), sheet_id.to_owned()) + }) + .collect() + } } #[cfg(test)] @@ -262,4 +271,29 @@ mod tests { assert_eq!(deserialized.cells.len(), 1); assert_eq!(deserialized.cells[&sheet_id].len(), 2); } + + #[test] + fn test_to_selections() { + let mut cells = CellsAccessed::default(); + let sheet_id = SheetId::new(); + cells.add(sheet_id, CellRefRange::ALL); + let selections = cells.to_selections(); + assert_eq!(selections.len(), 1); + assert_eq!(selections[0].sheet_id, sheet_id); + assert_eq!(selections[0].ranges, vec![CellRefRange::ALL]); + + let mut cells = CellsAccessed::default(); + let sheet_id = SheetId::new(); + cells.add( + sheet_id, + CellRefRange::new_relative_rect(Rect::new(1, 1, 3, 3)), + ); + let selections = cells.to_selections(); + assert_eq!(selections.len(), 1); + assert_eq!(selections[0].sheet_id, sheet_id); + assert_eq!( + selections[0].ranges, + vec![CellRefRange::new_relative_rect(Rect::new(1, 1, 3, 3))] + ); + } }