import std/assertions import "../../compiler/icnif" / [nifencoder, nifdecoder] import "../../compiler" / [idents, ast, astalgo, options, pathutils, modulegraphs, modules, msgs, pipelines, syntaxes, sem, llstream, lineinfos] # This test generates PNode by semchecks test code. # Then it is used to test icnif/nifencoder and nifdecoder. const TestCodeDir = currentSourcePath().AbsoluteFile.splitFile().dir / RelativeDir"testcode" proc newConfigRefForTest(): ConfigRef = var conf = newConfigRef() conf.setDefaultLibpath() conf.searchPaths.add(conf.libpath) excl(conf.notes, hintProcessing) excl(conf.mainPackageNotes, hintProcessing) result = conf proc newModuleGraphForSem(cache: IdentCache; conf: ConfigRef): ModuleGraph = var graph = newModuleGraph(cache, conf) graph.setPipeLinePass(SemPass) graph.compilePipelineSystemModule() result = graph proc sem(graph: ModuleGraph; path: AbsoluteFile): PNode = result = nil let fileIdx = fileInfoIdx(graph.config, path) var module = newModule(graph, fileIdx) registerModule(graph, module) var idgen = idGeneratorFromModule(module) let ctx = preparePContext(graph, module, idgen) var stream = llStreamOpen(path, fmRead) if stream == nil: rawMessage(graph.config, errCannotOpenFile, path.string) return nil var p: Parser = default(Parser) syntaxes.openParser(p, fileIdx, stream, graph.cache, graph.config) checkFirstLineIndentation(p) block processCode: if graph.stopCompile(): break processCode var n = parseTopLevelStmt(p) if n.kind == nkEmpty: break processCode # read everything, no streaming possible var sl = newNodeI(nkStmtList, n.info) sl.add n while true: var n = parseTopLevelStmt(p) if n.kind == nkEmpty: break sl.add n var semNode = semWithPContext(ctx, sl) return semNode type # Nim's AST has cycles that causes infinite recursive loop in eql procs. # this is used to prevent that happen. EqlContext = object nodeStack: seq[PNode] symStack: seq[PSym] typStack: seq[PType] confX, confY: ConfigRef # used to print the line info when there is a mismatch # and get path from FileIndex # Compare PType, PSym and PNode but ignores fields nifencoder and nifdecoder doesn't support # `x` is generated by sem.nim and `y` is decoded by icnif/nifdecoder. proc eql(x, y: PNode; c: var EqlContext): bool proc eql(x, y: PType; c: var EqlContext): bool proc eql(x, y: TLoc): bool = if x.k != y.k: echo "loc kind mismatch: ", x.k, "/", y.k result = false elif x.snippet != y.snippet: echo "loc snippet mismatch: ", x.snippet, "/", y.snippet result = false else: result = true proc eqlFileIndex(x, y: int; c: EqlContext): bool = let xpath = c.confX.toFullPath(x.FileIndex) let ypath = c.confY.toFullPath(y.FileIndex) if xpath != ypath: echo "file index mismatch: ", xpath, "/", ypath result = false else: result = true proc eql(x, y: TLineInfo; c: EqlContext): bool = # If parent PNode has a valid line info but it's child doesn't have one, # cannot translate such a tree to NIF. # Because in NIF, if a child node doesn't have line info, # nifstream assign the parent's line info to it. # So cannot have child node without line info if parent has a valid line info. if x == unknownLineInfo: result = true elif x.line != y.line: echo "line number mismatch: ", x.line, "/", y.line result = false elif x.col != y.col: echo "column number mismatch: ", x.col, "/", y.col result = false elif not eqlFileIndex(x.fileIndex.int, y.fileIndex.int, c): echo "file in line info mismatch" result = false else: result = true proc eqlSymPos(x, y: PSym; c: EqlContext): bool = if x.kind == skModule: result = eqlFileIndex(x.position, y.position, c) elif x.position != y.position: echo "symbol position mismatch: ", x.position, "/", y.position result = false else: result = true proc eqlItemId(x, y: ItemId; c: EqlContext): bool = if x.item != y.item: echo "itemId.item mismatch: ", x.item, "/", y.item result = false elif not eqlFileIndex(x.module, y.module, c): result = false else: result = true proc eql(x, y: PSym; c: var EqlContext): bool = if x == nil and y == nil: result = true elif x == nil or y == nil: echo "symbol is missing" result = false elif x.name.s != y.name.s: echo "symbol name mismatch: ", x.name.s, "/", y.name.s result = false elif not eqlItemId(x.itemId, y.itemId, c): echo "symbol itemId mismatch" result = false elif x.kind != y.kind: echo "symbol kind mismatch: ", x.kind, "/", y.kind result = false elif not eql(x.info, y.info, c): echo "symbol line info mismatch" result = false elif x.flags != y.flags: echo "symbol flag mismatch: ", x.flags, "/", y.flags result = false elif not eqlSymPos(x, y, c): result = false elif x.disamb != y.disamb: echo "symbol disamb mismatch: ", x.disamb, "/", y.disamb result = false elif not eql(x.loc, y.loc): echo "symbol.loc mismatch" result = false else: if c.symStack.len != 0: for i in countDown(c.symStack.len - 1, 0): if x == c.symStack[i]: return true c.symStack.add x if not eql(x.typ, y.typ, c): echo "symbol type mismatch:" result = false elif not eql(x.owner, y.owner, c): echo "Symbol owner mismatch:" debug(x.owner) debug(y.owner) result = false else: if x.kind in {skLet, skVar, skField, skForVar}: if not eql(x.guard, y.guard, c): echo "symbol guard mismatch" result = false elif x.bitsize != y.bitsize: echo "symbol bitsize mismatch: ", x.bitsize, "/", y.bitsize result = false elif x.alignment != y.alignment: echo "symbol alignment mismatch: ", x.alignment, "/", y.alignment result = false else: result = true else: result = true discard c.symStack.pop proc eql(x, y: PType; c: var EqlContext): bool = if x == nil and y == nil: result = true elif x == nil or y == nil: echo "type is missing" result = false elif not eqlItemId(x.itemId, y.itemId, c): echo "type itemId mismatch" result = false elif x.kind != y.kind: echo "type kind mismatch: ", x.kind, "/", y.kind result = false elif x.flags != y.flags: echo "type flag mismatch: ", x.flags, "/", y.flags result = false else: if c.typStack.len != 0: for i in countDown(c.typStack.len - 1, 0): if x == c.typStack[i]: # echo "cycle is detected in PType" return true c.typStack.add x if not eql(x.n, y.n, c): echo "type.n mismatch" debug(x.n) debug(y.n) result = false elif not eql(x.owner, y.owner, c): echo "type owner mismatch: " debug(x.owner) debug(y.owner) result = false elif not eql(x.sym, y.sym, c): echo "type sym mismatch:" debug(x.sym) debug(y.sym) result = false elif x.kidsLen != y.kidsLen: echo "type kidsLen mismatch" result = false else: result = true for i in 0 ..< x.kidsLen: if not eql(x[i], y[i], c): echo "type kids mismatch: " debug(x[i]) debug(y[i]) result = false break discard c.typStack.pop proc eql(x, y: PNode; c: var EqlContext): bool = if x == nil and y == nil: result = true elif x == nil or y == nil: result = false elif x.kind != y.kind: echo "node kind mismatch: ", x.kind, "/", y.kind result = false elif x.flags != y.flags: echo "node flag mismatch: ", x.flags, "/", y.flags debug(x) debug(y) result = false elif not eql(x.info, y. info, c): echo "node lineinfo mismatch at ", `$`(c.confX, x.info) debug(x) result = false elif x.safeLen == y.safeLen: if c.nodeStack.len != 0: for i in countDown(c.nodeStack.len - 1, 0): if x == c.nodeStack[i]: # echo "cycle is detected in PNode" return true c.nodeStack.add x if not eql(x.typ, y.typ, c): echo "PNode type mismatch at ", `$`(c.confX, x.info), ":" debug(x) debug(y) debug(x.typ) debug(y.typ) result = false else: case x.kind: of nkIdent: # these idents are generated from different IdentCache result = x.ident.s == y.ident.s if not result: echo "PNode identifier mismatch: ", `$`(c.confX, x.info), x.ident.s, "/", y.ident.s of nkSym: result = eql(x.sym, y.sym, c) if not result: echo "Symbol mismatch:" debug(x.sym) debug(y.sym) debug(x.sym.typ) debug(y.sym.typ) of nkCharLit .. nkTripleStrLit: result = sameValue(x, y) else: result = true for i in 0 ..< x.safeLen: if not eql(x[i], y[i], c): result = false break discard c.nodeStack.pop else: echo "node length mismatch" debug(x) debug(y) result = false proc testNifEncDec(graph: ModuleGraph; src: string) = let fullPath = TestCodeDir / RelativeFile(src) let n = sem(graph, fullPath) #debug(n) let nif = saveNifToBuffer(n, graph.config) #echo nif # Don't reuse the ModuleGraph used for semcheck when load NIF. var graphForLoad = newModuleGraph(newIdentCache(), newConfigRefForTest()) let n2 = loadNifFromBuffer(nif, graphForLoad) #debug(n2) var c = EqlContext(confX: graph.config, confY: graphForLoad.config) assert eql(n, n2, c) var conf = newConfigRefForTest() var cache = newIdentCache() var graph = newModuleGraphForSem(cache, conf) testNifEncDec(graph, "modtest1.nim") testNifEncDec(graph, "modtestliterals.nim") testNifEncDec(graph, "modtesttypesections.nim") testNifEncDec(graph, "modtestpragmas.nim")