thalir_parser/
annotations.rs1use crate::Rule;
2use pest::iterators::Pair;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum VisualCue {
6 ExternalCall,
7 StateWrite,
8 Warning,
9 Checked,
10 Safe,
11 Unsafe,
12}
13
14impl VisualCue {
15 pub fn from_str(s: &str) -> Option<Self> {
16 match s {
17 "🔴" | "[EXTERNAL_CALL]" => Some(Self::ExternalCall),
18 "🟡" | "[STATE_WRITE]" => Some(Self::StateWrite),
19 "⚠️" | "[WARNING]" => Some(Self::Warning),
20 "✓" | "[CHECKED]" => Some(Self::Checked),
21 "🟢" | "[SAFE]" => Some(Self::Safe),
22 "❌" | "[UNSAFE]" => Some(Self::Unsafe),
23 _ => None,
24 }
25 }
26
27 pub fn to_emoji(&self) -> &'static str {
28 match self {
29 Self::ExternalCall => "",
30 Self::StateWrite => "🟡",
31 Self::Warning => "",
32 Self::Checked => "",
33 Self::Safe => "🟢",
34 Self::Unsafe => "",
35 }
36 }
37
38 pub fn to_ascii(&self) -> &'static str {
39 match self {
40 Self::ExternalCall => "[EXTERNAL_CALL]",
41 Self::StateWrite => "[STATE_WRITE]",
42 Self::Warning => "[WARNING]",
43 Self::Checked => "[CHECKED]",
44 Self::Safe => "[SAFE]",
45 Self::Unsafe => "[UNSAFE]",
46 }
47 }
48}
49
50#[derive(Debug, Clone, Default)]
51pub struct InstructionAnnotations {
52 pub position: Option<usize>,
53 pub visual_cue: Option<VisualCue>,
54}
55
56#[derive(Debug, Clone)]
57pub enum AnalysisComment {
58 FunctionHeader {
59 name: String,
60 visibility: Option<String>,
61 },
62
63 OrderingHeader,
64
65 ExternalCallPosition(usize),
66
67 StateModificationPosition(usize),
68
69 OrderingComparison {
70 position1: usize,
71 operator: String,
72 position2: usize,
73 result: String,
74 },
75
76 Other(String),
77}
78
79pub fn extract_position(pair: &Pair<Rule>) -> Option<usize> {
80 pair.clone()
81 .into_inner()
82 .find(|p| p.as_rule() == Rule::position_marker)
83 .and_then(|p| {
84 let text = p.as_str();
85
86 text.trim_matches(|c| c == '[' || c == ']')
87 .parse::<usize>()
88 .ok()
89 })
90}
91
92pub fn extract_visual_cue(pair: &Pair<Rule>) -> Option<VisualCue> {
93 pair.clone().into_inner().find_map(|p| match p.as_rule() {
94 Rule::visual_marker => p
95 .into_inner()
96 .next()
97 .and_then(|inner| VisualCue::from_str(inner.as_str())),
98 _ => None,
99 })
100}
101
102pub fn extract_instruction_annotations(pair: &Pair<Rule>) -> InstructionAnnotations {
103 InstructionAnnotations {
104 position: extract_position(pair),
105 visual_cue: extract_visual_cue(pair),
106 }
107}
108
109pub fn extract_analysis_comment(pair: &Pair<Rule>) -> Option<AnalysisComment> {
110 if pair.as_rule() != Rule::analysis_comment {
111 return None;
112 }
113
114 let text = pair.as_str();
115
116 if text.contains("### Function:") {
117 let parts: Vec<&str> = text.split("Function:").collect();
118 if parts.len() > 1 {
119 let rest = parts[1].trim();
120 let (name, visibility) = if rest.contains('(') {
121 let name_parts: Vec<&str> = rest.split('(').collect();
122 let name = name_parts[0].trim().to_string();
123 let vis = name_parts
124 .get(1)
125 .and_then(|s| s.trim_end_matches(')').trim().split_whitespace().next())
126 .map(|s| s.to_string());
127 (name, vis)
128 } else {
129 (rest.to_string(), None)
130 };
131 return Some(AnalysisComment::FunctionHeader { name, visibility });
132 }
133 }
134
135 if text.contains("ORDERING ANALYSIS") {
136 return Some(AnalysisComment::OrderingHeader);
137 }
138
139 if text.contains("External call at position") {
140 if let Some(pos) = extract_position_from_text(text) {
141 return Some(AnalysisComment::ExternalCallPosition(pos));
142 }
143 }
144
145 if text.contains("State modification at position") {
146 if let Some(pos) = extract_position_from_text(text) {
147 return Some(AnalysisComment::StateModificationPosition(pos));
148 }
149 }
150
151 if text.contains("→") {
152 if let Some(comparison) = parse_ordering_comparison(text) {
153 return Some(comparison);
154 }
155 }
156
157 Some(AnalysisComment::Other(text.to_string()))
158}
159
160fn extract_position_from_text(text: &str) -> Option<usize> {
161 let start = text.find('[')?;
162 let end = text[start..].find(']')?;
163 let num_str = &text[start + 1..start + end];
164 num_str.parse::<usize>().ok()
165}
166
167fn parse_ordering_comparison(text: &str) -> Option<AnalysisComment> {
168 let parts: Vec<&str> = text.split('→').collect();
169 if parts.len() != 2 {
170 return None;
171 }
172
173 let comparison_part = parts[0].trim();
174 let result_part = parts[1].trim();
175
176 let tokens: Vec<&str> = comparison_part.split_whitespace().collect();
177 if tokens.len() < 3 {
178 return None;
179 }
180
181 let pos1_str = tokens.iter().find(|s| s.starts_with('['))?;
182 let pos2_str = tokens.iter().rev().find(|s| s.starts_with('['))?;
183 let operator = tokens
184 .iter()
185 .find(|s| ["<", ">", "==", "!=", "<=", ">="].contains(&s.trim()))?;
186
187 let pos1 = pos1_str
188 .trim_matches(|c| c == '[' || c == ']')
189 .parse::<usize>()
190 .ok()?;
191 let pos2 = pos2_str
192 .trim_matches(|c| c == '[' || c == ']')
193 .parse::<usize>()
194 .ok()?;
195
196 Some(AnalysisComment::OrderingComparison {
197 position1: pos1,
198 operator: operator.to_string(),
199 position2: pos2,
200 result: result_part.to_string(),
201 })
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn test_visual_cue_from_emoji() {
210 assert_eq!(VisualCue::from_str("🔴"), Some(VisualCue::ExternalCall));
211 assert_eq!(VisualCue::from_str("🟡"), Some(VisualCue::StateWrite));
212 assert_eq!(VisualCue::from_str("⚠️"), Some(VisualCue::Warning));
213 }
214
215 #[test]
216 fn test_visual_cue_from_ascii() {
217 assert_eq!(
218 VisualCue::from_str("[EXTERNAL_CALL]"),
219 Some(VisualCue::ExternalCall)
220 );
221 assert_eq!(
222 VisualCue::from_str("[STATE_WRITE]"),
223 Some(VisualCue::StateWrite)
224 );
225 assert_eq!(VisualCue::from_str("[WARNING]"), Some(VisualCue::Warning));
226 }
227
228 #[test]
229 fn test_extract_position_from_text() {
230 assert_eq!(
231 extract_position_from_text("; - External call at position [42]"),
232 Some(42)
233 );
234 assert_eq!(
235 extract_position_from_text("; - [5] < [8] → REENTRANCY RISK"),
236 Some(5)
237 );
238 }
239
240 #[test]
241 fn test_parse_ordering_comparison() {
242 let text = "; - [4] < [8] → REENTRANCY RISK";
243 let result = parse_ordering_comparison(text);
244
245 match result {
246 Some(AnalysisComment::OrderingComparison {
247 position1,
248 operator,
249 position2,
250 result,
251 }) => {
252 assert_eq!(position1, 4);
253 assert_eq!(operator, "<");
254 assert_eq!(position2, 8);
255 assert_eq!(result, "REENTRANCY RISK");
256 }
257 _ => panic!("Expected OrderingComparison"),
258 }
259 }
260}