1use crate::calc::formula::{LENGTH_LIMIT, NESTING_LIMIT};
81use crate::calc::value::ErrorKind;
82use crate::form::{Form, List};
83use crate::headtable::{self, FormulaSupport};
84use crate::intrinsics::{fold, is_numeric};
85use crate::messages::{raise, Refusal};
86use crate::printer::write_datum;
87use crate::reader::FORM_DEPTH_LIMIT;
88use crate::refers::{self, is_ident_char, quote_sheet, FormulaRef, Kind, Shape};
89use crate::sheet::column_letters;
90
91pub const TEXT_LIMIT: usize = 255;
94
95const CMP: u8 = 1;
97const CAT: u8 = 2;
98const ADD: u8 = 3;
99const MUL: u8 = 4;
100const POW: u8 = 5;
101const SIGN: u8 = 6;
102const PCT: u8 = 7;
103const ATOM: u8 = 8;
104
105const BINARY: &[&str] = &[
107 "=", "<>", "<", ">", "<=", ">=", "&", "+", "-", "*", "/", "^",
108];
109
110fn binary_level(op: &str) -> u8 {
111 match op {
112 "=" | "<>" | "<" | ">" | "<=" | ">=" => CMP,
113 "&" => CAT,
114 "+" | "-" => ADD,
115 "*" | "/" => MUL,
116 _ => POW,
117 }
118}
119
120pub fn write(expr: &Form) -> Result<String, Refusal> {
127 Ok(format!("={}", text(expr, 1)?))
128}
129
130fn level(f: &Form) -> u8 {
132 match f {
133 Form::Sym(s) if is_negative_numeral(s) => SIGN,
134 Form::List(l) => {
135 let Some(Form::Sym(head)) = l.items.first() else {
136 return ATOM;
137 };
138 let h = fold(head);
139 let n = l.items.len() - 1;
140 match h.as_str() {
141 "+" | "-" if n == 1 => SIGN,
142 "%" if n == 1 => PCT,
143 op if n >= 2 && BINARY.contains(&op) => binary_level(op),
144 _ => ATOM,
145 }
146 }
147 _ => ATOM,
148 }
149}
150
151fn operand(f: &Form, at: u8, right: bool, depth: u32) -> Result<String, Refusal> {
154 let t = text(f, depth + 1)?;
155 let l = level(f);
156 Ok(if l < at || (right && l == at) {
157 format!("({t})")
158 } else {
159 t
160 })
161}
162
163fn text(f: &Form, depth: u32) -> Result<String, Refusal> {
164 if depth > FORM_DEPTH_LIMIT {
165 return Err(raise(
166 "vla-form-too-deep",
167 &[("limit", &FORM_DEPTH_LIMIT.to_string()), ("loc", "")],
168 ));
169 }
170 match f {
171 Form::Str(s) => text_literal(s),
172 Form::Sym(s) => atom(s),
173 Form::List(l) => list(l, depth),
174 }
175}
176
177fn text_literal(s: &str) -> Result<String, Refusal> {
179 let n = s.encode_utf16().count();
180 if n > TEXT_LIMIT {
181 return Err(raise("vla-formula-text-too-long", &[("n", &n.to_string())]));
182 }
183 Ok(format!("\"{}\"", s.replace('"', "\"\"")))
184}
185
186fn atom(s: &str) -> Result<String, Refusal> {
189 if is_numeral(s) || is_negative_numeral(s) {
190 let unsigned = s.strip_prefix('-').unwrap_or(s);
191 if !unsigned.parse::<f64>().is_ok_and(f64::is_finite) {
192 return Err(raise("vla-formula-numeral-unspelled", &[("tok", s)]));
193 }
194 return Ok(s.to_string());
195 }
196 match fold(s).as_str() {
197 "true" => return Ok("TRUE".to_string()),
198 "false" => return Ok("FALSE".to_string()),
199 _ => {}
200 }
201 match whole_reference(s) {
202 Some(r) => match r.kind {
203 Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD | Kind::Name => {
204 Ok(reference_text(&r))
205 }
206 _ => Err(raise(
207 "vla-formula-unsupported",
208 &[("construct", &construct_of(&r))],
209 )),
210 },
211 None if is_numeric(s) => Err(raise("vla-formula-numeral-unspelled", &[("tok", s)])),
212 None => Err(raise("vla-formula-atom-unspelled", &[("tok", s)])),
213 }
214}
215
216fn whole_reference(s: &str) -> Option<FormulaRef> {
218 if s.is_empty() || s.starts_with('=') {
219 return None;
220 }
221 let mut found = refers::scan(s);
222 if found.len() != 1 {
223 return None;
224 }
225 let r = found.pop()?;
226 (r.pos == 0 && r.span == s.chars().count()).then_some(r)
227}
228
229fn construct_of(r: &FormulaRef) -> String {
231 let what = match r.kind {
232 Kind::Spill => "the spill reference",
233 Kind::Structured => "the structured reference",
234 Kind::External => "the reference into another workbook",
235 Kind::Broken => "the broken reference",
236 _ => "the reference",
237 };
238 format!("{what} {}", r.written)
239}
240
241fn reference_text(r: &FormulaRef) -> String {
245 let part = match (r.kind, r.shape) {
246 (Kind::Name, _) | (_, Shape::None) => r.part.clone(),
247 (_, Shape::Cell) => cell_text(r.col1, r.col_abs1, r.row1, r.row_abs1),
248 (_, Shape::Range) => format!(
249 "{}:{}",
250 cell_text(r.col1, r.col_abs1, r.row1, r.row_abs1),
251 cell_text(r.col2, r.col_abs2, r.row2, r.row_abs2)
252 ),
253 (_, Shape::Column) => format!(
254 "{}{}:{}{}",
255 mark(r.col_abs1),
256 column_letters(r.col1),
257 mark(r.col_abs2),
258 column_letters(r.col2)
259 ),
260 (_, Shape::Row) => format!(
261 "{}{}:{}{}",
262 mark(r.row_abs1),
263 r.row1,
264 mark(r.row_abs2),
265 r.row2
266 ),
267 };
268 if r.sheet_name.is_empty() {
269 part
270 } else {
271 format!("{}!{part}", sheet_text(&r.sheet_name))
272 }
273}
274
275fn cell_text(col: u32, col_abs: bool, row: u32, row_abs: bool) -> String {
276 format!(
277 "{}{}{}{}",
278 mark(col_abs),
279 column_letters(col),
280 mark(row_abs),
281 row
282 )
283}
284
285fn mark(abs: bool) -> &'static str {
286 if abs {
287 "$"
288 } else {
289 ""
290 }
291}
292
293fn sheet_text(sheet: &str) -> String {
296 match sheet.split_once(':') {
297 Some((first, last)) => {
298 if quote_sheet(first) == first && quote_sheet(last) == last {
299 format!("{first}:{last}")
300 } else {
301 format!(
302 "'{}:{}'",
303 first.replace('\'', "''"),
304 last.replace('\'', "''")
305 )
306 }
307 }
308 None => quote_sheet(sheet),
309 }
310}
311
312fn list(l: &List, depth: u32) -> Result<String, Refusal> {
314 let Some(head_form) = l.items.first() else {
315 return Err(raise("vla-formula-head-not-a-function", &[("head", "()")]));
316 };
317 let Form::Sym(head) = head_form else {
318 return Err(raise(
319 "vla-formula-head-not-a-function",
320 &[("head", &write_datum(head_form))],
321 ));
322 };
323 let h = headtable::resolve_head_alias(&fold(head));
324 let args = &l.items[1..];
325 let n = args.len();
326 match h.as_str() {
327 "+" | "-" if n == 1 => Ok(format!("{h}{}", operand(&args[0], SIGN, false, depth)?)),
328 "%" if n == 1 => Ok(format!("{}%", operand(&args[0], PCT, false, depth)?)),
329 "%" => Err(arity(head, "one operand", n)),
330 op if BINARY.contains(&op) => {
331 if n < 2 {
332 return Err(arity(
333 head,
334 if op == "+" || op == "-" {
335 "one operand or more"
336 } else {
337 "two operands or more"
338 },
339 n,
340 ));
341 }
342 let at = binary_level(op);
343 let mut out = operand(&args[0], at, false, depth)?;
344 for a in &args[1..] {
345 if op == "/" && out.ends_with("#N/A") {
350 out.truncate(out.len() - 4);
351 out.push_str("(#N/A)");
352 }
353 out.push_str(op);
354 out.push_str(&operand(a, at, true, depth)?);
355 }
356 Ok(out)
357 }
358 "error" => error_literal(head, args),
359 "ref" => ref_form(head, args),
360 "lambda" => lambda(head, args, depth),
361 "quote" => Err(raise(
362 "vla-formula-unsupported",
363 &[("construct", "an array constant, (quote ...)")],
364 )),
365 _ => {
366 if let Some(row) = headtable::row(&h) {
367 if row.formula != FormulaSupport::Yes {
368 return Err(raise("vla-formula-head-refused", &[("head", head)]));
369 }
370 }
371 let want = match h.as_str() {
372 "if" => (!(2..=3).contains(&n)).then_some("two or three arguments"),
373 "not" => (n != 1).then_some("one argument"),
374 "mod" => (n != 2).then_some("two arguments"),
375 _ => None,
376 };
377 if let Some(want) = want {
378 return Err(arity(head, want, n));
379 }
380 let name = if h == fold(head) {
381 head.as_str()
382 } else {
383 h.as_str()
384 };
385 if !is_function_name(name) {
386 return Err(raise("vla-formula-head-not-a-function", &[("head", head)]));
387 }
388 let mut parts = Vec::with_capacity(n);
389 for a in args {
390 parts.push(text(a, depth + 1)?);
391 }
392 Ok(format!(
393 "{}({})",
394 name.to_ascii_uppercase(),
395 parts.join(",")
396 ))
397 }
398 }
399}
400
401fn arity(head: &str, want: &str, n: usize) -> Refusal {
402 raise(
403 "vla-formula-arity",
404 &[("head", head), ("want", want), ("n", &n.to_string())],
405 )
406}
407
408fn error_literal(head: &str, args: &[Form]) -> Result<String, Refusal> {
410 let [Form::Str(t)] = args else {
411 return Err(arity(head, "one text, such as \"#N/A\"", args.len()));
412 };
413 match ErrorKind::parse(t) {
414 Some(k) => Ok(k.text().to_string()),
415 None => Err(raise(
416 "vla-formula-unsupported",
417 &[("construct", &format!("the error {t}"))],
418 )),
419 }
420}
421
422fn ref_form(head: &str, args: &[Form]) -> Result<String, Refusal> {
425 let built = match args {
426 [Form::Str(part)] => part.clone(),
427 [Form::Str(sheet), Form::Str(part)] => format!("{}!{part}", sheet_text(sheet)),
428 _ => {
429 return Err(arity(
430 head,
431 "a sheet and an address as texts, or an address alone",
432 args.len(),
433 ))
434 }
435 };
436 match whole_reference(&built) {
437 Some(r)
438 if matches!(
439 r.kind,
440 Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD | Kind::Name
441 ) =>
442 {
443 Ok(reference_text(&r))
444 }
445 _ => Err(raise("vla-formula-ref-invalid", &[("text", &built)])),
446 }
447}
448
449fn lambda(head: &str, args: &[Form], depth: u32) -> Result<String, Refusal> {
451 let [params, body] = args else {
452 return Err(arity(head, "a list of parameters and one body", args.len()));
453 };
454 let Form::List(params) = params else {
455 return Err(raise(
456 "vla-formula-lambda-params",
457 &[("tok", &write_datum(params))],
458 ));
459 };
460 let mut out = String::from("LAMBDA(");
461 for p in ¶ms.items {
462 let name = match p {
463 Form::Sym(s) => match whole_reference(s) {
464 Some(r) if r.kind == Kind::Name && r.sheet_name.is_empty() => s.clone(),
465 _ => return Err(raise("vla-formula-lambda-params", &[("tok", s)])),
466 },
467 other => {
468 return Err(raise(
469 "vla-formula-lambda-params",
470 &[("tok", &write_datum(other))],
471 ))
472 }
473 };
474 out.push_str(&name);
475 out.push(',');
476 }
477 out.push_str(&text(body, depth + 1)?);
478 out.push(')');
479 Ok(out)
480}
481
482pub fn is_numeral(s: &str) -> bool {
485 let b = s.as_bytes();
486 let mut i = 0;
487 let mut digits = 0;
488 while i < b.len() && b[i].is_ascii_digit() {
489 i += 1;
490 digits += 1;
491 }
492 if i < b.len() && b[i] == b'.' {
493 i += 1;
494 while i < b.len() && b[i].is_ascii_digit() {
495 i += 1;
496 digits += 1;
497 }
498 }
499 if digits == 0 {
500 return false;
501 }
502 if i < b.len() && (b[i] == b'e' || b[i] == b'E') {
503 i += 1;
504 if i < b.len() && (b[i] == b'+' || b[i] == b'-') {
505 i += 1;
506 }
507 let first = i;
508 while i < b.len() && b[i].is_ascii_digit() {
509 i += 1;
510 }
511 if i == first {
512 return false;
513 }
514 }
515 i == b.len()
516}
517
518fn is_negative_numeral(s: &str) -> bool {
519 s.strip_prefix('-').is_some_and(is_numeral)
520}
521
522fn is_function_name(s: &str) -> bool {
525 let mut cs = s.chars();
526 cs.next().is_some_and(|c| c.is_ascii_alphabetic())
527 && cs.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '_')
528}
529
530pub fn read(formula_text: &str) -> Result<Form, Refusal> {
537 let body = formula_text.strip_prefix('=').unwrap_or(formula_text);
538 if body.chars().count() > LENGTH_LIMIT {
539 return Err(malformed(
540 "it is longer than 8,192 characters, Excel's own limit",
541 ));
542 }
543 let toks = lex(formula_text)?;
544 if toks.is_empty() {
545 return Err(malformed("it is empty"));
546 }
547 let mut p = Reader {
548 toks,
549 at: 0,
550 depth: 0,
551 };
552 let e = p.comparison()?;
553 match p.peek() {
554 None => Ok(e),
555 Some(t) => Err(p.stray(t)),
556 }
557}
558
559fn malformed(what: &str) -> Refusal {
560 raise("vla-formula-read-malformed", &[("what", what)])
561}
562
563fn unsupported(construct: &str) -> Refusal {
564 raise("vla-formula-read-unsupported", &[("construct", construct)])
565}
566
567#[derive(Clone, Debug, PartialEq)]
568enum Tok {
569 Num(String),
571 Text(String),
572 Bool(bool),
573 Err(ErrorKind),
574 Ref(Box<FormulaRef>),
576 Func(String),
578 Op(&'static str),
579 Percent,
580 Open,
581 Close,
582 Comma,
583}
584
585fn lex(text: &str) -> Result<Vec<Tok>, Refusal> {
588 let s: Vec<char> = text.chars().collect();
589 let refs: Vec<FormulaRef> = refers::scan(text)
590 .into_iter()
591 .filter(|r| r.kind != Kind::Unreadable)
592 .collect();
593 let mut toks = Vec::new();
594 let mut i = usize::from(s.first() == Some(&'='));
595 let mut next_ref = 0;
596 while i < s.len() {
597 while next_ref < refs.len() && refs[next_ref].pos < i {
598 next_ref += 1;
599 }
600 if next_ref < refs.len() && refs[next_ref].pos == i {
601 let r = refs[next_ref].clone();
602 next_ref += 1;
603 i += r.span.max(1);
604 match r.kind {
605 Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD | Kind::Name => {
606 toks.push(Tok::Ref(Box::new(r)))
607 }
608 Kind::Broken if r.written == r.part && fold(&r.part) == "#ref!" => {
609 toks.push(Tok::Err(ErrorKind::Ref))
610 }
611 _ => return Err(unsupported(&construct_of(&r))),
612 }
613 continue;
614 }
615 let c = s[i];
616 if c.is_whitespace() {
617 i += 1;
618 } else if c == '"' {
619 let mut out = String::new();
620 let mut k = i + 1;
621 let mut closed = false;
622 while k < s.len() {
623 if s[k] == '"' {
624 if s.get(k + 1) == Some(&'"') {
625 out.push('"');
626 k += 2;
627 continue;
628 }
629 closed = true;
630 k += 1;
631 break;
632 }
633 out.push(s[k]);
634 k += 1;
635 }
636 if !closed {
637 return Err(malformed("a text that never closes"));
638 }
639 if out.encode_utf16().count() > TEXT_LIMIT {
640 return Err(malformed(
641 "a text longer than 255 characters, Excel's own limit",
642 ));
643 }
644 toks.push(Tok::Text(out));
645 i = k;
646 } else if c.is_ascii_digit()
647 || (c == '.' && s.get(i + 1).is_some_and(|d| d.is_ascii_digit()))
648 {
649 let mut k = i;
650 while k < s.len() && s[k].is_ascii_digit() {
651 k += 1;
652 }
653 if k < s.len() && s[k] == '.' {
654 k += 1;
655 while k < s.len() && s[k].is_ascii_digit() {
656 k += 1;
657 }
658 }
659 if k < s.len() && (s[k] == 'e' || s[k] == 'E') {
660 let mut j = k + 1;
661 if j < s.len() && (s[j] == '+' || s[j] == '-') {
662 j += 1;
663 }
664 let first = j;
665 while j < s.len() && s[j].is_ascii_digit() {
666 j += 1;
667 }
668 if j > first {
669 k = j;
670 }
671 }
672 let numeral: String = s[i..k].iter().collect();
673 if !numeral.parse::<f64>().is_ok_and(f64::is_finite) {
674 return Err(malformed(&format!(
675 "the number {numeral}, which no double holds"
676 )));
677 }
678 toks.push(Tok::Num(numeral));
679 i = k;
680 } else if c == '#' {
681 let mut k = i + 1;
682 while k < s.len() && (s[k].is_ascii_alphanumeric() || s[k] == '/' || s[k] == '_') {
683 k += 1;
684 }
685 if matches!(s.get(k), Some('!') | Some('?')) {
686 k += 1;
687 }
688 let tok: String = s[i..k].iter().collect();
689 match ErrorKind::parse(&tok) {
690 Some(kind) => toks.push(Tok::Err(kind)),
691 None if fold(&tok).starts_with("#n/a/") => return Err(unsupported(
692 "#N/A right before a /, which this version reads only parenthesized, (#N/A)",
693 )),
694 None => return Err(unsupported(&format!("the error {tok}"))),
695 }
696 i = k;
697 } else if c.is_ascii_alphabetic() || c == '_' || (c as u32) > 127 {
698 let mut k = i;
699 while k < s.len() && is_ident_char(s[k]) {
700 k += 1;
701 }
702 let tok: String = s[i..k].iter().collect();
703 if s.get(k) == Some(&'(') {
704 toks.push(Tok::Func(tok));
705 } else {
706 match fold(&tok).as_str() {
707 "true" => toks.push(Tok::Bool(true)),
708 "false" => toks.push(Tok::Bool(false)),
709 _ => return Err(malformed(&format!("'{tok}', which reads as no name"))),
710 }
711 }
712 i = k;
713 } else {
714 let two: String = s[i..(i + 2).min(s.len())].iter().collect();
715 let (tok, len) = match two.as_str() {
716 "<>" => (Tok::Op("<>"), 2),
717 "<=" => (Tok::Op("<="), 2),
718 ">=" => (Tok::Op(">="), 2),
719 _ => match c {
720 '<' => (Tok::Op("<"), 1),
721 '>' => (Tok::Op(">"), 1),
722 '=' => (Tok::Op("="), 1),
723 '+' => (Tok::Op("+"), 1),
724 '-' => (Tok::Op("-"), 1),
725 '*' => (Tok::Op("*"), 1),
726 '/' => (Tok::Op("/"), 1),
727 '^' => (Tok::Op("^"), 1),
728 '&' => (Tok::Op("&"), 1),
729 '%' => (Tok::Percent, 1),
730 '(' => (Tok::Open, 1),
731 ')' => (Tok::Close, 1),
732 ',' => (Tok::Comma, 1),
733 '{' => return Err(unsupported("an array constant, {...}")),
734 '@' => return Err(unsupported("the implicit intersection @")),
735 ':' => {
736 return Err(unsupported(
737 "the range operator : between computed operands",
738 ))
739 }
740 '[' => return Err(unsupported("a structured reference")),
741 other => {
742 return Err(malformed(&format!(
743 "'{other}', which no formula holds there"
744 )))
745 }
746 },
747 };
748 toks.push(tok);
749 i += len;
750 }
751 }
752 Ok(toks)
753}
754
755struct Reader {
756 toks: Vec<Tok>,
757 at: usize,
758 depth: u32,
759}
760
761impl Reader {
762 fn peek(&self) -> Option<&Tok> {
763 self.toks.get(self.at)
764 }
765
766 fn take(&mut self) -> Option<Tok> {
767 let t = self.toks.get(self.at).cloned();
768 if t.is_some() {
769 self.at += 1;
770 }
771 t
772 }
773
774 fn peek_op(&self) -> Option<&'static str> {
775 match self.peek() {
776 Some(Tok::Op(op)) => Some(op),
777 _ => None,
778 }
779 }
780
781 fn nested<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T, Refusal>) -> Result<T, Refusal> {
783 if self.depth >= NESTING_LIMIT {
784 return Err(malformed("it nests deeper than 64, Excel's own limit"));
785 }
786 self.depth += 1;
787 let r = f(self);
788 self.depth -= 1;
789 r
790 }
791
792 fn stray(&self, t: &Tok) -> Refusal {
794 match t {
795 Tok::Close => malformed("a ')' that opens nothing"),
796 Tok::Comma => malformed("a ',' outside a call"),
797 _ => malformed("two operands with no operator between them"),
798 }
799 }
800
801 fn binary(
804 &mut self,
805 ops: &[&'static str],
806 next: fn(&mut Self) -> Result<Form, Refusal>,
807 ) -> Result<Form, Refusal> {
808 let mut left = next(self)?;
809 while let Some(op) = self.peek_op() {
810 if !ops.contains(&op) {
811 break;
812 }
813 self.at += 1;
814 let right = next(self)?;
815 left = match left {
816 Form::List(mut l)
817 if l.items.len() >= 3
818 && matches!(l.items.first(), Some(Form::Sym(h)) if h == op) =>
819 {
820 l.items.push(right);
821 Form::List(l)
822 }
823 other => Form::list(vec![Form::sym(op), other, right]),
824 };
825 }
826 Ok(left)
827 }
828
829 fn comparison(&mut self) -> Result<Form, Refusal> {
830 self.binary(&["=", "<>", "<", ">", "<=", ">="], Self::concat)
831 }
832
833 fn concat(&mut self) -> Result<Form, Refusal> {
834 self.binary(&["&"], Self::additive)
835 }
836
837 fn additive(&mut self) -> Result<Form, Refusal> {
838 self.binary(&["+", "-"], Self::term)
839 }
840
841 fn term(&mut self) -> Result<Form, Refusal> {
842 self.binary(&["*", "/"], Self::power)
843 }
844
845 fn power(&mut self) -> Result<Form, Refusal> {
846 self.binary(&["^"], Self::unary)
847 }
848
849 fn unary(&mut self) -> Result<Form, Refusal> {
850 match self.peek_op() {
851 Some(op @ ("-" | "+")) => {
852 self.at += 1;
853 let x = self.nested(Self::unary)?;
854 Ok(match (op, x) {
855 ("-", Form::Sym(n)) if is_numeral(&n) => Form::Sym(format!("-{n}")),
856 (_, x) => Form::list(vec![Form::sym(op), x]),
857 })
858 }
859 _ => self.postfix(),
860 }
861 }
862
863 fn postfix(&mut self) -> Result<Form, Refusal> {
864 let mut e = self.primary()?;
865 let mut levels = 0;
866 while self.peek() == Some(&Tok::Percent) {
867 if self.depth + levels >= NESTING_LIMIT {
868 return Err(malformed("it nests deeper than 64, Excel's own limit"));
869 }
870 levels += 1;
871 self.at += 1;
872 e = Form::list(vec![Form::sym("%"), e]);
873 }
874 if let Some(t) = self.peek() {
875 if starts_operand(t) {
876 let both_refs = matches!(self.toks.get(self.at - 1), Some(Tok::Ref(_)))
877 && matches!(t, Tok::Ref(_));
878 return Err(if both_refs {
879 unsupported("the intersection of two references, a space between them")
880 } else {
881 malformed("two operands with no operator between them")
882 });
883 }
884 }
885 Ok(e)
886 }
887
888 fn primary(&mut self) -> Result<Form, Refusal> {
889 let Some(t) = self.take() else {
890 return Err(malformed("it ends where an operand was wanted"));
891 };
892 match t {
893 Tok::Num(n) => Ok(Form::Sym(n)),
894 Tok::Text(s) => Ok(Form::Str(s)),
895 Tok::Bool(b) => Ok(Form::sym(if b { "true" } else { "false" })),
896 Tok::Err(k) => Ok(Form::list(vec![Form::sym("error"), Form::string(k.text())])),
897 Tok::Ref(r) => Ok(reference_form(&r)),
898 Tok::Func(name) => {
899 self.at += 1; self.nested(|p| p.call(name))
901 }
902 Tok::Open => {
903 let e = self.nested(Self::comparison)?;
904 match self.take() {
905 Some(Tok::Close) => Ok(e),
906 Some(Tok::Comma) => Err(unsupported("the union of references, (A1,B2)")),
907 Some(other) => Err(self.stray(&other)),
908 None => Err(malformed("a '(' that never closes")),
909 }
910 }
911 Tok::Close => Err(malformed("a ')' where an operand was wanted")),
912 Tok::Comma => Err(malformed("a ',' where an operand was wanted")),
913 Tok::Op(op) => Err(malformed(&format!(
914 "the operator {op} where an operand was wanted"
915 ))),
916 Tok::Percent => Err(malformed("a '%' where an operand was wanted")),
917 }
918 }
919
920 fn call(&mut self, name: String) -> Result<Form, Refusal> {
922 let folded = fold(&name);
923 if folded.starts_with("_xl") {
924 return Err(unsupported(&format!("the file's spelling {name}")));
925 }
926 if !is_function_name(&name) {
927 return Err(unsupported(&format!("the function name {name}")));
928 }
929 let reserved = matches!(folded.as_str(), "error" | "ref" | "quote")
930 || headtable::row(&folded).is_some_and(|r| r.formula != FormulaSupport::Yes);
931 if reserved {
932 return Err(unsupported(&format!(
933 "a function named {name}, a word the syntax keeps for a form of its own"
934 )));
935 }
936 let mut args = Vec::new();
937 if self.peek() == Some(&Tok::Close) {
938 self.at += 1;
939 } else {
940 loop {
941 if matches!(self.peek(), Some(Tok::Comma) | Some(Tok::Close)) {
942 return Err(unsupported("an omitted argument"));
943 }
944 args.push(self.comparison()?);
945 match self.take() {
946 Some(Tok::Comma) => continue,
947 Some(Tok::Close) => break,
948 Some(other) => return Err(self.stray(&other)),
949 None => return Err(malformed(&format!("{name}( never closes"))),
950 }
951 }
952 }
953 if self.peek() == Some(&Tok::Open) {
954 return Err(unsupported(
955 "a function called in place, such as LAMBDA(x,x)(1)",
956 ));
957 }
958 let n = args.len();
959 let bad = match folded.as_str() {
960 "if" => (!(2..=3).contains(&n)).then_some("IF takes two or three arguments"),
961 "not" => (n != 1).then_some("NOT takes one argument"),
962 "mod" => (n != 2).then_some("MOD takes two arguments"),
963 "lambda" => (n == 0).then_some("LAMBDA takes at least its body"),
964 _ => None,
965 };
966 if let Some(bad) = bad {
967 return Err(malformed(bad));
968 }
969 if folded == "lambda" {
970 let body = args.pop().expect("LAMBDA's body, counted above");
971 let mut params = Vec::with_capacity(args.len());
972 for a in args {
973 match &a {
974 Form::Sym(s)
975 if whole_reference(s)
976 .is_some_and(|r| r.kind == Kind::Name && r.sheet_name.is_empty()) =>
977 {
978 params.push(a)
979 }
980 _ => return Err(malformed("a LAMBDA parameter that is no name")),
981 }
982 }
983 return Ok(Form::list(vec![
984 Form::sym("lambda"),
985 Form::list(params),
986 body,
987 ]));
988 }
989 let mut items = Vec::with_capacity(n + 1);
990 items.push(Form::Sym(folded));
991 items.extend(args);
992 Ok(Form::list(items))
993 }
994}
995
996fn starts_operand(t: &Tok) -> bool {
997 matches!(
998 t,
999 Tok::Num(_)
1000 | Tok::Text(_)
1001 | Tok::Bool(_)
1002 | Tok::Err(_)
1003 | Tok::Ref(_)
1004 | Tok::Func(_)
1005 | Tok::Open
1006 )
1007}
1008
1009fn reference_form(r: &FormulaRef) -> Form {
1013 let canonical = reference_text(r);
1014 if canonical
1015 .chars()
1016 .any(|c| matches!(c, ' ' | '\t' | '\r' | '\n' | '(' | ')' | ';' | '"'))
1017 {
1018 let part = match canonical.rfind('!') {
1019 Some(at) => canonical[at + 1..].to_string(),
1020 None => canonical.clone(),
1021 };
1022 Form::list(vec![
1023 Form::sym("ref"),
1024 Form::string(&r.sheet_name),
1025 Form::string(&part),
1026 ])
1027 } else {
1028 Form::Sym(canonical)
1029 }
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::*;
1035 use crate::reader::read_forms;
1036
1037 fn w(src: &str) -> String {
1038 write(&read_forms(src).unwrap()[0]).unwrap_or_else(|e| panic!("{src}: {}", e.text))
1039 }
1040
1041 fn w_err(src: &str) -> String {
1042 write(&read_forms(src).unwrap()[0]).unwrap_err().id
1043 }
1044
1045 fn r(text: &str) -> String {
1046 write_datum(&read(text).unwrap_or_else(|e| panic!("{text}: {}", e.text)))
1047 }
1048
1049 fn r_err(text: &str) -> String {
1050 read(text).unwrap_err().id
1051 }
1052
1053 #[test]
1054 fn the_atoms_are_written_in_their_canonical_spelling() {
1055 assert_eq!(w("5"), "=5");
1056 assert_eq!(w("1.50"), "=1.50");
1057 assert_eq!(w(".5"), "=.5");
1058 assert_eq!(w("1e3"), "=1e3");
1059 assert_eq!(w("-1"), "=-1");
1060 assert_eq!(w("\"say \\\"hi\\\"\""), "=\"say \"\"hi\"\"\"");
1061 assert_eq!(w("\"\""), "=\"\"");
1062 assert_eq!(w("True"), "=TRUE");
1063 assert_eq!(w("(error \"#div/0!\")"), "=#DIV/0!");
1064 assert_eq!(w("a1"), "=A1");
1065 assert_eq!(w("$a$1:B$2"), "=$A$1:B$2");
1066 assert_eq!(w("e:E"), "=E:E");
1067 assert_eq!(w("5:$7"), "=5:$7");
1068 assert_eq!(w("'Input'!b5"), "=Input!B5");
1069 assert_eq!(w("Tally.last!B16"), "=Tally.last!B16");
1070 assert_eq!(w("'2026'!A1"), "='2026'!A1");
1071 assert_eq!(w("Jan:Dec!A1"), "=Jan:Dec!A1");
1072 assert_eq!(w("Rate"), "=Rate");
1073 assert_eq!(w("Model!Local"), "=Model!Local");
1074 assert_eq!(w("(ref \"Q1 Data\" \"a1\")"), "='Q1 Data'!A1");
1075 assert_eq!(w("(ref \"Input\" \"B5\")"), "=Input!B5");
1076 assert_eq!(w("(ref \"C3\")"), "=C3");
1077 }
1078
1079 #[test]
1080 fn precedence_is_written_with_the_fewest_parentheses() {
1081 assert_eq!(w("(+ 1 (* 2 3))"), "=1+2*3");
1082 assert_eq!(w("(* (+ 1 2) 3)"), "=(1+2)*3");
1083 assert_eq!(w("(- a (- b c))"), "=a-(b-c)");
1084 assert_eq!(w("(- a b c)"), "=a-b-c");
1085 assert_eq!(w("(- (- a b) c)"), "=a-b-c");
1086 assert_eq!(w("(^ 2 (^ 3 2))"), "=2^(3^2)");
1087 assert_eq!(w("(^ 2 3 2)"), "=2^3^2");
1088 assert_eq!(w("(- (^ x 2))"), "=-(x^2)");
1089 assert_eq!(w("(^ (- x) 2)"), "=-x^2");
1090 assert_eq!(w("(^ -2 2)"), "=-2^2");
1091 assert_eq!(w("(% -5)"), "=(-5)%");
1092 assert_eq!(w("(- (% 5))"), "=-5%");
1093 assert_eq!(w("(% (+ a b))"), "=(a+b)%");
1094 assert_eq!(w("(+ x)"), "=+x");
1095 assert_eq!(w("(= (& 1 2) \"12\")"), "=1&2=\"12\"");
1096 assert_eq!(w("(& (= a b) c)"), "=(a=b)&c");
1097 assert_eq!(w("(< a b c)"), "=a<b<c");
1098 assert_eq!(w("(* a -1)"), "=a*-1");
1099 assert_eq!(w("(- 2 -1)"), "=2--1");
1100 assert_eq!(w("(- (- x))"), "=--x");
1101 assert_eq!(w("(/ (error \"#N/A\") x)"), "=(#N/A)/x");
1104 assert_eq!(w("(/ (^ 2 (error \"#N/A\")) x)"), "=2^(#N/A)/x");
1105 assert_eq!(w("(/ x (error \"#N/A\") y)"), "=x/(#N/A)/y");
1106 assert_eq!(w("(/ x (error \"#N/A\"))"), "=x/#N/A");
1107 assert_eq!(w("(/ (error \"#NUM!\") x)"), "=#NUM!/x");
1108 }
1109
1110 #[test]
1111 fn calls_and_the_syntax_s_own_forms() {
1112 assert_eq!(
1113 w("(if (> A1 3) (stdev.s A1:A9) (pi))"),
1114 "=IF(A1>3,STDEV.S(A1:A9),PI())"
1115 );
1116 assert_eq!(w("(SUM a)"), "=SUM(a)");
1117 assert_eq!(w("(si (> a 1) 1 0)"), "=IF(a>1,1,0)");
1118 assert_eq!(w("(log10 LOG10)"), "=LOG10(LOG10)");
1119 assert_eq!(
1120 w("(let x 1 (lambda (y) (+ x y)))"),
1121 "=LET(x,1,LAMBDA(y,x+y))"
1122 );
1123 assert_eq!(w("(lambda () 1)"), "=LAMBDA(1)");
1124 assert_eq!(
1125 w("(and true (not b) (or c d))"),
1126 "=AND(TRUE,NOT(b),OR(c,d))"
1127 );
1128 assert_eq!(w("(mod n 2)"), "=MOD(n,2)");
1129 assert_eq!(w("(xor (type A1) 1)"), "=XOR(TYPE(A1),1)");
1130 assert_eq!(w("(row)"), "=ROW()");
1131 assert_eq!(w("\"=A1+1\""), "=\"=A1+1\"");
1132 }
1133
1134 #[test]
1135 fn the_writer_refuses_what_it_cannot_spell_by_name() {
1136 assert_eq!(w_err("tax-rate"), "vla-formula-atom-unspelled");
1137 assert_eq!(w_err("A1+B1"), "vla-formula-atom-unspelled");
1138 assert_eq!(w_err("#N/A"), "vla-formula-atom-unspelled");
1139 assert_eq!(w_err("(f :key 1)"), "vla-formula-atom-unspelled");
1140 assert_eq!(w_err("&HFF"), "vla-formula-numeral-unspelled");
1141 assert_eq!(w_err("1d5"), "vla-formula-numeral-unspelled");
1142 assert_eq!(w_err("+5"), "vla-formula-numeral-unspelled");
1143 assert_eq!(w_err("1e999"), "vla-formula-numeral-unspelled");
1144 assert_eq!(
1145 w_err("((lambda (x) x) 1)"),
1146 "vla-formula-head-not-a-function"
1147 );
1148 assert_eq!(w_err("(\"sum\" 1)"), "vla-formula-head-not-a-function");
1149 assert_eq!(w_err("(a-b 1)"), "vla-formula-head-not-a-function");
1150 assert_eq!(
1151 w_err("(_xlfn.xlookup 1)"),
1152 "vla-formula-head-not-a-function"
1153 );
1154 assert_eq!(w_err("(set! x 1)"), "vla-formula-head-refused");
1155 assert_eq!(w_err("(fijar! x 1)"), "vla-formula-head-refused");
1156 assert_eq!(w_err("(new x)"), "vla-formula-head-refused");
1157 assert_eq!(w_err("(if (> x 1) (then 2))"), "vla-formula-head-refused");
1158 assert_eq!(w_err("(+)"), "vla-formula-arity");
1159 assert_eq!(w_err("(* a)"), "vla-formula-arity");
1160 assert_eq!(w_err("(% a b)"), "vla-formula-arity");
1161 assert_eq!(w_err("(not a b)"), "vla-formula-arity");
1162 assert_eq!(w_err("(if c)"), "vla-formula-arity");
1163 assert_eq!(w_err("(mod a)"), "vla-formula-arity");
1164 assert_eq!(w_err("(lambda x)"), "vla-formula-arity");
1165 assert_eq!(w_err("(quote (1 2))"), "vla-formula-unsupported");
1166 assert_eq!(w_err("(error \"#SPILL!\")"), "vla-formula-unsupported");
1167 assert_eq!(w_err("A1#"), "vla-formula-unsupported");
1168 assert_eq!(w_err("(lambda ((x)) x)"), "vla-formula-lambda-params");
1169 assert_eq!(w_err("(lambda (A1) A1)"), "vla-formula-lambda-params");
1170 assert_eq!(w_err("(ref \"Data\" \"A1+1\")"), "vla-formula-ref-invalid");
1171 let long = format!("\"{}\"", "a".repeat(256));
1172 assert_eq!(w_err(&long), "vla-formula-text-too-long");
1173 assert_eq!(w(&format!("\"{}\"", "a".repeat(255))).len(), 258);
1174 }
1175
1176 #[test]
1177 fn the_reader_gives_the_canonical_expression() {
1178 assert_eq!(r("=5"), "5");
1179 assert_eq!(r("=1.50"), "1.50");
1180 assert_eq!(r("=-1"), "-1");
1181 assert_eq!(r("=--1"), "(- -1)");
1182 assert_eq!(r("=-(1)"), "-1");
1183 assert_eq!(r("=-5%"), "(- (% 5))");
1184 assert_eq!(r("=(-5)%"), "(% -5)");
1185 assert_eq!(r("=\"say \"\"hi\"\"\""), "\"say \\\"hi\\\"\"");
1186 assert_eq!(r("=true"), "true");
1187 assert_eq!(r("=#n/a"), "(error \"#N/A\")");
1188 assert_eq!(r("=a1"), "A1");
1189 assert_eq!(r("='Input'!B5"), "Input!B5");
1190 assert_eq!(r("='Q1 Data'!A1"), "(ref \"Q1 Data\" \"A1\")");
1191 assert_eq!(r("= A1 + 1"), "(+ A1 1)");
1192 assert_eq!(r("=1+2+3"), "(+ 1 2 3)");
1193 assert_eq!(r("=1+(2+3)"), "(+ 1 (+ 2 3))");
1194 assert_eq!(r("=(1+2)+3"), "(+ 1 2 3)");
1195 assert_eq!(r("=-a-b"), "(- (- a) b)");
1196 assert_eq!(r("=a<b<c"), "(< a b c)");
1197 assert_eq!(r("=sum(a1:b2)"), "(sum A1:B2)");
1198 assert_eq!(r("=LAMBDA(x,y,x+y)"), "(lambda (x y) (+ x y))");
1199 assert_eq!(r("=LET(x,1,x+1)"), "(let x 1 (+ x 1))");
1200 assert_eq!(r("=PI()"), "(pi)");
1201 assert_eq!(r("=#REF!"), "(error \"#REF!\")");
1202 }
1203
1204 #[test]
1205 fn the_reader_tells_malformed_from_unsupported() {
1206 for bad in [
1207 "=",
1208 "=1+",
1209 "=(1",
1210 "=)",
1211 "=\"abc",
1212 "=1 2",
1213 "=SUM(1,2",
1214 "=IF(1)",
1215 "=NOT(1,2)",
1216 ] {
1217 assert_eq!(r_err(bad), "vla-formula-read-malformed", "{bad}");
1218 }
1219 for valid in [
1220 "={1,2;3,4}",
1221 "=@A1:A3",
1222 "=A1 B1",
1223 "=SUM((A1,B2))",
1224 "=A1#",
1225 "=SUM(Sales[Amount])",
1226 "=[Rates.xlsx]Sheet1!A1",
1227 "=IF(A1,,B1)",
1228 "=_xlfn.XLOOKUP(1,A:A,B:B)",
1229 "=A1:INDEX(B:B,5)",
1230 "=#SPILL!",
1231 "=LAMBDA(x,x)(1)",
1232 "=Sheet1!#REF!",
1233 "=CALL(1)",
1234 "=REF(1)",
1235 ] {
1236 assert_eq!(r_err(valid), "vla-formula-read-unsupported", "{valid}");
1237 }
1238 let long = format!("=\"{}\"", "a".repeat(256));
1239 assert_eq!(r_err(&long), "vla-formula-read-malformed");
1240 }
1241
1242 #[test]
1247 fn every_head_of_the_table_is_declared_and_the_writer_holds_to_it() {
1248 for row in headtable::rows() {
1249 assert_ne!(
1250 row.formula,
1251 FormulaSupport::Undeclared,
1252 "{} is undeclared",
1253 row.symbol
1254 );
1255 let form = Form::list(vec![Form::sym(&row.symbol), Form::sym("1"), Form::sym("2")]);
1256 match (row.formula, write(&form)) {
1257 (FormulaSupport::Refused, Err(e)) => {
1258 assert_eq!(e.id, "vla-formula-head-refused", "{}", row.symbol)
1259 }
1260 (FormulaSupport::Refused, Ok(t)) => panic!("{} written as {t}", row.symbol),
1261 (_, Ok(_)) => {}
1262 (_, Err(e)) => assert!(
1263 matches!(
1264 e.id.as_str(),
1265 "vla-formula-arity"
1266 | "vla-formula-unsupported"
1267 | "vla-formula-lambda-params"
1268 ),
1269 "{}: {}",
1270 row.symbol,
1271 e.id
1272 ),
1273 }
1274 }
1275 }
1276
1277 #[test]
1278 fn writing_and_reading_are_inverse_on_the_canonical_forms() {
1279 for text in [
1280 "=IF(B11=1,Day.last!$B$3+$B$6,Hustle.last!B13)",
1281 "=Tally.last!B1+IF(AND(Input!B1=1,Input.last!B1=0),1,0)",
1282 "=INT(B15/60)&\":\"&IF(MOD(B15,60)=0,\"00\",\"30\")",
1283 "=(COLUMN()-Gate!$A2)^2<=Gate!$B2",
1284 "='Q1 Data'!A1+'2026'!B2",
1285 "=-(x^2)+-x^2",
1286 "=(-5)%+-5%",
1287 "=a-(b-c)",
1288 "=2^(3^2)",
1289 ] {
1290 let v = read(text).unwrap();
1291 assert_eq!(write(&v).unwrap(), text, "{text}");
1292 assert_eq!(read(&write(&v).unwrap()).unwrap(), v, "{text}");
1293 }
1294 }
1295}