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(&current_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