Files
Nim/tests/icnif/tencode_node2node.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)