mirror of
https://github.com/nim-lang/Nim.git
synced 2026-01-03 03:32:32 +00:00
399 lines
13 KiB
Nim
399 lines
13 KiB
Nim
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)
|