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); }