SLang Metrics

Implementation approach (TODO: translate)

All lexems (rules ~ SLangParser.y) described by objects (OOP approach). At the result of parsing we will have "object tree".

For example: 
IfStatement will contain 3 objects: 
> ifBlock (condition is true), 
> elseBlock (condition is false), 
> elsifBlocks[] collection. 

Metrics possible for collection

Cyclomatic complexity (CC)

Classical metric considering number of decisions to be made on possible branching, more precisely number of possible paths through the code. This metric may be calculated in respect to the block of code (sequence of statements).

Pseudocode (description):

This metric is based on number of possible paths considering branching in our program, which is represented by if and loop statements. Each if statement consists of block executing if condition is true (minimum 1 path) and if condition is false (minimum 1 path, may be ommitted if no actions could be done in it). Also, we may have some number of elsif sub-blocks. Each sub-block of a given if statement ("on true", "on false", "else if"s) has it's own complexity, calculated earlier.

CC of block = multiplication of complexities of consequent branchings on the same level of nesting (if no branching in the block: CC = 1).
CC of if = sum of complexities of each sub-block.
CC of while = 1 (condition is initially false) + complexity of sub-block of main loop body

partial class Block
{
    int CC;

    void CalcCC()
    {
        int CC = 1;
        foreach (Statement s in this.statements)
        {
            if (IsBranching(s))  // If or While
            {
                CC *= s.CC;
            }
        }
        this.CC = CC;
    }
}

partial class If
{
    int CC;

    void CalcCC()
    {
        int CC = this.mainBlock.CC;
        if (HasElseBlock(this))
        {
            CC += this.elseBlock.CC;
        }
        foreach (Block b in this.elsifBlocks)
        {
            CC += b.CC;
        }
        this.CC = CC;
    }
}

partial class Loop
{
    int CC;

    void CalcCC()
    {
        int CC = 1 + this.loopBlock.CC;
        this.CC = CC;
    }
}

Software Sciences (SS)

It is a compound measure taking account of both size and vocabulary.
Program complexity is assumed as a function of the number of unique operators and operands, and the total number of occurrences, formally:

V=Nlog2n

N total number of occurrences of operators
+
operands
n
number of unique operators
+
operands (the vocabulary)

Pseudocode (description): (TODO: review)

