Nim/compiler/rodwrite.nim
2011-10-18 17:21:51 +02:00

544 lines
16 KiB
Nim
Executable file

#
#
# The Nimrod Compiler
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module is responsible for writing of rod files. Note that writing of
# rod files is a pass, reading of rod files is not! This is why reading and
# writing of rod files is split into two different modules.
import
intsets, os, options, strutils, nversion, ast, astalgo, msgs, platform,
condsyms, ropes, idents, crc, rodread, passes, importer, idgen, rodutils
proc rodwritePass*(): TPass
# implementation
type
TRodWriter = object of TPassContext
module: PSym
crc: TCrc32
options: TOptions
defines: string
inclDeps: string
modDeps: string
interf: string
compilerProcs: string
index, imports: TIndex
converters: string
init: string
data: string
filename: string
sstack: TSymSeq # a stack of symbols to process
tstack: TTypeSeq # a stack of types to process
files: TStringSeq
PRodWriter = ref TRodWriter
proc newRodWriter(modfilename: string, crc: TCrc32, module: PSym): PRodWriter
proc addModDep(w: PRodWriter, dep: string)
proc addInclDep(w: PRodWriter, dep: string)
proc addInterfaceSym(w: PRodWriter, s: PSym)
proc addStmt(w: PRodWriter, n: PNode)
proc writeRod(w: PRodWriter)
proc processStacks(w: PRodWriter)
proc getDefines(): string =
var it: TTabIter
var s = InitTabIter(it, gSymbols)
result = ""
while s != nil:
if s.position == 1:
if result.len != 0: add(result, " ")
add(result, s.name.s)
s = nextIter(it, gSymbols)
proc fileIdx(w: PRodWriter, filename: string): int =
for i in countup(0, high(w.files)):
if w.files[i] == filename:
return i
result = len(w.files)
setlen(w.files, result + 1)
w.files[result] = filename
proc newRodWriter(modfilename: string, crc: TCrc32, module: PSym): PRodWriter =
new(result)
result.sstack = @[]
result.tstack = @[]
InitIITable(result.index.tab)
InitIITable(result.imports.tab)
result.index.r = ""
result.imports.r = ""
result.filename = modfilename
result.crc = crc
result.module = module
result.defines = getDefines()
result.options = options.gOptions
result.files = @[]
result.inclDeps = ""
result.modDeps = ""
result.interf = newStringOfCap(2_000)
result.compilerProcs = ""
result.converters = ""
result.init = ""
result.data = newStringOfCap(12_000)
proc addModDep(w: PRodWriter, dep: string) =
if w.modDeps.len != 0: add(w.modDeps, ' ')
encodeVInt(fileIdx(w, dep), w.modDeps)
const
rodNL = "\x0A"
proc addInclDep(w: PRodWriter, dep: string) =
encodeVInt(fileIdx(w, dep), w.inclDeps)
add(w.inclDeps, " ")
encodeVInt(crcFromFile(dep), w.inclDeps)
add(w.inclDeps, rodNL)
proc pushType(w: PRodWriter, t: PType) =
# check so that the stack does not grow too large:
if IiTableGet(w.index.tab, t.id) == invalidKey:
w.tstack.add(t)
proc pushSym(w: PRodWriter, s: PSym) =
# check so that the stack does not grow too large:
if IiTableGet(w.index.tab, s.id) == invalidKey:
w.sstack.add(s)
proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode,
result: var string) =
if n == nil:
# nil nodes have to be stored too:
result.add("()")
return
result.add('(')
encodeVInt(ord(n.kind), result)
# we do not write comments for now
# Line information takes easily 20% or more of the filesize! Therefore we
# omit line information if it is the same as the father's line information:
if finfo.fileIndex != n.info.fileIndex:
result.add('?')
encodeVInt(n.info.col, result)
result.add(',')
encodeVInt(n.info.line, result)
result.add(',')
encodeVInt(fileIdx(w, toFilename(n.info)), result)
elif finfo.line != n.info.line:
result.add('?')
encodeVInt(n.info.col, result)
result.add(',')
encodeVInt(n.info.line, result)
elif finfo.col != n.info.col:
result.add('?')
encodeVInt(n.info.col, result)
# No need to output the file index, as this is the serialization of one
# file.
var f = n.flags * PersistentNodeFlags
if f != {}:
result.add('$')
encodeVInt(cast[int32](f), result)
if n.typ != nil:
result.add('^')
encodeVInt(n.typ.id, result)
pushType(w, n.typ)
case n.kind
of nkCharLit..nkInt64Lit:
if n.intVal != 0:
result.add('!')
encodeVBiggestInt(n.intVal, result)
of nkFloatLit..nkFloat64Lit:
if n.floatVal != 0.0:
result.add('!')
encodeStr($n.floatVal, result)
of nkStrLit..nkTripleStrLit:
if n.strVal != "":
result.add('!')
encodeStr(n.strVal, result)
of nkIdent:
result.add('!')
encodeStr(n.ident.s, result)
of nkSym:
result.add('!')
encodeVInt(n.sym.id, result)
pushSym(w, n.sym)
else:
for i in countup(0, sonsLen(n) - 1):
encodeNode(w, n.info, n.sons[i], result)
add(result, ')')
proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) =
var oldLen = result.len
result.add('<')
if loc.k != low(loc.k): encodeVInt(ord(loc.k), result)
if loc.s != low(loc.s):
add(result, '*')
encodeVInt(ord(loc.s), result)
if loc.flags != {}:
add(result, '$')
encodeVInt(cast[int32](loc.flags), result)
if loc.t != nil:
add(result, '^')
encodeVInt(cast[int32](loc.t.id), result)
pushType(w, loc.t)
if loc.r != nil:
add(result, '!')
encodeStr(ropeToStr(loc.r), result)
if loc.a != 0:
add(result, '?')
encodeVInt(loc.a, result)
if oldlen + 1 == result.len:
# no data was necessary, so remove the '<' again:
setLen(result, oldLen)
else:
add(result, '>')
proc encodeType(w: PRodWriter, t: PType, result: var string) =
if t == nil:
# nil nodes have to be stored too:
result.add("[]")
return
# we need no surrounding [] here because the type is in a line of its own
if t.kind == tyForward: InternalError("encodeType: tyForward")
encodeVInt(ord(t.kind), result)
add(result, '+')
encodeVInt(t.id, result)
if t.n != nil:
encodeNode(w, UnknownLineInfo(), t.n, result)
if t.flags != {}:
add(result, '$')
encodeVInt(cast[int32](t.flags), result)
if t.callConv != low(t.callConv):
add(result, '?')
encodeVInt(ord(t.callConv), result)
if t.owner != nil:
add(result, '*')
encodeVInt(t.owner.id, result)
pushSym(w, t.owner)
if t.sym != nil:
add(result, '&')
encodeVInt(t.sym.id, result)
pushSym(w, t.sym)
if t.size != - 1:
add(result, '/')
encodeVBiggestInt(t.size, result)
if t.align != 2:
add(result, '=')
encodeVInt(t.align, result)
if t.containerID != 0:
add(result, '@')
encodeVInt(t.containerID, result)
encodeLoc(w, t.loc, result)
for i in countup(0, sonsLen(t) - 1):
if t.sons[i] == nil:
add(result, "^()")
else:
add(result, '^')
encodeVInt(t.sons[i].id, result)
pushType(w, t.sons[i])
proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) =
add(result, '|')
encodeVInt(ord(lib.kind), result)
add(result, '|')
encodeStr(ropeToStr(lib.name), result)
add(result, '|')
encodeNode(w, info, lib.path, result)
proc encodeSym(w: PRodWriter, s: PSym, result: var string) =
if s == nil:
# nil nodes have to be stored too:
result.add("{}")
return
# we need no surrounding {} here because the symbol is in a line of its own
encodeVInt(ord(s.kind), result)
result.add('+')
encodeVInt(s.id, result)
result.add('&')
encodeStr(s.name.s, result)
if s.typ != nil:
result.add('^')
encodeVInt(s.typ.id, result)
pushType(w, s.typ)
result.add('?')
if s.info.col != -1'i16: encodeVInt(s.info.col, result)
result.add(',')
if s.info.line != -1'i16: encodeVInt(s.info.line, result)
result.add(',')
encodeVInt(fileIdx(w, toFilename(s.info)), result)
if s.owner != nil:
result.add('*')
encodeVInt(s.owner.id, result)
pushSym(w, s.owner)
if s.flags != {}:
result.add('$')
encodeVInt(cast[int32](s.flags), result)
if s.magic != mNone:
result.add('@')
encodeVInt(ord(s.magic), result)
if s.ast != nil:
var codeAst: PNode = nil
if not astNeeded(s):
codeAst = s.ast.sons[codePos]
# ugly hack to not store the AST:
s.ast.sons[codePos] = nil
encodeNode(w, s.info, s.ast, result)
if codeAst != nil:
# resore the AST:
s.ast.sons[codePos] = codeAst
if s.options != w.options:
result.add('!')
encodeVInt(cast[int32](s.options), result)
if s.position != 0:
result.add('%')
encodeVInt(s.position, result)
if s.offset != - 1:
result.add('`')
encodeVInt(s.offset, result)
encodeLoc(w, s.loc, result)
if s.annex != nil: encodeLib(w, s.annex, s.info, result)
proc addToIndex(w: var TIndex, key, val: int) =
if key - w.lastIdxKey == 1:
# we do not store a key-diff of 1 to safe space
encodeVInt(val - w.lastIdxVal, w.r)
else:
encodeVInt(key - w.lastIdxKey, w.r)
add(w.r, ' ')
encodeVInt(val - w.lastIdxVal, w.r)
add(w.r, rodNL)
w.lastIdxKey = key
w.lastIdxVal = val
IiTablePut(w.tab, key, val)
var debugWritten: TIntSet
proc symStack(w: PRodWriter) =
var i = 0
while i < len(w.sstack):
var s = w.sstack[i]
if IiTableGet(w.index.tab, s.id) == invalidKey:
var m = getModule(s)
if m == nil: InternalError("symStack: module nil: " & s.name.s)
if (m.id == w.module.id) or (sfFromGeneric in s.flags):
# put definition in here
var L = w.data.len
addToIndex(w.index, s.id, L)
#intSetIncl(debugWritten, s.id)
encodeSym(w, s, w.data)
add(w.data, rodNL)
if sfExported in s.flags:
encodeStr(s.name.s, w.interf)
add(w.interf, ' ')
encodeVInt(s.id, w.interf)
add(w.interf, rodNL)
if sfCompilerProc in s.flags:
encodeStr(s.name.s, w.compilerProcs)
add(w.compilerProcs, ' ')
encodeVInt(s.id, w.compilerProcs)
add(w.compilerProcs, rodNL)
if s.kind == skConverter:
if w.converters.len != 0: add(w.converters, ' ')
encodeVInt(s.id, w.converters)
elif IiTableGet(w.imports.tab, s.id) == invalidKey:
addToIndex(w.imports, s.id, m.id) #if not Contains(debugWritten, s.id):
# MessageOut(w.filename);
# debug(s.owner);
# debug(s);
# InternalError('BUG!!!!');
#end
inc(i)
setlen(w.sstack, 0)
proc typeStack(w: PRodWriter) =
var i = 0
while i < len(w.tstack):
if IiTableGet(w.index.tab, w.tstack[i].id) == invalidKey:
var L = w.data.len
addToIndex(w.index, w.tstack[i].id, L)
encodeType(w, w.tstack[i], w.data)
add(w.data, rodNL)
inc(i)
setlen(w.tstack, 0)
proc processStacks(w: PRodWriter) =
while (len(w.tstack) > 0) or (len(w.sstack) > 0):
symStack(w)
typeStack(w)
proc rawAddInterfaceSym(w: PRodWriter, s: PSym) =
pushSym(w, s)
processStacks(w)
proc addInterfaceSym(w: PRodWriter, s: PSym) =
if w == nil: return
if {sfExported, sfCompilerProc} * s.flags != {}:
rawAddInterfaceSym(w, s)
proc addStmt(w: PRodWriter, n: PNode) =
encodeVInt(w.data.len, w.init)
add(w.init, rodNL)
encodeNode(w, UnknownLineInfo(), n, w.data)
add(w.data, rodNL)
processStacks(w)
proc writeRod(w: PRodWriter) =
processStacks(w)
var f: TFile
if not open(f, completeGeneratedFilePath(changeFileExt(w.filename, "rod")),
fmWrite):
return
# write header:
f.write("NIM:")
f.write(FileVersion)
f.write(rodNL)
var id = "ID:"
encodeVInt(w.module.id, id)
f.write(id)
f.write(rodNL)
var crc = "CRC:"
encodeVInt(w.crc, crc)
f.write(crc)
f.write(rodNL)
var options = "OPTIONS:"
encodeVInt(cast[int32](w.options), options)
f.write(options)
f.write(rodNL)
f.write("DEFINES:")
f.write(w.defines)
f.write(rodNL)
var files = "FILES(" & rodNL
for i in countup(0, high(w.files)):
encodeStr(w.files[i], files)
files.add(rodNL)
f.write(files)
f.write(')' & rodNL)
f.write("INCLUDES(" & rodNL)
f.write(w.inclDeps)
f.write(')' & rodNL)
f.write("DEPS:")
f.write(w.modDeps)
f.write(rodNL)
f.write("INTERF(" & rodNL)
f.write(w.interf)
f.write(')' & rodNL)
f.write("COMPILERPROCS(" & rodNL)
f.write(w.compilerProcs)
f.write(')' & rodNL)
f.write("INDEX(" & rodNL)
f.write(w.index.r)
f.write(')' & rodNL)
f.write("IMPORTS(" & rodNL)
f.write(w.imports.r)
f.write(')' & rodNL)
f.write("CONVERTERS:")
f.write(w.converters)
f.write(rodNL)
f.write("INIT(" & rodNL)
f.write(w.init)
f.write(')' & rodNL)
f.write("DATA(" & rodNL)
f.write(w.data)
f.write(')' & rodNL)
f.close()
#MessageOut('interf ' + ToString(ropeLen(w.interf)))
#MessageOut('index ' + ToString(ropeLen(w.indexRope)))
#MessageOut('init ' + ToString(ropeLen(w.init)))
#MessageOut('data ' + ToString(ropeLen(w.data)))
proc process(c: PPassContext, n: PNode): PNode =
result = n
if c == nil: return
var w = PRodWriter(c)
case n.kind
of nkStmtList:
for i in countup(0, sonsLen(n) - 1): discard process(c, n.sons[i])
of nkTemplateDef, nkMacroDef:
var s = n.sons[namePos].sym
addInterfaceSym(w, s)
of nkProcDef, nkMethodDef, nkIteratorDef, nkConverterDef:
var s = n.sons[namePos].sym
if s == nil: InternalError(n.info, "rodwrite.process")
if (n.sons[codePos] != nil) or (s.magic != mNone) or
not (sfForward in s.flags):
addInterfaceSym(w, s)
of nkVarSection:
for i in countup(0, sonsLen(n) - 1):
var a = n.sons[i]
if a.kind == nkCommentStmt: continue
if a.kind != nkIdentDefs: InternalError(a.info, "rodwrite.process")
addInterfaceSym(w, a.sons[0].sym)
of nkConstSection:
for i in countup(0, sonsLen(n) - 1):
var a = n.sons[i]
if a.kind == nkCommentStmt: continue
if a.kind != nkConstDef: InternalError(a.info, "rodwrite.process")
addInterfaceSym(w, a.sons[0].sym)
of nkTypeSection:
for i in countup(0, sonsLen(n) - 1):
var a = n.sons[i]
if a.kind == nkCommentStmt: continue
if a.sons[0].kind != nkSym: InternalError(a.info, "rodwrite.process")
var s = a.sons[0].sym
addInterfaceSym(w, s)
# this takes care of enum fields too
# Note: The check for ``s.typ.kind = tyEnum`` is wrong for enum
# type aliasing! Otherwise the same enum symbol would be included
# several times!
#
# if (a.sons[2] <> nil) and (a.sons[2].kind = nkEnumTy) then begin
# a := s.typ.n;
# for j := 0 to sonsLen(a)-1 do
# addInterfaceSym(w, a.sons[j].sym);
# end
of nkImportStmt:
for i in countup(0, sonsLen(n) - 1): addModDep(w, getModuleFile(n.sons[i]))
addStmt(w, n)
of nkFromStmt:
addModDep(w, getModuleFile(n.sons[0]))
addStmt(w, n)
of nkIncludeStmt:
for i in countup(0, sonsLen(n) - 1): addInclDep(w, getModuleFile(n.sons[i]))
of nkPragma:
addStmt(w, n)
else:
nil
proc myOpen(module: PSym, filename: string): PPassContext =
if module.id < 0: InternalError("rodwrite: module ID not set")
var w = newRodWriter(filename, rodread.GetCRC(filename), module)
rawAddInterfaceSym(w, module)
result = w
proc myClose(c: PPassContext, n: PNode): PNode =
var w = PRodWriter(c)
writeRod(w)
result = n
idgen.saveMaxIds(options.projectPath / options.projectName)
proc rodwritePass(): TPass =
initPass(result)
if optSymbolFiles in gGlobalOptions:
result.open = myOpen
result.close = myClose
result.process = process
debugWritten = initIntSet()