mirror of https://gitlab.com/basile.b/dexed.git
856 lines
17 KiB
D
856 lines
17 KiB
D
module halstead;
|
|
|
|
import
|
|
std.meta, std.algorithm.iteration, std.json, std.conv;
|
|
import
|
|
dparse.lexer, dparse.parser, dparse.ast, dparse.rollback_allocator;
|
|
import
|
|
iz.memory, iz.containers;
|
|
version(unittest){} else import
|
|
common;
|
|
|
|
void performHalsteadMetrics(const(Module) mod)
|
|
{
|
|
HalsteadMetric hm = construct!(HalsteadMetric);
|
|
hm.visit(mod);
|
|
hm.serialize;
|
|
}
|
|
|
|
private struct Function
|
|
{
|
|
size_t line;
|
|
string name;
|
|
size_t N1, n1;
|
|
size_t N2, n2;
|
|
alias operatorsSum = N1;
|
|
alias operatorsKinds = n1;
|
|
alias operandsSum = N2;
|
|
alias operandsKinds = n2;
|
|
}
|
|
|
|
private final class HalsteadMetric: ASTVisitor
|
|
{
|
|
alias visit = ASTVisitor.visit;
|
|
|
|
Function[] functions;
|
|
size_t[string] operators;
|
|
size_t[string] operands;
|
|
size_t functionNesting;
|
|
bool functionCall;
|
|
bool ifStatement;
|
|
JSONValue fs;
|
|
|
|
this()
|
|
{
|
|
fs = parseJSON("[]");
|
|
}
|
|
|
|
void serialize()
|
|
{
|
|
import std.stdio: write;
|
|
JSONValue js;
|
|
js["functions"] = fs;
|
|
js.toString.write;
|
|
}
|
|
|
|
override void visit(const(PragmaExpression)){}
|
|
override void visit(const(Unittest)){}
|
|
|
|
void beginFunction()
|
|
{
|
|
operators.clear;
|
|
operands.clear;
|
|
if (functionNesting++ == 0)
|
|
functions.length = functions.length + 1;
|
|
}
|
|
|
|
void endFunction(string name, size_t line)
|
|
{
|
|
functions[$-1].name = name;
|
|
functions[$-1].line = line;
|
|
|
|
if (operators.length)
|
|
{
|
|
functions[$-1].N1 = operators.byValue.fold!((a,b) => b = a + b);
|
|
functions[$-1].n1 = operators.length;
|
|
}
|
|
if (operands.length)
|
|
{
|
|
functions[$-1].N2 = operands.byValue.fold!((a,b) => b = a + b);
|
|
functions[$-1].n2 = operands.length;
|
|
}
|
|
|
|
JSONValue f;
|
|
f["name"] = functions[$-1].name;
|
|
f["line"] = functions[$-1].line;
|
|
f["n1Sum"] = functions[$-1].N1;
|
|
f["n1Count"] = functions[$-1].n1;
|
|
f["n2Sum"] = functions[$-1].N2;
|
|
f["n2Count"] = functions[$-1].n2;
|
|
fs ~= [f];
|
|
|
|
version(unittest)
|
|
{
|
|
import std.stdio;
|
|
writeln(functions[$-1]);
|
|
writeln('\t',operators);
|
|
writeln('\t',operands);
|
|
}
|
|
|
|
functionNesting--;
|
|
}
|
|
|
|
override void visit(const(FunctionCallExpression) expr)
|
|
{
|
|
if (expr.unaryExpression.primaryExpression)
|
|
{
|
|
const(PrimaryExpression) p = expr.unaryExpression.primaryExpression;
|
|
if (p.identifierOrTemplateInstance)
|
|
{
|
|
if (p.identifierOrTemplateInstance.templateInstance)
|
|
++operators[p.identifierOrTemplateInstance.templateInstance.identifier.text];
|
|
else
|
|
++operators[p.identifierOrTemplateInstance.identifier.text];
|
|
}
|
|
}
|
|
if (expr.templateArguments)
|
|
{
|
|
if (expr.templateArguments.templateSingleArgument)
|
|
++operands[expr.templateArguments.templateSingleArgument.token.text];
|
|
else if (expr.templateArguments.templateArgumentList)
|
|
{
|
|
foreach(arg; expr.templateArguments.templateArgumentList.items)
|
|
{}//++operands[arg.token.text];
|
|
}
|
|
}
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(FunctionDeclaration) decl)
|
|
{
|
|
beginFunction;
|
|
if (!decl.functionBody)
|
|
return;
|
|
decl.accept(this);
|
|
endFunction(decl.name.text, decl.name.line);
|
|
}
|
|
|
|
override void visit(const(SharedStaticConstructor) ssc)
|
|
{
|
|
beginFunction;
|
|
ssc.accept(this);
|
|
endFunction("sharedStaticCtorL" ~ to!string(ssc.line), ssc.line);
|
|
}
|
|
|
|
override void visit(const(StaticConstructor) sc)
|
|
{
|
|
beginFunction;
|
|
sc.accept(this);
|
|
endFunction("staticCtorL" ~ to!string(sc.line), sc.line);
|
|
}
|
|
|
|
override void visit(const(Constructor) sc)
|
|
{
|
|
beginFunction;
|
|
sc.accept(this);
|
|
endFunction("ctorL" ~ to!string(sc.line), sc.line);
|
|
}
|
|
|
|
override void visit(const(SharedStaticDestructor) ssc)
|
|
{
|
|
beginFunction;
|
|
ssc.accept(this);
|
|
endFunction("sharedStaticDtorL" ~ to!string(ssc.line), ssc.line);
|
|
}
|
|
|
|
override void visit(const(StaticDestructor) sc)
|
|
{
|
|
beginFunction;
|
|
sc.accept(this);
|
|
endFunction("staticDtorL" ~ to!string(sc.line), sc.line);
|
|
}
|
|
|
|
override void visit(const(Destructor) sc)
|
|
{
|
|
beginFunction;
|
|
sc.accept(this);
|
|
endFunction("dtorL" ~ to!string(sc.line), sc.line);
|
|
}
|
|
|
|
override void visit(const(PrimaryExpression) primary)
|
|
{
|
|
if (primary.identifierOrTemplateInstance !is null
|
|
&& primary.identifierOrTemplateInstance.identifier != tok!"")
|
|
{
|
|
if (!functionCall)
|
|
++operands[primary.identifierOrTemplateInstance.identifier.text];
|
|
|
|
}
|
|
else if (primary.primary.type.isLiteral)
|
|
{
|
|
import std.digest.crc: crc32Of, toHexString;
|
|
++operands["literal" ~ primary.primary.text.crc32Of.toHexString.idup];
|
|
}
|
|
|
|
functionCall = false;
|
|
|
|
primary.accept(this);
|
|
}
|
|
|
|
override void visit(const(UnaryExpression) expr)
|
|
{
|
|
if (expr.prefix.type)
|
|
++operators[str(expr.prefix.type)];
|
|
if (expr.suffix.type)
|
|
++operators[str(expr.suffix.type)];
|
|
|
|
if (expr.functionCallExpression)
|
|
functionCall = true;
|
|
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(AndAndExpression) expr)
|
|
{
|
|
++operators["&&"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(OrOrExpression) expr)
|
|
{
|
|
++operators["||"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(AndExpression) expr)
|
|
{
|
|
++operators["&"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(AsmAndExp) expr)
|
|
{
|
|
++operators["&"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(OrExpression) expr)
|
|
{
|
|
++operators["|"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(InExpression) expr)
|
|
{
|
|
++operators["in"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(PowExpression) expr)
|
|
{
|
|
++operators["^"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(XorExpression) expr)
|
|
{
|
|
++operators["^^"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(IndexExpression) expr)
|
|
{
|
|
++operators["[]"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(NewExpression) expr)
|
|
{
|
|
++operators["new"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(NewAnonClassExpression) expr)
|
|
{
|
|
++operators["new"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(DeleteExpression) expr)
|
|
{
|
|
++operators["delete"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(CastExpression) expr)
|
|
{
|
|
++operators["cast"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(IsExpression) expr)
|
|
{
|
|
++operators["is"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(TypeidExpression) expr)
|
|
{
|
|
++operators["typeid"];
|
|
expr.accept(this);
|
|
}
|
|
|
|
override void visit(const(IfStatement) st)
|
|
{
|
|
++operators["if"];
|
|
st.accept(this);
|
|
if (st.thenStatement)
|
|
++operators["then"];
|
|
if (st.elseStatement)
|
|
++operators["else"];
|
|
}
|
|
|
|
override void visit(const(DeclarationOrStatement) st)
|
|
{
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(WhileStatement) st)
|
|
{
|
|
++operators["while"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(ForStatement) st)
|
|
{
|
|
++operators["for"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(ForeachStatement) st)
|
|
{
|
|
++operators["foreach"];
|
|
if (st.foreachTypeList)
|
|
foreach(ft; st.foreachTypeList.items)
|
|
++operands[ft.identifier.text];
|
|
if (st.foreachType)
|
|
++operands[st.foreachType.identifier.text];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(ReturnStatement) st)
|
|
{
|
|
++operators["return"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(BreakStatement) st)
|
|
{
|
|
++operators["break"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(ContinueStatement) st)
|
|
{
|
|
++operators["continue"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(GotoStatement) st)
|
|
{
|
|
++operators["goto"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(SwitchStatement) st)
|
|
{
|
|
++operators["switch"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(CaseStatement) st)
|
|
{
|
|
++operators["case"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(CaseRangeStatement) st)
|
|
{
|
|
++++operators["case"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(DefaultStatement) st)
|
|
{
|
|
++operators["case"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(ThrowStatement) st)
|
|
{
|
|
++operators["throw"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(TryStatement) st)
|
|
{
|
|
++operators["try"];
|
|
st.accept(this);
|
|
}
|
|
|
|
override void visit(const(Catch) c)
|
|
{
|
|
++operators["catch"];
|
|
c.accept(this);
|
|
if (c.identifier.text)
|
|
++operands[c.identifier.text];
|
|
}
|
|
|
|
override void visit(const(VariableDeclaration) decl)
|
|
{
|
|
if (decl.declarators)
|
|
foreach (elem; decl.declarators)
|
|
{
|
|
++operands[elem.name.text];
|
|
if (elem.initializer)
|
|
++operators["="];
|
|
}
|
|
else if (decl.autoDeclaration)
|
|
visit(decl.autoDeclaration);
|
|
decl.accept(this);
|
|
}
|
|
|
|
final override void visit(const AutoDeclarationPart decl)
|
|
{
|
|
++operands[decl.identifier.text];
|
|
++operators["="];
|
|
decl.accept(this);
|
|
}
|
|
|
|
static string exprAliases()
|
|
{
|
|
import std.range: iota;
|
|
|
|
alias ExprWithOp = AliasSeq!(
|
|
AddExpression,
|
|
AsmAddExp,
|
|
AsmEqualExp,
|
|
AsmMulExp,
|
|
AsmRelExp,
|
|
AsmShiftExp,
|
|
AssignExpression,
|
|
EqualExpression,
|
|
MulExpression,
|
|
RelExpression,
|
|
ShiftExpression,
|
|
);
|
|
|
|
enum exprOverride(T) = "
|
|
override void visit(const(" ~ T.stringof ~ ") expr)
|
|
{
|
|
static assert(__traits(hasMember," ~ T.stringof ~ ", \"operator\"));
|
|
++operators[str(expr.operator)];
|
|
expr.accept(this);
|
|
}";
|
|
|
|
string result;
|
|
foreach(i; aliasSeqOf!(iota(0, ExprWithOp.length)))
|
|
result ~= exprOverride!(ExprWithOp[i]);
|
|
return result;
|
|
}
|
|
|
|
mixin(exprAliases);
|
|
|
|
}
|
|
|
|
version(unittest)
|
|
{
|
|
T parseAndVisit(T : ASTVisitor)(const(char)[] source)
|
|
{
|
|
RollbackAllocator allocator;
|
|
LexerConfig config = LexerConfig("", StringBehavior.source, WhitespaceBehavior.skip);
|
|
StringCache cache = StringCache(StringCache.defaultBucketCount);
|
|
const(Token)[] tokens = getTokensForParser(cast(ubyte[]) source, config, &cache);
|
|
Module mod = parseModule(tokens, "", &allocator);
|
|
T result = construct!(T);
|
|
result.visit(mod);
|
|
return result;
|
|
}
|
|
|
|
Function test(string source)
|
|
{
|
|
import std.typecons;
|
|
HalsteadMetric hm = parseAndVisit!(HalsteadMetric)(source);
|
|
scope(exit) destruct(hm);
|
|
return hm.functions[$-1];
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
Object o = new Object;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 1);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
auto o = new Object;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 1);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
auto o = 1 + 2;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
foo(bar,baz);
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
int i = foo(bar,baz) + foo(bar,baz);
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 3);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
enum E{e0}
|
|
E e;
|
|
bar!(e,"lit")(baz(e));
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo();
|
|
}.test;
|
|
assert(r.operandsKinds == 0);
|
|
assert(r.operatorsKinds == 0);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
shared static this()
|
|
{
|
|
int i = 0;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
shared static ~this()
|
|
{
|
|
int i = 0;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
static this()
|
|
{
|
|
int i = 0;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
static ~this()
|
|
{
|
|
int i = 0;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
class Foo
|
|
{
|
|
this()
|
|
{
|
|
int i = 0;
|
|
}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
class Foo
|
|
{
|
|
~this()
|
|
{
|
|
int i = 0;
|
|
}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
i += a << b;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
++h;
|
|
i--;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
++i--;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 1);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
i = a | b & c && d || e^f + g^^h - a in b + a[0];
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 10);
|
|
assert(r.operatorsKinds == 11);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
Bar bar = new Bar;
|
|
auto baz = cast(Baz) bar;
|
|
delete bar;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 4);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
foreach(i,a;z){}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
foreach(i; l..h){}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
for(i = 0; i < len; i++){}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 4);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
for(;;){continue;}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 0);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
int foo()
|
|
{
|
|
while(true) {return 0;}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 2);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
switch(a)
|
|
{
|
|
default: break;
|
|
case 1: return;
|
|
case 2: .. case 8: ;
|
|
}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 4);
|
|
assert(r.operatorsKinds == 4);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
try a();
|
|
catch(Exception e)
|
|
throw v;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 2);
|
|
assert(r.operatorsKinds == 4);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
if (true) {} else {i = 0;}
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 3);
|
|
assert(r.operatorsKinds == 4);
|
|
}
|
|
|
|
version(none) unittest
|
|
{
|
|
// TODO: detect function call w/o parens
|
|
Function r =
|
|
q{
|
|
void foo()
|
|
{
|
|
bar;
|
|
}
|
|
}.test;
|
|
assert(r.operandsKinds == 0);
|
|
assert(r.operatorsKinds == 1);
|
|
}
|
|
|