It could be implemented by adding static field in each significant for rule class (decide by yourself, is using one common predecessor's field applicable for your case, because this approach has self pros and cons in terms of reusability by other metrics). At the end of the parsing we can just count

N and
n
at the significant classes and get SS.

...
static int creationCounter = 0;
...
CoolOperator() {
    creationCounter += 1;
    ...
}
...

Weighted Routines per Unit (WRU)

This is the sum of the complexities of all the routines within considered unit.

Pseudocode (description):

Calculation is based on the results of calculation of CC.
The complexity of inner units are not considered in the parent unit.

partial class Unit
{
    int WMU;

    void CalcWMU()
    {
        int WMU = 0;
        foreach (UnitMember m in this.members)
        {
            if (IsRoutine(m))
            {
                WMU += m.cc;
            }
        }
        this.WMU = WMU;
    }
}

Non-Private Weighted Routines per Unit

See Weighted Methods per Unit (WMU), but considered just for methods without access level specifiers.

Depth of Inheritance Tree (DIT)

This metric measures the position on a unit within the inheritance hierarchy.

Pseudocode (description):

Calculated after parsing is complete.

partial class Unit
{
    List<Unit> parents;  // collected during parsing
    List<Unit> children;  // initially empty
    Set<Unit> descendants { get; }; 

    /// <summary>
    /// Go through inheritance tree and calculate set 
    /// of descendants for each node.
    /// After that we can get number of descendants 
    /// for each node.
    /// </summary>
    void FindDescendants()
    {
        descendants.UnionWith(children);
        foreach (Unit c in children)
        {
            c.FindDescendants();
            descendants.UnionWith(c.descendants);
        }
        
    }

    // Probably deprecated due to newer method with 
    int CalcDepth()
    {
        if (!children.Any())
        {
            return 1;
        }
        else
        {
            int max = 0;
            foreach (Unit c in children)
            {
               int current = c.CalcDepth();
               if (max < current)
               {
                   max = current;
               }
            }
            return max;
        }
    }

    void InformParents()
    {
        foreach (Unit p in parents)
        {
            p.AddChild(this);
            p.InformParents();
        }
    }

    void AddChild(ref Unit u)
    {
        children.Add(u);
    }

    /// <summary>
    /// Reverse function finding paths from root to 
    /// all childs that will give all routes in 
    /// inheritance tree.
    /// Instead of finding all paths from root node, we find
    /// paths to root node from each of leaf nodes.
    /// </summary>
    List<List<Unit>> inheritancePaths()
    {
        List<List<Unit>> allPaths;

        List<Unit> leafNodes = GetLeafNodes();
        foreach (Unit leaf in leafNodes)
        {
            allPaths.UnionWith(leaf.pathsToLeaf());
        }

        int minDepth = Integer.MAX_VALUE;
        int maxDepth = 0;
        int averageDepth = 0;
        
        foreach(List<Unit> path in allPaths)
        {
            if(path.Count<minDepth){
                minDepth = path.Count;
            }
            if(path.Count>maxDepth){
                maxDepth = path.Count;
            }
            averageDepth+=path.Count;
        }
        averageDepth /= allPaths.Count;

        //TODO: return / use calculated values

        return allPaths;
    }

    List<List<Unit>> pathsToLeaf()
    {
        if (!parents.Any())
        {
            return new List<List<Unit>>();
        }
        else
        {
            List<List<Unit>> childPaths;
            foreach (Unit c in parents)
            {
                childPaths.UnionWith(c.pathsToLeaf());
            }
            foreach (List<Unit> path in childPaths)
            {
                path.Add(this);
            }
            return childPaths;
        }
    }
}

Number of Descendants (NOD)

As the name states, this metric counts the number of direct childs or sub-units for this unit.

Calculated in DIT.

Coupling between Object units (CBO)

Measures the total number of units to which a given unit is linked.

Probably not our work (it is for semantic analysis)

Pseudocode (description):

partial class Unit
{
    //
}

Response For a Class (RFC)

Number of member routines that can be executed in response to a message received by that unit, summed for all distinct messages.

Probably not our work (it is for semantic analysis)

Pseudocode (description):

partial class Unit
{
    //
}

Lack of Cohesion in Methods (LCOM)

How much sharing of data members occurs between the member routines of the unit. More sharing means higher cohesion.

Probably not our work (it is for semantic analysis)

Pseudocode (description):

partial class Unit
{
    //
}

Maximum Hierarchy Height (MHH)

Maximum height of hierarchy of nested structures. MHH of the module is MHH of it's block of statements (outer scope).

Pseudocode (description):

partial class Unit
{
    int MHH;

    void CalcMHH()
    {
        int membersMHH = 0;
        foreach (UnitMember m in this.members)
        {
            if (IsNesting(m) && membersMHH < m.MHH)
            {
                membersMHH = m.MHH;
            }
        }
        this.MHH = membersMHH + 1;
    }
}

partial class Routine
{
    int MHH;

    void CalcMHH()
    {
        this.MHH = this.block.MHH;
    }
}

partial class Block
{
    int MHH;

    void CalcMHH()
    {
        int statementsMHH = 0;
        foreach (Statement s in this.statements)
        {
            if (IsNesting(s) && statementsMHH < s.MHH)
            {
                statementsMHH = s.MHH;
            }
        }
        this.MHH = statementsMHH + 1;
    }
}

// Same things for each nested statement...

Average Hierarchy Height (AHH)

Average height of hierarchy of nested structures.

Pseudocode (description):

partial class Unit
{
    // TODO
    // Similar to inheritance hierarchy claculation but 
}

Average Number of Derived Units

Average count of units inherited from given unit.

Can be attached to DIT calculation

Afferent Coupling

Number of units from another module that depend on units from given module.

Probably Calculated in semantic analysis

Efferent Coupling

Number of units that use given class.

Probably Calculated in semantic analysis

Number of elements of language

Number of Units, Routines, Attributes (w/ default, hidden, hidden final)

Number of abstract Units
Number of concrete Units
Number of public fields and routines of unit
Number of all fields and routins of unit
Number of statements
Number of declarations
Number of units
Number of routines
Number of all public routines
Number of all fields and routines of all units

Implemented as counters in parser

Program size: lines of code, number of symbols, commented lines

Calculated during lexical analysis

Number Of Overwritten Routines

Calculated in parser

NPath Complexity

This metric includes looking for routines usage in program execution path. Need semantics to be considered.

References

  1. Measuring Complexity in C++ Application
    Software f. g. wilkie and b. hylands, 1998 https://onlinelibrary.wiley.com/doi/abs/10.1002/(SICI)1097-024X(19980425)28%3A5<513%3A%3AAID-SPE165>3.0.CO%3B2-H
  2. PHP metrics https://codengineering.ru/post/41
  3. Maintainanility Index http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm
  4. Software Sciences http://www.projectcodemeter.com/cost_estimation/help/GL_halstead.htm