diff --git a/mort.nimble b/mort.nimble new file mode 100644 index 0000000..2b2828f --- /dev/null +++ b/mort.nimble @@ -0,0 +1,13 @@ +# Package + +version = "0.1.0" +author = "Joey Yakimowich-Payne" +description = "A dead code locator for Nim" +license = "MIT" +srcDir = "src" + + + +# Dependencies + +requires "nim >= 1.0.0" diff --git a/src/mort.nim b/src/mort.nim new file mode 100644 index 0000000..5338e66 --- /dev/null +++ b/src/mort.nim @@ -0,0 +1,118 @@ +import macros, tables, strformat, algorithm + +type + IndexStack[T] = seq[T] + +template childAt(node: NimNode, indexList: seq[int]): NimNode = + var curNode = node + for i in indexList: + curNode = curNode[i] + curNode + +iterator iterNode(node: NimNode): NimNode = + ## Recursive iterator for NimNodes. There's weird index stuff going on + ## because deep nodes can't be modified in place for some reason when iterating + var + stack: seq[IndexStack[int]] = @[newSeq[int]()] + curNode = node + + # traverse the node tree inorder + while stack.len() > 0: + let indexList = stack.pop() + var newIndices: IndexStack[int] = indexList + + var n = curNode.childAt(indexList) + yield n + for i in 0 ..< n.len: + var nstack = newIndices + nstack.add(i) + stack.add(nstack) + +template sortedIter(iter: untyped): seq[(int, int)] = + ## Iterate over a pairs iterator and return a sorted iterator + var arr: seq[(int, int)] = @[] + for k, v in iter: + arr.add((k, v)) + sorted(arr, SortOrder.Ascending) + + +var UsageTable = newTable[string, TableRef[int, int]]() + +proc initUsage(filename: string, line: int) = + ## This needs to be called at the start of the profiled code + ## in order to capture empty lines + ## This is done automatically by the macro + var lineTable = UsageTable.mgetOrPut(filename, newTable[int, int]()) + discard lineTable.hasKeyOrPut(line, 0) + +proc logUsage(filename: string, line: int) = + ## This needs to be sprinkled in between the code where a path begins + ## This is done automatically by the macro + UsageTable[filename][line] += 1 + +######################## User API ################################ + +proc printCodeUsage*() = + ## Prints usage for all code paths + for filename, lineTable in UsageTable.pairs: + echo fmt"Printing usage in: {filename}" + for _, (lineNum, count) in sortedIter(lineTable.pairs()): + echo fmt"{lineNum}: {count}" + echo "" + +proc printDeadLines*() = + ## Prints only the locations where dead lines exist + for filename, lineTable in UsageTable.pairs: + for _, (lineNum, count) in sortedIter(lineTable.pairs()): + if count == 0: + echo fmt"Dead code found in {filename}" + echo fmt"at line {lineNum}" + echo "" + +macro findDeadCode*(code: untyped): untyped = + ## This is the main macro. It can be used in the style of a pragma or + ## as a block. To activate it, use the ``-d:findDeadCode`` compiler flag. + ## + ## After sprinkling it around your code, call ``printCodeUsage`` or + ## ``printDeadLines`` to get output about where dead lines exist in + ## your code. + ## + ## + ## .. code-block:: nim + ## + ## findDeadCode: + ## # ... Put a bunch of Nim code here + ## discard + ## + ## proc myFancyProc() {.findDeadCode.} = + ## # ... Put a bunch of Nim code here + ## discard + ## + result = code + + if not defined(findDeadCode): + return + + template logUsageTemplate(lineInfo) = + logUsage(lineInfo.filename, lineInfo.line) + + template initUsageTemplate(lineInfo) = + initUsage(lineInfo.filename, lineInfo.line) + + var initUsages = newNimNode(nnkStmtList) + + for n in iterNode(result): + if n.kind == nnkStmtList: + let + lineInfo = n.lineInfoObj() + callNode = getAst(logUsageTemplate(lineInfo)) + n.insert(0, callNode) + initUsages.add(getAst(initUsageTemplate(lineInfo))) + + if result.kind == nnkStmtList: + result.insert(0, initUsages) + else: + for child in result.children: + if child.kind == nnkStmtList: + child.insert(0, initUsages) + break diff --git a/tests/config.nims b/tests/config.nims new file mode 100644 index 0000000..3bb69f8 --- /dev/null +++ b/tests/config.nims @@ -0,0 +1 @@ +switch("path", "$projectDir/../src") \ No newline at end of file diff --git a/tests/testmort.nim b/tests/testmort.nim new file mode 100644 index 0000000..4d220f0 --- /dev/null +++ b/tests/testmort.nim @@ -0,0 +1,33 @@ +import strutils + +import mort + +proc mysnakeToCamel(s: cstring): string {.findDeadCode.} = + var i, j: int + result = newString(s.len) + if s[i] == '_': inc(i) + while true: + if s[i] == '_' and (s[i + 1] == '\0' or s[i + 1] == '_'): + inc(i) + elif (s[i] == '_' or s[i] == '-') and s[i + 1].isLowerAscii and not s[i - 1].isUpperAscii: + inc(i) + result[j] = toUpperAscii(s[i]) + inc(i) + inc(j) + else: + result[j] = s[i] + inc(i) + inc(j) + if s[i] == '\0': + result.setLen(j) + break + if result[0] == '\0': # this may result, so we emit a dummy name as marker + result = "QQQ" + +proc main() = + discard mySnakeToCamel("diff_is_good") + discard mySnakeToCamel("_diff_is_good") + printCodeUsage() + printDeadLines() + +main()