1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity average)
4 module cmd.Statistics;
5 
6 import cmd.Command,
7        cmd.ASTStats;
8 import dil.lexer.Lexer,
9        dil.lexer.Token;
10 import dil.parser.Parser;
11 import dil.ast.NodesEnum;
12 import dil.Compilation;
13 import dil.SourceText;
14 import common;
15 
16 /// The statistics comman.
17 class StatsCommand : Command
18 {
19   cstring[] filePaths; /// Module file paths.
20   bool printTokensTable; /// Whether to print the tokens table.
21   bool printNodesTable; /// Whether to print the nodes table.
22   CompilationContext cc; /// The context.
23 
24   /// Execute the command.
25   override void run()
26   {
27     Statistics[] stats;
28     foreach (filePath; filePaths)
29       stats ~= getStatistics(cc, filePath, printTokensTable, printNodesTable);
30 
31     auto total = Statistics(printTokensTable, printNodesTable);
32 
33     foreach (i, ref stat; stats)
34     {
35       total += stat;
36       Stdout.formatln(
37         "----\n"
38         "File: {}\n"
39         "Whitespace character count: {}\n"
40         "Whitespace token count: {}\n"
41         "Keyword count: {}\n"
42         "Identifier count: {}\n"
43         "Number count: {}\n"
44         "Comment count: {}\n"
45         "All tokens count: {}\n"
46         "Lines of code: {}",
47         filePaths[i],
48         stat.whitespaceCount,
49         stat.wsTokenCount,
50         stat.keywordCount,
51         stat.identCount,
52         stat.numberCount,
53         stat.commentCount,
54         stat.tokenCount,
55         stat.linesOfCode
56       );
57     }
58 
59     if (filePaths.length > 1)
60     {
61       Stdout.formatln(
62         "--------------------------------------------------------------------------------\n"
63         "Total of {} files:\n"
64         "Whitespace character count: {}\n"
65         "Whitespace token count: {}\n"
66         "Keyword count: {}\n"
67         "Identifier count: {}\n"
68         "Number count: {}\n"
69         "Comment count: {}\n"
70         "All tokens count: {}\n"
71         "Lines of code: {}",
72         filePaths.length,
73         total.whitespaceCount,
74         total.wsTokenCount,
75         total.keywordCount,
76         total.identCount,
77         total.numberCount,
78         total.commentCount,
79         total.tokenCount,
80         total.linesOfCode
81       );
82     }
83 
84     if (printTokensTable)
85     {
86       Stdout("Table of tokens:").newline;
87       Stdout.formatln(" {,10} | {}", "Count", "Token kind");
88       Stdout("-----------------------------").newline;
89       foreach (i, count; total.tokensTable)
90         Stdout.formatln(" {,10} | {}", count, (cast(TOK)i).toString);
91       Stdout("// End of tokens table.").newline;
92     }
93 
94     if (printNodesTable)
95     {
96       Stdout("Table of nodes:").newline;
97       Stdout.formatln(" {,10} | {}", "Count", "Node kind");
98       Stdout("-----------------------------").newline;
99       foreach (i, count; total.nodesTable)
100         Stdout.formatln(" {,10} | {}", count, NodeClassNames[i]);
101       Stdout("// End of nodes table.").newline;
102     }
103   }
104 }
105 
106 /// A group of statistics variables.
107 struct Statistics
108 {
109   uint whitespaceCount; /// Counter for whitespace characters.
110   uint wsTokenCount;    /// Counter for all whitespace tokens.
111   uint keywordCount;    /// Counter for keywords.
112   uint identCount;      /// Counter for identifiers.
113   uint numberCount;     /// Counter for number literals.
114   uint commentCount;    /// Counter for comments.
115   uint tokenCount;      /// Counter for all tokens produced by the Lexer.
116   uint linesOfCode;     /// Number of lines.
117   uint[] tokensTable;   /// Table of counters for all token kinds.
118   uint[] nodesTable;    /// Table of counters for all node kinds.
119 
120   static Statistics opCall(bool allocateTokensTable, bool allocateNodesTable = false)
121   {
122     Statistics s;
123     if (allocateTokensTable)
124       s.tokensTable = new uint[TOK.MAX];
125     if (allocateNodesTable)
126       s.nodesTable = new uint[NodeClassNames.length];
127     return s;
128   }
129 
130   void opAddAssign(Statistics s)
131   {
132     this.whitespaceCount += s.whitespaceCount;
133     this.wsTokenCount    += s.wsTokenCount;
134     this.keywordCount    += s.keywordCount;
135     this.identCount      += s.identCount;
136     this.numberCount     += s.numberCount;
137     this.commentCount    += s.commentCount;
138     this.tokenCount      += s.tokenCount;
139     this.linesOfCode     += s.linesOfCode;
140     foreach (i, count; s.tokensTable)
141       this.tokensTable[i] += count;
142     foreach (i, count; s.nodesTable)
143       this.nodesTable[i] += count;
144   }
145 }
146 
147 /// Returns the statistics for a D source file.
148 Statistics getStatistics(CompilationContext cc, cstring filePath,
149   bool printTokensTable, bool printNodesTable)
150 {
151   // Create a new record.
152   auto stats = Statistics(printTokensTable);
153 
154   auto sourceText = new SourceText(filePath, true);
155   Parser parser;
156   Lexer lx;
157   if (printNodesTable)
158   {
159     parser = new Parser(sourceText, cc.tables.lxtables);
160     auto rootNode = parser.start();
161     // Count nodes.
162     stats.nodesTable = (new ASTStats).count(rootNode);
163     lx = parser.lexer;
164   }
165   else
166   {
167     lx = new Lexer(sourceText, cc.tables.lxtables);
168     lx.scanAll();
169   }
170 
171   // Count tokens.
172   // Lexer creates HEAD + Newline, which are not in the source text.
173   // No token left behind!
174   stats.tokenCount = 2;
175   stats.linesOfCode = lx.lineNum;
176   if (printTokensTable)
177   {
178     stats.tokensTable[TOK.HEAD] = 1;
179     stats.tokensTable[TOK.Newline] = 1;
180   }
181 
182   // Traverse linked list.
183   foreach (token; lx.tokenList[0..$-1])
184   {
185     stats.tokenCount += 1;
186 
187     if (printTokensTable)
188       stats.tokensTable[token.kind] += 1;
189 
190     // Count whitespace characters
191     if (token.ws !is null)
192       stats.whitespaceCount += token.start - token.ws;
193 
194     switch (token.kind)
195     {
196     case TOK.Identifier:
197       stats.identCount++;
198       break;
199     case TOK.Comment:
200       stats.commentCount++;
201       break;
202     case TOK.Int32, TOK.Int64, TOK.UInt32, TOK.UInt64,
203          TOK.Float32, TOK.Float64, TOK.Float80,
204          TOK.IFloat32, TOK.IFloat64, TOK.IFloat80:
205       stats.numberCount++;
206       break;
207     case TOK.Newline:
208       break;
209     default:
210       if (token.isKeyword)
211         stats.keywordCount++;
212       else if (token.isWhitespace)
213         stats.wsTokenCount++;
214     }
215   }
216   return stats;
217 }