1use crate::error::IoError;
2use crate::traits::{LoadStrategy, SpreadsheetReader, SpreadsheetWriter};
3use chrono::Timelike;
4use formualizer_common::{
5 LiteralValue, RangeAddress,
6 error::{ExcelError, ExcelErrorKind},
7};
8use formualizer_eval::engine::eval::EvalPlan;
9use formualizer_eval::engine::named_range::{NameScope, NamedDefinition};
10use parking_lot::RwLock;
11use std::collections::{BTreeMap, BTreeSet};
12use std::sync::Arc;
13
14#[cfg(feature = "wasm_plugins")]
15use wasmparser::{Parser, Payload};
16
17#[cfg(all(feature = "wasm_runtime_wasmtime", not(target_arch = "wasm32")))]
18use crate::wasm_runtime_wasmtime::new_wasmtime_runtime;
19
20fn normalize_custom_fn_name(name: &str) -> Result<String, ExcelError> {
21 let trimmed = name.trim();
22 if trimmed.is_empty() {
23 return Err(
24 ExcelError::new(ExcelErrorKind::Name).with_message("Function name cannot be empty")
25 );
26 }
27 Ok(trimmed.to_ascii_uppercase())
28}
29
30pub const WASM_MANIFEST_SCHEMA_V1: &str = "formualizer.udf.module/v1";
31pub const WASM_MANIFEST_SECTION_V1: &str = "formualizer.udf.manifest.v1";
32pub const WASM_ABI_VERSION_V1: u32 = 1;
33pub const WASM_CODEC_VERSION_V1: u32 = 1;
34
35fn normalize_wasm_module_id(module_id: &str) -> Result<String, ExcelError> {
36 let trimmed = module_id.trim();
37 if trimmed.is_empty() {
38 return Err(
39 ExcelError::new(ExcelErrorKind::Value).with_message("WASM module_id cannot be empty")
40 );
41 }
42 Ok(trimmed.to_string())
43}
44
45#[cfg(not(target_arch = "wasm32"))]
46fn read_wasm_file_bytes(path: &std::path::Path) -> Result<Vec<u8>, ExcelError> {
47 std::fs::read(path).map_err(|err| {
48 ExcelError::new(ExcelErrorKind::Value).with_message(format!(
49 "Failed to read WASM module file {}: {err}",
50 path.display()
51 ))
52 })
53}
54
55#[cfg(not(target_arch = "wasm32"))]
56fn collect_wasm_files_in_dir(dir: &std::path::Path) -> Result<Vec<std::path::PathBuf>, ExcelError> {
57 if !dir.is_dir() {
58 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
59 "WASM module directory does not exist or is not a directory: {}",
60 dir.display()
61 )));
62 }
63
64 let mut files = Vec::new();
65 let entries = std::fs::read_dir(dir).map_err(|err| {
66 ExcelError::new(ExcelErrorKind::Value).with_message(format!(
67 "Failed to read WASM module directory {}: {err}",
68 dir.display()
69 ))
70 })?;
71
72 for entry in entries {
73 let entry = entry.map_err(|err| {
74 ExcelError::new(ExcelErrorKind::Value).with_message(format!(
75 "Failed to iterate WASM module directory {}: {err}",
76 dir.display()
77 ))
78 })?;
79
80 let path = entry.path();
81 if !path.is_file() {
82 continue;
83 }
84
85 let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else {
86 continue;
87 };
88
89 if ext.eq_ignore_ascii_case("wasm") {
90 files.push(path);
91 }
92 }
93
94 files.sort();
95 Ok(files)
96}
97
98fn stable_fn_salt(name: &str) -> u64 {
99 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
100 const FNV_PRIME: u64 = 0x100000001b3;
101 let mut hash = FNV_OFFSET;
102 for b in name.as_bytes() {
103 hash ^= u64::from(*b);
104 hash = hash.wrapping_mul(FNV_PRIME);
105 }
106 hash
107}
108
109fn validate_custom_arity(name: &str, options: &CustomFnOptions) -> Result<(), ExcelError> {
110 if let Some(max_args) = options.max_args
111 && max_args < options.min_args
112 {
113 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
114 "Invalid arity for {name}: max_args ({max_args}) < min_args ({})",
115 options.min_args
116 )));
117 }
118 Ok(())
119}
120
121fn validate_wasm_spec(spec: &WasmFunctionSpec) -> Result<(), ExcelError> {
122 if spec.module_id.trim().is_empty() {
123 return Err(ExcelError::new(ExcelErrorKind::Value)
124 .with_message("WASM function module_id cannot be empty"));
125 }
126 if spec.export_name.trim().is_empty() {
127 return Err(ExcelError::new(ExcelErrorKind::Value)
128 .with_message("WASM function export_name cannot be empty"));
129 }
130 Ok(())
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct CustomFnOptions {
135 pub min_args: usize,
136 pub max_args: Option<usize>,
137 pub volatile: bool,
138 pub thread_safe: bool,
139 pub deterministic: bool,
140 pub allow_override_builtin: bool,
141}
142
143impl Default for CustomFnOptions {
144 fn default() -> Self {
145 Self {
146 min_args: 0,
147 max_args: None,
148 volatile: false,
149 thread_safe: false,
150 deterministic: true,
151 allow_override_builtin: false,
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct CustomFnInfo {
158 pub name: String,
159 pub options: CustomFnOptions,
160}
161
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct WasmFunctionSpec {
164 pub module_id: String,
165 pub export_name: String,
166 pub codec_version: u32,
167 pub runtime_hint: Option<WasmRuntimeHint>,
168 pub reserved: BTreeMap<String, String>,
169}
170
171impl WasmFunctionSpec {
172 pub fn new(
173 module_id: impl Into<String>,
174 export_name: impl Into<String>,
175 codec_version: u32,
176 ) -> Self {
177 Self {
178 module_id: module_id.into(),
179 export_name: export_name.into(),
180 codec_version,
181 runtime_hint: None,
182 reserved: BTreeMap::new(),
183 }
184 }
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Default)]
188pub struct WasmRuntimeHint {
189 pub fuel_limit: Option<u64>,
190 pub memory_limit_bytes: Option<u64>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct WasmModuleInfo {
195 pub module_id: String,
196 pub version: String,
197 pub abi_version: u32,
198 pub codec_version: u32,
199 pub function_count: usize,
200 pub module_size_bytes: usize,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
204pub struct WasmModuleManifest {
205 pub schema: String,
206 pub module: WasmManifestModule,
207 pub functions: Vec<WasmManifestFunction>,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
211pub struct WasmManifestModule {
212 pub id: String,
213 pub version: String,
214 pub abi: u32,
215 pub codec: u32,
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
219pub struct WasmManifestFunction {
220 pub id: u32,
221 pub name: String,
222 #[serde(default)]
223 pub aliases: Vec<String>,
224 #[serde(rename = "export")]
225 pub export_name: String,
226 pub min_args: usize,
227 #[serde(default)]
228 pub max_args: Option<usize>,
229 #[serde(default)]
230 pub volatile: bool,
231 #[serde(default = "default_true")]
232 pub deterministic: bool,
233 #[serde(default)]
234 pub thread_safe: bool,
235 #[serde(default)]
236 pub params: Vec<WasmManifestParam>,
237 #[serde(default)]
238 pub returns: Option<WasmManifestReturn>,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
242pub struct WasmManifestParam {
243 pub name: String,
244 #[serde(default)]
245 pub kinds: Vec<String>,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
249pub struct WasmManifestReturn {
250 #[serde(default)]
251 pub kinds: Vec<String>,
252}
253
254fn default_true() -> bool {
255 true
256}
257
258pub trait WasmUdfRuntime: Send + Sync {
259 fn can_bind_functions(&self) -> bool {
260 true
261 }
262
263 fn validate_module(
264 &self,
265 _module_id: &str,
266 _wasm_bytes: &[u8],
267 _manifest: &WasmModuleManifest,
268 ) -> Result<(), ExcelError> {
269 Ok(())
270 }
271
272 fn invoke(
273 &self,
274 module_id: &str,
275 export_name: &str,
276 function_name: &str,
277 codec_version: u32,
278 args: &[LiteralValue],
279 runtime_hint: Option<&WasmRuntimeHint>,
280 ) -> Result<LiteralValue, ExcelError>;
281}
282
283#[cfg(feature = "wasm_plugins")]
284#[derive(Default)]
285struct PendingWasmRuntime;
286
287#[cfg(feature = "wasm_plugins")]
288impl WasmUdfRuntime for PendingWasmRuntime {
289 fn can_bind_functions(&self) -> bool {
290 false
291 }
292
293 fn invoke(
294 &self,
295 module_id: &str,
296 export_name: &str,
297 function_name: &str,
298 codec_version: u32,
299 _args: &[LiteralValue],
300 _runtime_hint: Option<&WasmRuntimeHint>,
301 ) -> Result<LiteralValue, ExcelError> {
302 Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
303 "WASM plugin runtime integration is pending for {function_name} (module_id={module_id}, export_name={export_name}, codec_version={codec_version})"
304 )))
305 }
306}
307
308pub fn validate_wasm_manifest(manifest: &WasmModuleManifest) -> Result<(), ExcelError> {
309 if manifest.schema != WASM_MANIFEST_SCHEMA_V1 {
310 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
311 "Unsupported WASM manifest schema: {}",
312 manifest.schema
313 )));
314 }
315
316 let module_id = normalize_wasm_module_id(&manifest.module.id)?;
317 if module_id != manifest.module.id {
318 return Err(ExcelError::new(ExcelErrorKind::Value)
319 .with_message("WASM manifest module.id must not have leading/trailing whitespace"));
320 }
321
322 if manifest.module.version.trim().is_empty() {
323 return Err(ExcelError::new(ExcelErrorKind::Value)
324 .with_message("WASM manifest module.version cannot be empty"));
325 }
326
327 if manifest.module.abi != WASM_ABI_VERSION_V1 {
328 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
329 "Unsupported WASM ABI version {} (expected {})",
330 manifest.module.abi, WASM_ABI_VERSION_V1
331 )));
332 }
333
334 if manifest.module.codec != WASM_CODEC_VERSION_V1 {
335 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
336 "Unsupported WASM codec version {} (expected {})",
337 manifest.module.codec, WASM_CODEC_VERSION_V1
338 )));
339 }
340
341 if manifest.functions.is_empty() {
342 return Err(ExcelError::new(ExcelErrorKind::Value)
343 .with_message("WASM manifest must define at least one function"));
344 }
345
346 let mut function_ids = BTreeSet::new();
347 let mut export_names = BTreeSet::new();
348 let mut names_and_aliases = BTreeSet::new();
349
350 for function in &manifest.functions {
351 if !function_ids.insert(function.id) {
352 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
353 "Duplicate WASM manifest function id {}",
354 function.id
355 )));
356 }
357
358 if function.export_name.trim().is_empty() {
359 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
360 "WASM function {} has empty export name",
361 function.id
362 )));
363 }
364
365 if !export_names.insert(function.export_name.clone()) {
366 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
367 "Duplicate WASM export name: {}",
368 function.export_name
369 )));
370 }
371
372 let canonical_name = normalize_custom_fn_name(&function.name)?;
373 if !names_and_aliases.insert(canonical_name.clone()) {
374 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
375 "Duplicate WASM function name or alias: {}",
376 function.name
377 )));
378 }
379
380 if let Some(max_args) = function.max_args
381 && max_args < function.min_args
382 {
383 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
384 "Invalid WASM function arity for {}: max_args ({max_args}) < min_args ({})",
385 function.name, function.min_args
386 )));
387 }
388
389 for alias in &function.aliases {
390 let canonical_alias = normalize_custom_fn_name(alias)?;
391 if !names_and_aliases.insert(canonical_alias.clone()) {
392 return Err(ExcelError::new(ExcelErrorKind::Value)
393 .with_message(format!("Duplicate WASM function alias: {alias}")));
394 }
395 }
396 }
397
398 Ok(())
399}
400
401#[cfg(feature = "wasm_plugins")]
402pub fn parse_wasm_manifest_json(bytes: &[u8]) -> Result<WasmModuleManifest, ExcelError> {
403 let manifest = serde_json::from_slice::<WasmModuleManifest>(bytes).map_err(|err| {
404 ExcelError::new(ExcelErrorKind::Value)
405 .with_message(format!("Failed to parse WASM manifest JSON: {err}"))
406 })?;
407 validate_wasm_manifest(&manifest)?;
408 Ok(manifest)
409}
410
411#[cfg(feature = "wasm_plugins")]
412pub fn extract_wasm_manifest_json_from_module(wasm_bytes: &[u8]) -> Result<Vec<u8>, ExcelError> {
413 let mut found: Option<Vec<u8>> = None;
414
415 for payload in Parser::new(0).parse_all(wasm_bytes) {
416 let payload = payload.map_err(|err| {
417 ExcelError::new(ExcelErrorKind::Value)
418 .with_message(format!("Invalid WASM module bytes: {err}"))
419 })?;
420
421 if let Payload::CustomSection(section) = payload
422 && section.name() == WASM_MANIFEST_SECTION_V1
423 {
424 if found.is_some() {
425 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
426 "WASM module has multiple formualizer manifest custom sections",
427 ));
428 }
429 found = Some(section.data().to_vec());
430 }
431 }
432
433 found.ok_or_else(|| {
434 ExcelError::new(ExcelErrorKind::Value).with_message(format!(
435 "WASM module is missing required custom section: {WASM_MANIFEST_SECTION_V1}"
436 ))
437 })
438}
439
440#[cfg(feature = "wasm_plugins")]
441fn wasm_module_info_from_manifest(
442 module_id: String,
443 module_size_bytes: usize,
444 manifest: &WasmModuleManifest,
445) -> WasmModuleInfo {
446 WasmModuleInfo {
447 module_id,
448 version: manifest.module.version.clone(),
449 abi_version: manifest.module.abi,
450 codec_version: manifest.module.codec,
451 function_count: manifest.functions.len(),
452 module_size_bytes,
453 }
454}
455
456#[derive(Clone)]
457struct RegisteredWasmModule {
458 info: WasmModuleInfo,
459 #[allow(dead_code)]
460 manifest: WasmModuleManifest,
461 wasm_bytes: Arc<Vec<u8>>,
462}
463
464#[cfg_attr(not(feature = "wasm_plugins"), derive(Default))]
465struct WasmPluginManager {
466 modules: BTreeMap<String, RegisteredWasmModule>,
467 #[cfg(feature = "wasm_plugins")]
468 runtime: Arc<dyn WasmUdfRuntime>,
469}
470
471#[cfg(feature = "wasm_plugins")]
472impl Default for WasmPluginManager {
473 fn default() -> Self {
474 Self {
475 modules: BTreeMap::new(),
476 runtime: Arc::new(PendingWasmRuntime),
477 }
478 }
479}
480
481impl WasmPluginManager {
482 #[cfg(feature = "wasm_plugins")]
483 fn set_runtime(&mut self, runtime: Arc<dyn WasmUdfRuntime>) {
484 self.runtime = runtime;
485 }
486
487 #[cfg(feature = "wasm_plugins")]
488 fn runtime(&self) -> Arc<dyn WasmUdfRuntime> {
489 self.runtime.clone()
490 }
491 fn list_module_infos(&self) -> Vec<WasmModuleInfo> {
492 self.modules
493 .values()
494 .map(|registered| {
495 let mut info = registered.info.clone();
496 info.module_size_bytes = registered.wasm_bytes.len();
497 info
498 })
499 .collect()
500 }
501
502 #[cfg(feature = "wasm_plugins")]
503 fn get(&self, module_id: &str) -> Option<&RegisteredWasmModule> {
504 self.modules.get(module_id)
505 }
506
507 #[cfg(feature = "wasm_plugins")]
508 fn unregister_module(&mut self, module_id: &str) -> Result<(), ExcelError> {
509 if self.modules.remove(module_id).is_none() {
510 return Err(ExcelError::new(ExcelErrorKind::Name)
511 .with_message(format!("WASM module {module_id} is not registered")));
512 }
513 Ok(())
514 }
515
516 #[cfg(feature = "wasm_plugins")]
517 fn register_module_bytes(
518 &mut self,
519 requested_module_id: &str,
520 wasm_bytes: &[u8],
521 ) -> Result<WasmModuleInfo, ExcelError> {
522 if self.modules.contains_key(requested_module_id) {
523 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
524 "WASM module {requested_module_id} is already registered"
525 )));
526 }
527
528 let manifest_json = extract_wasm_manifest_json_from_module(wasm_bytes)?;
529 let manifest = parse_wasm_manifest_json(&manifest_json)?;
530
531 if manifest.module.id != requested_module_id {
532 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
533 "WASM manifest module id mismatch: requested {requested_module_id}, manifest {}",
534 manifest.module.id
535 )));
536 }
537
538 self.runtime
539 .validate_module(requested_module_id, wasm_bytes, &manifest)?;
540
541 let info = wasm_module_info_from_manifest(
542 requested_module_id.to_string(),
543 wasm_bytes.len(),
544 &manifest,
545 );
546
547 self.modules.insert(
548 requested_module_id.to_string(),
549 RegisteredWasmModule {
550 info: info.clone(),
551 manifest,
552 wasm_bytes: Arc::new(wasm_bytes.to_vec()),
553 },
554 );
555
556 Ok(info)
557 }
558}
559
560pub trait CustomFnHandler: Send + Sync {
561 fn call(&self, args: &[LiteralValue]) -> Result<LiteralValue, ExcelError>;
562
563 fn call_batch(&self, _rows: &[Vec<LiteralValue>]) -> Option<Result<LiteralValue, ExcelError>> {
564 None
565 }
566}
567
568impl<F> CustomFnHandler for F
569where
570 F: Fn(&[LiteralValue]) -> Result<LiteralValue, ExcelError> + Send + Sync,
571{
572 fn call(&self, args: &[LiteralValue]) -> Result<LiteralValue, ExcelError> {
573 (self)(args)
574 }
575}
576
577#[derive(Clone)]
578struct RegisteredCustomFn {
579 info: CustomFnInfo,
580 function: Arc<dyn formualizer_eval::function::Function>,
581}
582
583type CustomFnRegistry = BTreeMap<String, RegisteredCustomFn>;
584
585struct WorkbookCustomFunction {
586 canonical_name: String,
587 options: CustomFnOptions,
588 handler: Arc<dyn CustomFnHandler>,
589}
590
591impl WorkbookCustomFunction {
592 fn new(name: String, options: CustomFnOptions, handler: Arc<dyn CustomFnHandler>) -> Self {
593 Self {
594 canonical_name: name,
595 options,
596 handler,
597 }
598 }
599
600 fn validate_arity(&self, provided: usize) -> Result<(), ExcelError> {
601 if provided < self.options.min_args {
602 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
603 "{} expects at least {} argument(s), got {}",
604 self.canonical_name, self.options.min_args, provided
605 )));
606 }
607 if let Some(max) = self.options.max_args
608 && provided > max
609 {
610 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
611 "{} expects at most {} argument(s), got {}",
612 self.canonical_name, max, provided
613 )));
614 }
615 Ok(())
616 }
617
618 fn materialize_arg<'a, 'b>(
619 arg: &formualizer_eval::traits::ArgumentHandle<'a, 'b>,
620 ) -> Result<LiteralValue, ExcelError> {
621 match arg.value_or_range()? {
622 formualizer_eval::traits::EvaluatedArg::LiteralValue(v) => Ok(v.into_owned()),
623 formualizer_eval::traits::EvaluatedArg::Range(r) => {
624 Ok(LiteralValue::Array(r.materialise().into_owned()))
625 }
626 }
627 }
628}
629
630impl formualizer_eval::function::Function for WorkbookCustomFunction {
631 fn caps(&self) -> formualizer_eval::function::FnCaps {
632 let mut caps = formualizer_eval::function::FnCaps::empty();
633 if self.options.volatile {
634 caps |= formualizer_eval::function::FnCaps::VOLATILE;
635 } else if self.options.deterministic {
636 caps |= formualizer_eval::function::FnCaps::PURE;
637 }
638 caps
639 }
640
641 fn name(&self) -> &'static str {
642 "__WORKBOOK_CUSTOM__"
643 }
644
645 fn function_salt(&self) -> u64 {
646 stable_fn_salt(&self.canonical_name)
647 }
648
649 fn eval<'a, 'b, 'c>(
650 &self,
651 args: &'c [formualizer_eval::traits::ArgumentHandle<'a, 'b>],
652 _ctx: &dyn formualizer_eval::traits::FunctionContext<'b>,
653 ) -> Result<formualizer_eval::traits::CalcValue<'b>, ExcelError> {
654 self.validate_arity(args.len())?;
655
656 let mut materialized = Vec::with_capacity(args.len());
657 for arg in args {
658 materialized.push(Self::materialize_arg(arg)?);
659 }
660
661 let callback_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
662 self.handler.call(&materialized)
663 }));
664
665 match callback_result {
666 Ok(Ok(value)) => Ok(formualizer_eval::traits::CalcValue::Scalar(value)),
667 Ok(Err(err)) => Err(err),
668 Err(_) => Err(ExcelError::new(ExcelErrorKind::Value)
669 .with_message("Custom function callback panicked")),
670 }
671 }
672}
673
674#[cfg(feature = "wasm_plugins")]
675struct WorkbookWasmFunction {
676 canonical_name: String,
677 options: CustomFnOptions,
678 module_id: String,
679 export_name: String,
680 codec_version: u32,
681 runtime_hint: Option<WasmRuntimeHint>,
682 runtime: Arc<dyn WasmUdfRuntime>,
683}
684
685#[cfg(feature = "wasm_plugins")]
686impl WorkbookWasmFunction {
687 fn validate_arity(&self, provided: usize) -> Result<(), ExcelError> {
688 if provided < self.options.min_args {
689 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
690 "{} expects at least {} argument(s), got {}",
691 self.canonical_name, self.options.min_args, provided
692 )));
693 }
694 if let Some(max) = self.options.max_args
695 && provided > max
696 {
697 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
698 "{} expects at most {} argument(s), got {}",
699 self.canonical_name, max, provided
700 )));
701 }
702 Ok(())
703 }
704}
705
706#[cfg(feature = "wasm_plugins")]
707impl formualizer_eval::function::Function for WorkbookWasmFunction {
708 fn caps(&self) -> formualizer_eval::function::FnCaps {
709 let mut caps = formualizer_eval::function::FnCaps::empty();
710 if self.options.volatile {
711 caps |= formualizer_eval::function::FnCaps::VOLATILE;
712 } else if self.options.deterministic {
713 caps |= formualizer_eval::function::FnCaps::PURE;
714 }
715 caps
716 }
717
718 fn name(&self) -> &'static str {
719 "__WORKBOOK_WASM__"
720 }
721
722 fn function_salt(&self) -> u64 {
723 stable_fn_salt(&self.canonical_name)
724 }
725
726 fn eval<'a, 'b, 'c>(
727 &self,
728 args: &'c [formualizer_eval::traits::ArgumentHandle<'a, 'b>],
729 _ctx: &dyn formualizer_eval::traits::FunctionContext<'b>,
730 ) -> Result<formualizer_eval::traits::CalcValue<'b>, ExcelError> {
731 self.validate_arity(args.len())?;
732
733 let mut materialized = Vec::with_capacity(args.len());
734 for arg in args {
735 materialized.push(WorkbookCustomFunction::materialize_arg(arg)?);
736 }
737
738 let runtime_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
739 self.runtime.invoke(
740 &self.module_id,
741 &self.export_name,
742 &self.canonical_name,
743 self.codec_version,
744 &materialized,
745 self.runtime_hint.as_ref(),
746 )
747 }));
748
749 match runtime_result {
750 Ok(Ok(value)) => Ok(formualizer_eval::traits::CalcValue::Scalar(value)),
751 Ok(Err(err)) => Err(err),
752 Err(_) => Err(ExcelError::new(ExcelErrorKind::Value)
753 .with_message("WASM function runtime panicked")),
754 }
755 }
756}
757
758#[derive(Clone)]
760pub struct WBResolver {
761 custom_functions: Arc<RwLock<CustomFnRegistry>>,
762}
763
764impl Default for WBResolver {
765 fn default() -> Self {
766 Self {
767 custom_functions: Arc::new(RwLock::new(BTreeMap::new())),
768 }
769 }
770}
771
772impl WBResolver {
773 fn new(custom_functions: Arc<RwLock<CustomFnRegistry>>) -> Self {
774 Self { custom_functions }
775 }
776}
777
778impl formualizer_eval::traits::ReferenceResolver for WBResolver {
779 fn resolve_cell_reference(
780 &self,
781 _sheet: Option<&str>,
782 _row: u32,
783 _col: u32,
784 ) -> Result<LiteralValue, formualizer_common::error::ExcelError> {
785 Err(formualizer_common::error::ExcelError::from(
786 formualizer_common::error::ExcelErrorKind::NImpl,
787 ))
788 }
789}
790impl formualizer_eval::traits::RangeResolver for WBResolver {
791 fn resolve_range_reference(
792 &self,
793 _sheet: Option<&str>,
794 _sr: Option<u32>,
795 _sc: Option<u32>,
796 _er: Option<u32>,
797 _ec: Option<u32>,
798 ) -> Result<Box<dyn formualizer_eval::traits::Range>, formualizer_common::error::ExcelError>
799 {
800 Err(formualizer_common::error::ExcelError::from(
801 formualizer_common::error::ExcelErrorKind::NImpl,
802 ))
803 }
804}
805impl formualizer_eval::traits::NamedRangeResolver for WBResolver {
806 fn resolve_named_range_reference(
807 &self,
808 _name: &str,
809 ) -> Result<Vec<Vec<LiteralValue>>, formualizer_common::error::ExcelError> {
810 Err(ExcelError::new(ExcelErrorKind::Name)
811 .with_message(format!("Undefined name: {}", _name)))
812 }
813}
814impl formualizer_eval::traits::TableResolver for WBResolver {
815 fn resolve_table_reference(
816 &self,
817 _tref: &formualizer_parse::parser::TableReference,
818 ) -> Result<Box<dyn formualizer_eval::traits::Table>, formualizer_common::error::ExcelError>
819 {
820 Err(formualizer_common::error::ExcelError::from(
821 formualizer_common::error::ExcelErrorKind::NImpl,
822 ))
823 }
824}
825impl formualizer_eval::traits::SourceResolver for WBResolver {}
826impl formualizer_eval::traits::FunctionProvider for WBResolver {
827 fn get_function(
828 &self,
829 ns: &str,
830 name: &str,
831 ) -> Option<std::sync::Arc<dyn formualizer_eval::function::Function>> {
832 if ns.is_empty() {
833 let key = name.to_ascii_uppercase();
834 if let Some(local) = self.custom_functions.read().get(&key) {
835 return Some(local.function.clone());
836 }
837 }
838 formualizer_eval::function_registry::get(ns, name)
839 }
840}
841impl formualizer_eval::traits::Resolver for WBResolver {}
842impl formualizer_eval::traits::EvaluationContext for WBResolver {}
843
844pub struct Workbook {
846 engine: formualizer_eval::engine::Engine<WBResolver>,
847 custom_functions: Arc<RwLock<CustomFnRegistry>>,
848 wasm_plugins: WasmPluginManager,
849 enable_changelog: bool,
850 log: formualizer_eval::engine::ChangeLog,
851 undo: formualizer_eval::engine::graph::editor::undo_engine::UndoEngine,
852}
853
854trait WorkbookActionOps {
855 fn set_value(
856 &mut self,
857 sheet: &str,
858 row: u32,
859 col: u32,
860 value: LiteralValue,
861 ) -> Result<(), IoError>;
862
863 fn set_formula(
864 &mut self,
865 sheet: &str,
866 row: u32,
867 col: u32,
868 formula: &str,
869 ) -> Result<(), IoError>;
870
871 fn set_values(
872 &mut self,
873 sheet: &str,
874 start_row: u32,
875 start_col: u32,
876 rows: &[Vec<LiteralValue>],
877 ) -> Result<(), IoError>;
878
879 fn write_range(
880 &mut self,
881 sheet: &str,
882 start: (u32, u32),
883 cells: BTreeMap<(u32, u32), crate::traits::CellData>,
884 ) -> Result<(), IoError>;
885}
886
887pub struct WorkbookAction<'a> {
892 ops: &'a mut dyn WorkbookActionOps,
893}
894
895impl WorkbookAction<'_> {
896 #[inline]
897 pub fn set_value(
898 &mut self,
899 sheet: &str,
900 row: u32,
901 col: u32,
902 value: LiteralValue,
903 ) -> Result<(), IoError> {
904 self.ops.set_value(sheet, row, col, value)
905 }
906
907 #[inline]
908 pub fn set_formula(
909 &mut self,
910 sheet: &str,
911 row: u32,
912 col: u32,
913 formula: &str,
914 ) -> Result<(), IoError> {
915 self.ops.set_formula(sheet, row, col, formula)
916 }
917
918 #[inline]
919 pub fn set_values(
920 &mut self,
921 sheet: &str,
922 start_row: u32,
923 start_col: u32,
924 rows: &[Vec<LiteralValue>],
925 ) -> Result<(), IoError> {
926 self.ops.set_values(sheet, start_row, start_col, rows)
927 }
928
929 #[inline]
930 pub fn write_range(
931 &mut self,
932 sheet: &str,
933 start: (u32, u32),
934 cells: BTreeMap<(u32, u32), crate::traits::CellData>,
935 ) -> Result<(), IoError> {
936 self.ops.write_range(sheet, start, cells)
937 }
938}
939
940#[derive(Clone, Copy, Debug, PartialEq, Eq)]
941pub enum WorkbookMode {
942 Ephemeral,
944 Interactive,
946}
947
948#[derive(Clone, Debug)]
949pub struct WorkbookConfig {
950 pub eval: formualizer_eval::engine::EvalConfig,
951 pub enable_changelog: bool,
952}
953
954impl WorkbookConfig {
955 pub fn ephemeral() -> Self {
956 Self {
957 eval: formualizer_eval::engine::EvalConfig::default(),
958 enable_changelog: false,
959 }
960 }
961
962 pub fn interactive() -> Self {
963 let eval = formualizer_eval::engine::EvalConfig {
964 defer_graph_building: true,
965 formula_parse_policy: formualizer_eval::engine::FormulaParsePolicy::CoerceToError,
966 ..Default::default()
967 };
968 Self {
969 eval,
970 enable_changelog: true,
971 }
972 }
973}
974
975impl Default for Workbook {
976 fn default() -> Self {
977 Self::new()
978 }
979}
980
981impl Workbook {
982 pub fn new_with_config(mut config: WorkbookConfig) -> Self {
983 config.eval.arrow_storage_enabled = true;
984 config.eval.delta_overlay_enabled = true;
985 config.eval.write_formula_overlay_enabled = true;
986
987 let custom_functions = Arc::new(RwLock::new(BTreeMap::new()));
988 let resolver = WBResolver::new(custom_functions.clone());
989 let engine = formualizer_eval::engine::Engine::new(resolver, config.eval);
990
991 let mut log = formualizer_eval::engine::ChangeLog::new();
992 log.set_enabled(config.enable_changelog);
993 Self {
994 engine,
995 custom_functions,
996 wasm_plugins: WasmPluginManager::default(),
997 enable_changelog: config.enable_changelog,
998 log,
999 undo: formualizer_eval::engine::graph::editor::undo_engine::UndoEngine::new(),
1000 }
1001 }
1002 pub fn new_with_mode(mode: WorkbookMode) -> Self {
1003 let config = match mode {
1004 WorkbookMode::Ephemeral => WorkbookConfig::ephemeral(),
1005 WorkbookMode::Interactive => WorkbookConfig::interactive(),
1006 };
1007 Self::new_with_config(config)
1008 }
1009 pub fn new() -> Self {
1010 Self::new_with_mode(WorkbookMode::Interactive)
1011 }
1012
1013 pub fn register_custom_function(
1014 &mut self,
1015 name: &str,
1016 options: CustomFnOptions,
1017 handler: Arc<dyn CustomFnHandler>,
1018 ) -> Result<(), ExcelError> {
1019 let canonical_name = normalize_custom_fn_name(name)?;
1020
1021 validate_custom_arity(&canonical_name, &options)?;
1022
1023 if self.custom_functions.read().contains_key(&canonical_name) {
1024 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
1025 "Custom function {canonical_name} is already registered"
1026 )));
1027 }
1028
1029 if !options.allow_override_builtin
1030 && formualizer_eval::function_registry::get("", &canonical_name).is_some()
1031 {
1032 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
1033 "Custom function {canonical_name} conflicts with a global function; set allow_override_builtin=true to override"
1034 )));
1035 }
1036
1037 let info = CustomFnInfo {
1038 name: canonical_name.clone(),
1039 options: options.clone(),
1040 };
1041 let function = Arc::new(WorkbookCustomFunction::new(
1042 canonical_name.clone(),
1043 options,
1044 handler,
1045 ));
1046
1047 self.custom_functions
1048 .write()
1049 .insert(canonical_name, RegisteredCustomFn { info, function });
1050 Ok(())
1051 }
1052
1053 pub fn inspect_wasm_module_bytes(
1055 &self,
1056 wasm_bytes: &[u8],
1057 ) -> Result<WasmModuleInfo, ExcelError> {
1058 #[cfg(feature = "wasm_plugins")]
1059 {
1060 let manifest_json = extract_wasm_manifest_json_from_module(wasm_bytes)?;
1061 let manifest = parse_wasm_manifest_json(&manifest_json)?;
1062 let canonical_module_id = normalize_wasm_module_id(&manifest.module.id)?;
1063 Ok(wasm_module_info_from_manifest(
1064 canonical_module_id,
1065 wasm_bytes.len(),
1066 &manifest,
1067 ))
1068 }
1069
1070 #[cfg(not(feature = "wasm_plugins"))]
1071 {
1072 let _ = wasm_bytes;
1073 Err(ExcelError::new(ExcelErrorKind::NImpl)
1074 .with_message("WASM module inspection requires the `wasm_plugins` feature"))
1075 }
1076 }
1077
1078 pub fn register_wasm_module_bytes(
1079 &mut self,
1080 module_id: &str,
1081 wasm_bytes: &[u8],
1082 ) -> Result<WasmModuleInfo, ExcelError> {
1083 let canonical_module_id = normalize_wasm_module_id(module_id)?;
1084
1085 #[cfg(feature = "wasm_plugins")]
1086 {
1087 self.wasm_plugins
1088 .register_module_bytes(&canonical_module_id, wasm_bytes)
1089 }
1090
1091 #[cfg(not(feature = "wasm_plugins"))]
1092 {
1093 let _ = wasm_bytes;
1094 Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
1095 "WASM module registration for {canonical_module_id} requires the `wasm_plugins` feature"
1096 )))
1097 }
1098 }
1099
1100 pub fn inspect_wasm_module_file(
1102 &self,
1103 path: impl AsRef<std::path::Path>,
1104 ) -> Result<WasmModuleInfo, ExcelError> {
1105 #[cfg(not(target_arch = "wasm32"))]
1106 {
1107 let bytes = read_wasm_file_bytes(path.as_ref())?;
1108 self.inspect_wasm_module_bytes(&bytes)
1109 }
1110
1111 #[cfg(target_arch = "wasm32")]
1112 {
1113 let _ = path;
1114 Err(ExcelError::new(ExcelErrorKind::NImpl)
1115 .with_message("WASM module file inspection is not available on wasm32 hosts"))
1116 }
1117 }
1118
1119 pub fn inspect_wasm_modules_dir(
1121 &self,
1122 dir: impl AsRef<std::path::Path>,
1123 ) -> Result<Vec<WasmModuleInfo>, ExcelError> {
1124 #[cfg(not(target_arch = "wasm32"))]
1125 {
1126 let mut infos = Vec::new();
1127 for path in collect_wasm_files_in_dir(dir.as_ref())? {
1128 let bytes = read_wasm_file_bytes(&path)?;
1129 infos.push(self.inspect_wasm_module_bytes(&bytes)?);
1130 }
1131 Ok(infos)
1132 }
1133
1134 #[cfg(target_arch = "wasm32")]
1135 {
1136 let _ = dir;
1137 Err(ExcelError::new(ExcelErrorKind::NImpl)
1138 .with_message("WASM module directory inspection is not available on wasm32 hosts"))
1139 }
1140 }
1141
1142 pub fn attach_wasm_module_bytes(
1144 &mut self,
1145 module_id: &str,
1146 wasm_bytes: &[u8],
1147 ) -> Result<WasmModuleInfo, ExcelError> {
1148 self.register_wasm_module_bytes(module_id, wasm_bytes)
1149 }
1150
1151 pub fn attach_wasm_module_file(
1153 &mut self,
1154 path: impl AsRef<std::path::Path>,
1155 ) -> Result<WasmModuleInfo, ExcelError> {
1156 #[cfg(not(target_arch = "wasm32"))]
1157 {
1158 let bytes = read_wasm_file_bytes(path.as_ref())?;
1159 let info = self.inspect_wasm_module_bytes(&bytes)?;
1160 self.attach_wasm_module_bytes(&info.module_id, &bytes)
1161 }
1162
1163 #[cfg(target_arch = "wasm32")]
1164 {
1165 let _ = path;
1166 Err(ExcelError::new(ExcelErrorKind::NImpl)
1167 .with_message("WASM module file attachment is not available on wasm32 hosts"))
1168 }
1169 }
1170
1171 pub fn attach_wasm_modules_dir(
1173 &mut self,
1174 dir: impl AsRef<std::path::Path>,
1175 ) -> Result<Vec<WasmModuleInfo>, ExcelError> {
1176 #[cfg(not(target_arch = "wasm32"))]
1177 {
1178 let mut attached = Vec::new();
1179 for path in collect_wasm_files_in_dir(dir.as_ref())? {
1180 attached.push(self.attach_wasm_module_file(path)?);
1181 }
1182 Ok(attached)
1183 }
1184
1185 #[cfg(target_arch = "wasm32")]
1186 {
1187 let _ = dir;
1188 Err(ExcelError::new(ExcelErrorKind::NImpl)
1189 .with_message("WASM module directory attachment is not available on wasm32 hosts"))
1190 }
1191 }
1192
1193 pub fn list_wasm_modules(&self) -> Vec<WasmModuleInfo> {
1194 self.wasm_plugins.list_module_infos()
1195 }
1196
1197 pub fn unregister_wasm_module(&mut self, module_id: &str) -> Result<(), ExcelError> {
1198 let canonical_module_id = normalize_wasm_module_id(module_id)?;
1199
1200 #[cfg(feature = "wasm_plugins")]
1201 {
1202 self.wasm_plugins.unregister_module(&canonical_module_id)
1203 }
1204
1205 #[cfg(not(feature = "wasm_plugins"))]
1206 {
1207 Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
1208 "WASM module unregistration for {canonical_module_id} requires the `wasm_plugins` feature"
1209 )))
1210 }
1211 }
1212
1213 #[cfg(feature = "wasm_plugins")]
1214 #[doc(hidden)]
1215 pub fn set_wasm_runtime(&mut self, runtime: Arc<dyn WasmUdfRuntime>) {
1216 self.wasm_plugins.set_runtime(runtime);
1217 }
1218
1219 #[cfg(all(feature = "wasm_runtime_wasmtime", not(target_arch = "wasm32")))]
1220 pub fn use_wasmtime_runtime(&mut self) {
1221 self.wasm_plugins
1222 .set_runtime(Arc::new(new_wasmtime_runtime()));
1223 }
1224
1225 pub fn register_wasm_function(
1226 &mut self,
1227 name: &str,
1228 options: CustomFnOptions,
1229 spec: WasmFunctionSpec,
1230 ) -> Result<(), ExcelError> {
1231 let canonical_name = normalize_custom_fn_name(name)?;
1232 validate_custom_arity(&canonical_name, &options)?;
1233 validate_wasm_spec(&spec)?;
1234
1235 #[cfg(feature = "wasm_plugins")]
1236 {
1237 let module_id = normalize_wasm_module_id(&spec.module_id)?;
1238 let module = self.wasm_plugins.get(&module_id).ok_or_else(|| {
1239 ExcelError::new(ExcelErrorKind::Name)
1240 .with_message(format!("WASM module {module_id} is not registered"))
1241 })?;
1242
1243 if module.manifest.module.codec != spec.codec_version {
1244 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
1245 "WASM codec mismatch for {canonical_name}: spec codec {} != module codec {}",
1246 spec.codec_version, module.manifest.module.codec
1247 )));
1248 }
1249
1250 if !module
1251 .manifest
1252 .functions
1253 .iter()
1254 .any(|function| function.export_name == spec.export_name)
1255 {
1256 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
1257 "WASM export {} is not declared in module {}",
1258 spec.export_name, module_id
1259 )));
1260 }
1261
1262 if self.custom_functions.read().contains_key(&canonical_name) {
1263 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
1264 "Custom function {canonical_name} is already registered"
1265 )));
1266 }
1267
1268 if !options.allow_override_builtin
1269 && formualizer_eval::function_registry::get("", &canonical_name).is_some()
1270 {
1271 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
1272 "Custom function {canonical_name} conflicts with a global function; set allow_override_builtin=true to override"
1273 )));
1274 }
1275
1276 let runtime = self.wasm_plugins.runtime();
1277 if !runtime.can_bind_functions() {
1278 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
1279 "WASM plugin runtime integration is pending for {canonical_name} (module_id={}, export_name={}, codec_version={})",
1280 module_id, spec.export_name, spec.codec_version
1281 )));
1282 }
1283
1284 let info = CustomFnInfo {
1285 name: canonical_name.clone(),
1286 options: options.clone(),
1287 };
1288 let function = Arc::new(WorkbookWasmFunction {
1289 canonical_name: canonical_name.clone(),
1290 options,
1291 module_id,
1292 export_name: spec.export_name,
1293 codec_version: spec.codec_version,
1294 runtime_hint: spec.runtime_hint,
1295 runtime,
1296 });
1297
1298 self.custom_functions
1299 .write()
1300 .insert(canonical_name, RegisteredCustomFn { info, function });
1301 Ok(())
1302 }
1303
1304 #[cfg(not(feature = "wasm_plugins"))]
1305 {
1306 Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
1307 "WASM plugin registration for {canonical_name} requires the `wasm_plugins` feature (module_id={}, export_name={}, codec_version={})",
1308 spec.module_id, spec.export_name, spec.codec_version
1309 )))
1310 }
1311 }
1312
1313 pub fn bind_wasm_function(
1315 &mut self,
1316 name: &str,
1317 options: CustomFnOptions,
1318 spec: WasmFunctionSpec,
1319 ) -> Result<(), ExcelError> {
1320 self.register_wasm_function(name, options, spec)
1321 }
1322
1323 pub fn unregister_custom_function(&mut self, name: &str) -> Result<(), ExcelError> {
1324 let canonical_name = normalize_custom_fn_name(name)?;
1325 if self
1326 .custom_functions
1327 .write()
1328 .remove(&canonical_name)
1329 .is_none()
1330 {
1331 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
1332 "Custom function {canonical_name} is not registered"
1333 )));
1334 }
1335 Ok(())
1336 }
1337
1338 pub fn list_custom_functions(&self) -> Vec<CustomFnInfo> {
1339 self.custom_functions
1340 .read()
1341 .values()
1342 .map(|registered| registered.info.clone())
1343 .collect()
1344 }
1345
1346 pub fn engine(&self) -> &formualizer_eval::engine::Engine<WBResolver> {
1347 &self.engine
1348 }
1349 pub fn engine_mut(&mut self) -> &mut formualizer_eval::engine::Engine<WBResolver> {
1350 &mut self.engine
1351 }
1352 pub fn eval_config(&self) -> &formualizer_eval::engine::EvalConfig {
1353 &self.engine.config
1354 }
1355
1356 pub fn deterministic_mode(&self) -> &formualizer_eval::engine::DeterministicMode {
1357 &self.engine.config.deterministic_mode
1358 }
1359
1360 pub fn set_deterministic_mode(
1361 &mut self,
1362 mode: formualizer_eval::engine::DeterministicMode,
1363 ) -> Result<(), IoError> {
1364 self.engine
1365 .set_deterministic_mode(mode)
1366 .map_err(IoError::Engine)
1367 }
1368
1369 pub fn set_changelog_enabled(&mut self, enabled: bool) {
1371 self.enable_changelog = enabled;
1372 self.log.set_enabled(enabled);
1373 }
1374
1375 pub fn set_actor_id(&mut self, actor_id: Option<String>) {
1377 self.log.set_actor_id(actor_id);
1378 }
1379
1380 pub fn set_correlation_id(&mut self, correlation_id: Option<String>) {
1381 self.log.set_correlation_id(correlation_id);
1382 }
1383
1384 pub fn set_reason(&mut self, reason: Option<String>) {
1385 self.log.set_reason(reason);
1386 }
1387 pub fn begin_action(&mut self, description: impl Into<String>) {
1388 if self.enable_changelog {
1389 self.log.begin_compound(description.into());
1390 }
1391 }
1392 pub fn end_action(&mut self) {
1393 if self.enable_changelog {
1394 self.log.end_compound();
1395 }
1396 }
1397
1398 pub fn action<T>(
1408 &mut self,
1409 name: &str,
1410 f: impl FnOnce(&mut WorkbookAction<'_>) -> Result<T, IoError>,
1411 ) -> Result<T, IoError> {
1412 let mut user_err: Option<IoError> = None;
1413
1414 if self.enable_changelog {
1415 let res = self.engine.action_with_logger(&mut self.log, name, |tx| {
1416 struct TxOps<'a, 'e> {
1417 tx: &'a mut formualizer_eval::engine::EngineAction<'e, WBResolver>,
1418 }
1419 impl WorkbookActionOps for TxOps<'_, '_> {
1420 fn set_value(
1421 &mut self,
1422 sheet: &str,
1423 row: u32,
1424 col: u32,
1425 value: LiteralValue,
1426 ) -> Result<(), IoError> {
1427 self.tx
1428 .set_cell_value(sheet, row, col, value)
1429 .map_err(|e| match e {
1430 formualizer_eval::engine::EditorError::Excel(excel) => {
1431 IoError::Engine(excel)
1432 }
1433 other => IoError::from_backend("editor", other),
1434 })
1435 }
1436
1437 fn set_formula(
1438 &mut self,
1439 sheet: &str,
1440 row: u32,
1441 col: u32,
1442 formula: &str,
1443 ) -> Result<(), IoError> {
1444 let with_eq = if formula.starts_with('=') {
1445 formula.to_string()
1446 } else {
1447 format!("={formula}")
1448 };
1449 let ast = formualizer_parse::parser::parse(&with_eq)
1450 .map_err(|e| IoError::from_backend("parser", e))?;
1451 self.tx
1452 .set_cell_formula(sheet, row, col, ast)
1453 .map_err(|e| match e {
1454 formualizer_eval::engine::EditorError::Excel(excel) => {
1455 IoError::Engine(excel)
1456 }
1457 other => IoError::from_backend("editor", other),
1458 })
1459 }
1460
1461 fn set_values(
1462 &mut self,
1463 sheet: &str,
1464 start_row: u32,
1465 start_col: u32,
1466 rows: &[Vec<LiteralValue>],
1467 ) -> Result<(), IoError> {
1468 for (ri, rvals) in rows.iter().enumerate() {
1469 let r = start_row + ri as u32;
1470 for (ci, v) in rvals.iter().enumerate() {
1471 let c = start_col + ci as u32;
1472 self.set_value(sheet, r, c, v.clone())?;
1473 }
1474 }
1475 Ok(())
1476 }
1477
1478 fn write_range(
1479 &mut self,
1480 sheet: &str,
1481 _start: (u32, u32),
1482 cells: BTreeMap<(u32, u32), crate::traits::CellData>,
1483 ) -> Result<(), IoError> {
1484 for ((r, c), d) in cells.into_iter() {
1485 if let Some(v) = d.value {
1486 self.set_value(sheet, r, c, v)?;
1487 }
1488 if let Some(f) = d.formula.as_ref() {
1489 self.set_formula(sheet, r, c, f)?;
1490 }
1491 }
1492 Ok(())
1493 }
1494 }
1495
1496 let mut ops = TxOps { tx };
1497 let mut wtx = WorkbookAction { ops: &mut ops };
1498 match f(&mut wtx) {
1499 Ok(v) => Ok(v),
1500 Err(e) => {
1501 user_err = Some(e);
1502 Err(formualizer_eval::engine::EditorError::TransactionFailed {
1503 reason: "Workbook::action aborted".to_string(),
1504 })
1505 }
1506 }
1507 });
1508
1509 if let Some(e) = user_err {
1510 return Err(e);
1511 }
1512 return res.map_err(|e| match e {
1513 formualizer_eval::engine::EditorError::Excel(excel) => IoError::Engine(excel),
1514 other => IoError::from_backend("editor", other),
1515 });
1516 }
1517
1518 let res = self.engine.action_atomic_journal(name.to_string(), |tx| {
1519 struct TxOps<'a, 'e> {
1520 tx: &'a mut formualizer_eval::engine::EngineAction<'e, WBResolver>,
1521 }
1522 impl WorkbookActionOps for TxOps<'_, '_> {
1523 fn set_value(
1524 &mut self,
1525 sheet: &str,
1526 row: u32,
1527 col: u32,
1528 value: LiteralValue,
1529 ) -> Result<(), IoError> {
1530 self.tx
1531 .set_cell_value(sheet, row, col, value)
1532 .map_err(|e| match e {
1533 formualizer_eval::engine::EditorError::Excel(excel) => {
1534 IoError::Engine(excel)
1535 }
1536 other => IoError::from_backend("editor", other),
1537 })
1538 }
1539
1540 fn set_formula(
1541 &mut self,
1542 sheet: &str,
1543 row: u32,
1544 col: u32,
1545 formula: &str,
1546 ) -> Result<(), IoError> {
1547 let with_eq = if formula.starts_with('=') {
1548 formula.to_string()
1549 } else {
1550 format!("={formula}")
1551 };
1552 let ast = formualizer_parse::parser::parse(&with_eq)
1553 .map_err(|e| IoError::from_backend("parser", e))?;
1554 self.tx
1555 .set_cell_formula(sheet, row, col, ast)
1556 .map_err(|e| match e {
1557 formualizer_eval::engine::EditorError::Excel(excel) => {
1558 IoError::Engine(excel)
1559 }
1560 other => IoError::from_backend("editor", other),
1561 })
1562 }
1563
1564 fn set_values(
1565 &mut self,
1566 sheet: &str,
1567 start_row: u32,
1568 start_col: u32,
1569 rows: &[Vec<LiteralValue>],
1570 ) -> Result<(), IoError> {
1571 for (ri, rvals) in rows.iter().enumerate() {
1572 let r = start_row + ri as u32;
1573 for (ci, v) in rvals.iter().enumerate() {
1574 let c = start_col + ci as u32;
1575 self.set_value(sheet, r, c, v.clone())?;
1576 }
1577 }
1578 Ok(())
1579 }
1580
1581 fn write_range(
1582 &mut self,
1583 sheet: &str,
1584 _start: (u32, u32),
1585 cells: BTreeMap<(u32, u32), crate::traits::CellData>,
1586 ) -> Result<(), IoError> {
1587 for ((r, c), d) in cells.into_iter() {
1588 if let Some(v) = d.value {
1589 self.set_value(sheet, r, c, v)?;
1590 }
1591 if let Some(f) = d.formula.as_ref() {
1592 self.set_formula(sheet, r, c, f)?;
1593 }
1594 }
1595 Ok(())
1596 }
1597 }
1598
1599 let mut ops = TxOps { tx };
1600 let mut wtx = WorkbookAction { ops: &mut ops };
1601 match f(&mut wtx) {
1602 Ok(v) => Ok(v),
1603 Err(e) => {
1604 user_err = Some(e);
1605 Err(formualizer_eval::engine::EditorError::TransactionFailed {
1606 reason: "Workbook::action aborted".to_string(),
1607 })
1608 }
1609 }
1610 });
1611
1612 if let Some(e) = user_err {
1613 return Err(e);
1614 }
1615 let (v, journal) = res.map_err(|e| match e {
1616 formualizer_eval::engine::EditorError::Excel(excel) => IoError::Engine(excel),
1617 other => IoError::from_backend("editor", other),
1618 })?;
1619 self.undo.push_action(journal);
1620 Ok(v)
1621 }
1622 pub fn undo(&mut self) -> Result<(), IoError> {
1623 if self.enable_changelog {
1624 self.engine
1625 .undo_logged(&mut self.undo, &mut self.log)
1626 .map_err(|e| IoError::from_backend("editor", e))?;
1627 } else {
1628 self.engine
1629 .undo_action(&mut self.undo)
1630 .map_err(|e| IoError::from_backend("editor", e))?;
1631 }
1632 Ok(())
1633 }
1634 pub fn redo(&mut self) -> Result<(), IoError> {
1635 if self.enable_changelog {
1636 self.engine
1637 .redo_logged(&mut self.undo, &mut self.log)
1638 .map_err(|e| IoError::from_backend("editor", e))?;
1639 } else {
1640 self.engine
1641 .redo_action(&mut self.undo)
1642 .map_err(|e| IoError::from_backend("editor", e))?;
1643 }
1644 Ok(())
1645 }
1646
1647 fn ensure_arrow_sheet_capacity(&mut self, sheet: &str, min_rows: usize, min_cols: usize) {
1648 use formualizer_eval::arrow_store::ArrowSheet;
1649
1650 if self.engine.sheet_store().sheet(sheet).is_none() {
1651 self.engine.sheet_store_mut().sheets.push(ArrowSheet {
1652 name: std::sync::Arc::<str>::from(sheet),
1653 columns: Vec::new(),
1654 nrows: 0,
1655 chunk_starts: Vec::new(),
1656 chunk_rows: 32 * 1024,
1657 });
1658 }
1659
1660 let asheet = self
1661 .engine
1662 .sheet_store_mut()
1663 .sheet_mut(sheet)
1664 .expect("ArrowSheet must exist");
1665
1666 if min_rows > asheet.nrows as usize {
1668 asheet.ensure_row_capacity(min_rows);
1669 }
1670
1671 let cur_cols = asheet.columns.len();
1673 if min_cols > cur_cols {
1674 asheet.insert_columns(cur_cols, min_cols - cur_cols);
1675 }
1676 }
1677
1678 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
1679 use formualizer_eval::arrow_store::OverlayValue;
1680 if !(self.engine.config.arrow_storage_enabled && self.engine.config.delta_overlay_enabled) {
1681 return;
1682 }
1683 let date_system = self.engine.config.date_system;
1684 let row0 = row.saturating_sub(1) as usize;
1685 let col0 = col.saturating_sub(1) as usize;
1686 self.ensure_arrow_sheet_capacity(sheet, row0 + 1, col0 + 1);
1687 let asheet = self
1688 .engine
1689 .sheet_store_mut()
1690 .sheet_mut(sheet)
1691 .expect("ArrowSheet must exist");
1692 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
1693 let ov = match value {
1694 LiteralValue::Empty => OverlayValue::Empty,
1695 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
1696 LiteralValue::Number(n) => OverlayValue::Number(*n),
1697 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
1698 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
1699 LiteralValue::Error(e) => {
1700 OverlayValue::Error(formualizer_eval::arrow_store::map_error_code(e.kind))
1701 }
1702 LiteralValue::Date(d) => {
1703 let dt = d.and_hms_opt(0, 0, 0).unwrap();
1704 let serial = formualizer_eval::builtins::datetime::datetime_to_serial_for(
1705 date_system,
1706 &dt,
1707 );
1708 OverlayValue::DateTime(serial)
1709 }
1710 LiteralValue::DateTime(dt) => {
1711 let serial = formualizer_eval::builtins::datetime::datetime_to_serial_for(
1712 date_system,
1713 dt,
1714 );
1715 OverlayValue::DateTime(serial)
1716 }
1717 LiteralValue::Time(t) => {
1718 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
1719 OverlayValue::DateTime(serial)
1720 }
1721 LiteralValue::Duration(d) => {
1722 let serial = d.num_seconds() as f64 / 86_400.0;
1723 OverlayValue::Duration(serial)
1724 }
1725 LiteralValue::Pending => OverlayValue::Pending,
1726 LiteralValue::Array(_) => {
1727 OverlayValue::Error(formualizer_eval::arrow_store::map_error_code(
1728 formualizer_common::ExcelErrorKind::Value,
1729 ))
1730 }
1731 };
1732 if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
1734 ch.overlay.set(in_off, ov);
1735 }
1736 }
1737 }
1738
1739 pub fn sheet_names(&self) -> Vec<String> {
1741 self.engine
1742 .sheet_store()
1743 .sheets
1744 .iter()
1745 .map(|s| s.name.as_ref().to_string())
1746 .collect()
1747 }
1748 pub fn sheet_dimensions(&self, name: &str) -> Option<(u32, u32)> {
1750 self.engine
1751 .sheet_store()
1752 .sheet(name)
1753 .map(|s| (s.nrows, s.columns.len() as u32))
1754 }
1755 pub fn has_sheet(&self, name: &str) -> bool {
1756 self.engine.sheet_id(name).is_some()
1757 }
1758 pub fn add_sheet(&mut self, name: &str) -> Result<(), ExcelError> {
1759 self.engine.add_sheet(name)?;
1760 self.ensure_arrow_sheet_capacity(name, 0, 0);
1761 Ok(())
1762 }
1763 pub fn delete_sheet(&mut self, name: &str) -> Result<(), ExcelError> {
1764 if let Some(id) = self.engine.sheet_id(name) {
1765 self.engine.remove_sheet(id)?;
1766 }
1767 self.engine
1769 .sheet_store_mut()
1770 .sheets
1771 .retain(|s| s.name.as_ref() != name);
1772 Ok(())
1773 }
1774 pub fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), ExcelError> {
1775 if let Some(id) = self.engine.sheet_id(old) {
1776 self.engine.rename_sheet(id, new)?;
1777 }
1778 if let Some(asheet) = self.engine.sheet_store_mut().sheet_mut(old) {
1779 asheet.name = std::sync::Arc::<str>::from(new);
1780 }
1781 Ok(())
1782 }
1783
1784 pub fn set_value(
1786 &mut self,
1787 sheet: &str,
1788 row: u32,
1789 col: u32,
1790 value: LiteralValue,
1791 ) -> Result<(), IoError> {
1792 self.ensure_arrow_sheet_capacity(sheet, row as usize, col as usize);
1793 if self.enable_changelog {
1794 let sheet_id = self
1796 .engine
1797 .sheet_id(sheet)
1798 .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
1799 let cell = formualizer_eval::reference::CellRef::new(
1800 sheet_id,
1801 formualizer_eval::reference::Coord::from_excel(row, col, true, true),
1802 );
1803
1804 let old_value = self.engine.get_cell_value(sheet, row, col);
1807 let old_formula = self
1808 .engine
1809 .get_cell(sheet, row, col)
1810 .and_then(|(ast, _)| ast);
1811
1812 self.engine.edit_with_logger(&mut self.log, |editor| {
1813 editor.set_cell_value(cell, value.clone());
1814 });
1815
1816 self.log
1817 .patch_last_cell_event_old_state(cell, old_value, old_formula);
1818 self.mirror_value_to_overlay(sheet, row, col, &value);
1819 self.engine.mark_data_edited();
1820 Ok(())
1821 } else {
1822 self.engine
1823 .set_cell_value(sheet, row, col, value)
1824 .map_err(IoError::Engine)
1825 }
1826 }
1827
1828 pub fn set_formula(
1829 &mut self,
1830 sheet: &str,
1831 row: u32,
1832 col: u32,
1833 formula: &str,
1834 ) -> Result<(), IoError> {
1835 self.ensure_arrow_sheet_capacity(sheet, row as usize, col as usize);
1836 if self.engine.config.defer_graph_building {
1837 if self.engine.get_cell(sheet, row, col).is_some() {
1838 let with_eq = if formula.starts_with('=') {
1839 formula.to_string()
1840 } else {
1841 format!("={formula}")
1842 };
1843 let ast = formualizer_parse::parser::parse(&with_eq)
1844 .map_err(|e| IoError::from_backend("parser", e))?;
1845 if self.enable_changelog {
1846 let sheet_id = self
1847 .engine
1848 .sheet_id(sheet)
1849 .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
1850 let cell = formualizer_eval::reference::CellRef::new(
1851 sheet_id,
1852 formualizer_eval::reference::Coord::from_excel(row, col, true, true),
1853 );
1854
1855 let old_value = self.engine.get_cell_value(sheet, row, col);
1856 let old_formula = self.engine.get_cell(sheet, row, col).and_then(|(a, _)| a);
1857
1858 self.engine.edit_with_logger(&mut self.log, |editor| {
1859 editor.set_cell_formula(cell, ast);
1860 });
1861
1862 self.log
1863 .patch_last_cell_event_old_state(cell, old_value, old_formula);
1864 self.engine.mark_data_edited();
1865 Ok(())
1866 } else {
1867 self.engine
1868 .set_cell_formula(sheet, row, col, ast)
1869 .map_err(IoError::Engine)
1870 }
1871 } else {
1872 self.engine
1873 .stage_formula_text(sheet, row, col, formula.to_string());
1874 Ok(())
1875 }
1876 } else {
1877 let with_eq = if formula.starts_with('=') {
1878 formula.to_string()
1879 } else {
1880 format!("={formula}")
1881 };
1882 let ast = formualizer_parse::parser::parse(&with_eq)
1883 .map_err(|e| IoError::from_backend("parser", e))?;
1884 if self.enable_changelog {
1885 let sheet_id = self
1886 .engine
1887 .sheet_id(sheet)
1888 .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
1889 let cell = formualizer_eval::reference::CellRef::new(
1890 sheet_id,
1891 formualizer_eval::reference::Coord::from_excel(row, col, true, true),
1892 );
1893 self.engine.edit_with_logger(&mut self.log, |editor| {
1894 editor.set_cell_formula(cell, ast);
1895 });
1896 self.engine.mark_data_edited();
1897 Ok(())
1898 } else {
1899 self.engine
1900 .set_cell_formula(sheet, row, col, ast)
1901 .map_err(IoError::Engine)
1902 }
1903 }
1904 }
1905
1906 pub fn get_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
1907 self.engine.get_cell_value(sheet, row, col)
1908 }
1909 pub fn get_formula(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
1910 if let Some(s) = self.engine.get_staged_formula_text(sheet, row, col) {
1911 return Some(s);
1912 }
1913 self.engine
1914 .get_cell(sheet, row, col)
1915 .and_then(|(ast, _)| ast.map(|a| formualizer_parse::pretty::canonical_formula(&a)))
1916 }
1917
1918 pub fn read_range(&self, addr: &RangeAddress) -> Vec<Vec<LiteralValue>> {
1920 let mut out = Vec::with_capacity(addr.height() as usize);
1921 if let Some(asheet) = self.engine.sheet_store().sheet(&addr.sheet) {
1922 let sr0 = addr.start_row.saturating_sub(1) as usize;
1923 let sc0 = addr.start_col.saturating_sub(1) as usize;
1924 let er0 = addr.end_row.saturating_sub(1) as usize;
1925 let ec0 = addr.end_col.saturating_sub(1) as usize;
1926 let view = asheet.range_view(sr0, sc0, er0, ec0);
1927 let (h, w) = view.dims();
1928 for rr in 0..h {
1929 let mut row = Vec::with_capacity(w);
1930 for cc in 0..w {
1931 row.push(view.get_cell(rr, cc));
1932 }
1933 out.push(row);
1934 }
1935 } else {
1936 for r in addr.start_row..=addr.end_row {
1938 let mut row = Vec::with_capacity(addr.width() as usize);
1939 for c in addr.start_col..=addr.end_col {
1940 row.push(
1941 self.engine
1942 .get_cell_value(&addr.sheet, r, c)
1943 .unwrap_or(LiteralValue::Empty),
1944 );
1945 }
1946 out.push(row);
1947 }
1948 }
1949 out
1950 }
1951 pub fn write_range(
1952 &mut self,
1953 sheet: &str,
1954 _start: (u32, u32),
1955 cells: BTreeMap<(u32, u32), crate::traits::CellData>,
1956 ) -> Result<(), IoError> {
1957 if self.enable_changelog {
1958 let sheet_id = self
1959 .engine
1960 .sheet_id(sheet)
1961 .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
1962 let defer_graph_building = self.engine.config.defer_graph_building;
1963
1964 #[allow(clippy::type_complexity)]
1967 let mut items: Vec<(
1968 u32,
1969 u32,
1970 crate::traits::CellData,
1971 formualizer_eval::reference::CellRef,
1972 Option<LiteralValue>,
1973 Option<formualizer_parse::ASTNode>,
1974 )> = Vec::with_capacity(cells.len());
1975 for ((r, c), d) in cells.into_iter() {
1976 let cell = formualizer_eval::reference::CellRef::new(
1977 sheet_id,
1978 formualizer_eval::reference::Coord::from_excel(r, c, true, true),
1979 );
1980 let old_value = self.engine.get_cell_value(sheet, r, c);
1981 let old_formula = self.engine.get_cell(sheet, r, c).and_then(|(ast, _)| ast);
1982 items.push((r, c, d, cell, old_value, old_formula));
1983 }
1984
1985 let mut overlay_ops: Vec<(u32, u32, LiteralValue)> = Vec::new();
1986 let mut staged_forms: Vec<(u32, u32, String)> = Vec::new();
1987
1988 self.engine
1989 .edit_with_logger(&mut self.log, |editor| -> Result<(), IoError> {
1990 for (r, c, d, cell, _old_value, _old_formula) in items.iter() {
1991 if let Some(v) = d.value.clone() {
1992 editor.set_cell_value(*cell, v.clone());
1993 if d.formula.is_none() {
1997 overlay_ops.push((*r, *c, v));
1998 }
1999 }
2000 if let Some(f) = d.formula.as_ref() {
2001 if defer_graph_building {
2002 staged_forms.push((*r, *c, f.clone()));
2003 } else {
2004 let with_eq = if f.starts_with('=') {
2005 f.clone()
2006 } else {
2007 format!("={f}")
2008 };
2009 let ast = formualizer_parse::parser::parse(&with_eq)
2010 .map_err(|e| IoError::from_backend("parser", e))?;
2011 editor.set_cell_formula(*cell, ast);
2012 }
2013 }
2014 }
2015 Ok(())
2016 })?;
2017
2018 for (_r, _c, _d, cell, old_value, old_formula) in items.iter().rev() {
2020 self.log.patch_last_cell_event_old_state(
2021 *cell,
2022 old_value.clone(),
2023 old_formula.clone(),
2024 );
2025 }
2026
2027 for (r, c, v) in overlay_ops {
2028 self.mirror_value_to_overlay(sheet, r, c, &v);
2029 }
2030 for (r, c, f) in staged_forms {
2031 self.engine.stage_formula_text(sheet, r, c, f);
2032 }
2033 self.engine.mark_data_edited();
2034 Ok(())
2035 } else {
2036 for ((r, c), d) in cells.into_iter() {
2037 if let Some(v) = d.value.clone() {
2038 self.engine
2039 .set_cell_value(sheet, r, c, v)
2040 .map_err(IoError::Engine)?;
2041 }
2042 if let Some(f) = d.formula.as_ref() {
2043 if self.engine.config.defer_graph_building {
2044 self.engine.stage_formula_text(sheet, r, c, f.clone());
2045 } else {
2046 let with_eq = if f.starts_with('=') {
2047 f.clone()
2048 } else {
2049 format!("={f}")
2050 };
2051 let ast = formualizer_parse::parser::parse(&with_eq)
2052 .map_err(|e| IoError::from_backend("parser", e))?;
2053 self.engine
2054 .set_cell_formula(sheet, r, c, ast)
2055 .map_err(IoError::Engine)?;
2056 }
2057 }
2058 }
2059 Ok(())
2060 }
2061 }
2062
2063 pub fn set_values(
2065 &mut self,
2066 sheet: &str,
2067 start_row: u32,
2068 start_col: u32,
2069 rows: &[Vec<LiteralValue>],
2070 ) -> Result<(), IoError> {
2071 if self.enable_changelog {
2072 let sheet_id = self
2073 .engine
2074 .sheet_id(sheet)
2075 .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
2076
2077 #[allow(clippy::type_complexity)]
2079 let mut items: Vec<(
2080 u32,
2081 u32,
2082 LiteralValue,
2083 formualizer_eval::reference::CellRef,
2084 Option<LiteralValue>,
2085 Option<formualizer_parse::ASTNode>,
2086 )> = Vec::new();
2087 for (ri, rvals) in rows.iter().enumerate() {
2088 let r = start_row + ri as u32;
2089 for (ci, v) in rvals.iter().enumerate() {
2090 let c = start_col + ci as u32;
2091 let cell = formualizer_eval::reference::CellRef::new(
2092 sheet_id,
2093 formualizer_eval::reference::Coord::from_excel(r, c, true, true),
2094 );
2095 let old_value = self.engine.get_cell_value(sheet, r, c);
2096 let old_formula = self.engine.get_cell(sheet, r, c).and_then(|(ast, _)| ast);
2097 items.push((r, c, v.clone(), cell, old_value, old_formula));
2098 }
2099 }
2100
2101 self.engine.edit_with_logger(&mut self.log, |editor| {
2102 for (_r, _c, v, cell, _old_value, _old_formula) in items.iter() {
2103 editor.set_cell_value(*cell, v.clone());
2104 }
2105 });
2106
2107 for (_r, _c, _v, cell, old_value, old_formula) in items.iter().rev() {
2108 self.log.patch_last_cell_event_old_state(
2109 *cell,
2110 old_value.clone(),
2111 old_formula.clone(),
2112 );
2113 }
2114
2115 for (r, c, v, _cell, _old_value, _old_formula) in items {
2116 self.mirror_value_to_overlay(sheet, r, c, &v);
2117 }
2118 self.engine.mark_data_edited();
2119 Ok(())
2120 } else {
2121 for (ri, rvals) in rows.iter().enumerate() {
2122 let r = start_row + ri as u32;
2123 for (ci, v) in rvals.iter().enumerate() {
2124 let c = start_col + ci as u32;
2125 self.engine
2126 .set_cell_value(sheet, r, c, v.clone())
2127 .map_err(IoError::Engine)?;
2128 }
2129 }
2130 Ok(())
2131 }
2132 }
2133
2134 pub fn set_formulas(
2136 &mut self,
2137 sheet: &str,
2138 start_row: u32,
2139 start_col: u32,
2140 rows: &[Vec<String>],
2141 ) -> Result<(), IoError> {
2142 let height = rows.len();
2143 let width = rows.iter().map(|r| r.len()).max().unwrap_or(0);
2144 if height == 0 || width == 0 {
2145 return Ok(());
2146 }
2147 let end_row = start_row.saturating_add((height - 1) as u32);
2148 let end_col = start_col.saturating_add((width - 1) as u32);
2149 self.ensure_arrow_sheet_capacity(sheet, end_row as usize, end_col as usize);
2150
2151 if self.engine.config.defer_graph_building {
2152 for (ri, rforms) in rows.iter().enumerate() {
2153 let r = start_row + ri as u32;
2154 for (ci, f) in rforms.iter().enumerate() {
2155 let c = start_col + ci as u32;
2156 self.engine.stage_formula_text(sheet, r, c, f.clone());
2157 }
2158 }
2159 Ok(())
2160 } else if self.enable_changelog {
2161 let sheet_id = self
2162 .engine
2163 .sheet_id(sheet)
2164 .unwrap_or_else(|| self.engine.add_sheet(sheet).expect("add sheet"));
2165
2166 self.engine
2167 .edit_with_logger(&mut self.log, |editor| -> Result<(), IoError> {
2168 for (ri, rforms) in rows.iter().enumerate() {
2169 let r = start_row + ri as u32;
2170 for (ci, f) in rforms.iter().enumerate() {
2171 let c = start_col + ci as u32;
2172 let cell = formualizer_eval::reference::CellRef::new(
2173 sheet_id,
2174 formualizer_eval::reference::Coord::from_excel(r, c, true, true),
2175 );
2176 let with_eq = if f.starts_with('=') {
2177 f.clone()
2178 } else {
2179 format!("={f}")
2180 };
2181 let ast = formualizer_parse::parser::parse(&with_eq)
2182 .map_err(|e| IoError::from_backend("parser", e))?;
2183 editor.set_cell_formula(cell, ast);
2184 }
2185 }
2186 Ok(())
2187 })?;
2188
2189 self.engine.mark_data_edited();
2190 Ok(())
2191 } else {
2192 for (ri, rforms) in rows.iter().enumerate() {
2193 let r = start_row + ri as u32;
2194 for (ci, f) in rforms.iter().enumerate() {
2195 let c = start_col + ci as u32;
2196 let with_eq = if f.starts_with('=') {
2197 f.clone()
2198 } else {
2199 format!("={f}")
2200 };
2201 let ast = formualizer_parse::parser::parse(&with_eq)
2202 .map_err(|e| IoError::from_backend("parser", e))?;
2203 self.engine
2204 .set_cell_formula(sheet, r, c, ast)
2205 .map_err(IoError::Engine)?;
2206 }
2207 }
2208 Ok(())
2209 }
2210 }
2211
2212 pub fn prepare_graph_all(&mut self) -> Result<(), IoError> {
2214 self.engine
2215 .build_graph_all()
2216 .map_err(|e| IoError::from_backend("parser", e))
2217 }
2218 pub fn prepare_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
2219 &mut self,
2220 sheets: I,
2221 ) -> Result<(), IoError> {
2222 self.engine
2223 .build_graph_for_sheets(sheets)
2224 .map_err(|e| IoError::from_backend("parser", e))
2225 }
2226 pub fn evaluate_cell(
2227 &mut self,
2228 sheet: &str,
2229 row: u32,
2230 col: u32,
2231 ) -> Result<LiteralValue, IoError> {
2232 self.engine
2233 .evaluate_cell(sheet, row, col)
2234 .map_err(IoError::Engine)
2235 .map(|value| value.unwrap_or(LiteralValue::Empty))
2236 }
2237 pub fn evaluate_cells(
2238 &mut self,
2239 targets: &[(&str, u32, u32)],
2240 ) -> Result<Vec<LiteralValue>, IoError> {
2241 self.engine
2242 .evaluate_cells(targets)
2243 .map_err(IoError::Engine)
2244 .map(|values| {
2245 values
2246 .into_iter()
2247 .map(|v| v.unwrap_or(LiteralValue::Empty))
2248 .collect()
2249 })
2250 }
2251
2252 pub fn evaluate_cells_cancellable(
2253 &mut self,
2254 targets: &[(&str, u32, u32)],
2255 cancel_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
2256 ) -> Result<Vec<LiteralValue>, IoError> {
2257 self.engine
2258 .evaluate_cells_cancellable(targets, cancel_flag)
2259 .map_err(IoError::Engine)
2260 .map(|values| {
2261 values
2262 .into_iter()
2263 .map(|v| v.unwrap_or(LiteralValue::Empty))
2264 .collect()
2265 })
2266 }
2267 pub fn evaluate_all(&mut self) -> Result<formualizer_eval::engine::EvalResult, IoError> {
2268 self.engine.evaluate_all().map_err(IoError::Engine)
2269 }
2270
2271 pub fn evaluate_all_cancellable(
2272 &mut self,
2273 cancel_flag: std::sync::Arc<std::sync::atomic::AtomicBool>,
2274 ) -> Result<formualizer_eval::engine::EvalResult, IoError> {
2275 self.engine
2276 .evaluate_all_cancellable(cancel_flag)
2277 .map_err(IoError::Engine)
2278 }
2279
2280 pub fn evaluate_with_plan(
2281 &mut self,
2282 plan: &formualizer_eval::engine::RecalcPlan,
2283 ) -> Result<formualizer_eval::engine::EvalResult, IoError> {
2284 self.engine
2285 .evaluate_recalc_plan(plan)
2286 .map_err(IoError::Engine)
2287 }
2288
2289 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, IoError> {
2290 self.engine.get_eval_plan(targets).map_err(IoError::Engine)
2291 }
2292
2293 pub fn define_named_range(
2295 &mut self,
2296 name: &str,
2297 address: &RangeAddress,
2298 scope: crate::traits::NamedRangeScope,
2299 ) -> Result<(), IoError> {
2300 let (definition, scope) = self.named_definition_with_scope(address, scope)?;
2301 if self.enable_changelog {
2302 let result = self.engine.edit_with_logger(&mut self.log, |editor| {
2303 editor.define_name(name, definition, scope)
2304 });
2305 result.map_err(|e| IoError::from_backend("editor", e))
2306 } else {
2307 self.engine
2308 .define_name(name, definition, scope)
2309 .map_err(IoError::Engine)
2310 }
2311 }
2312
2313 pub fn update_named_range(
2314 &mut self,
2315 name: &str,
2316 address: &RangeAddress,
2317 scope: crate::traits::NamedRangeScope,
2318 ) -> Result<(), IoError> {
2319 let (definition, scope) = self.named_definition_with_scope(address, scope)?;
2320 if self.enable_changelog {
2321 let result = self.engine.edit_with_logger(&mut self.log, |editor| {
2322 editor.update_name(name, definition, scope)
2323 });
2324 result.map_err(|e| IoError::from_backend("editor", e))
2325 } else {
2326 self.engine
2327 .update_name(name, definition, scope)
2328 .map_err(IoError::Engine)
2329 }
2330 }
2331
2332 pub fn delete_named_range(
2333 &mut self,
2334 name: &str,
2335 scope: crate::traits::NamedRangeScope,
2336 sheet: Option<&str>,
2337 ) -> Result<(), IoError> {
2338 let scope = self.name_scope_from_hint(scope, sheet)?;
2339 if self.enable_changelog {
2340 let result = self
2341 .engine
2342 .edit_with_logger(&mut self.log, |editor| editor.delete_name(name, scope));
2343 result.map_err(|e| IoError::from_backend("editor", e))
2344 } else {
2345 self.engine
2346 .delete_name(name, scope)
2347 .map_err(IoError::Engine)
2348 }
2349 }
2350
2351 pub fn named_range_address(&self, name: &str) -> Option<RangeAddress> {
2353 if let Some((_, named)) = self
2354 .engine
2355 .named_ranges_iter()
2356 .find(|(n, _)| n.as_str() == name)
2357 {
2358 return self.named_definition_to_address(&named.definition);
2359 }
2360
2361 let mut resolved: Option<RangeAddress> = None;
2362 for ((_sheet_id, candidate), named) in self.engine.sheet_named_ranges_iter() {
2363 if candidate == name
2364 && let Some(address) = self.named_definition_to_address(&named.definition)
2365 {
2366 if resolved.is_some() {
2367 return None; }
2369 resolved = Some(address);
2370 }
2371 }
2372 resolved
2373 }
2374
2375 fn named_definition_with_scope(
2376 &mut self,
2377 address: &RangeAddress,
2378 scope: crate::traits::NamedRangeScope,
2379 ) -> Result<(NamedDefinition, NameScope), IoError> {
2380 let sheet_id = self.ensure_sheet_for_address(address)?;
2381 let scope = match scope {
2382 crate::traits::NamedRangeScope::Workbook => NameScope::Workbook,
2383 crate::traits::NamedRangeScope::Sheet => NameScope::Sheet(sheet_id),
2384 };
2385 let sr0 = address.start_row.saturating_sub(1);
2386 let sc0 = address.start_col.saturating_sub(1);
2387 let er0 = address.end_row.saturating_sub(1);
2388 let ec0 = address.end_col.saturating_sub(1);
2389 let start_ref = formualizer_eval::reference::CellRef::new(
2390 sheet_id,
2391 formualizer_eval::reference::Coord::new(sr0, sc0, true, true),
2392 );
2393 if sr0 == er0 && sc0 == ec0 {
2394 Ok((NamedDefinition::Cell(start_ref), scope))
2395 } else {
2396 let end_ref = formualizer_eval::reference::CellRef::new(
2397 sheet_id,
2398 formualizer_eval::reference::Coord::new(er0, ec0, true, true),
2399 );
2400 let range_ref = formualizer_eval::reference::RangeRef::new(start_ref, end_ref);
2401 Ok((NamedDefinition::Range(range_ref), scope))
2402 }
2403 }
2404
2405 fn name_scope_from_hint(
2406 &mut self,
2407 scope: crate::traits::NamedRangeScope,
2408 sheet: Option<&str>,
2409 ) -> Result<NameScope, IoError> {
2410 match scope {
2411 crate::traits::NamedRangeScope::Workbook => Ok(NameScope::Workbook),
2412 crate::traits::NamedRangeScope::Sheet => {
2413 let sheet = sheet.ok_or_else(|| IoError::Backend {
2414 backend: "workbook".to_string(),
2415 message: "Sheet scope requires a sheet name".to_string(),
2416 })?;
2417 let sheet_id = self
2418 .engine
2419 .sheet_id(sheet)
2420 .ok_or_else(|| IoError::Backend {
2421 backend: "workbook".to_string(),
2422 message: "Sheet not found".to_string(),
2423 })?;
2424 Ok(NameScope::Sheet(sheet_id))
2425 }
2426 }
2427 }
2428
2429 fn ensure_sheet_for_address(
2430 &mut self,
2431 address: &RangeAddress,
2432 ) -> Result<formualizer_eval::SheetId, IoError> {
2433 let sheet_id = self
2434 .engine
2435 .sheet_id(&address.sheet)
2436 .or_else(|| self.engine.add_sheet(&address.sheet).ok())
2437 .ok_or_else(|| IoError::Backend {
2438 backend: "workbook".to_string(),
2439 message: "Sheet not found".to_string(),
2440 })?;
2441 self.ensure_arrow_sheet_capacity(
2442 &address.sheet,
2443 address.end_row as usize,
2444 address.end_col as usize,
2445 );
2446 Ok(sheet_id)
2447 }
2448
2449 fn named_definition_to_address(&self, definition: &NamedDefinition) -> Option<RangeAddress> {
2450 match definition {
2451 NamedDefinition::Cell(cell) => {
2452 let sheet = self.engine.sheet_name(cell.sheet_id).to_string();
2453 let row = cell.coord.row() + 1;
2454 let col = cell.coord.col() + 1;
2455 RangeAddress::new(sheet, row, col, row, col).ok()
2456 }
2457 NamedDefinition::Range(range) => {
2458 if range.start.sheet_id != range.end.sheet_id {
2459 return None;
2460 }
2461 let sheet = self.engine.sheet_name(range.start.sheet_id).to_string();
2462 let start_row = range.start.coord.row() + 1;
2463 let start_col = range.start.coord.col() + 1;
2464 let end_row = range.end.coord.row() + 1;
2465 let end_col = range.end.coord.col() + 1;
2466 RangeAddress::new(sheet, start_row, start_col, end_row, end_col).ok()
2467 }
2468 NamedDefinition::Literal(_) => None,
2469 NamedDefinition::Formula { .. } => {
2470 #[cfg(feature = "tracing")]
2471 tracing::debug!("formula-backed named ranges are not yet supported");
2472 None
2473 }
2474 }
2475 }
2476
2477 pub fn begin_tx<'a, W: SpreadsheetWriter>(
2479 &'a mut self,
2480 writer: &'a mut W,
2481 ) -> crate::transaction::WriteTransaction<'a, W> {
2482 crate::transaction::WriteTransaction::new(writer)
2483 }
2484
2485 pub fn from_reader<B>(
2487 mut backend: B,
2488 _strategy: LoadStrategy,
2489 config: WorkbookConfig,
2490 ) -> Result<Self, IoError>
2491 where
2492 B: SpreadsheetReader + formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>,
2493 IoError: From<<B as formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>>::Error>,
2494 {
2495 let mut wb = Self::new_with_config(config);
2496 backend
2497 .stream_into_engine(&mut wb.engine)
2498 .map_err(IoError::from)?;
2499 Ok(wb)
2500 }
2501
2502 pub fn from_reader_with_config<B>(
2503 backend: B,
2504 strategy: LoadStrategy,
2505 config: WorkbookConfig,
2506 ) -> Result<Self, IoError>
2507 where
2508 B: SpreadsheetReader + formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>,
2509 IoError: From<<B as formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>>::Error>,
2510 {
2511 Self::from_reader(backend, strategy, config)
2512 }
2513
2514 pub fn from_reader_with_mode<B>(
2515 backend: B,
2516 strategy: LoadStrategy,
2517 mode: WorkbookMode,
2518 ) -> Result<Self, IoError>
2519 where
2520 B: SpreadsheetReader + formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>,
2521 IoError: From<<B as formualizer_eval::engine::ingest::EngineLoadStream<WBResolver>>::Error>,
2522 {
2523 let config = match mode {
2524 WorkbookMode::Ephemeral => WorkbookConfig::ephemeral(),
2525 WorkbookMode::Interactive => WorkbookConfig::interactive(),
2526 };
2527 Self::from_reader(backend, strategy, config)
2528 }
2529}
2530
2531impl SpreadsheetWriter for Workbook {
2533 type Error = IoError;
2534
2535 fn write_cell(
2536 &mut self,
2537 sheet: &str,
2538 row: u32,
2539 col: u32,
2540 data: crate::traits::CellData,
2541 ) -> Result<(), Self::Error> {
2542 if let Some(v) = data.value {
2543 self.set_value(sheet, row, col, v)?;
2544 }
2545 if let Some(f) = data.formula {
2546 self.set_formula(sheet, row, col, &f)?;
2547 }
2548 Ok(())
2549 }
2550 fn write_range(
2551 &mut self,
2552 sheet: &str,
2553 cells: BTreeMap<(u32, u32), crate::traits::CellData>,
2554 ) -> Result<(), Self::Error> {
2555 for ((r, c), d) in cells {
2556 self.write_cell(sheet, r, c, d)?;
2557 }
2558 Ok(())
2559 }
2560 fn clear_range(
2561 &mut self,
2562 sheet: &str,
2563 start: (u32, u32),
2564 end: (u32, u32),
2565 ) -> Result<(), Self::Error> {
2566 for r in start.0..=end.0 {
2567 for c in start.1..=end.1 {
2568 self.set_value(sheet, r, c, LiteralValue::Empty)?;
2569 }
2570 }
2571 Ok(())
2572 }
2573 fn create_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
2574 self.add_sheet(name).map_err(IoError::Engine)
2575 }
2576 fn delete_sheet(&mut self, name: &str) -> Result<(), Self::Error> {
2577 self.delete_sheet(name).map_err(IoError::Engine)
2578 }
2579 fn rename_sheet(&mut self, old: &str, new: &str) -> Result<(), Self::Error> {
2580 self.rename_sheet(old, new).map_err(IoError::Engine)
2581 }
2582 fn flush(&mut self) -> Result<(), Self::Error> {
2583 Ok(())
2584 }
2585}