Skip to main content

nwnrs_nwscript/
graphviz.rs

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/// Renders one parsed script as Graphviz DOT.
9#[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(&parameter.ty);
85            self.edge(param, ty, Some("type"));
86            if let Some(default) = &parameter.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}