ftd/p2/
interpreter.rs

1#[derive(Debug, Default)]
2pub struct InterpreterState {
3    pub id: String,
4    pub bag: ftd::Map<ftd::p2::Thing>,
5    pub document_stack: Vec<ParsedDocument>,
6    pub parsed_libs: ftd::Map<Vec<String>>,
7}
8
9impl InterpreterState {
10    fn new(id: String) -> InterpreterState {
11        InterpreterState {
12            id,
13            bag: ftd::p2::interpreter::default_bag(),
14            ..Default::default()
15        }
16    }
17
18    pub fn tdoc<'a>(
19        &'a self,
20        local_variables: &'a mut ftd::Map<ftd::p2::Thing>,
21        referenced_local_variables: &'a mut ftd::Map<String>,
22    ) -> ftd::p2::TDoc<'a> {
23        let l = self.document_stack.len() - 1;
24        ftd::p2::TDoc {
25            name: &self.document_stack[l].name,
26            aliases: &self.document_stack[l].doc_aliases,
27            bag: &self.bag,
28            local_variables,
29            referenced_local_variables,
30        }
31    }
32
33    fn library_in_the_bag(&self, name: &str) -> bool {
34        self.parsed_libs.contains_key(name)
35    }
36
37    fn add_library_to_bag(&mut self, name: &str) {
38        if !self.library_in_the_bag(name) {
39            self.parsed_libs.insert(name.to_string(), vec![]);
40        }
41    }
42
43    fn continue_(mut self) -> ftd::p1::Result<Interpreter> {
44        if self.document_stack.is_empty() {
45            panic!()
46        }
47
48        let l = self.document_stack.len() - 1; // Get the top of the stack
49
50        // Removing commented parts from the parsed document
51        self.document_stack[l].ignore_comments();
52        // beyond this point commented things will no longer exist in the parsed document
53
54        if self.document_stack[l].processing_imports {
55            // Check for all the imports
56            // break the loop only when there's no more `import` statement
57            loop {
58                let top = &mut self.document_stack[l];
59                let module = Self::process_imports(top, &self.bag)?;
60                if let Some(module) = module {
61                    if !self.library_in_the_bag(module.as_str()) {
62                        self.add_library_to_bag(module.as_str());
63                        return Ok(Interpreter::StuckOnImport {
64                            state: self,
65                            module,
66                        });
67                    }
68                    if let Some(foreign_var_prefix) = self.parsed_libs.get(module.as_str()) {
69                        self.document_stack[l]
70                            .foreign_variable_prefix
71                            .extend_from_slice(foreign_var_prefix.as_slice());
72                    }
73                } else {
74                    break;
75                }
76            }
77            self.document_stack[l].done_processing_imports();
78            self.document_stack[l].reorder(&self.bag)?;
79        }
80        let parsed_document = &mut self.document_stack[l];
81
82        while let Some(p1) = parsed_document.sections.last_mut() {
83            // first resolve the foreign_variables in the section before proceeding further
84            let doc = ftd::p2::TDoc {
85                name: &parsed_document.name,
86                aliases: &parsed_document.doc_aliases,
87                bag: &self.bag,
88                local_variables: &mut Default::default(),
89                referenced_local_variables: &mut Default::default(),
90            };
91
92            if let Some(variable) = Self::resolve_foreign_variable(
93                p1,
94                parsed_document.foreign_variable_prefix.as_slice(),
95                &doc,
96            )? {
97                return Ok(Interpreter::StuckOnForeignVariable {
98                    variable,
99                    state: self,
100                });
101            }
102
103            // Once the foreign_variables are resolved for the section, then pop and evaluate it.
104            // This ensures that a section is evaluated once only.
105            let p1 = parsed_document.sections.pop().unwrap();
106
107            // while this is a specific to entire document, we are still creating it in a loop
108            // because otherwise the self.interpret() call won't compile.
109
110            let var_data = ftd::variable::VariableData::get_name_kind(
111                &p1.name,
112                &doc,
113                p1.line_number,
114                &parsed_document.var_types,
115            );
116
117            let mut thing = vec![];
118
119            if p1.name.starts_with("record ") {
120                // declare a record
121                let d =
122                    ftd::p2::Record::from_p1(p1.name.as_str(), &p1.header, &doc, p1.line_number)?;
123                let name = doc.resolve_name(p1.line_number, &d.name.to_string())?;
124                if self.bag.contains_key(name.as_str()) {
125                    return ftd::p2::utils::e2(
126                        format!("{} is already declared", d.name),
127                        doc.name,
128                        p1.line_number,
129                    );
130                }
131                thing.push((name, ftd::p2::Thing::Record(d)));
132            } else if p1.name.starts_with("or-type ") {
133                // declare a record
134                let d = ftd::OrType::from_p1(&p1, &doc)?;
135                let name = doc.resolve_name(p1.line_number, &d.name.to_string())?;
136                if self.bag.contains_key(name.as_str()) {
137                    return ftd::p2::utils::e2(
138                        format!("{} is already declared", d.name),
139                        doc.name,
140                        p1.line_number,
141                    );
142                }
143                thing.push((name, ftd::p2::Thing::OrType(d)));
144            } else if p1.name.starts_with("map ") {
145                let d = ftd::Variable::map_from_p1(&p1, &doc)?;
146                let name = doc.resolve_name(p1.line_number, &d.name.to_string())?;
147                if self.bag.contains_key(name.as_str()) {
148                    return ftd::p2::utils::e2(
149                        format!("{} is already declared", d.name),
150                        doc.name,
151                        p1.line_number,
152                    );
153                }
154                thing.push((name, ftd::p2::Thing::Variable(d)));
155                // } else if_two_words(p1.name.as_str() {
156                //   TODO: <record-name> <variable-name>: foo can be used to create a variable/
157                //         Not sure if its a good idea tho.
158                // }
159            } else if p1.name == "container" {
160                parsed_document
161                    .instructions
162                    .push(ftd::Instruction::ChangeContainer {
163                        name: doc.resolve_name_with_instruction(
164                            p1.line_number,
165                            p1.caption(p1.line_number, doc.name)?.as_str(),
166                            &parsed_document.instructions,
167                        )?,
168                    });
169            } else if let Ok(ftd::variable::VariableData {
170                type_: ftd::variable::Type::Component,
171                ..
172            }) = var_data
173            {
174                // declare a function
175                let d = ftd::Component::from_p1(&p1, &doc)?;
176                let name = doc.resolve_name(p1.line_number, &d.full_name.to_string())?;
177                if self.bag.contains_key(name.as_str()) {
178                    return ftd::p2::utils::e2(
179                        format!("{} is already declared", d.full_name),
180                        doc.name,
181                        p1.line_number,
182                    );
183                }
184                thing.push((name, ftd::p2::Thing::Component(d)));
185                // processed_p1.push(p1.name.to_string());
186            } else if let Ok(ref var_data) = var_data {
187                let d = if p1
188                    .header
189                    .str(doc.name, p1.line_number, "$processor$")
190                    .is_ok()
191                {
192                    // processor case: 1
193                    return Ok(Interpreter::StuckOnProcessor {
194                        state: self,
195                        section: p1,
196                    });
197                } else if var_data.is_none() || var_data.is_optional() {
198                    // declare and instantiate a variable
199                    ftd::Variable::from_p1(&p1, &doc)?
200                } else {
201                    // declare and instantiate a list
202                    ftd::Variable::list_from_p1(&p1, &doc)?
203                };
204                let name = doc.resolve_name(p1.line_number, &d.name)?;
205                if self.bag.contains_key(name.as_str()) {
206                    return ftd::p2::utils::e2(
207                        format!("{} is already declared", d.name),
208                        doc.name,
209                        p1.line_number,
210                    );
211                }
212                thing.push((name, ftd::p2::Thing::Variable(d)));
213            } else if let ftd::p2::Thing::Variable(mut v) =
214                doc.get_thing(p1.line_number, p1.name.as_str())?
215            {
216                assert!(
217                    !(p1.header
218                        .str_optional(doc.name, p1.line_number, "if")?
219                        .is_some()
220                        && p1
221                            .header
222                            .str_optional(doc.name, p1.line_number, "$processor$")?
223                            .is_some())
224                );
225                let (doc_name, remaining) = ftd::p2::utils::get_doc_name_and_remaining(
226                    doc.resolve_name(p1.line_number, p1.name.as_str())?.as_str(),
227                )?;
228                if remaining.is_some()
229                    && p1
230                        .header
231                        .str_optional(doc.name, p1.line_number, "if")?
232                        .is_some()
233                {
234                    return ftd::p2::utils::e2(
235                        "Currently not supporting `if` for field value update.",
236                        doc.name,
237                        p1.line_number,
238                    );
239                }
240                if let Some(expr) = p1.header.str_optional(doc.name, p1.line_number, "if")? {
241                    let val = v.get_value(&p1, &doc)?;
242                    v.conditions.push((
243                        ftd::p2::Boolean::from_expression(
244                            expr,
245                            &doc,
246                            &Default::default(),
247                            (None, None),
248                            p1.line_number,
249                        )?,
250                        val,
251                    ));
252                } else if p1
253                    .header
254                    .str_optional(doc.name, p1.line_number, "$processor$")?
255                    .is_some()
256                {
257                    // processor case: 2
258                    return Ok(Interpreter::StuckOnProcessor {
259                        state: self,
260                        section: p1.to_owned(),
261                    });
262                    // let start = std::time::Instant::now();
263                    // let value = self.lib.process(p1, &doc)?;
264                    // *d_processor = d_processor.saturating_add(std::time::Instant::now() - start);
265                    // v.value = ftd::PropertyValue::Value { value };
266                } else {
267                    v.update_from_p1(&p1, &doc)?;
268                }
269                thing.push((
270                    doc.resolve_name(p1.line_number, doc_name.as_str())?,
271                    ftd::p2::Thing::Variable(doc.set_value(p1.line_number, p1.name.as_str(), v)?),
272                ));
273            } else {
274                // cloning because https://github.com/rust-lang/rust/issues/59159
275                match (doc.get_thing(p1.line_number, p1.name.as_str())?).clone() {
276                    ftd::p2::Thing::Variable(_) => {
277                        return ftd::p2::utils::e2(
278                            format!("variable should have prefix $, found: `{}`", p1.name),
279                            doc.name,
280                            p1.line_number,
281                        );
282                    }
283                    ftd::p2::Thing::Component(_) => {
284                        if p1
285                            .header
286                            .str_optional(doc.name, p1.line_number, "$processor$")?
287                            .is_some()
288                        {
289                            // processor case: 3
290                            return Ok(Interpreter::StuckOnProcessor {
291                                state: self,
292                                section: p1.to_owned(),
293                            });
294                        }
295                        if let Ok(loop_data) = p1.header.str(doc.name, p1.line_number, "$loop$") {
296                            let section_to_subsection = ftd::p1::SubSection {
297                                name: p1.name.to_string(),
298                                caption: p1.caption.to_owned(),
299                                header: p1.header.to_owned(),
300                                body: p1.body.to_owned(),
301                                is_commented: p1.is_commented,
302                                line_number: p1.line_number,
303                            };
304                            parsed_document.instructions.push(
305                                ftd::Instruction::RecursiveChildComponent {
306                                    child: ftd::component::recursive_child_component(
307                                        loop_data,
308                                        &section_to_subsection,
309                                        &doc,
310                                        &Default::default(),
311                                        None,
312                                    )?,
313                                },
314                            );
315                        } else {
316                            let parent = ftd::ChildComponent::from_p1(
317                                p1.line_number,
318                                p1.name.as_str(),
319                                &p1.header,
320                                &p1.caption,
321                                &p1.body,
322                                &doc,
323                                &Default::default(),
324                            )?;
325
326                            let mut children = vec![];
327
328                            for sub in p1.sub_sections.0.iter() {
329                                if let Ok(loop_data) =
330                                    sub.header.str(doc.name, p1.line_number, "$loop$")
331                                {
332                                    children.push(ftd::component::recursive_child_component(
333                                        loop_data,
334                                        sub,
335                                        &doc,
336                                        &parent.arguments,
337                                        None,
338                                    )?);
339                                } else {
340                                    let root_name = ftd::p2::utils::get_root_component_name(
341                                        &doc,
342                                        parent.root.as_str(),
343                                        sub.line_number,
344                                    )?;
345                                    let child = if root_name.eq("ftd#text") {
346                                        ftd::p2::utils::get_markup_child(
347                                            sub,
348                                            &doc,
349                                            &parent.arguments,
350                                        )?
351                                    } else {
352                                        ftd::ChildComponent::from_p1(
353                                            sub.line_number,
354                                            sub.name.as_str(),
355                                            &sub.header,
356                                            &sub.caption,
357                                            &sub.body,
358                                            &doc,
359                                            &parent.arguments,
360                                        )?
361                                    };
362                                    children.push(child);
363                                }
364                            }
365
366                            parsed_document
367                                .instructions
368                                .push(ftd::Instruction::Component { children, parent })
369                        }
370                    }
371                    ftd::p2::Thing::Record(mut r) => {
372                        r.add_instance(&p1, &doc)?;
373                        thing.push((
374                            doc.resolve_name(p1.line_number, &p1.name)?,
375                            ftd::p2::Thing::Record(r),
376                        ));
377                    }
378                    ftd::p2::Thing::OrType(_r) => {
379                        // do we allow initialization of a record by name? nopes
380                        return ftd::p2::utils::e2(
381                            format!("'{}' is an or-type", p1.name.as_str()),
382                            doc.name,
383                            p1.line_number,
384                        );
385                    }
386                    ftd::p2::Thing::OrTypeWithVariant { .. } => {
387                        // do we allow initialization of a record by name? nopes
388                        return ftd::p2::utils::e2(
389                            format!("'{}' is an or-type variant", p1.name.as_str(),),
390                            doc.name,
391                            p1.line_number,
392                        );
393                    }
394                };
395            }
396            self.bag.extend(thing);
397        }
398
399        if self.document_stack.len() > 1 {
400            return self.continue_after_pop();
401        }
402
403        let mut rt = ftd::RT::from(
404            &self.id,
405            self.document_stack[0].get_doc_aliases(),
406            self.bag,
407            self.document_stack[0].instructions.clone(),
408        );
409
410        let main = if cfg!(test) {
411            rt.render_()?
412        } else {
413            rt.render()?
414        };
415
416        let d = ftd::p2::document::Document {
417            main,
418            name: rt.name,
419            data: rt.bag.clone(),
420            aliases: rt.aliases,
421            instructions: rt.instructions,
422        };
423
424        Ok(Interpreter::Done { document: d })
425    }
426
427    fn resolve_foreign_variable_name(name: &str) -> String {
428        name.replace('.', "-")
429    }
430
431    fn resolve_foreign_variable(
432        section: &mut ftd::p1::Section,
433        foreign_variables: &[String],
434        doc: &ftd::p2::TDoc,
435    ) -> ftd::p1::Result<Option<String>> {
436        if let Some(variable) = resolve_all_properties(
437            &mut section.caption,
438            &mut section.header,
439            &mut section.body,
440            section.line_number,
441            foreign_variables,
442            doc,
443        )? {
444            return Ok(Some(variable));
445        }
446
447        for subsection in section.sub_sections.0.iter_mut() {
448            if let Some(variable) = resolve_all_properties(
449                &mut subsection.caption,
450                &mut subsection.header,
451                &mut subsection.body,
452                subsection.line_number,
453                foreign_variables,
454                doc,
455            )? {
456                return Ok(Some(variable));
457            }
458        }
459
460        return Ok(None);
461
462        fn resolve_all_properties(
463            caption: &mut Option<String>,
464            header: &mut ftd::p1::Header,
465            body: &mut Option<(usize, String)>,
466            line_number: usize,
467            foreign_variables: &[String],
468            doc: &ftd::p2::TDoc,
469        ) -> ftd::p1::Result<Option<String>> {
470            if let Some(ref mut caption) = caption {
471                if let Some(cap) =
472                    process_foreign_variables(caption, foreign_variables, doc, line_number)?
473                {
474                    return Ok(Some(cap));
475                }
476            }
477
478            for (line_number, _, header) in header.0.iter_mut() {
479                if let Some(h) =
480                    process_foreign_variables(header, foreign_variables, doc, *line_number)?
481                {
482                    return Ok(Some(h));
483                }
484            }
485
486            if let Some((line_number, ref mut body)) = body {
487                if let Some(b) =
488                    process_foreign_variables(body, foreign_variables, doc, *line_number)?
489                {
490                    return Ok(Some(b));
491                }
492            }
493
494            Ok(None)
495        }
496
497        fn process_foreign_variables(
498            value: &mut String,
499            foreign_variables: &[String],
500            doc: &ftd::p2::TDoc,
501            line_number: usize,
502        ) -> ftd::p1::Result<Option<String>> {
503            if value.contains('#') {
504                return Ok(None);
505            }
506            if let Some(val) = value.clone().strip_prefix('$') {
507                if is_foreign_variable(val, foreign_variables, doc, line_number)? {
508                    let val = doc.resolve_name(line_number, val)?;
509                    *value = ftd::InterpreterState::resolve_foreign_variable_name(
510                        format!("${}", val.as_str()).as_str(),
511                    );
512                    return Ok(Some(val));
513                }
514            }
515            Ok(None)
516        }
517
518        fn is_foreign_variable(
519            variable: &str,
520            foreign_variables: &[String],
521            doc: &ftd::p2::TDoc,
522            line_number: usize,
523        ) -> ftd::p1::Result<bool> {
524            let var_name = doc.resolve_name(line_number, variable)?;
525
526            if foreign_variables.iter().any(|v| var_name.starts_with(v)) {
527                return Ok(true);
528            }
529            Ok(false)
530        }
531    }
532
533    fn process_imports(
534        top: &mut ParsedDocument,
535        bag: &ftd::Map<ftd::p2::Thing>,
536    ) -> ftd::p1::Result<Option<String>> {
537        let mut iteration_index = 0;
538        while iteration_index < top.sections.len() {
539            if top.sections[iteration_index].name != "import" {
540                iteration_index += 1;
541                continue;
542            }
543            let (library_name, alias) = ftd::p2::utils::parse_import(
544                &top.sections[iteration_index].caption,
545                top.name.as_str(),
546                top.sections[iteration_index].line_number,
547            )?;
548
549            top.doc_aliases.insert(alias, library_name.clone());
550
551            if bag.contains_key(library_name.as_str()) {
552                iteration_index += 1;
553                continue;
554            }
555
556            top.sections.remove(iteration_index);
557            return Ok(Some(library_name));
558        }
559
560        Ok(None)
561    }
562
563    pub fn add_foreign_variable_prefix(&mut self, module: &str, prefix: Vec<String>) {
564        if let Some(document) = self.document_stack.last_mut() {
565            document
566                .foreign_variable_prefix
567                .extend_from_slice(prefix.as_slice());
568        }
569        self.parsed_libs.insert(module.to_string(), prefix);
570    }
571
572    pub fn continue_after_import(mut self, id: &str, source: &str) -> ftd::p1::Result<Interpreter> {
573        self.document_stack.push(ParsedDocument::parse(id, source)?);
574        self.continue_()
575        // interpret then
576        // handle top / start_from
577    }
578
579    pub fn continue_after_variable(
580        mut self,
581        variable: &str,
582        value: ftd::Value,
583    ) -> ftd::p1::Result<Interpreter> {
584        let l = self.document_stack.len() - 1;
585        let doc = ftd::p2::TDoc {
586            name: &self.document_stack[l].name,
587            aliases: &self.document_stack[l].doc_aliases,
588            bag: &self.bag,
589            local_variables: &mut Default::default(),
590            referenced_local_variables: &mut Default::default(),
591        };
592        let var_name = ftd::InterpreterState::resolve_foreign_variable_name(
593            doc.resolve_name(0, variable)?.as_str(),
594        );
595        self.bag.insert(
596            var_name.clone(),
597            ftd::p2::Thing::Variable(ftd::Variable {
598                name: var_name,
599                value: ftd::PropertyValue::Value { value },
600                conditions: vec![],
601                flags: Default::default(),
602            }),
603        );
604        self.continue_()
605    }
606
607    pub fn continue_after_pop(mut self) -> ftd::p1::Result<Interpreter> {
608        self.document_stack.pop();
609        self.continue_()
610        // interpret then
611        // handle top / start_from
612    }
613
614    pub fn continue_after_processor(
615        mut self,
616        p1: &ftd::p1::Section,
617        value: ftd::Value,
618    ) -> ftd::p1::Result<Interpreter> {
619        let l = self.document_stack.len() - 1;
620        let parsed_document = &mut self.document_stack[l];
621
622        let doc = ftd::p2::TDoc {
623            name: &parsed_document.name,
624            aliases: &parsed_document.doc_aliases,
625            bag: &self.bag,
626            local_variables: &mut Default::default(),
627            referenced_local_variables: &mut Default::default(),
628        };
629
630        let var_data = ftd::variable::VariableData::get_name_kind(
631            &p1.name,
632            &doc,
633            p1.line_number,
634            &parsed_document.var_types,
635        );
636
637        if let Ok(ftd::variable::VariableData {
638            type_: ftd::variable::Type::Variable,
639            name,
640            ..
641        }) = var_data
642        {
643            let name = doc.resolve_name(p1.line_number, &name)?;
644            let variable = ftd::p2::Thing::Variable(ftd::Variable {
645                name: name.clone(),
646                value: ftd::PropertyValue::Value { value },
647                conditions: vec![],
648                flags: ftd::variable::VariableFlags::from_p1(&p1.header, doc.name, p1.line_number)?,
649            });
650            self.bag.insert(name, variable);
651            return self.continue_();
652        }
653
654        match doc.get_thing(p1.line_number, p1.name.as_str())? {
655            ftd::p2::Thing::Variable(mut v) => {
656                // for case: 2
657                let doc_name = ftd::p2::utils::get_doc_name_and_remaining(
658                    doc.resolve_name(p1.line_number, p1.name.as_str())?.as_str(),
659                )?
660                .0;
661                v.value = ftd::PropertyValue::Value { value };
662                let key = doc.resolve_name(p1.line_number, doc_name.as_str())?;
663                let variable =
664                    ftd::p2::Thing::Variable(doc.set_value(p1.line_number, p1.name.as_str(), v)?);
665                self.bag.insert(key, variable);
666            }
667            ftd::p2::Thing::Component(_) => {
668                // for case: 3
669                let mut p1 = p1.clone();
670                Self::p1_from_processor(&mut p1, value);
671                parsed_document.sections.push(p1.to_owned());
672            }
673            _ => todo!(), // throw error
674        }
675        self.continue_()
676        // interpret then
677        // handle top / start_from
678    }
679
680    pub(crate) fn p1_from_processor(p1: &mut ftd::p1::Section, value: ftd::Value) {
681        for (idx, (_, k, _)) in p1.header.0.iter().enumerate() {
682            if k.eq("$processor$") {
683                p1.header.0.remove(idx);
684                break;
685            }
686        }
687        if let ftd::Value::Object { values } = value {
688            for (k, v) in values {
689                let v = if let ftd::PropertyValue::Value { value } = v {
690                    if let Some(v) = value.to_string() {
691                        v
692                    } else {
693                        continue;
694                    }
695                } else {
696                    continue;
697                };
698
699                if k.eq("$body$") {
700                    p1.body = Some((p1.line_number, v));
701                } else if k.eq("$caption$") {
702                    p1.caption = Some(v);
703                } else {
704                    p1.header.0.push((p1.line_number, k, v));
705                }
706            }
707        }
708    }
709}
710
711#[derive(Debug, Clone)]
712pub struct ParsedDocument {
713    name: String,
714    sections: Vec<ftd::p1::Section>,
715    processing_imports: bool,
716    doc_aliases: ftd::Map<String>,
717    var_types: Vec<String>,
718    foreign_variable_prefix: Vec<String>,
719    instructions: Vec<ftd::Instruction>,
720}
721
722impl ParsedDocument {
723    fn parse(id: &str, source: &str) -> ftd::p1::Result<ParsedDocument> {
724        Ok(ParsedDocument {
725            name: id.to_string(),
726            sections: ftd::p1::parse(source, id)?,
727            processing_imports: true,
728            doc_aliases: ftd::p2::interpreter::default_aliases(),
729            var_types: Default::default(),
730            foreign_variable_prefix: vec![],
731            instructions: vec![],
732        })
733    }
734
735    fn done_processing_imports(&mut self) {
736        self.processing_imports = false;
737    }
738
739    /// Filters out commented parts from the parsed document.
740    ///
741    /// # Comments are ignored for
742    /// 1.  /-- section: caption
743    ///
744    /// 2.  /section-header: value
745    ///
746    /// 3.  /body
747    ///
748    /// 4.  /--- subsection: caption
749    ///
750    /// 5.  /sub-section-header: value
751    ///
752    /// ## Note: To allow ["/content"] inside body, use ["\\/content"].
753    ///
754    /// Only '/' comments are ignored here.
755    /// ';' comments are ignored inside the [`parser`] itself.
756    ///
757    /// uses [`Section::remove_comments()`] and [`Subsection::remove_comments()`] to remove comments
758    /// in sections and subsections accordingly.
759    ///
760    /// [`parser`]: ftd::p1::parser::parse
761    /// [`Section::remove_comments()`]: ftd::p1::section::Section::remove_comments
762    /// [`SubSection::remove_comments()`]: ftd::p1::sub_section::SubSection::remove_comments
763    fn ignore_comments(&mut self) {
764        self.sections = self
765            .sections
766            .iter()
767            .filter(|s| !s.is_commented)
768            .map(|s| s.remove_comments())
769            .collect::<Vec<ftd::p1::Section>>();
770    }
771
772    fn reorder(&mut self, bag: &ftd::Map<ftd::p2::Thing>) -> ftd::p1::Result<()> {
773        let (mut new_p1, var_types) = ftd::p2::utils::reorder(
774            &self.sections,
775            &ftd::p2::TDoc {
776                name: &self.name,
777                aliases: &self.doc_aliases,
778                bag,
779                local_variables: &mut Default::default(),
780                referenced_local_variables: &mut Default::default(),
781            },
782        )?;
783        new_p1.reverse();
784        self.sections = new_p1;
785        self.var_types = var_types;
786        Ok(())
787    }
788
789    pub fn get_doc_aliases(&self) -> ftd::Map<String> {
790        self.doc_aliases.clone()
791    }
792}
793
794#[allow(clippy::large_enum_variant)]
795#[derive(Debug)]
796pub enum Interpreter {
797    StuckOnImport {
798        module: String,
799        state: InterpreterState,
800    },
801    StuckOnProcessor {
802        state: InterpreterState,
803        section: ftd::p1::Section,
804    },
805    StuckOnForeignVariable {
806        variable: String,
807        state: InterpreterState,
808    },
809    Done {
810        document: ftd::p2::Document,
811    },
812}
813
814pub fn interpret(id: &str, source: &str) -> ftd::p1::Result<Interpreter> {
815    let mut s = InterpreterState::new(id.to_string());
816    s.document_stack.push(ParsedDocument::parse(id, source)?);
817    s.continue_()
818}
819
820#[allow(clippy::large_enum_variant)]
821#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
822#[serde(tag = "type")]
823pub enum Thing {
824    Component(ftd::Component),
825    Variable(ftd::Variable),
826    Record(ftd::p2::Record),
827    OrType(ftd::OrType),
828    OrTypeWithVariant { e: ftd::OrType, variant: String },
829    // Library -> Name of library successfully parsed
830}
831
832pub fn default_bag() -> ftd::Map<ftd::p2::Thing> {
833    let record = |n: &str, r: &str| (n.to_string(), ftd::p2::Kind::record(r));
834    let color = |n: &str| record(n, "ftd#color");
835    std::iter::IntoIterator::into_iter([
836        (
837            "ftd#row".to_string(),
838            ftd::p2::Thing::Component(ftd::p2::element::row_function()),
839        ),
840        (
841            "ftd#column".to_string(),
842            ftd::p2::Thing::Component(ftd::p2::element::column_function()),
843        ),
844        (
845            "ftd#text-block".to_string(),
846            ftd::p2::Thing::Component(ftd::p2::element::text_function()),
847        ),
848        (
849            "ftd#code".to_string(),
850            ftd::p2::Thing::Component(ftd::p2::element::code_function()),
851        ),
852        (
853            "ftd#image".to_string(),
854            ftd::p2::Thing::Component(ftd::p2::element::image_function()),
855        ),
856        (
857            "ftd#iframe".to_string(),
858            ftd::p2::Thing::Component(ftd::p2::element::iframe_function()),
859        ),
860        (
861            "ftd#integer".to_string(),
862            ftd::p2::Thing::Component(ftd::p2::element::integer_function()),
863        ),
864        (
865            "ftd#decimal".to_string(),
866            ftd::p2::Thing::Component(ftd::p2::element::decimal_function()),
867        ),
868        (
869            "ftd#boolean".to_string(),
870            ftd::p2::Thing::Component(ftd::p2::element::boolean_function()),
871        ),
872        (
873            "ftd#scene".to_string(),
874            ftd::p2::Thing::Component(ftd::p2::element::scene_function()),
875        ),
876        (
877            "ftd#grid".to_string(),
878            ftd::p2::Thing::Component(ftd::p2::element::grid_function()),
879        ),
880        (
881            "ftd#text".to_string(),
882            ftd::p2::Thing::Component(ftd::p2::element::markup_function()),
883        ),
884        (
885            "ftd#input".to_string(),
886            ftd::p2::Thing::Component(ftd::p2::element::input_function()),
887        ),
888        (
889            "ftd#null".to_string(),
890            ftd::p2::Thing::Component(ftd::p2::element::null()),
891        ),
892        (
893            "ftd#dark-mode".to_string(),
894            ftd::p2::Thing::Variable(ftd::Variable {
895                name: "ftd#dark-mode".to_string(),
896                value: ftd::PropertyValue::Value {
897                    value: ftd::Value::Boolean { value: false },
898                },
899                conditions: vec![],
900                flags: ftd::VariableFlags {
901                    always_include: Some(true),
902                },
903            }),
904        ),
905        (
906            "ftd#system-dark-mode".to_string(),
907            ftd::p2::Thing::Variable(ftd::Variable {
908                name: "ftd#system-dark-mode".to_string(),
909                value: ftd::PropertyValue::Value {
910                    value: ftd::Value::Boolean { value: false },
911                },
912                conditions: vec![],
913                flags: ftd::VariableFlags {
914                    always_include: Some(true),
915                },
916            }),
917        ),
918        (
919            "ftd#follow-system-dark-mode".to_string(),
920            ftd::p2::Thing::Variable(ftd::Variable {
921                name: "ftd#follow-system-dark-mode".to_string(),
922                value: ftd::PropertyValue::Value {
923                    value: ftd::Value::Boolean { value: true },
924                },
925                conditions: vec![],
926                flags: ftd::VariableFlags {
927                    always_include: Some(true),
928                },
929            }),
930        ),
931        (
932            "ftd#device".to_string(),
933            ftd::p2::Thing::Variable(ftd::Variable {
934                name: "ftd#device".to_string(),
935                value: ftd::PropertyValue::Value {
936                    value: ftd::Value::String {
937                        text: "desktop".to_string(),
938                        source: ftd::TextSource::Header,
939                    },
940                },
941                conditions: vec![],
942                flags: ftd::VariableFlags {
943                    always_include: Some(true),
944                },
945            }),
946        ),
947        (
948            "ftd#mobile-breakpoint".to_string(),
949            ftd::p2::Thing::Variable(ftd::Variable {
950                name: "ftd#mobile-breakpoint".to_string(),
951                value: ftd::PropertyValue::Value {
952                    value: ftd::Value::Integer { value: 768 },
953                },
954                conditions: vec![],
955                flags: ftd::VariableFlags {
956                    always_include: Some(true),
957                },
958            }),
959        ),
960        (
961            "ftd#desktop-breakpoint".to_string(),
962            ftd::p2::Thing::Variable(ftd::Variable {
963                name: "ftd#desktop-breakpoint".to_string(),
964                value: ftd::PropertyValue::Value {
965                    value: ftd::Value::Integer { value: 1440 },
966                },
967                conditions: vec![],
968                flags: ftd::VariableFlags {
969                    always_include: Some(true),
970                },
971            }),
972        ),
973        (
974            "ftd#markdown-color-data".to_string(),
975            ftd::p2::Thing::Record(ftd::p2::Record {
976                name: "ftd#markdown-color-data".to_string(),
977                fields: std::iter::IntoIterator::into_iter([
978                    ("link".to_string(), ftd::p2::Kind::record("ftd#color")),
979                    ("code".to_string(), ftd::p2::Kind::record("ftd#color")),
980                    ("link-code".to_string(), ftd::p2::Kind::record("ftd#color")),
981                    (
982                        "link-visited".to_string(),
983                        ftd::p2::Kind::record("ftd#color"),
984                    ),
985                    (
986                        "link-visited-code".to_string(),
987                        ftd::p2::Kind::record("ftd#color"),
988                    ),
989                    (
990                        "ul-ol-li-before".to_string(),
991                        ftd::p2::Kind::record("ftd#color"),
992                    ),
993                ])
994                .collect(),
995                instances: Default::default(),
996                order: vec![
997                    "link".to_string(),
998                    "code".to_string(),
999                    "link-code".to_string(),
1000                    "link-visited".to_string(),
1001                    "link-visited-code".to_string(),
1002                    "ul-ol-li-before".to_string(),
1003                ],
1004            }),
1005        ),
1006        ("ftd#markdown-color".to_string(), markdown::color()),
1007        (
1008            "ftd#markdown-background-color-data".to_string(),
1009            ftd::p2::Thing::Record(ftd::p2::Record {
1010                name: "ftd#markdown-background-color-data".to_string(),
1011                fields: std::iter::IntoIterator::into_iter([
1012                    ("link".to_string(), ftd::p2::Kind::record("ftd#color")),
1013                    ("code".to_string(), ftd::p2::Kind::record("ftd#color")),
1014                    ("link-code".to_string(), ftd::p2::Kind::record("ftd#color")),
1015                    (
1016                        "link-visited".to_string(),
1017                        ftd::p2::Kind::record("ftd#color"),
1018                    ),
1019                    (
1020                        "link-visited-code".to_string(),
1021                        ftd::p2::Kind::record("ftd#color"),
1022                    ),
1023                    (
1024                        "ul-ol-li-before".to_string(),
1025                        ftd::p2::Kind::record("ftd#color"),
1026                    ),
1027                ])
1028                .collect(),
1029                instances: Default::default(),
1030                order: vec![
1031                    "link".to_string(),
1032                    "code".to_string(),
1033                    "link-code".to_string(),
1034                    "link-visited".to_string(),
1035                    "link-visited-code".to_string(),
1036                    "ul-ol-li-before".to_string(),
1037                ],
1038            }),
1039        ),
1040        (
1041            "ftd#markdown-background-color".to_string(),
1042            markdown::background_color(),
1043        ),
1044        (
1045            "ftd#image-src".to_string(),
1046            ftd::p2::Thing::Record(ftd::p2::Record {
1047                name: "ftd#image-src".to_string(),
1048                fields: std::iter::IntoIterator::into_iter([
1049                    ("light".to_string(), ftd::p2::Kind::caption()),
1050                    ("dark".to_string(), ftd::p2::Kind::string()),
1051                ])
1052                .collect(),
1053                instances: Default::default(),
1054                order: vec!["light".to_string(), "dark".to_string()],
1055            }),
1056        ),
1057        (
1058            "ftd#color".to_string(),
1059            ftd::p2::Thing::Record(ftd::p2::Record {
1060                name: "ftd#color".to_string(),
1061                fields: std::iter::IntoIterator::into_iter([
1062                    ("light".to_string(), ftd::p2::Kind::caption()),
1063                    ("dark".to_string(), ftd::p2::Kind::string()),
1064                ])
1065                .collect(),
1066                instances: Default::default(),
1067                order: vec!["light".to_string(), "dark".to_string()],
1068            }),
1069        ),
1070        (
1071            "ftd#font-size".to_string(),
1072            ftd::p2::Thing::Record(ftd::p2::Record {
1073                name: "ftd#font-size".to_string(),
1074                fields: std::iter::IntoIterator::into_iter([
1075                    ("line-height".to_string(), ftd::p2::Kind::integer()),
1076                    ("size".to_string(), ftd::p2::Kind::integer()),
1077                    (
1078                        "letter-spacing".to_string(),
1079                        ftd::p2::Kind::integer().set_default(Some("0".to_string())),
1080                    ),
1081                ])
1082                .collect(),
1083                instances: Default::default(),
1084                order: vec![
1085                    "line-height".to_string(),
1086                    "size".to_string(),
1087                    "letter-spacing".to_string(),
1088                ],
1089            }),
1090        ),
1091        (
1092            "ftd#type".to_string(),
1093            ftd::p2::Thing::Record(ftd::p2::Record {
1094                name: "ftd#type".to_string(),
1095                fields: std::iter::IntoIterator::into_iter([
1096                    ("font".to_string(), ftd::p2::Kind::caption()),
1097                    (
1098                        "desktop".to_string(),
1099                        ftd::p2::Kind::record("ftd#font-size"),
1100                    ),
1101                    ("mobile".to_string(), ftd::p2::Kind::record("ftd#font-size")),
1102                    ("xl".to_string(), ftd::p2::Kind::record("ftd#font-size")),
1103                    (
1104                        "weight".to_string(),
1105                        ftd::p2::Kind::integer().set_default(Some("400".to_string())),
1106                    ),
1107                    ("style".to_string(), ftd::p2::Kind::string().into_optional()),
1108                ])
1109                .collect(),
1110                instances: Default::default(),
1111                order: vec![
1112                    "font".to_string(),
1113                    "desktop".to_string(),
1114                    "mobile".to_string(),
1115                    "xl".to_string(),
1116                    "weight".to_string(),
1117                    "style".to_string(),
1118                ],
1119            }),
1120        ),
1121        (
1122            "ftd#btb".to_string(),
1123            ftd::p2::Thing::Record(ftd::p2::Record {
1124                name: "ftd#btb".to_string(),
1125                fields: std::iter::IntoIterator::into_iter([
1126                    color("base"),
1127                    color("text"),
1128                    color("border"),
1129                ])
1130                .collect(),
1131                instances: Default::default(),
1132                order: vec!["base".to_string(), "text".to_string(), "border".to_string()],
1133            }),
1134        ),
1135        (
1136            "ftd#pst".to_string(),
1137            ftd::p2::Thing::Record(ftd::p2::Record {
1138                name: "ftd#pst".to_string(),
1139                fields: std::iter::IntoIterator::into_iter([
1140                    color("primary"),
1141                    color("secondary"),
1142                    color("tertiary"),
1143                ])
1144                .collect(),
1145                instances: Default::default(),
1146                order: vec![
1147                    "primary".to_string(),
1148                    "secondary".to_string(),
1149                    "tertiary".to_string(),
1150                ],
1151            }),
1152        ),
1153        (
1154            "ftd#background-colors".to_string(),
1155            ftd::p2::Thing::Record(ftd::p2::Record {
1156                name: "ftd#background-colors".to_string(),
1157                fields: std::iter::IntoIterator::into_iter([
1158                    color("base"),
1159                    color("step-1"),
1160                    color("step-2"),
1161                    color("overlay"),
1162                    color("code"),
1163                ])
1164                .collect(),
1165                instances: Default::default(),
1166                order: vec![
1167                    "base".to_string(),
1168                    "step-1".to_string(),
1169                    "step-2".to_string(),
1170                    "overlay".to_string(),
1171                    "code".to_string(),
1172                ],
1173            }),
1174        ),
1175        (
1176            "ftd#custom-colors".to_string(),
1177            ftd::p2::Thing::Record(ftd::p2::Record {
1178                name: "ftd#custom-colors".to_string(),
1179                fields: std::iter::IntoIterator::into_iter([
1180                    color("one"),
1181                    color("two"),
1182                    color("three"),
1183                    color("four"),
1184                    color("five"),
1185                    color("six"),
1186                    color("seven"),
1187                    color("eight"),
1188                    color("nine"),
1189                    color("ten"),
1190                ])
1191                .collect(),
1192                instances: Default::default(),
1193                order: vec![
1194                    "one".to_string(),
1195                    "two".to_string(),
1196                    "three".to_string(),
1197                    "four".to_string(),
1198                    "five".to_string(),
1199                    "six".to_string(),
1200                    "seven".to_string(),
1201                    "eight".to_string(),
1202                    "nine".to_string(),
1203                    "ten".to_string(),
1204                ],
1205            }),
1206        ),
1207        (
1208            "ftd#cta-colors".to_string(),
1209            ftd::p2::Thing::Record(ftd::p2::Record {
1210                name: "ftd#cta-colors".to_string(),
1211                fields: std::iter::IntoIterator::into_iter([
1212                    color("base"),
1213                    color("hover"),
1214                    color("pressed"),
1215                    color("disabled"),
1216                    color("focused"),
1217                    color("border"),
1218                    color("text"),
1219                ])
1220                .collect(),
1221                instances: Default::default(),
1222                order: vec![
1223                    "base".to_string(),
1224                    "hover".to_string(),
1225                    "pressed".to_string(),
1226                    "disabled".to_string(),
1227                    "focused".to_string(),
1228                    "border".to_string(),
1229                    "text".to_string(),
1230                ],
1231            }),
1232        ),
1233        (
1234            "ftd#color-scheme".to_string(),
1235            ftd::p2::Thing::Record(ftd::p2::Record {
1236                name: "ftd#color-scheme".to_string(),
1237                fields: std::iter::IntoIterator::into_iter([
1238                    record("background", "ftd#background-colors"),
1239                    color("border"),
1240                    color("border-strong"),
1241                    color("text"),
1242                    color("text-strong"),
1243                    color("shadow"),
1244                    color("scrim"),
1245                    record("cta-primary", "ftd#cta-colors"),
1246                    record("cta-secondary", "ftd#cta-colors"),
1247                    record("cta-tertiary", "ftd#cta-colors"),
1248                    record("cta-danger", "ftd#cta-colors"),
1249                    record("accent", "ftd#pst"),
1250                    record("error", "ftd#btb"),
1251                    record("success", "ftd#btb"),
1252                    record("info", "ftd#btb"),
1253                    record("warning", "ftd#btb"),
1254                    record("custom", "ftd#custom-colors"),
1255                ])
1256                .collect(),
1257                instances: Default::default(),
1258                order: vec![
1259                    "background".to_string(),
1260                    "border".to_string(),
1261                    "border-strong".to_string(),
1262                    "text".to_string(),
1263                    "text-strong".to_string(),
1264                    "shadow".to_string(),
1265                    "scrim".to_string(),
1266                    "cta-primary".to_string(),
1267                    "cta-secondary".to_string(),
1268                    "cta-tertiary".to_string(),
1269                    "cta-danger".to_string(),
1270                    "accent".to_string(),
1271                    "error".to_string(),
1272                    "success".to_string(),
1273                    "info".to_string(),
1274                    "warning".to_string(),
1275                    "custom".to_string(),
1276                ],
1277            }),
1278        ),
1279    ])
1280    .collect()
1281}
1282
1283pub fn default_aliases() -> ftd::Map<String> {
1284    std::iter::IntoIterator::into_iter([("ftd".to_string(), "ftd".to_string())]).collect()
1285}
1286
1287pub fn default_column() -> ftd::Column {
1288    ftd::Column {
1289        common: ftd::Common {
1290            width: Some(ftd::Length::Fill),
1291            height: Some(ftd::Length::Fill),
1292            position: Some(ftd::Position::Center),
1293            ..Default::default()
1294        },
1295        spacing: None,
1296        ..Default::default()
1297    }
1298}
1299
1300pub mod markdown {
1301    fn theme_color(light: &str, dark: &str) -> ftd::PropertyValue {
1302        ftd::PropertyValue::Value {
1303            value: ftd::Value::Record {
1304                name: "ftd#color".to_string(),
1305                fields: std::iter::IntoIterator::into_iter([
1306                    (
1307                        "light".to_string(),
1308                        ftd::PropertyValue::Value {
1309                            value: ftd::Value::String {
1310                                text: light.to_string(),
1311                                source: ftd::TextSource::Caption,
1312                            },
1313                        },
1314                    ),
1315                    (
1316                        "dark".to_string(),
1317                        ftd::PropertyValue::Value {
1318                            value: ftd::Value::String {
1319                                text: dark.to_string(),
1320                                source: ftd::TextSource::Header,
1321                            },
1322                        },
1323                    ),
1324                ])
1325                .collect(),
1326            },
1327        }
1328    }
1329
1330    fn link(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1331        ("link".to_string(), theme_color(light, dark))
1332    }
1333
1334    fn code(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1335        ("code".to_string(), theme_color(light, dark))
1336    }
1337
1338    fn link_visited(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1339        ("link-visited".to_string(), theme_color(light, dark))
1340    }
1341
1342    fn link_code(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1343        ("link-code".to_string(), theme_color(light, dark))
1344    }
1345
1346    fn link_visited_code(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1347        ("link-visited-code".to_string(), theme_color(light, dark))
1348    }
1349
1350    fn ul_ol_li_before(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1351        ("ul-ol-li-before".to_string(), theme_color(light, dark))
1352    }
1353
1354    fn blockquote(light: &str, dark: &str) -> (String, ftd::PropertyValue) {
1355        ("blockquote".to_string(), theme_color(light, dark))
1356    }
1357
1358    pub fn color() -> ftd::p2::Thing {
1359        ftd::p2::Thing::Variable(ftd::Variable {
1360            name: "ftd#markdown-color".to_string(),
1361            value: ftd::PropertyValue::Value {
1362                value: ftd::Value::Record {
1363                    name: "ftd#markdown-color-data".to_string(),
1364                    fields: std::iter::IntoIterator::into_iter([
1365                        link("#136351", "#25c19f"),
1366                        code("#000000", "#25c19f"),
1367                        link_visited("#7b3ee8", "#0f5750"),
1368                        link_code("#136351", "#25c19f"),
1369                        link_visited_code("#136351", "#0f5750"),
1370                        ul_ol_li_before("#000000", "#ffffff"),
1371                    ])
1372                    .collect(),
1373                },
1374            },
1375            conditions: vec![],
1376            flags: ftd::VariableFlags {
1377                always_include: Some(true),
1378            },
1379        })
1380    }
1381
1382    pub fn background_color() -> ftd::p2::Thing {
1383        ftd::p2::Thing::Variable(ftd::Variable {
1384            name: "ftd#markdown-background-color".to_string(),
1385            value: ftd::PropertyValue::Value {
1386                value: ftd::Value::Record {
1387                    name: "ftd#markdown-background-color-data".to_string(),
1388                    fields: std::iter::IntoIterator::into_iter([
1389                        link("#136351", "#25c19f"),
1390                        code("#f6f7f8", "#ffffff"),
1391                        link_visited("#7b3ee8", "#0f5750"),
1392                        link_code("#136351", "#25c19f"),
1393                        link_visited_code("#136351", "#0f5750"),
1394                        ul_ol_li_before("#000000", "#ffffff"),
1395                        blockquote("#f6f7f8", "#f0f0f0"),
1396                    ])
1397                    .collect(),
1398                },
1399            },
1400            conditions: vec![],
1401            flags: ftd::VariableFlags {
1402                always_include: Some(true),
1403            },
1404        })
1405    }
1406}
1407
1408// #[cfg(test)]
1409// pub fn elapsed(e: std::time::Duration) -> String {
1410//     // NOTE: there is a copy of this function in ftd also
1411//     let nanos = e.subsec_nanos();
1412//     let fraction = match nanos {
1413//         t if nanos < 1000 => format!("{}ns", t),
1414//         t if nanos < 1_000_000 => format!("{:.*}µs", 3, f64::from(t) / 1000.0),
1415//         t => format!("{:.*}ms", 3, f64::from(t) / 1_000_000.0),
1416//     };
1417//     let secs = e.as_secs();
1418//     match secs {
1419//         _ if secs == 0 => fraction,
1420//         t if secs < 5 => format!("{}.{:06}s", t, nanos / 1000),
1421//         t if secs < 60 => format!("{}.{:03}s", t, nanos / 1_000_000),
1422//         t if secs < 3600 => format!("{}m {}s", t / 60, t % 60),
1423//         t if secs < 86400 => format!("{}h {}m", t / 3600, (t % 3600) / 60),
1424//         t => format!("{}s", t),
1425//     }
1426// }