reorder.nim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. import
  2. intsets, ast, idents, algorithm, renderer, parser, os, strutils,
  3. sequtils, msgs, modulegraphs, syntaxes, options, modulepaths, tables,
  4. lineinfos
  5. type
  6. DepN = ref object
  7. pnode: PNode
  8. id, idx, lowLink: int
  9. onStack: bool
  10. kids: seq[DepN]
  11. hAQ, hIS, hB, hCmd: int
  12. when defined(debugReorder):
  13. expls: seq[string]
  14. DepG = seq[DepN]
  15. when defined(debugReorder):
  16. var idNames = newTable[int, string]()
  17. proc newDepN(id: int, pnode: PNode): DepN =
  18. new(result)
  19. result.id = id
  20. result.pnode = pnode
  21. result.idx = -1
  22. result.lowLink = -1
  23. result.onStack = false
  24. result.kids = @[]
  25. result.hAQ = -1
  26. result.hIS = -1
  27. result.hB = -1
  28. result.hCmd = -1
  29. when defined(debugReorder):
  30. result.expls = @[]
  31. proc accQuoted(cache: IdentCache; n: PNode): PIdent =
  32. var id = ""
  33. for i in 0 ..< n.len:
  34. let x = n[i]
  35. case x.kind
  36. of nkIdent: id.add(x.ident.s)
  37. of nkSym: id.add(x.sym.name.s)
  38. else: discard
  39. result = getIdent(cache, id)
  40. proc addDecl(cache: IdentCache; n: PNode; declares: var IntSet) =
  41. case n.kind
  42. of nkPostfix: addDecl(cache, n[1], declares)
  43. of nkPragmaExpr: addDecl(cache, n[0], declares)
  44. of nkIdent:
  45. declares.incl n.ident.id
  46. when defined(debugReorder):
  47. idNames[n.ident.id] = n.ident.s
  48. of nkSym:
  49. declares.incl n.sym.name.id
  50. when defined(debugReorder):
  51. idNames[n.sym.name.id] = n.sym.name.s
  52. of nkAccQuoted:
  53. let a = accQuoted(cache, n)
  54. declares.incl a.id
  55. when defined(debugReorder):
  56. idNames[a.id] = a.s
  57. of nkEnumFieldDef:
  58. addDecl(cache, n[0], declares)
  59. else: discard
  60. proc computeDeps(cache: IdentCache; n: PNode, declares, uses: var IntSet; topLevel: bool) =
  61. template deps(n) = computeDeps(cache, n, declares, uses, false)
  62. template decl(n) =
  63. if topLevel: addDecl(cache, n, declares)
  64. case n.kind
  65. of procDefs, nkMacroDef, nkTemplateDef:
  66. decl(n[0])
  67. for i in 1..bodyPos: deps(n[i])
  68. of nkLetSection, nkVarSection, nkUsingStmt:
  69. for a in n:
  70. if a.kind in {nkIdentDefs, nkVarTuple}:
  71. for j in countup(0, a.len-3): decl(a[j])
  72. for j in a.len-2..a.len-1: deps(a[j])
  73. of nkConstSection, nkTypeSection:
  74. for a in n:
  75. if a.len >= 3:
  76. decl(a[0])
  77. for i in 1..<a.len:
  78. if a[i].kind == nkEnumTy:
  79. # declare enum members
  80. for b in a[i]:
  81. decl(b)
  82. else:
  83. deps(a[i])
  84. of nkIdentDefs:
  85. for i in 1..<n.len: # avoid members identifiers in object definition
  86. deps(n[i])
  87. of nkIdent: uses.incl n.ident.id
  88. of nkSym: uses.incl n.sym.name.id
  89. of nkAccQuoted: uses.incl accQuoted(cache, n).id
  90. of nkOpenSymChoice, nkClosedSymChoice:
  91. uses.incl n.sons[0].sym.name.id
  92. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  93. for i in 0..<len(n): computeDeps(cache, n[i], declares, uses, topLevel)
  94. of nkPragma:
  95. let a = n.sons[0]
  96. if a.kind == nkExprColonExpr and a.sons[0].kind == nkIdent and
  97. a.sons[0].ident.s == "pragma":
  98. # user defined pragma
  99. decl(a.sons[1])
  100. else:
  101. for i in 0..<safeLen(n): deps(n[i])
  102. else:
  103. for i in 0..<safeLen(n): deps(n[i])
  104. proc cleanPath(s: string): string =
  105. # Here paths may have the form A / B or "A/B"
  106. result = ""
  107. for c in s:
  108. if c != ' ' and c != '\"':
  109. result.add c
  110. proc joinPath(parts: seq[string]): string =
  111. let nb = parts.len
  112. assert nb > 0
  113. if nb == 1:
  114. return parts[0]
  115. result = parts[0] / parts[1]
  116. for i in 2..<parts.len:
  117. result = result / parts[i]
  118. proc getIncludePath(n: PNode, modulePath: string): string =
  119. let istr = n.renderTree.cleanPath
  120. let (pdir, _) = modulePath.splitPath
  121. let p = istr.split('/').joinPath.addFileExt("nim")
  122. result = pdir / p
  123. proc hasIncludes(n:PNode): bool =
  124. for a in n:
  125. if a.kind == nkIncludeStmt:
  126. return true
  127. proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode {.procvar.} =
  128. result = syntaxes.parseFile(fileIdx, graph.cache, graph.config)
  129. graph.addDep(s, fileIdx)
  130. graph.addIncludeDep(FileIndex s.position, fileIdx)
  131. proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode,
  132. modulePath: string, includedFiles: var IntSet): PNode =
  133. # Parses includes and injects them in the current tree
  134. if not n.hasIncludes:
  135. return n
  136. result = newNodeI(nkStmtList, n.info)
  137. for a in n:
  138. if a.kind == nkIncludeStmt:
  139. for i in 0..<a.len:
  140. var f = checkModuleName(graph.config, a.sons[i])
  141. if f != InvalidFileIDX:
  142. if containsOrIncl(includedFiles, f.int):
  143. localError(graph.config, a.info, "recursive dependency: '$1'" %
  144. toFilename(graph.config, f))
  145. else:
  146. let nn = includeModule(graph, module, f)
  147. let nnn = expandIncludes(graph, module, nn, modulePath,
  148. includedFiles)
  149. excl(includedFiles, f.int)
  150. for b in nnn:
  151. result.add b
  152. else:
  153. result.add a
  154. proc splitSections(n: PNode): PNode =
  155. # Split typeSections and ConstSections into
  156. # sections that contain only one definition
  157. assert n.kind == nkStmtList
  158. result = newNodeI(nkStmtList, n.info)
  159. for a in n:
  160. if a.kind in {nkTypeSection, nkConstSection} and a.len > 1:
  161. for b in a:
  162. var s = newNode(a.kind)
  163. s.info = b.info
  164. s.add b
  165. result.add s
  166. else:
  167. result.add a
  168. proc haveSameKind(dns: seq[DepN]): bool =
  169. # Check if all the nodes in a strongly connected
  170. # component have the same kind
  171. result = true
  172. let kind = dns[0].pnode.kind
  173. for dn in dns:
  174. if dn.pnode.kind != kind:
  175. return false
  176. proc mergeSections(conf: ConfigRef; comps: seq[seq[DepN]], res: PNode) =
  177. # Merges typeSections and ConstSections when they form
  178. # a strong component (ex: circular type definition)
  179. for c in comps:
  180. assert c.len > 0
  181. if c.len == 1:
  182. res.add c[0].pnode
  183. else:
  184. let fstn = c[0].pnode
  185. let kind = fstn.kind
  186. # always return to the original order when we got circular dependencies
  187. let cs = c.sortedByIt(it.id)
  188. if kind in {nkTypeSection, nkConstSection} and haveSameKind(cs):
  189. # Circular dependency between type or const sections, we just
  190. # need to merge them
  191. var sn = newNode(kind)
  192. for dn in cs:
  193. sn.add dn.pnode.sons[0]
  194. res.add sn
  195. else:
  196. # Problematic circular dependency, we arrange the nodes into
  197. # their original relative order and make sure to re-merge
  198. # consecutive type and const sections
  199. var wmsg = "Circular dependency detected. reorder pragma may not be able to" &
  200. " reorder some nodes properely"
  201. when defined(debugReorder):
  202. wmsg &= ":\n"
  203. for i in 0..<cs.len-1:
  204. for j in i..<cs.len:
  205. for ci in 0..<cs[i].kids.len:
  206. if cs[i].kids[ci].id == cs[j].id:
  207. wmsg &= "line " & $cs[i].pnode.info.line &
  208. " depends on line " & $cs[j].pnode.info.line &
  209. ": " & cs[i].expls[ci] & "\n"
  210. for j in 0..<cs.len-1:
  211. for ci in 0..<cs[^1].kids.len:
  212. if cs[^1].kids[ci].id == cs[j].id:
  213. wmsg &= "line " & $cs[^1].pnode.info.line &
  214. " depends on line " & $cs[j].pnode.info.line &
  215. ": " & cs[^1].expls[ci] & "\n"
  216. message(conf, cs[0].pnode.info, warnUser, wmsg)
  217. var i = 0
  218. while i < cs.len:
  219. if cs[i].pnode.kind in {nkTypeSection, nkConstSection}:
  220. let ckind = cs[i].pnode.kind
  221. var sn = newNode(ckind)
  222. sn.add cs[i].pnode[0]
  223. inc i
  224. while i < cs.len and cs[i].pnode.kind == ckind:
  225. sn.add cs[i].pnode[0]
  226. inc i
  227. res.add sn
  228. else:
  229. res.add cs[i].pnode
  230. inc i
  231. proc hasImportStmt(n: PNode): bool =
  232. # Checks if the node is an import statement or
  233. # i it contains one
  234. case n.kind
  235. of nkImportStmt, nkFromStmt, nkImportExceptStmt:
  236. return true
  237. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  238. for a in n:
  239. if a.hasImportStmt:
  240. return true
  241. else:
  242. result = false
  243. proc hasImportStmt(n: DepN): bool =
  244. if n.hIS < 0:
  245. n.hIS = ord(n.pnode.hasImportStmt)
  246. result = bool(n.hIS)
  247. proc hasCommand(n: PNode): bool =
  248. # Checks if the node is a command or a call
  249. # or if it contains one
  250. case n.kind
  251. of nkCommand, nkCall:
  252. result = true
  253. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse,
  254. nkStaticStmt, nkLetSection, nkConstSection, nkVarSection,
  255. nkIdentDefs:
  256. for a in n:
  257. if a.hasCommand:
  258. return true
  259. else:
  260. return false
  261. proc hasCommand(n: DepN): bool =
  262. if n.hCmd < 0:
  263. n.hCmd = ord(n.pnode.hasCommand)
  264. result = bool(n.hCmd)
  265. proc hasAccQuoted(n: PNode): bool =
  266. if n.kind == nkAccQuoted:
  267. return true
  268. for a in n:
  269. if hasAccQuoted(a):
  270. return true
  271. const extandedProcDefs = procDefs + {nkMacroDef, nkTemplateDef}
  272. proc hasAccQuotedDef(n: PNode): bool =
  273. # Checks if the node is a function, macro, template ...
  274. # with a quoted name or if it contains one
  275. case n.kind
  276. of extandedProcDefs:
  277. result = n[0].hasAccQuoted
  278. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  279. for a in n:
  280. if a.hasAccQuotedDef:
  281. return true
  282. else:
  283. result = false
  284. proc hasAccQuotedDef(n: DepN): bool =
  285. if n.hAQ < 0:
  286. n.hAQ = ord(n.pnode.hasAccQuotedDef)
  287. result = bool(n.hAQ)
  288. proc hasBody(n: PNode): bool =
  289. # Checks if the node is a function, macro, template ...
  290. # with a body or if it contains one
  291. case n.kind
  292. of nkCommand, nkCall:
  293. result = true
  294. of extandedProcDefs:
  295. result = n[^1].kind == nkStmtList
  296. of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
  297. for a in n:
  298. if a.hasBody:
  299. return true
  300. else:
  301. result = false
  302. proc hasBody(n: DepN): bool =
  303. if n.hB < 0:
  304. n.hB = ord(n.pnode.hasBody)
  305. result = bool(n.hB)
  306. proc intersects(s1, s2: IntSet): bool =
  307. for a in s1:
  308. if s2.contains(a):
  309. return true
  310. proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG =
  311. # Build a dependency graph
  312. result = newSeqOfCap[DepN](deps.len)
  313. for i in 0..<deps.len:
  314. result.add newDepN(i, n.sons[i])
  315. for i in 0..<deps.len:
  316. var ni = result[i]
  317. let uses = deps[i][1]
  318. let niHasBody = ni.hasBody
  319. let niHasCmd = ni.hasCommand
  320. for j in 0..<deps.len:
  321. if i == j: continue
  322. var nj = result[j]
  323. let declares = deps[j][0]
  324. if j < i and nj.hasCommand and niHasCmd:
  325. # Preserve order for commands and calls
  326. ni.kids.add nj
  327. when defined(debugReorder):
  328. ni.expls.add "both have commands and one comes after the other"
  329. elif j < i and nj.hasImportStmt:
  330. # Every node that comes after an import statement must
  331. # depend on that import
  332. ni.kids.add nj
  333. when defined(debugReorder):
  334. ni.expls.add "parent is, or contains, an import statement and child comes after it"
  335. elif j < i and niHasBody and nj.hasAccQuotedDef:
  336. # Every function, macro, template... with a body depends
  337. # on precedent function declarations that have quoted names.
  338. # That's because it is hard to detect the use of functions
  339. # like "[]=", "[]", "or" ... in their bodies.
  340. ni.kids.add nj
  341. when defined(debugReorder):
  342. ni.expls.add "one declares a quoted identifier and the other has a body and comes after it"
  343. elif j < i and niHasBody and not nj.hasBody and
  344. intersects(deps[i][0], declares):
  345. # Keep function declaration before function definition
  346. ni.kids.add nj
  347. when defined(debugReorder):
  348. for dep in deps[i][0]:
  349. if dep in declares:
  350. ni.expls.add "one declares \"" & idNames[dep] & "\" and the other defines it"
  351. else:
  352. for d in declares:
  353. if uses.contains(d):
  354. ni.kids.add nj
  355. when defined(debugReorder):
  356. ni.expls.add "one declares \"" & idNames[d] & "\" and the other uses it"
  357. proc strongConnect(v: var DepN, idx: var int, s: var seq[DepN],
  358. res: var seq[seq[DepN]]) =
  359. # Recursive part of trajan's algorithm
  360. v.idx = idx
  361. v.lowLink = idx
  362. inc idx
  363. s.add v
  364. v.onStack = true
  365. for w in v.kids.mitems:
  366. if w.idx < 0:
  367. strongConnect(w, idx, s, res)
  368. v.lowLink = min(v.lowLink, w.lowLink)
  369. elif w.onStack:
  370. v.lowLink = min(v.lowLink, w.idx)
  371. if v.lowLink == v.idx:
  372. var comp = newSeq[DepN]()
  373. while true:
  374. var w = s.pop
  375. w.onStack = false
  376. comp.add w
  377. if w.id == v.id: break
  378. res.add comp
  379. proc getStrongComponents(g: var DepG): seq[seq[DepN]] =
  380. ## Tarjan's algorithm. Performs a topological sort
  381. ## and detects strongly connected components.
  382. result = newSeq[seq[DepN]]()
  383. var s = newSeq[DepN]()
  384. var idx = 0
  385. for v in g.mitems:
  386. if v.idx < 0:
  387. strongConnect(v, idx, s, result)
  388. proc hasForbiddenPragma(n: PNode): bool =
  389. # Checks if the tree node has some pragmas that do not
  390. # play well with reordering, like the push/pop pragma
  391. for a in n:
  392. if a.kind == nkPragma and a[0].kind == nkIdent and
  393. a[0].ident.s == "push":
  394. return true
  395. proc reorder*(graph: ModuleGraph, n: PNode, module: PSym): PNode =
  396. if n.hasForbiddenPragma:
  397. return n
  398. var includedFiles = initIntSet()
  399. let mpath = toFullPath(graph.config, module.fileIdx)
  400. let n = expandIncludes(graph, module, n, mpath,
  401. includedFiles).splitSections
  402. result = newNodeI(nkStmtList, n.info)
  403. var deps = newSeq[(IntSet, IntSet)](n.len)
  404. for i in 0..<n.len:
  405. deps[i][0] = initIntSet()
  406. deps[i][1] = initIntSet()
  407. computeDeps(graph.cache, n[i], deps[i][0], deps[i][1], true)
  408. var g = buildGraph(n, deps)
  409. let comps = getStrongComponents(g)
  410. mergeSections(graph.config, comps, result)