Add sections (type, var, let, const, using) support for reorder pragma (#6326)
This commit is contained in:
parent
e135135469
commit
e2af486434
10 changed files with 551 additions and 129 deletions
|
|
@ -1,9 +1,36 @@
|
|||
|
||||
import intsets, tables, ast, idents, renderer
|
||||
import
|
||||
intsets, ast, idents, algorithm, renderer, parser, ospaths, strutils,
|
||||
sequtils, msgs, modulegraphs, syntaxes, options, modulepaths, tables
|
||||
|
||||
const
|
||||
nfTempMark = nfTransf
|
||||
nfPermMark = nfNoRewrite
|
||||
type
|
||||
DepN = ref object
|
||||
pnode: PNode
|
||||
id, idx, lowLink: int
|
||||
onStack: bool
|
||||
kids: seq[DepN]
|
||||
hAQ, hIS, hB, hCmd: int
|
||||
when not defined(release):
|
||||
expls: seq[string]
|
||||
DepG = seq[DepN]
|
||||
|
||||
when not defined(release):
|
||||
var idNames = newTable[int, string]()
|
||||
|
||||
proc newDepN(id: int, pnode: PNode): DepN =
|
||||
new(result)
|
||||
result.id = id
|
||||
result.pnode = pnode
|
||||
result.idx = -1
|
||||
result.lowLink = -1
|
||||
result.onStack = false
|
||||
result.kids = @[]
|
||||
result.hAQ = -1
|
||||
result.hIS = -1
|
||||
result.hB = -1
|
||||
result.hCmd = -1
|
||||
when not defined(release):
|
||||
result.expls = @[]
|
||||
|
||||
proc accQuoted(n: PNode): PIdent =
|
||||
var id = ""
|
||||
|
|
@ -21,10 +48,19 @@ proc addDecl(n: PNode; declares: var IntSet) =
|
|||
of nkPragmaExpr: addDecl(n[0], declares)
|
||||
of nkIdent:
|
||||
declares.incl n.ident.id
|
||||
when not defined(release):
|
||||
idNames[n.ident.id] = n.ident.s
|
||||
of nkSym:
|
||||
declares.incl n.sym.name.id
|
||||
when not defined(release):
|
||||
idNames[n.sym.name.id] = n.sym.name.s
|
||||
of nkAccQuoted:
|
||||
declares.incl accQuoted(n).id
|
||||
let a = accQuoted(n)
|
||||
declares.incl a.id
|
||||
when not defined(release):
|
||||
idNames[a.id] = a.s
|
||||
of nkEnumFieldDef:
|
||||
addDecl(n[0], declares)
|
||||
else: discard
|
||||
|
||||
proc computeDeps(n: PNode, declares, uses: var IntSet; topLevel: bool) =
|
||||
|
|
@ -32,7 +68,7 @@ proc computeDeps(n: PNode, declares, uses: var IntSet; topLevel: bool) =
|
|||
template decl(n) =
|
||||
if topLevel: addDecl(n, declares)
|
||||
case n.kind
|
||||
of procDefs:
|
||||
of procDefs, nkMacroDef, nkTemplateDef:
|
||||
decl(n[0])
|
||||
for i in 1..bodyPos: deps(n[i])
|
||||
of nkLetSection, nkVarSection, nkUsingStmt:
|
||||
|
|
@ -44,43 +80,358 @@ proc computeDeps(n: PNode, declares, uses: var IntSet; topLevel: bool) =
|
|||
for a in n:
|
||||
if a.len >= 3:
|
||||
decl(a[0])
|
||||
for i in 1..<a.len: deps(a[i])
|
||||
for i in 1..<a.len:
|
||||
if a[i].kind == nkEnumTy:
|
||||
# declare enum members
|
||||
for b in a[i]:
|
||||
decl(b)
|
||||
else:
|
||||
deps(a[i])
|
||||
of nkIdentDefs:
|
||||
for i in 1..<n.len: # avoid members identifiers in object definition
|
||||
deps(n[i])
|
||||
of nkIdent: uses.incl n.ident.id
|
||||
of nkSym: uses.incl n.sym.name.id
|
||||
of nkAccQuoted: uses.incl accQuoted(n).id
|
||||
of nkOpenSymChoice, nkClosedSymChoice:
|
||||
uses.incl n.sons[0].sym.name.id
|
||||
of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse:
|
||||
of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
|
||||
for i in 0..<len(n): computeDeps(n[i], declares, uses, topLevel)
|
||||
of nkPragma:
|
||||
let a = n.sons[0]
|
||||
if a.kind == nkExprColonExpr and a.sons[0].kind == nkIdent and
|
||||
a.sons[0].ident.s == "pragma":
|
||||
# user defined pragma
|
||||
decl(a.sons[1])
|
||||
else:
|
||||
for i in 0..<safeLen(n): deps(n[i])
|
||||
else:
|
||||
for i in 0..<safeLen(n): deps(n[i])
|
||||
|
||||
proc visit(i: int; all, res: PNode; deps: var seq[(IntSet, IntSet)]): bool =
|
||||
let n = all[i]
|
||||
if nfTempMark in n.flags:
|
||||
# not a DAG!
|
||||
proc cleanPath(s: string): string =
|
||||
# Here paths may have the form A / B or "A/B"
|
||||
result = ""
|
||||
for c in s:
|
||||
if c != ' ' and c != '\"':
|
||||
result.add c
|
||||
|
||||
proc joinPath(parts: seq[string]): string =
|
||||
let nb = parts.len
|
||||
assert nb > 0
|
||||
if nb == 1:
|
||||
return parts[0]
|
||||
result = parts[0] / parts[1]
|
||||
for i in 2..<parts.len:
|
||||
result = result / parts[i]
|
||||
|
||||
proc getIncludePath(n: PNode, modulePath: string): string =
|
||||
let istr = n.renderTree.cleanPath
|
||||
let (pdir, _) = modulePath.splitPath
|
||||
let p = istr.split('/').joinPath.addFileExt("nim")
|
||||
result = pdir / p
|
||||
|
||||
proc hasIncludes(n:PNode): bool =
|
||||
for a in n:
|
||||
if a.kind == nkIncludeStmt:
|
||||
return true
|
||||
|
||||
proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: int32;
|
||||
cache: IdentCache): PNode {.procvar.} =
|
||||
result = syntaxes.parseFile(fileIdx, cache)
|
||||
graph.addDep(s, fileIdx)
|
||||
graph.addIncludeDep(s.position.int32, fileIdx)
|
||||
|
||||
proc expandIncludes(graph: ModuleGraph, module: PSym, n: PNode,
|
||||
modulePath: string, includedFiles: var IntSet,
|
||||
cache: IdentCache): PNode =
|
||||
# Parses includes and injects them in the current tree
|
||||
if not n.hasIncludes:
|
||||
return n
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
for a in n:
|
||||
if a.kind == nkIncludeStmt:
|
||||
for i in 0..<a.len:
|
||||
var f = checkModuleName(a.sons[i])
|
||||
if f != InvalidFileIDX:
|
||||
if containsOrIncl(includedFiles, f):
|
||||
localError(a.info, errRecursiveDependencyX, f.toFilename)
|
||||
else:
|
||||
let nn = includeModule(graph, module, f, cache)
|
||||
let nnn = expandIncludes(graph, module, nn, modulePath,
|
||||
includedFiles, cache)
|
||||
excl(includedFiles, f)
|
||||
for b in nnn:
|
||||
result.add b
|
||||
else:
|
||||
result.add a
|
||||
|
||||
proc splitSections(n: PNode): PNode =
|
||||
# Split typeSections and ConstSections into
|
||||
# sections that contain only one definition
|
||||
assert n.kind == nkStmtList
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
for a in n:
|
||||
if a.kind in {nkTypeSection, nkConstSection} and a.len > 1:
|
||||
for b in a:
|
||||
var s = newNode(a.kind)
|
||||
s.info = b.info
|
||||
s.add b
|
||||
result.add s
|
||||
else:
|
||||
result.add a
|
||||
|
||||
proc haveSameKind(dns: seq[DepN]): bool =
|
||||
# Check if all the nodes in a strongly connected
|
||||
# component have the same kind
|
||||
result = true
|
||||
let kind = dns[0].pnode.kind
|
||||
for dn in dns:
|
||||
if dn.pnode.kind != kind:
|
||||
return false
|
||||
|
||||
proc mergeSections(comps: seq[seq[DepN]], res: PNode) =
|
||||
# Merges typeSections and ConstSections when they form
|
||||
# a strong component (ex: circular type definition)
|
||||
for c in comps:
|
||||
assert c.len > 0
|
||||
if c.len == 1:
|
||||
res.add c[0].pnode
|
||||
else:
|
||||
let fstn = c[0].pnode
|
||||
let kind = fstn.kind
|
||||
# always return to the original order when we got circular dependencies
|
||||
let cs = c.sortedByIt(it.id)
|
||||
if kind in {nkTypeSection, nkConstSection} and haveSameKind(cs):
|
||||
# Circular dependency between type or const sections, we just
|
||||
# need to merge them
|
||||
var sn = newNode(kind)
|
||||
for dn in cs:
|
||||
sn.add dn.pnode.sons[0]
|
||||
res.add sn
|
||||
else:
|
||||
# Problematic circular dependency, we arrange the nodes into
|
||||
# their original relative order and make sure to re-merge
|
||||
# consecutive type and const sections
|
||||
var wmsg = "Circular dependency detected. reorder pragma may not be able to" &
|
||||
" reorder some nodes properely"
|
||||
when not defined(release):
|
||||
wmsg &= ":\n"
|
||||
for i in 0..<cs.len-1:
|
||||
for j in i..<cs.len:
|
||||
for ci in 0..<cs[i].kids.len:
|
||||
if cs[i].kids[ci].id == cs[j].id:
|
||||
wmsg &= "line " & $cs[i].pnode.info.line &
|
||||
" depends on line " & $cs[j].pnode.info.line &
|
||||
": " & cs[i].expls[ci] & "\n"
|
||||
for j in 0..<cs.len-1:
|
||||
for ci in 0..<cs[^1].kids.len:
|
||||
if cs[^1].kids[ci].id == cs[j].id:
|
||||
wmsg &= "line " & $cs[^1].pnode.info.line &
|
||||
" depends on line " & $cs[j].pnode.info.line &
|
||||
": " & cs[^1].expls[ci] & "\n"
|
||||
message(cs[0].pnode.info, warnUser, wmsg)
|
||||
|
||||
var i = 0
|
||||
while i < cs.len:
|
||||
if cs[i].pnode.kind in {nkTypeSection, nkConstSection}:
|
||||
let ckind = cs[i].pnode.kind
|
||||
var sn = newNode(ckind)
|
||||
sn.add cs[i].pnode[0]
|
||||
inc i
|
||||
while i < cs.len and cs[i].pnode.kind == ckind :
|
||||
sn.add cs[i].pnode[0]
|
||||
inc i
|
||||
res.add sn
|
||||
else:
|
||||
res.add cs[i].pnode
|
||||
inc i
|
||||
|
||||
proc hasImportStmt(n: PNode): bool =
|
||||
# Checks if the node is an import statement or
|
||||
# i it contains one
|
||||
case n.kind
|
||||
of nkImportStmt, nkFromStmt, nkImportExceptStmt:
|
||||
return true
|
||||
if nfPermMark notin n.flags:
|
||||
incl n.flags, nfTempMark
|
||||
var uses = deps[i][1]
|
||||
for j in 0..<all.len:
|
||||
if j != i:
|
||||
let declares = deps[j][0]
|
||||
of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
|
||||
for a in n:
|
||||
if a.hasImportStmt:
|
||||
return true
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc hasImportStmt(n: DepN): bool =
|
||||
if n.hIS < 0:
|
||||
n.hIS = ord(n.pnode.hasImportStmt)
|
||||
result = bool(n.hIS)
|
||||
|
||||
proc hasCommand(n: PNode): bool =
|
||||
# Checks if the node is a command or a call
|
||||
# or if it contains one
|
||||
case n.kind
|
||||
of nkCommand, nkCall:
|
||||
result = true
|
||||
of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse,
|
||||
nkStaticStmt, nkLetSection, nkConstSection, nkVarSection,
|
||||
nkIdentDefs:
|
||||
for a in n:
|
||||
if a.hasCommand:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
proc hasCommand(n: DepN): bool =
|
||||
if n.hCmd < 0:
|
||||
n.hCmd = ord(n.pnode.hasCommand)
|
||||
result = bool(n.hCmd)
|
||||
|
||||
proc hasAccQuoted(n: PNode): bool =
|
||||
if n.kind == nkAccQuoted:
|
||||
return true
|
||||
for a in n:
|
||||
if hasAccQuoted(a):
|
||||
return true
|
||||
|
||||
const extandedProcDefs = procDefs + {nkMacroDef, nkTemplateDef}
|
||||
|
||||
proc hasAccQuotedDef(n: PNode): bool =
|
||||
# Checks if the node is a function, macro, template ...
|
||||
# with a quoted name or if it contains one
|
||||
case n.kind
|
||||
of extandedProcDefs:
|
||||
result = n[0].hasAccQuoted
|
||||
of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
|
||||
for a in n:
|
||||
if a.hasAccQuotedDef:
|
||||
return true
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc hasAccQuotedDef(n: DepN): bool =
|
||||
if n.hAQ < 0:
|
||||
n.hAQ = ord(n.pnode.hasAccQuotedDef)
|
||||
result = bool(n.hAQ)
|
||||
|
||||
proc hasBody(n: PNode): bool =
|
||||
# Checks if the node is a function, macro, template ...
|
||||
# with a body or if it contains one
|
||||
case n.kind
|
||||
of nkCommand, nkCall:
|
||||
result = true
|
||||
of extandedProcDefs:
|
||||
result = n[^1].kind == nkStmtList
|
||||
of nkStmtList, nkStmtListExpr, nkWhenStmt, nkElifBranch, nkElse, nkStaticStmt:
|
||||
for a in n:
|
||||
if a.hasBody:
|
||||
return true
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc hasBody(n: DepN): bool =
|
||||
if n.hB < 0:
|
||||
n.hB = ord(n.pnode.hasBody)
|
||||
result = bool(n.hB)
|
||||
|
||||
proc intersects(s1, s2: IntSet): bool =
|
||||
for a in s1:
|
||||
if s2.contains(a):
|
||||
return true
|
||||
|
||||
proc buildGraph(n: PNode, deps: seq[(IntSet, IntSet)]): DepG =
|
||||
# Build a dependency graph
|
||||
result = newSeqOfCap[DepN](deps.len)
|
||||
for i in 0..<deps.len:
|
||||
result.add newDepN(i, n.sons[i])
|
||||
for i in 0..<deps.len:
|
||||
var ni = result[i]
|
||||
let uses = deps[i][1]
|
||||
let niHasBody = ni.hasBody
|
||||
let niHasCmd = ni.hasCommand
|
||||
for j in 0..<deps.len:
|
||||
if i == j: continue
|
||||
var nj = result[j]
|
||||
let declares = deps[j][0]
|
||||
if j < i and nj.hasCommand and niHasCmd:
|
||||
# Preserve order for commands and calls
|
||||
ni.kids.add nj
|
||||
when not defined(release):
|
||||
ni.expls.add "both have commands and one comes after the other"
|
||||
elif j < i and nj.hasImportStmt:
|
||||
# Every node that comes after an import statement must
|
||||
# depend on that import
|
||||
ni.kids.add nj
|
||||
when not defined(release):
|
||||
ni.expls.add "parent is, or contains, an import statement and child comes after it"
|
||||
elif j < i and niHasBody and nj.hasAccQuotedDef:
|
||||
# Every function, macro, template... with a body depends
|
||||
# on precedent function declarations that have quoted names.
|
||||
# That's because it is hard to detect the use of functions
|
||||
# like "[]=", "[]", "or" ... in their bodies.
|
||||
ni.kids.add nj
|
||||
when not defined(release):
|
||||
ni.expls.add "one declares a quoted identifier and the other has a body and comes after it"
|
||||
elif j < i and niHasBody and not nj.hasBody and
|
||||
intersects(deps[i][0], declares):
|
||||
# Keep function declaration before function definition
|
||||
ni.kids.add nj
|
||||
when not defined(release):
|
||||
for dep in deps[i][0]:
|
||||
if dep in declares:
|
||||
ni.expls.add "one declares \"" & idNames[dep] & "\" and the other defines it"
|
||||
else:
|
||||
for d in declares:
|
||||
if uses.contains(d):
|
||||
let oldLen = res.len
|
||||
if visit(j, all, res, deps):
|
||||
result = true
|
||||
# rollback what we did, it turned out to be a dependency that caused
|
||||
# trouble:
|
||||
for k in oldLen..<res.len:
|
||||
res.sons[k].flags = res.sons[k].flags - {nfPermMark, nfTempMark}
|
||||
if oldLen != res.len: res.sons.setLen oldLen
|
||||
break
|
||||
n.flags = n.flags + {nfPermMark} - {nfTempMark}
|
||||
res.add n
|
||||
ni.kids.add nj
|
||||
when not defined(release):
|
||||
ni.expls.add "one declares \"" & idNames[d] & "\" and the other uses it"
|
||||
|
||||
proc reorder*(n: PNode): PNode =
|
||||
proc strongConnect(v: var DepN, idx: var int, s: var seq[DepN],
|
||||
res: var seq[seq[DepN]]) =
|
||||
# Recursive part of trajan's algorithm
|
||||
v.idx = idx
|
||||
v.lowLink = idx
|
||||
inc idx
|
||||
s.add v
|
||||
v.onStack = true
|
||||
for w in v.kids.mitems:
|
||||
if w.idx < 0:
|
||||
strongConnect(w, idx, s, res)
|
||||
v.lowLink = min(v.lowLink, w.lowLink)
|
||||
elif w.onStack:
|
||||
v.lowLink = min(v.lowLink, w.idx)
|
||||
if v.lowLink == v.idx:
|
||||
var comp = newSeq[DepN]()
|
||||
while true:
|
||||
var w = s.pop
|
||||
w.onStack = false
|
||||
comp.add w
|
||||
if w.id == v.id: break
|
||||
res.add comp
|
||||
|
||||
proc getStrongComponents(g: var DepG): seq[seq[DepN]] =
|
||||
## Tarjan's algorithm. Performs a topological sort
|
||||
## and detects strongly connected components.
|
||||
result = newSeq[seq[DepN]]()
|
||||
var s = newSeq[DepN]()
|
||||
var idx = 0
|
||||
for v in g.mitems:
|
||||
if v.idx < 0:
|
||||
strongConnect(v, idx, s, result)
|
||||
|
||||
proc hasForbiddenPragma(n: PNode): bool =
|
||||
# Checks if the tree node has some pragmas that do not
|
||||
# play well with reordering, like the push/pop pragma
|
||||
for a in n:
|
||||
if a.kind == nkPragma and a[0].kind == nkIdent and
|
||||
a[0].ident.s == "push":
|
||||
return true
|
||||
|
||||
proc reorder*(graph: ModuleGraph, n: PNode, module: PSym, cache: IdentCache): PNode =
|
||||
if n.hasForbiddenPragma:
|
||||
return n
|
||||
var includedFiles = initIntSet()
|
||||
let mpath = module.fileIdx.toFullPath
|
||||
let n = expandIncludes(graph, module, n, mpath,
|
||||
includedFiles, cache).splitSections
|
||||
result = newNodeI(nkStmtList, n.info)
|
||||
var deps = newSeq[(IntSet, IntSet)](n.len)
|
||||
for i in 0..<n.len:
|
||||
|
|
@ -88,15 +439,6 @@ proc reorder*(n: PNode): PNode =
|
|||
deps[i][1] = initIntSet()
|
||||
computeDeps(n[i], deps[i][0], deps[i][1], true)
|
||||
|
||||
for i in 0 .. n.len-1:
|
||||
discard visit(i, n, result, deps)
|
||||
for i in 0..<result.len:
|
||||
result.sons[i].flags = result.sons[i].flags - {nfTempMark, nfPermMark}
|
||||
when false:
|
||||
# reverse the result:
|
||||
let L = result.len-1
|
||||
for i in 0 .. result.len div 2:
|
||||
result.sons[i].flags = result.sons[i].flags - {nfTempMark, nfPermMark}
|
||||
result.sons[L - i].flags = result.sons[L - i].flags - {nfTempMark, nfPermMark}
|
||||
swap(result.sons[i], result.sons[L - i])
|
||||
#echo result
|
||||
var g = buildGraph(n, deps)
|
||||
let comps = getStrongComponents(g)
|
||||
mergeSections(comps, result)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue