1use crate::{
2 AssignmentOp, BinaryOp, BlockStmt, CaseStmt, Declaration, DefaultStmt, DoWhileStmt, Expr,
3 ExprKind, ExpressionStmt, ForStmt, FunctionDecl, IfStmt, IncludeDirective, Literal, Script,
4 SimpleStmt, SourceMap, Stmt, StructDecl, StructFieldDecl, SwitchStmt, TypeKind, TypeSpec,
5 UnaryOp, VarDeclarator, WhileStmt,
6};
7
8#[must_use]
10pub fn render_script_graphviz(script: &Script, source_map: Option<&SourceMap>) -> String {
11 let mut renderer = GraphvizRenderer::new(source_map);
12 renderer.render_script(script);
13 renderer.finish()
14}
15
16struct GraphvizRenderer<'a> {
17 source_map: Option<&'a SourceMap>,
18 next_id: usize,
19 nodes: Vec<String>,
20 edges: Vec<String>,
21}
22
23impl<'a> GraphvizRenderer<'a> {
24 fn new(source_map: Option<&'a SourceMap>) -> Self {
25 Self {
26 source_map,
27 next_id: 0,
28 nodes: Vec::new(),
29 edges: Vec::new(),
30 }
31 }
32
33 fn finish(self) -> String {
34 let mut dot = String::from("digraph nwscript {\n rankdir=TB;\n node [shape=box];\n");
35 for node in self.nodes {
36 dot.push_str(" ");
37 dot.push_str(&node);
38 dot.push('\n');
39 }
40 for edge in self.edges {
41 dot.push_str(" ");
42 dot.push_str(&edge);
43 dot.push('\n');
44 }
45 dot.push_str("}\n");
46 dot
47 }
48
49 fn render_script(&mut self, script: &Script) {
50 let root = self.node("Script");
51 for item in &script.items {
52 match item {
53 crate::TopLevelItem::Include(include) => {
54 let child = self.render_include(include);
55 self.edge(root, child, None);
56 }
57 crate::TopLevelItem::Global(declaration) => {
58 let child = self.render_declaration("Global", declaration);
59 self.edge(root, child, None);
60 }
61 crate::TopLevelItem::Function(function) => {
62 let child = self.render_function(function);
63 self.edge(root, child, None);
64 }
65 crate::TopLevelItem::Struct(struct_decl) => {
66 let child = self.render_struct(struct_decl);
67 self.edge(root, child, None);
68 }
69 }
70 }
71 }
72
73 fn render_include(&mut self, include: &IncludeDirective) -> usize {
74 self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Include {0}", include.path))
})format!("Include {}", include.path), include.span)
75 }
76
77 fn render_function(&mut self, function: &FunctionDecl) -> usize {
78 let root = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Function {0}", function.name))
})format!("Function {}", function.name), function.span);
79 let return_type = self.render_type(&function.return_type);
80 self.edge(root, return_type, Some("return"));
81 for parameter in &function.parameters {
82 let param = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Param {0}", parameter.name))
})format!("Param {}", parameter.name), parameter.span);
83 self.edge(root, param, None);
84 let ty = self.render_type(¶meter.ty);
85 self.edge(param, ty, Some("type"));
86 if let Some(default) = ¶meter.default {
87 let expr = self.render_expr(default);
88 self.edge(param, expr, Some("default"));
89 }
90 }
91 if let Some(body) = &function.body {
92 let block = self.render_block(body);
93 self.edge(root, block, Some("body"));
94 }
95 root
96 }
97
98 fn render_struct(&mut self, struct_decl: &StructDecl) -> usize {
99 let root = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Struct {0}", struct_decl.name))
})format!("Struct {}", struct_decl.name), struct_decl.span);
100 for field in &struct_decl.fields {
101 let child = self.render_struct_field(field);
102 self.edge(root, child, None);
103 }
104 root
105 }
106
107 fn render_struct_field(&mut self, field: &StructFieldDecl) -> usize {
108 let root = self.node_with_span("StructField", field.span);
109 let ty = self.render_type(&field.ty);
110 self.edge(root, ty, Some("type"));
111 for name in &field.names {
112 let child = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Name {0}", name.name))
})format!("Name {}", name.name), name.span);
113 self.edge(root, child, None);
114 }
115 root
116 }
117
118 fn render_declaration(&mut self, kind: &str, declaration: &Declaration) -> usize {
119 let root = self.node_with_span(kind, declaration.span);
120 let ty = self.render_type(&declaration.ty);
121 self.edge(root, ty, Some("type"));
122 for declarator in &declaration.declarators {
123 let child = self.render_declarator(declarator);
124 self.edge(root, child, None);
125 }
126 root
127 }
128
129 fn render_declarator(&mut self, declarator: &VarDeclarator) -> usize {
130 let root = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Var {0}", declarator.name))
})format!("Var {}", declarator.name), declarator.span);
131 if let Some(initializer) = &declarator.initializer {
132 let expr = self.render_expr(initializer);
133 self.edge(root, expr, Some("init"));
134 }
135 root
136 }
137
138 fn render_block(&mut self, block: &BlockStmt) -> usize {
139 let root = self.node_with_span("Block", block.span);
140 for statement in &block.statements {
141 let child = self.render_stmt(statement);
142 self.edge(root, child, None);
143 }
144 root
145 }
146
147 fn render_stmt(&mut self, statement: &Stmt) -> usize {
148 match statement {
149 Stmt::Block(block) => self.render_block(block),
150 Stmt::Declaration(declaration) => self.render_declaration("Declaration", declaration),
151 Stmt::Expression(expression) => self.render_expression_stmt(expression),
152 Stmt::If(stmt) => self.render_if(stmt),
153 Stmt::Switch(stmt) => self.render_switch(stmt),
154 Stmt::Return(stmt) => self.render_return(stmt),
155 Stmt::While(stmt) => self.render_while(stmt),
156 Stmt::DoWhile(stmt) => self.render_do_while(stmt),
157 Stmt::For(stmt) => self.render_for(stmt),
158 Stmt::Case(stmt) => self.render_case(stmt),
159 Stmt::Default(stmt) => self.render_default(stmt),
160 Stmt::Break(stmt) => self.render_simple("Break", stmt),
161 Stmt::Continue(stmt) => self.render_simple("Continue", stmt),
162 Stmt::Empty(stmt) => self.render_simple("Empty", stmt),
163 }
164 }
165
166 fn render_expression_stmt(&mut self, statement: &ExpressionStmt) -> usize {
167 let root = self.node_with_span("ExpressionStmt", statement.span);
168 let expr = self.render_expr(&statement.expr);
169 self.edge(root, expr, None);
170 root
171 }
172
173 fn render_if(&mut self, statement: &IfStmt) -> usize {
174 let root = self.node_with_span("If", statement.span);
175 let condition = self.render_expr(&statement.condition);
176 self.edge(root, condition, Some("condition"));
177 let then_branch = self.render_stmt(&statement.then_branch);
178 self.edge(root, then_branch, Some("then"));
179 if let Some(else_branch) = &statement.else_branch {
180 let child = self.render_stmt(else_branch);
181 self.edge(root, child, Some("else"));
182 }
183 root
184 }
185
186 fn render_switch(&mut self, statement: &SwitchStmt) -> usize {
187 let root = self.node_with_span("Switch", statement.span);
188 let condition = self.render_expr(&statement.condition);
189 self.edge(root, condition, Some("condition"));
190 let body = self.render_stmt(&statement.body);
191 self.edge(root, body, Some("body"));
192 root
193 }
194
195 fn render_return(&mut self, statement: &crate::ReturnStmt) -> usize {
196 let root = self.node_with_span("Return", statement.span);
197 if let Some(value) = &statement.value {
198 let expr = self.render_expr(value);
199 self.edge(root, expr, Some("value"));
200 }
201 root
202 }
203
204 fn render_while(&mut self, statement: &WhileStmt) -> usize {
205 let root = self.node_with_span("While", statement.span);
206 let condition = self.render_expr(&statement.condition);
207 self.edge(root, condition, Some("condition"));
208 let body = self.render_stmt(&statement.body);
209 self.edge(root, body, Some("body"));
210 root
211 }
212
213 fn render_do_while(&mut self, statement: &DoWhileStmt) -> usize {
214 let root = self.node_with_span("DoWhile", statement.span);
215 let body = self.render_stmt(&statement.body);
216 self.edge(root, body, Some("body"));
217 let condition = self.render_expr(&statement.condition);
218 self.edge(root, condition, Some("condition"));
219 root
220 }
221
222 fn render_for(&mut self, statement: &ForStmt) -> usize {
223 let root = self.node_with_span("For", statement.span);
224 if let Some(initializer) = &statement.initializer {
225 let child = self.render_expr(initializer);
226 self.edge(root, child, Some("init"));
227 }
228 if let Some(condition) = &statement.condition {
229 let child = self.render_expr(condition);
230 self.edge(root, child, Some("condition"));
231 }
232 if let Some(update) = &statement.update {
233 let child = self.render_expr(update);
234 self.edge(root, child, Some("update"));
235 }
236 let body = self.render_stmt(&statement.body);
237 self.edge(root, body, Some("body"));
238 root
239 }
240
241 fn render_case(&mut self, statement: &CaseStmt) -> usize {
242 let root = self.node_with_span("Case", statement.span);
243 let expr = self.render_expr(&statement.value);
244 self.edge(root, expr, Some("value"));
245 root
246 }
247
248 fn render_default(&mut self, statement: &DefaultStmt) -> usize {
249 self.node_with_span("Default", statement.span)
250 }
251
252 fn render_simple(&mut self, kind: &str, statement: &SimpleStmt) -> usize {
253 self.node_with_span(kind, statement.span)
254 }
255
256 fn render_expr(&mut self, expr: &Expr) -> usize {
257 match &expr.kind {
258 ExprKind::Literal(literal) => {
259 self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Literal {0}",
literal_label(literal)))
})format!("Literal {}", literal_label(literal)), expr.span)
260 }
261 ExprKind::Identifier(name) => {
262 self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Identifier {0}", name))
})format!("Identifier {}", name), expr.span)
263 }
264 ExprKind::Call {
265 callee,
266 arguments,
267 } => {
268 let root = self.node_with_span("Call", expr.span);
269 let callee_id = self.render_expr(callee);
270 self.edge(root, callee_id, Some("callee"));
271 for argument in arguments {
272 let child = self.render_expr(argument);
273 self.edge(root, child, Some("arg"));
274 }
275 root
276 }
277 ExprKind::FieldAccess {
278 base,
279 field,
280 } => {
281 let root = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Field {0}", field))
})format!("Field {}", field), expr.span);
282 let child = self.render_expr(base);
283 self.edge(root, child, Some("base"));
284 root
285 }
286 ExprKind::Unary {
287 op,
288 expr: inner,
289 } => {
290 let root = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Unary {0}", unary_label(*op)))
})format!("Unary {}", unary_label(*op)), expr.span);
291 let child = self.render_expr(inner);
292 self.edge(root, child, None);
293 root
294 }
295 ExprKind::Binary {
296 op,
297 left,
298 right,
299 } => {
300 let root = self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Binary {0}", binary_label(*op)))
})format!("Binary {}", binary_label(*op)), expr.span);
301 let left_id = self.render_expr(left);
302 let right_id = self.render_expr(right);
303 self.edge(root, left_id, Some("left"));
304 self.edge(root, right_id, Some("right"));
305 root
306 }
307 ExprKind::Conditional {
308 condition,
309 when_true,
310 when_false,
311 } => {
312 let root = self.node_with_span("Conditional", expr.span);
313 let condition_id = self.render_expr(condition);
314 let true_id = self.render_expr(when_true);
315 let false_id = self.render_expr(when_false);
316 self.edge(root, condition_id, Some("condition"));
317 self.edge(root, true_id, Some("then"));
318 self.edge(root, false_id, Some("else"));
319 root
320 }
321 ExprKind::Assignment {
322 op,
323 left,
324 right,
325 } => {
326 let root =
327 self.node_with_span(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Assignment {0}",
assignment_label(*op)))
})format!("Assignment {}", assignment_label(*op)), expr.span);
328 let left_id = self.render_expr(left);
329 let right_id = self.render_expr(right);
330 self.edge(root, left_id, Some("left"));
331 self.edge(root, right_id, Some("right"));
332 root
333 }
334 }
335 }
336
337 fn render_type(&mut self, ty: &TypeSpec) -> usize {
338 self.node_with_span(
339 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("Type {0}",
type_label(&ty.kind, ty.is_const)))
})format!("Type {}", type_label(&ty.kind, ty.is_const)),
340 ty.span,
341 )
342 }
343
344 fn node(&mut self, label: impl Into<String>) -> usize {
345 let id = self.next_id;
346 self.next_id += 1;
347 self.nodes
348 .push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("n{1} [label=\"{0}\"];",
escape(&label.into()), id))
})format!("n{id} [label=\"{}\"];", escape(&label.into())));
349 id
350 }
351
352 fn node_with_span(&mut self, label: impl Into<String>, span: crate::Span) -> usize {
353 let mut full = label.into();
354 if let Some(source_map) = self.source_map
355 && let Some(file) = source_map.get(span.source_id)
356 {
357 full.push_str("\\n");
358 full.push_str(&file.name);
359 full.push(':');
360 full.push_str(&span.start.to_string());
361 full.push('-');
362 full.push_str(&span.end.to_string());
363 }
364 self.node(full)
365 }
366
367 fn edge(&mut self, from: usize, to: usize, label: Option<&str>) {
368 match label {
369 Some(label) => self
370 .edges
371 .push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("n{1} -> n{2} [label=\"{0}\"];",
escape(label), from, to))
})format!("n{from} -> n{to} [label=\"{}\"];", escape(label))),
372 None => self.edges.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("n{0} -> n{1};", from, to))
})format!("n{from} -> n{to};")),
373 }
374 }
375}
376
377fn escape(input: &str) -> String {
378 input.replace('\\', "\\\\").replace('"', "\\\"")
379}
380
381fn type_label(kind: &TypeKind, is_const: bool) -> String {
382 let prefix = if is_const { "const " } else { "" };
383 match kind {
384 TypeKind::Void => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}void", prefix))
})format!("{prefix}void"),
385 TypeKind::Int => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}int", prefix))
})format!("{prefix}int"),
386 TypeKind::Float => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}float", prefix))
})format!("{prefix}float"),
387 TypeKind::String => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}string", prefix))
})format!("{prefix}string"),
388 TypeKind::Object => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}object", prefix))
})format!("{prefix}object"),
389 TypeKind::Vector => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}vector", prefix))
})format!("{prefix}vector"),
390 TypeKind::Struct(name) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}struct {1}", prefix, name))
})format!("{prefix}struct {name}"),
391 TypeKind::EngineStructure(name) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0}{1}", prefix, name))
})format!("{prefix}{name}"),
392 }
393}
394
395fn literal_label(literal: &Literal) -> String {
396 match literal {
397 Literal::Integer(value) => value.to_string(),
398 Literal::Float(value) => value.to_string(),
399 Literal::String(value) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", value))
})format!("{value:?}"),
400 Literal::ObjectSelf => "OBJECT_SELF".to_string(),
401 Literal::ObjectInvalid => "OBJECT_INVALID".to_string(),
402 Literal::LocationInvalid => "LOCATION_INVALID".to_string(),
403 Literal::Json(value) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("json({0})", value))
})format!("json({value})"),
404 Literal::Vector(value) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("<{0}, {1}, {2}>", value[0],
value[1], value[2]))
})format!("<{}, {}, {}>", value[0], value[1], value[2]),
405 Literal::Magic(value) => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0:?}", value))
})format!("{value:?}"),
406 }
407}
408
409fn unary_label(op: UnaryOp) -> &'static str {
410 match op {
411 UnaryOp::Negate => "-",
412 UnaryOp::OnesComplement => "~",
413 UnaryOp::BooleanNot => "!",
414 UnaryOp::PreIncrement => "++pre",
415 UnaryOp::PreDecrement => "--pre",
416 UnaryOp::PostIncrement => "++post",
417 UnaryOp::PostDecrement => "--post",
418 }
419}
420
421fn binary_label(op: BinaryOp) -> &'static str {
422 match op {
423 BinaryOp::Multiply => "*",
424 BinaryOp::Divide => "/",
425 BinaryOp::Modulus => "%",
426 BinaryOp::Add => "+",
427 BinaryOp::Subtract => "-",
428 BinaryOp::ShiftLeft => "<<",
429 BinaryOp::ShiftRight => ">>",
430 BinaryOp::UnsignedShiftRight => ">>>",
431 BinaryOp::GreaterEqual => ">=",
432 BinaryOp::GreaterThan => ">",
433 BinaryOp::LessThan => "<",
434 BinaryOp::LessEqual => "<=",
435 BinaryOp::NotEqual => "!=",
436 BinaryOp::EqualEqual => "==",
437 BinaryOp::BooleanAnd => "&",
438 BinaryOp::ExclusiveOr => "^",
439 BinaryOp::InclusiveOr => "|",
440 BinaryOp::LogicalAnd => "&&",
441 BinaryOp::LogicalOr => "||",
442 }
443}
444
445fn assignment_label(op: AssignmentOp) -> &'static str {
446 match op {
447 AssignmentOp::Assign => "=",
448 AssignmentOp::AssignMinus => "-=",
449 AssignmentOp::AssignPlus => "+=",
450 AssignmentOp::AssignMultiply => "*=",
451 AssignmentOp::AssignDivide => "/=",
452 AssignmentOp::AssignModulus => "%=",
453 AssignmentOp::AssignAnd => "&=",
454 AssignmentOp::AssignXor => "^=",
455 AssignmentOp::AssignOr => "|=",
456 AssignmentOp::AssignShiftLeft => "<<=",
457 AssignmentOp::AssignShiftRight => ">>=",
458 AssignmentOp::AssignUnsignedShiftRight => ">>>=",
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::render_script_graphviz;
465 use crate::{SourceId, parse_text};
466
467 #[test]
468 fn renders_graphviz_for_simple_script() -> Result<(), Box<dyn std::error::Error>> {
469 let script = parse_text(
470 SourceId::new(0),
471 r#"void main() { int value = 1; PrintInteger(value); }"#,
472 None,
473 )?;
474
475 let dot = render_script_graphviz(&script, None);
476 assert!(dot.contains("digraph nwscript"));
477 assert!(dot.contains("Function main"));
478 assert!(dot.contains("Call"));
479 Ok(())
480 }
481}