1 module cogito.visitor;
2 
3 import core.stdc.string;
4 import dmd.ast_node;
5 import dmd.astcodegen;
6 import dmd.parsetimevisitor;
7 import dmd.visitor;
8 import dmd.tokens;
9 
10 import cogito.list;
11 import cogito.meter;
12 import std.algorithm;
13 import std.stdio;
14 
15 alias AST = ASTCodegen;
16 
17 private Meter.Type declarationType(Declaration : AST.Dsymbol)(Declaration declaration)
18 {
19     if (declaration.isUnionDeclaration())
20     {
21         return Meter.Type.union_;
22     }
23     else if (declaration.isStructDeclaration())
24     {
25         return Meter.Type.struct_;
26     }
27     else if (declaration.isInterfaceDeclaration())
28     {
29         return Meter.Type.interface_;
30     }
31     else if (declaration.isClassDeclaration())
32     {
33         return Meter.Type.class_;
34     }
35     else if (declaration.isTemplateDeclaration())
36     {
37         return Meter.Type.template_;
38     }
39     return Meter.Type.aggregate;
40 }
41 
42 private string moduleName(AST.ModuleDeclaration* moduleDeclaration)
43 {
44     return moduleDeclaration is null
45         ? "app"
46         : moduleDeclaration.toString.idup;
47 }
48 
49 extern(C++) final class CognitiveVisitor : SemanticTimeTransitiveVisitor
50 {
51     alias visit = SemanticTimeTransitiveVisitor.visit;
52 
53     private uint depth = 0U;
54     private Source source_;
55     private List!TOK stack;
56     private Meter* parent;
57 
58     extern(D) this()
59     {
60         this.source_ = Source(List!Meter());
61     }
62 
63     extern(D) this(string filename)
64     {
65         this.source_ = Source(List!Meter(), filename);
66     }
67 
68     /**
69      * Returns collected scores.
70      */
71     @property ref List!Meter meter()
72     {
73         return this.parent is null ? this.source_.inner : this.parent.inner;
74     }
75 
76     /**
77      * Returns collected source file score.
78      */
79     @property ref Source source()
80     {
81         return this.source_;
82     }
83 
84     /**
85      * Increases the score in the current or the top-level scope.
86      */
87     private void increase(uint by = 1U)
88     {
89         if (this.parent !is null)
90         {
91             this.parent.ownScore += by;
92         }
93         else
94         {
95             this.source.ownScore += by;
96         }
97     }
98 
99     override void visit(AST.DebugStatement statement)
100     {
101         debug writeln("Debug statement ", statement);
102         // Handled as ConditionalStatement or Condition
103         super.visit(statement);
104     }
105 
106     override void visit(AST.SharedStaticCtorDeclaration declaration)
107     {
108         debug writeln("Shared static constructor declaration ", declaration);
109 
110         stepInFunction!(AST.SharedStaticCtorDeclaration)(declaration);
111     }
112 
113     override void visit(AST.SharedStaticDtorDeclaration declaration)
114     {
115         debug writeln("Shared static destructor declaration ", declaration);
116 
117         stepInFunction!(AST.SharedStaticDtorDeclaration)(declaration);
118     }
119 
120     override void visit(AST.UnionDeclaration statement)
121     {
122         // Unions are handled as StructDeclarations
123         super.visit(statement);
124     }
125 
126     override void visit(AST.InterfaceDeclaration statement)
127     {
128         // Interfaces are handled as ClassDeclarations
129         super.visit(statement);
130     }
131 
132     override void visit(AST.StaticForeachStatement statement)
133     {
134         debug writeln("Static foreach statement ", statement);
135 
136         stepInStaticDeclaration(statement);
137     }
138 
139     override void visit(AST.GotoStatement statement)
140     { // There are also GotoDefaultStatement and GotoCaseStatement
141         debug writeln("Goto statement ", statement);
142 
143         increase;
144         super.visit(statement);
145     }
146 
147     override void visit(AST.StructDeclaration structDeclaration)
148     {
149         stepInAggregate!(AST.StructDeclaration)(structDeclaration);
150     }
151 
152     override void visit(AST.ClassDeclaration classDeclaration)
153     {
154         stepInAggregate!(AST.ClassDeclaration)(classDeclaration);
155     }
156 
157     private void stepInAggregate(Declaration : AST.Dsymbol)(Declaration declaration)
158     {
159         debug writeln("Aggregate declaration ", declaration);
160 
161         auto meterType = declarationType(declaration);
162         auto newMeter = Meter(declaration.ident, declaration.loc, meterType);
163         auto parent = this.parent;
164         this.parent = &newMeter;
165 
166         super.visit(declaration);
167 
168         this.parent = parent;
169         this.meter.insert(newMeter);
170     }
171 
172     override void visit(AST.FuncLiteralDeclaration declaration)
173     {
174         debug writeln("Function literal ", declaration);
175 
176         stepInFunction(declaration);
177     }
178 
179     override void visit(AST.FuncDeclaration declaration)
180     {
181         debug writeln("Function declaration ", declaration);
182 
183         stepInFunction(declaration);
184     }
185 
186     override void visit(AST.DtorDeclaration declaration)
187     {
188         debug writeln("Destructor ", declaration);
189 
190         stepInFunction(declaration);
191     }
192 
193     private void stepInFunction(T : AST.FuncDeclaration)(T declaration)
194     {
195         auto newMeter = Meter(declaration.ident, declaration.loc, Meter.Type.callable);
196         auto parent = this.parent;
197         this.parent = &newMeter;
198 
199         ++this.depth;
200         super.visit(declaration);
201         --this.depth;
202 
203         this.parent = parent;
204         this.meter.insert(newMeter);
205     }
206 
207     override void visit(AST.TemplateDeclaration declaration)
208     {
209         stepInAggregate!(AST.TemplateDeclaration)(declaration);
210     }
211 
212     override void visit(AST.BinExp expression)
213     {
214         debug writeln("Binary expression ", expression);
215 
216         if (expression.isLogicalExp()) {
217             // Each operator like && or || is counted once in an expression
218             // chain.
219             if (find(this.stack[], expression.op).empty)
220             {
221                 increase;
222             }
223             this.stack.insert(expression.op);
224         }
225 
226         super.visit(expression);
227 
228         if (expression.isLogicalExp())
229         {
230             this.stack.removeFront();
231         }
232     }
233 
234     override void visit(AST.IfStatement statement)
235     {
236         debug writeln("if statement ", statement);
237 
238         statement.condition.accept(this);
239 
240         if (statement.ifbody)
241         {
242             increase(this.depth);
243 
244             ++this.depth;
245             statement.ifbody.accept(this);
246             --this.depth;
247         }
248         visitElseStatement(statement.elsebody);
249     }
250 
251     private void visitElseStatement(AST.Statement statement)
252     {
253         if (statement is null)
254         {
255             return;
256         }
257         auto elseIf = statement.isIfStatement();
258         if (elseIf !is null)
259         {
260             if (elseIf.ifbody)
261             {
262                 increase;
263 
264                 ++this.depth;
265                 elseIf.ifbody.accept(this);
266                 --this.depth;
267             }
268             visitElseStatement(elseIf.elsebody);
269         }
270         else
271         {
272             increase;
273 
274             ++this.depth;
275             statement.accept(this);
276             --this.depth;
277         }
278     }
279 
280     override void visit(AST.StaticIfDeclaration declaration)
281     {
282         debug writeln("static if declaration ", declaration);
283 
284         declaration.condition.accept(this);
285 
286         if (declaration.decl)
287         {
288             increase(max(1, this.depth));
289             visitNestedDeclarations(declaration);
290         }
291         visitStaticElseDeclaration(declaration.elsedecl);
292     }
293 
294     private void visitStaticElseDeclaration(AST.Dsymbols* declaration)
295     {
296         if (declaration is null)
297         {
298             return;
299         }
300         if (declaration.length == 0)
301         {
302             increase;
303         }
304         each!(x => forEachStaticElseDeclaration(x))((*declaration)[]);
305     }
306 
307     private void forEachStaticElseDeclaration(AST.Dsymbol elseDeclaration)
308     {
309         if (strcmp(elseDeclaration.kind, "static if") != 0)
310         {
311             increase;
312 
313             ++this.depth;
314             elseDeclaration.accept(this);
315             --this.depth;
316 
317             return;
318         }
319         auto elseIf = cast(AST.StaticIfDeclaration) elseDeclaration;
320 
321         if (elseIf.decl !is null)
322         {
323             increase;
324             visitNestedDeclarations(elseIf);
325         }
326         visitStaticElseDeclaration(elseIf.elsedecl);
327     }
328 
329     private void visitNestedDeclarations(ref AST.StaticIfDeclaration elseIf)
330     {
331         ++this.depth;
332         each!(de => de.accept(this))(*elseIf.decl);
333         --this.depth;
334     }
335 
336     override void visit(AST.ConditionalStatement statement)
337     {
338         debug writeln("Conditional statement ", statement);
339 
340         statement.condition.accept(this);
341 
342         if (statement.ifbody)
343         {
344             increase(this.depth);
345 
346             ++this.depth;
347             statement.ifbody.accept(this);
348             --this.depth;
349         }
350         visitStaticElseStatement(statement.elsebody);
351     }
352 
353     private void visitStaticElseStatement(AST.Statement statement)
354     {
355         if (statement is null)
356         {
357             return;
358         }
359         auto elseIf = statement.isConditionalStatement();
360         if (elseIf is null)
361         {
362             increase;
363 
364             ++this.depth;
365             statement.accept(this);
366             --this.depth;
367 
368             return;
369         }
370         if (elseIf.ifbody)
371         {
372             increase;
373 
374             ++this.depth;
375             elseIf.ifbody.accept(this);
376             --this.depth;
377         }
378         visitStaticElseStatement(elseIf.elsebody);
379     }
380 
381     override void visit(AST.StaticForeachDeclaration foreachDeclaration)
382     {
383         debug writeln("Static foreach declaration ", foreachDeclaration);
384 
385         stepInStaticDeclaration(foreachDeclaration);
386     }
387 
388     private void stepInStaticDeclaration(T : ASTNode)(T declaration)
389     {
390         increase(max(this.depth, 1));
391 
392         ++this.depth;
393         super.visit(declaration);
394         --this.depth;
395     }
396 
397     override void visit(AST.WhileStatement whileStatement)
398     {
399         debug writeln("while statement ", whileStatement);
400 
401         stepInLoop(whileStatement);
402     }
403 
404     override void visit(AST.DoStatement doStatement)
405     {
406         debug writeln("do statement ", doStatement);
407 
408         stepInLoop(doStatement);
409     }
410 
411     override void visit(AST.ForStatement forStatement)
412     {
413         debug writeln("for statement ", forStatement);
414 
415         stepInLoop(forStatement);
416     }
417 
418     override void visit(AST.ForeachStatement foreachStatement)
419     {
420         debug writeln("foreach statement ", foreachStatement);
421 
422         stepInLoop(foreachStatement);
423     }
424 
425     private void stepInLoop(T : ASTNode)(T statement)
426     {
427          increase(this.depth);
428 
429         ++this.depth;
430         super.visit(statement);
431         --this.depth;
432    }
433 
434     override void visit(AST.Module moduleDeclaration)
435     {
436         debug writeln("Module declaration ", moduleDeclaration);
437 
438         this.source_.moduleName = moduleName(moduleDeclaration.md);
439 
440         super.visit(moduleDeclaration);
441     }
442 
443     override void visit(AST.CondExp expression)
444     {
445         debug writeln("Ternary operator ", expression);
446 
447         stepInLoop(expression);
448     }
449 
450     override void visit(AST.SwitchStatement statement)
451     {
452         debug writeln("Switch ", statement);
453 
454         stepInLoop(statement);
455     }
456 
457     override void visit(AST.TryCatchStatement statement)
458     {
459         debug writeln("try-catch statement ", statement);
460 
461         if (statement._body)
462         {
463             increase(this.depth);
464 
465             statement._body.accept(this);
466         }
467         ++this.depth;
468         each!(catch_ => this.visit(catch_))(*statement.catches);
469         --this.depth;
470     }
471 
472     override void visit(AST.BreakStatement statement)
473     {
474         debug writeln("Break ", statement.ident);
475 
476         stepInStatementWithLabel(statement);
477     }
478 
479     private void stepInStatementWithLabel(T : AST.Statement)(T statement)
480     {
481         if (statement.ident !is null)
482         {
483             increase;
484         }
485         super.visit(statement);
486     }
487 
488     override void visit(AST.ContinueStatement statement)
489     {
490         debug writeln("Label ", statement);
491 
492         stepInStatementWithLabel(statement);
493     }
494 
495     override void visit(AST.PostBlitDeclaration declaration)
496     {
497         debug writeln("Blit ", declaration);
498 
499         stepInFunction(declaration);
500     }
501 
502     override void visit(AST.VersionCondition condition)
503     {
504         debug writeln("Version condition ", condition);
505 
506         stepInStaticDeclaration(condition);
507     }
508 }