1 use crate::{Context, Interrupt};
2
3 pub enum InlineFendResultComponent {
4 Unprocessed(String),
5 FendOutput(String),
6 FendError(String),
7 }
8
9 impl InlineFendResultComponent {
get_contents(&self) -> &str10 pub fn get_contents(&self) -> &str {
11 match self {
12 Self::Unprocessed(s) | Self::FendOutput(s) | Self::FendError(s) => s.as_str(),
13 }
14 }
15
to_json(&self, out: &mut String)16 fn to_json(&self, out: &mut String) {
17 out.push_str("{\"type\": ");
18 match self {
19 Self::Unprocessed(_) => out.push_str("\"unprocessed\""),
20 Self::FendOutput(_) => out.push_str("\"fend_output\""),
21 Self::FendError(_) => out.push_str("\"fend_error\""),
22 }
23 out.push_str(", \"contents\": \"");
24 crate::json::escape_string(self.get_contents(), out);
25 out.push_str("\"}");
26 }
27 }
28
29 pub struct InlineFendResult {
30 parts: Vec<InlineFendResultComponent>,
31 }
32
33 impl InlineFendResult {
get_parts(&self) -> &[InlineFendResultComponent]34 pub fn get_parts(&self) -> &[InlineFendResultComponent] {
35 self.parts.as_slice()
36 }
37
to_json(&self) -> String38 pub fn to_json(&self) -> String {
39 let mut res = String::new();
40 res.push('[');
41 for (i, part) in self.get_parts().iter().enumerate() {
42 if i > 0 {
43 res.push(',');
44 }
45 part.to_json(&mut res);
46 }
47 res.push(']');
48 res
49 }
50 }
51
52 /// Evaluates fend syntax embedded in Markdown or similarly-formatted strings.
53 ///
54 /// Any calculations in `[[` and `]]` are evaluated and replaced with their
55 /// results.
56 ///
57 /// This function implements a subset of markdown parsing, ensuring that
58 /// backtick-escaped notation will not be evaluated.
59 ///
60 /// # Examples
61 /// ```
62 /// let mut ctx = fend_core::Context::new();
63 /// struct NeverInterrupt;
64 /// impl fend_core::Interrupt for NeverInterrupt {
65 /// fn should_interrupt(&self) -> bool {
66 /// false
67 /// }
68 /// }
69 /// let int = NeverInterrupt;
70 ///
71 /// let result = fend_core::substitute_inline_fend_expressions(
72 /// "The answer is [[1+1]].", &mut ctx, &int);
73 ///
74 /// assert_eq!(result.get_parts().len(), 3);
75 /// assert_eq!(result.get_parts()[0].get_contents(), "The answer is ");
76 /// assert_eq!(result.get_parts()[1].get_contents(), "2");
77 /// assert_eq!(result.get_parts()[2].get_contents(), ".");
78 /// ```
substitute_inline_fend_expressions( input: &str, context: &mut Context, int: &impl Interrupt, ) -> InlineFendResult79 pub fn substitute_inline_fend_expressions(
80 input: &str,
81 context: &mut Context,
82 int: &impl Interrupt,
83 ) -> InlineFendResult {
84 let mut result = InlineFendResult { parts: vec![] };
85 let mut current_component = String::new();
86 let mut inside_fend_expr = false;
87 let mut inside_backticks = false;
88 for ch in input.chars() {
89 current_component.push(ch);
90 if ch == '`' {
91 inside_backticks = !inside_backticks;
92 }
93 if !inside_fend_expr && !inside_backticks && current_component.ends_with("[[") {
94 current_component.truncate(current_component.len() - 2);
95 result
96 .parts
97 .push(InlineFendResultComponent::Unprocessed(current_component));
98 current_component = String::new();
99 inside_fend_expr = true;
100 } else if inside_fend_expr && !inside_backticks && current_component.ends_with("]]") {
101 current_component.truncate(current_component.len() - 2);
102 match crate::evaluate_with_interrupt(¤t_component, context, int) {
103 Ok(res) => result.parts.push(InlineFendResultComponent::FendOutput(
104 res.get_main_result().to_string(),
105 )),
106 Err(msg) => result.parts.push(InlineFendResultComponent::FendError(msg)),
107 }
108 current_component = String::new();
109 inside_fend_expr = false;
110 }
111 }
112 if inside_fend_expr {
113 current_component.insert_str(0, "[[");
114 }
115 result
116 .parts
117 .push(InlineFendResultComponent::Unprocessed(current_component));
118 result
119 }
120
121 #[cfg(test)]
122 mod tests {
123 use super::*;
124
125 #[track_caller]
simple_test(input: &str, expected: &str)126 fn simple_test(input: &str, expected: &str) {
127 let mut ctx = crate::Context::new();
128 let int = crate::interrupt::Never;
129 let mut result = String::new();
130 for part in substitute_inline_fend_expressions(input, &mut ctx, &int).parts {
131 result.push_str(part.get_contents());
132 }
133 if expected == "=" {
134 assert_eq!(result, input);
135 } else {
136 assert_eq!(result, expected);
137 }
138 }
139
140 #[test]
trivial_tests()141 fn trivial_tests() {
142 simple_test("", "");
143 simple_test("a", "a");
144 }
145
146 #[test]
longer_unprocessed_test()147 fn longer_unprocessed_test() {
148 simple_test(
149 "auidhwiaudb \n\naiusdfba!!! `code`\n\n\n```rust\nfn foo() {}\n```",
150 "=",
151 );
152 }
153
154 #[test]
simple_fend_expr()155 fn simple_fend_expr() {
156 simple_test("[[1+1]]", "2");
157 simple_test("[[2+2]][[6*6]]", "436");
158 simple_test("[[a = 5; 3a]]\n[[6a]]", "15\n30");
159 simple_test("[[2+\n\r\n2\n\n\r\n]][[1]]", "41");
160 simple_test(
161 "The answer is [[\n # let's work out 40 + 2:\n 40+2\n]].",
162 "The answer is 42.",
163 );
164 simple_test("[[]]", "");
165 simple_test("[[", "[[");
166 simple_test("]]", "]]");
167 }
168
169 #[test]
escaped_exprs()170 fn escaped_exprs() {
171 simple_test("`[[1+1]]` = [[1+1]]", "`[[1+1]]` = 2");
172 simple_test("`[[1+1]]` = [[1+1\n\n]]", "`[[1+1]]` = 2");
173 simple_test("```\n[[2+2]]\n```", "=");
174 }
175 }
176