import std/[assertions, math, tables] 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) # Make PNode from sem pass assigned to graph.systemModule.ast let oldCmd = graph.config.cmd graph.config.cmd = cmdIdeTools graph.compilePipelineSystemModule() graph.config.cmd = oldCmd result = graph proc getSystemNif(graph: ModuleGraph): seq[string] = result = newSeqOfCap[string](graph.ifaces.len) for i, iface in graph.ifaces.mpairs: if iface.module != nil: let n = iface.module.ast assert n != nil # if nil is not assigned, it generates large NIF iface.module.ast = nil result.add saveNifToBuffer(n, graph.config, iface.module) #writeFile(iface.module.name.s & ".nif", result[^1]) proc sem(graph: ModuleGraph; path: AbsoluteFile): (PNode, PSym) = result = (nil, 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, 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 result = (semWithPContext(ctx, sl), module) 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] checkedSyms: Table[ItemId, PSym] # used to check if each PSym has unique ItemId # and also prevents inifinite loop checkedTypes: Table[ItemId, PType]# used like checkedSyms 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 not eqlItemId(x.itemId, y.itemId, c): echo "symbol itemId mismatch" result = false elif c.checkedSyms.hasKeyOrPut(y.itemId, y): if c.checkedSyms[y.itemId] == y: result = true else: echo "detected duplicated symbol ItemId:" debug(x) debug(c.checkedSyms[y.itemId]) debug(y) result = false elif x.name.s != y.name.s: echo "symbol name mismatch: ", x.name.s, "/", y.name.s result = false elif x.kind != y.kind: echo "symbol kind mismatch: ", x.kind, "/", y.kind result = false elif x.magic != y.magic: echo "symbol magic mismatch: ", x.magic, "/", y.magic result = false elif x.kind != skPackage and not eql(x.info, y.info, c): # fileIndex of info of skPackage is just a path of first semchecked module in the package echo "symbol line info mismatch" result = false elif x.kind != skModule and x.flags != y.flags: # TODO: check the flag of skModule echo "symbol flag mismatch: ", x.flags, "/", y.flags result = false elif x.options != y.options: echo "symbol options mismatch: ", x.options, "/", y.options result = false elif not eqlSymPos(x, y, c): result = false elif x.offset != y.offset: echo "symbol offset mismatch: ", x.offset, "/", y.offset 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 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 elif not eql(x.ast, y.ast, c): echo "symbol ast mismatch" result = false elif not eql(x.constraint, y.constraint, c): echo "symbol constraint mismatch" result = false elif not eql(x.instantiatedFrom, y.instantiatedFrom, c): echo "symbol instantiatedFrom mismatch" 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 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 c.checkedTypes.hasKeyOrPut(y.itemId, y): result = true #[ if c.checkedTypes[y.itemId] == y: result = true else: echo "detected duplicated type ItemId:" debug(x) debug(c.checkedTypes[y.itemId]) debug(y) 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 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 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, " at ", `$`(c.confX, x.info) 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) if y.sym == nil: echo "y.sym = nil" else: debug(y.sym) debug(x.sym.typ) debug(y.sym.typ) of nkCharLit .. nkUInt64Lit, nkStrLit .. nkTripleStrLit: result = sameValue(x, y) of nkFloatLit .. nkFloat128Lit: # want to know if x and y are identical float value. # so x == y doesn't work if both x and y are NaN or x == 0 and y == -0. let xc = classify(x.floatVal) let yc = classify(y.floatVal) if xc == yc: if xc in {fcNormal, fcSubnormal}: if x.floatVal != y.floatVal: echo "float literal mismatch: ", x.floatVal, "/", y.floatVal result = false else: result = true else: result = true else: echo "float literal mismatch: ", xc, "/", yc result = false 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; systemNif: openArray[string]) = let fullPath = TestCodeDir / RelativeFile(src) let (n, module) = sem(graph, fullPath) assert n != nil, "failed to sem " & $fullPath assert module.owner.kind == skPackage #debug(n) let nif = saveNifToBuffer(n, graph.config, module) #echo nif #echo "NIF size of ", src, ": ", nif.len #writeFile(src & ".nif", nif) # Don't reuse the ModuleGraph used for semcheck when load NIF. var graphForLoad = newModuleGraph(newIdentCache(), newConfigRefForTest()) var prog = NifProgram() for sysNif in systemNif: discard loadNifFromBuffer(sysNif, graphForLoad, prog) let n2 = loadNifFromBuffer(nif, graphForLoad, prog) #debug(n2) var c = EqlContext(confX: graph.config, confY: graphForLoad.config) assert eql(n, n2, c), "test failed: " & $fullPath var conf = newConfigRefForTest() var cache = newIdentCache() var graph = newModuleGraphForSem(cache, conf) let systemNif = getSystemNif(graph) testNifEncDec(graph, "modtest1.nim", systemNif) testNifEncDec(graph, "modtestliterals.nim", systemNif) testNifEncDec(graph, "modtesttypesections.nim", systemNif) #testNifEncDec(graph, "modtestpragmas.nim", systemNif) testNifEncDec(graph, "modtestprocs.nim", systemNif) #testNifEncDec(graph, "modteststatements.nim", systemNif) #testNifEncDec(graph, "modtestgenerics.nim", systemNif) #testNifEncDec(graph, "modtestexprs.nim", systemNif)