python_to_mermaid/convert/
python_to_flowchart.rs1use crate::flowchart::{self, Flowchart, FlowchartItem};
2use anyhow::Result;
3use ast::{StmtFunctionDef, Visitor as _};
4use itertools::Itertools as _;
5use rustpython_parser::{ast, Parse};
6
7pub fn enumerate_fn_defs(source: &str) -> Result<Vec<FnDef>> {
8 let ast = ast::ModModule::parse(source, ".")?;
9 let mut collector = FnDefCollector::new();
10 for stmt in ast.body {
11 collector.visit_stmt(stmt);
12 }
13
14 Ok(collector
15 .fn_defs
16 .into_iter()
17 .map(|(name, fn_def)| FnDef { name, fn_def })
18 .collect())
19}
20
21pub fn convert(fn_def: FnDef) -> Result<Flowchart> {
22 let mut fc = Flowchart::new_root(fn_def.name);
23 let mut gen = FlowchartGenerator::new(&mut fc);
24 gen.visit_stmts(fn_def.fn_def.body);
25 Ok(fc)
26}
27
28#[derive(Debug, Clone)]
29pub struct FnDef {
30 pub name: String,
31 pub fn_def: ast::StmtFunctionDef,
32}
33
34trait VisitorExt: ast::Visitor {
35 fn visit_stmts<I: IntoIterator<Item = ast::Stmt>>(&mut self, stmts: I) {
36 for stmt in stmts {
37 self.visit_stmt(stmt);
38 }
39 }
40}
41
42impl<V: ast::Visitor> VisitorExt for V {}
43
44struct FnDefCollector {
45 path: Vec<String>,
46 fn_defs: Vec<(String, ast::StmtFunctionDef)>,
47}
48
49impl FnDefCollector {
50 pub fn new() -> Self {
51 Self {
52 path: Vec::new(),
53 fn_defs: Vec::new(),
54 }
55 }
56}
57
58impl ast::Visitor for FnDefCollector {
59 fn visit_stmt_class_def(&mut self, node: ast::StmtClassDef) {
60 self.path.push(node.name.to_string());
61 self.generic_visit_stmt_class_def(node);
62 self.path.pop();
63 }
64
65 fn visit_stmt_function_def(&mut self, node: StmtFunctionDef) {
66 self.path.push(node.name.to_string());
67 self.fn_defs.push((self.path.join("."), node.clone()));
68 self.generic_visit_stmt_function_def(node);
69 self.path.pop();
70 }
71}
72
73struct FlowchartGenerator<'f> {
74 fc: &'f mut Flowchart,
75}
76
77impl<'f> FlowchartGenerator<'f> {
78 pub fn new(fc: &'f mut Flowchart) -> Self {
79 Self { fc }
80 }
81}
82
83impl<'f> ast::Visitor for FlowchartGenerator<'f> {
84 fn visit_stmt_return(&mut self, node: ast::StmtReturn) {
85 let label = format!(
86 "return: {}",
87 node.value.map_or("".to_string(), |v| v.to_string())
88 );
89
90 self.fc
91 .items
92 .push(FlowchartItem::Terminal(flowchart::Terminal::new(label)));
93 }
94
95 fn visit_stmt_delete(&mut self, node: ast::StmtDelete) {
96 let label = format!("delete: {}", node.targets.iter().format(", "));
97
98 self.fc
99 .items
100 .push(FlowchartItem::Step(flowchart::Step::new(label)));
101 }
102
103 fn visit_stmt_assign(&mut self, node: ast::StmtAssign) {
104 let label = format!("{} = {}", node.targets.iter().format(", "), node.value);
105
106 self.fc
107 .items
108 .push(FlowchartItem::Step(flowchart::Step::new(label)));
109 }
110
111 fn visit_stmt_aug_assign(&mut self, node: ast::StmtAugAssign) {
112 let op = op_to_string(&node.op);
113 let label = format!("{} {}= {}", node.target, op, node.value);
114
115 self.fc
116 .items
117 .push(FlowchartItem::Step(flowchart::Step::new(label)));
118 }
119
120 fn visit_stmt_ann_assign(&mut self, node: ast::StmtAnnAssign) {
121 let label = format!(
122 "{}: {}{}",
123 node.target,
124 node.annotation,
125 node.value.map_or_else(String::new, |v| format!(" = {}", v))
126 );
127
128 self.fc
129 .items
130 .push(FlowchartItem::Step(flowchart::Step::new(label)));
131 }
132
133 fn visit_stmt_for(&mut self, node: ast::StmtFor) {
134 let label = format!("for {} in {}", node.target, node.iter);
135 let mut fc = Flowchart::new_sub(label);
136 let mut gen = FlowchartGenerator::new(&mut fc);
137 gen.visit_stmts(node.body);
138 self.fc.items.push(FlowchartItem::SubFlowchart(fc));
139 }
140
141 fn visit_stmt_while(&mut self, node: ast::StmtWhile) {
142 let label = format!("while {}", node.test);
143 let mut fc = Flowchart::new_sub(label);
144 let mut gen = FlowchartGenerator::new(&mut fc);
145 gen.visit_stmts(node.body);
146 self.fc.items.push(FlowchartItem::SubFlowchart(fc));
147 }
148
149 fn visit_stmt_if(&mut self, node: ast::StmtIf) {
150 let then_items = {
151 let mut fc = Flowchart::new_unlabeled_sub();
152 let mut gen = FlowchartGenerator::new(&mut fc);
153 gen.visit_stmts(node.body);
154 fc.items
155 };
156
157 let else_items = {
158 let mut fc = Flowchart::new_unlabeled_sub();
159 let mut gen = FlowchartGenerator::new(&mut fc);
160 gen.visit_stmts(node.orelse);
161 fc.items
162 };
163
164 self.fc
165 .items
166 .push(FlowchartItem::Condition(flowchart::Condition {
167 condition: format!("if: {} ?", node.test),
168 then_items,
169 else_items,
170 }));
171 }
172
173 fn visit_stmt_with(&mut self, node: ast::StmtWith) {
174 let label = format!(
175 "with {}",
176 node.items
177 .iter()
178 .map(|item| &item.context_expr)
179 .format(", ")
180 );
181 let mut fc = Flowchart::new_sub(label);
182 let mut gen = FlowchartGenerator::new(&mut fc);
183 gen.visit_stmts(node.body);
184 self.fc.items.push(FlowchartItem::SubFlowchart(fc));
185 }
186
187 fn visit_stmt_match(&mut self, node: ast::StmtMatch) {
188 let label = format!("match {}", node.subject);
189 let mut fc = Flowchart::new_sub(label);
190
191 let mut curr = &mut fc.items;
192 for case in node.cases {
193 let items = {
194 let mut fc = Flowchart::new_unlabeled_sub();
195 let mut gen = FlowchartGenerator::new(&mut fc);
196 gen.visit_stmts(case.body);
197 fc.items
198 };
199
200 if matches!(
201 case.pattern,
202 ast::Pattern::MatchAs(ast::PatternMatchAs {
203 pattern: None,
204 name: None,
205 ..
206 })
207 ) {
208 curr.extend(items);
210 } else {
211 let label = format!("case: {} ?", pattern_to_string(&case.pattern));
213 curr.push(FlowchartItem::Condition(flowchart::Condition::new(label)));
214 let FlowchartItem::Condition(cond) = curr.last_mut().unwrap() else {
215 unreachable!("bug: last item is not a condition");
216 };
217 cond.then_items = items;
218 curr = &mut cond.else_items;
219 }
220 }
221
222 self.fc.items.push(FlowchartItem::SubFlowchart(fc));
223 }
224
225 fn visit_stmt_raise(&mut self, node: ast::StmtRaise) {
226 let label = format!(
227 "raise {}",
228 node.exc.map_or_else(String::new, |e| e.to_string())
229 );
230
231 self.fc
232 .items
233 .push(FlowchartItem::Terminal(flowchart::Terminal::new(label)));
234 }
235
236 fn visit_stmt_try(&mut self, node: ast::StmtTry) {
237 let mut fc = Flowchart::new_sub("try");
238 let mut gen = FlowchartGenerator::new(&mut fc);
239 gen.visit_stmts(node.body);
240 self.fc.items.push(FlowchartItem::SubFlowchart(fc));
241
242 let mut curr = &mut self.fc.items;
243 for handler in node.handlers {
244 let ast::ExceptHandler::ExceptHandler(handler) = handler;
245
246 let label = match (&handler.type_, &handler.name) {
247 (None, None) => "except ?".to_string(),
248 (Some(t), None) => format!("except {} ?", t),
249 (None, Some(n)) => format!("except as {} ?", n),
250 (Some(t), Some(n)) => format!("except {} as {} ?", t, n),
251 };
252
253 let items = {
254 let mut fc = Flowchart::new_unlabeled_sub();
255 let mut gen = FlowchartGenerator::new(&mut fc);
256 gen.visit_stmts(handler.body);
257 fc.items
258 };
259
260 curr.push(FlowchartItem::Condition(flowchart::Condition::new(label)));
261 let FlowchartItem::Condition(cond) = curr.last_mut().unwrap() else {
262 unreachable!("bug: last item is not a condition");
263 };
264
265 cond.then_items = items;
266 curr = &mut cond.else_items;
267 }
268 }
269
270 fn visit_stmt_expr(&mut self, node: ast::StmtExpr) {
271 let label = node.value.to_string().replace('"', "");
272
273 self.fc
274 .items
275 .push(FlowchartItem::Step(flowchart::Step::new(label)));
276 }
277
278 fn visit_stmt_break(&mut self, _node: ast::StmtBreak) {
279 let label = "break".to_string();
280 self.fc
281 .items
282 .push(FlowchartItem::Break(flowchart::Break::new(label)));
283 }
284
285 fn visit_stmt_continue(&mut self, _node: ast::StmtContinue) {
286 let label = "continue".to_string();
287 self.fc
288 .items
289 .push(FlowchartItem::Continue(flowchart::Continue::new(label)));
290 }
291}
292
293fn op_to_string(op: &ast::Operator) -> &str {
294 match op {
295 ast::Operator::Add => "+",
296 ast::Operator::Sub => "-",
297 ast::Operator::Mult => "*",
298 ast::Operator::MatMult => "@",
299 ast::Operator::Div => "/",
300 ast::Operator::Mod => "%",
301 ast::Operator::Pow => "**",
302 ast::Operator::LShift => "<<",
303 ast::Operator::RShift => ">>",
304 ast::Operator::BitOr => "|",
305 ast::Operator::BitXor => "^",
306 ast::Operator::BitAnd => "&",
307 ast::Operator::FloorDiv => "//",
308 }
309}
310
311fn pattern_to_string(pattern: &ast::Pattern) -> String {
312 match pattern {
313 ast::Pattern::MatchSingleton(ast::PatternMatchSingleton { value, .. }) => value.to_string(),
314 ast::Pattern::MatchValue(ast::PatternMatchValue { value, .. }) => value.to_string(),
315 ast::Pattern::MatchAs(ast::PatternMatchAs { pattern, name, .. }) => {
316 let pattern = pattern
317 .as_ref()
318 .map_or_else(|| "_".to_string(), |p| pattern_to_string(p));
319 if let Some(name) = name {
320 format!("{} as {}", pattern, name)
321 } else {
322 pattern
323 }
324 }
325 ast::Pattern::MatchSequence(ast::PatternMatchSequence { .. }) => "[...]".to_string(),
326 ast::Pattern::MatchMapping(ast::PatternMatchMapping { .. }) => "{...}".to_string(),
327 ast::Pattern::MatchClass(ast::PatternMatchClass { cls, .. }) => {
328 format!("{}()", cls)
329 }
330 ast::Pattern::MatchStar(ast::PatternMatchStar { name, .. }) => {
331 if let Some(name) = name {
332 format!("*{}", name)
333 } else {
334 "*".to_string()
335 }
336 }
337 ast::Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => {
338 format!("({})", patterns.iter().map(pattern_to_string).format(" | "))
339 }
340 }
341}