1use crate::form::Form;
18use crate::printer::write_datum;
19use crate::refers::{self, FormulaRef, Kind};
20
21#[derive(Clone, Debug, PartialEq, Eq)]
23pub enum Value {
24 Number(String),
26 Text(String),
27 Bool(bool),
28 Error(String),
30 Date(String),
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Visibility {
38 Visible,
39 Hidden,
40 VeryHidden,
42}
43
44impl Visibility {
45 pub fn word(self) -> &'static str {
47 match self {
48 Visibility::Visible => "visible",
49 Visibility::Hidden => "hidden",
50 Visibility::VeryHidden => "very-hidden",
51 }
52 }
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
58pub enum RefersTo {
59 Reference(String),
61 Unreadable(&'static str),
63}
64
65impl RefersTo {
66 pub fn of(r: &FormulaRef, home_sheet: &str) -> RefersTo {
69 match r.kind {
70 Kind::Unreadable => RefersTo::Unreadable(r.unreadable_name()),
71 _ => RefersTo::Reference(refers::spell(r, home_sheet)),
72 }
73 }
74}
75
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum Row<'r> {
79 Sheet {
80 name: &'r str,
81 visibility: Visibility,
82 },
83 Name {
84 name: &'r str,
85 refers_to: &'r str,
86 },
87 Table {
88 name: &'r str,
89 sheet: &'r str,
90 range: &'r str,
91 },
92 Cell {
93 sheet: &'r str,
94 addr: &'r str,
95 value: &'r Value,
96 },
97 Formula {
98 sheet: &'r str,
99 addr: &'r str,
100 text: &'r str,
101 },
102 Refers {
105 sheet: &'r str,
106 addr: &'r str,
107 to: &'r RefersTo,
108 },
109}
110
111pub trait Sink {
113 fn row(&mut self, row: &Row<'_>);
114}
115
116pub fn sheet_prefix(name: &str) -> String {
120 refers::quote_sheet(name)
121}
122
123pub fn quoted(s: &str) -> String {
125 write_datum(&Form::string(s))
126}
127
128pub fn target(to: &RefersTo) -> String {
131 match to {
132 RefersTo::Reference(text) => quoted(text),
133 RefersTo::Unreadable(name) => format!("(unreadable {})", quoted(name)),
134 }
135}
136
137pub fn datum(v: &Value) -> String {
139 match v {
140 Value::Number(text) => text.clone(),
141 Value::Text(text) => quoted(text),
142 Value::Bool(true) => "true".to_string(),
143 Value::Bool(false) => "false".to_string(),
144 Value::Error(text) => format!("(error {})", quoted(text)),
145 Value::Date(text) => format!("(date {})", quoted(text)),
146 }
147}
148
149pub fn line(row: &Row<'_>) -> String {
151 match row {
152 Row::Sheet { name, visibility } => {
153 format!("(sheet {} {})", quoted(name), visibility.word())
154 }
155 Row::Name { name, refers_to } => {
156 format!("(name {} {})", quoted(name), quoted(refers_to))
157 }
158 Row::Table { name, sheet, range } => {
159 format!(
160 "(table {} {} {})",
161 quoted(name),
162 quoted(sheet),
163 quoted(range)
164 )
165 }
166 Row::Cell { sheet, addr, value } => {
167 format!("(cell {} {} {})", quoted(sheet), quoted(addr), datum(value))
168 }
169 Row::Formula { sheet, addr, text } => {
170 format!(
171 "(formula {} {} {})",
172 quoted(sheet),
173 quoted(addr),
174 quoted(text)
175 )
176 }
177 Row::Refers { sheet, addr, to } => {
178 format!(
179 "(refers {} {})",
180 quoted(&format!("{}!{}", sheet_prefix(sheet), addr)),
181 target(to)
182 )
183 }
184 }
185}
186
187#[derive(Default)]
189pub struct Printer {
190 pub out: String,
191}
192
193impl Sink for Printer {
194 fn row(&mut self, row: &Row<'_>) {
195 self.out.push_str(&line(row));
196 self.out.push('\n');
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn each_row_prints_in_the_corpus_notation() {
206 assert_eq!(
207 line(&Row::Sheet {
208 name: "Q1 Data",
209 visibility: Visibility::VeryHidden
210 }),
211 "(sheet \"Q1 Data\" very-hidden)"
212 );
213 assert_eq!(
214 line(&Row::Name {
215 name: "Rate",
216 refers_to: "Model!$B$1"
217 }),
218 "(name \"Rate\" \"Model!$B$1\")"
219 );
220 assert_eq!(
221 line(&Row::Table {
222 name: "Sales",
223 sheet: "Data",
224 range: "A1:B4"
225 }),
226 "(table \"Sales\" \"Data\" \"A1:B4\")"
227 );
228 assert_eq!(
229 line(&Row::Cell {
230 sheet: "Model",
231 addr: "B1",
232 value: &Value::Number("1200".to_string())
233 }),
234 "(cell \"Model\" \"B1\" 1200)"
235 );
236 assert_eq!(
237 line(&Row::Cell {
238 sheet: "Model",
239 addr: "A1",
240 value: &Value::Text("say \"hi\"".to_string())
241 }),
242 "(cell \"Model\" \"A1\" \"say \\\"hi\\\"\")"
243 );
244 assert_eq!(datum(&Value::Bool(true)), "true");
245 assert_eq!(datum(&Value::Bool(false)), "false");
246 assert_eq!(
247 datum(&Value::Error("#DIV/0!".to_string())),
248 "(error \"#DIV/0!\")"
249 );
250 assert_eq!(
251 datum(&Value::Date("2026-10-03".to_string())),
252 "(date \"2026-10-03\")"
253 );
254 assert_eq!(
255 line(&Row::Formula {
256 sheet: "Model",
257 addr: "B3",
258 text: "=B1-B2"
259 }),
260 "(formula \"Model\" \"B3\" \"=B1-B2\")"
261 );
262 assert_eq!(
263 line(&Row::Refers {
264 sheet: "Q1 Data",
265 addr: "B3",
266 to: &RefersTo::Reference("Model!B1".to_string())
267 }),
268 "(refers \"'Q1 Data'!B3\" \"Model!B1\")"
269 );
270 assert_eq!(
271 line(&Row::Refers {
272 sheet: "Model",
273 addr: "D2",
274 to: &RefersTo::Unreadable("INDIRECT")
275 }),
276 "(refers \"Model!D2\" (unreadable \"INDIRECT\"))"
277 );
278 assert!(
280 target(&RefersTo::Reference("Model!B1".to_string()))
281 < target(&RefersTo::Unreadable("OFFSET"))
282 );
283 let mut p = Printer::default();
284 p.row(&Row::Sheet {
285 name: "A",
286 visibility: Visibility::Visible,
287 });
288 p.row(&Row::Sheet {
289 name: "B",
290 visibility: Visibility::Hidden,
291 });
292 assert_eq!(p.out, "(sheet \"A\" visible)\n(sheet \"B\" hidden)\n");
293 }
294
295 #[test]
296 fn a_sheet_name_is_quoted_as_a_reference_quotes_it() {
297 assert_eq!(sheet_prefix("Model"), "Model");
298 assert_eq!(sheet_prefix("Sheet.1"), "Sheet.1");
299 assert_eq!(sheet_prefix("Q1 Data"), "'Q1 Data'");
300 assert_eq!(sheet_prefix("It's"), "'It''s'");
301 assert_eq!(sheet_prefix("2026"), "'2026'");
302 assert_eq!(sheet_prefix("Donn\u{e9}es"), "Donn\u{e9}es");
303 assert_eq!(sheet_prefix("a-b"), "'a-b'");
304 assert_eq!(sheet_prefix("A1"), "'A1'");
306 assert_eq!(sheet_prefix("R1C1"), "'R1C1'");
307 }
308
309 #[test]
310 fn a_target_is_a_reference_or_an_unreadable_call() {
311 let refs = refers::scan("=OFFSET(B1,1,0)+'Q1 Data'!A1");
312 assert_eq!(
313 RefersTo::of(&refs[0], "Model"),
314 RefersTo::Unreadable("OFFSET")
315 );
316 assert_eq!(
317 RefersTo::of(&refs[1], "Model"),
318 RefersTo::Reference("Model!B1".to_string())
319 );
320 assert_eq!(
321 RefersTo::of(&refs[2], "Model"),
322 RefersTo::Reference("'Q1 Data'!A1".to_string())
323 );
324 }
325}