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