#
#
# The Nim Compiler
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements the merge operation of 2 different C files. This
## is needed for incremental compilation.
import
ast, ropes, options, strutils, nimlexbase, cgendata, rodutils,
intsets, llstream, tables, modulegraphs, pathutils
# Careful! Section marks need to contain a tabulator so that they cannot
# be part of C string literals.
const
CFileSectionNames: array[TCFileSection, string] = [
cfsHeaders: "NIM_merge_HEADERS",
cfsFrameDefines: "NIM_merge_FRAME_DEFINES",
cfsForwardTypes: "NIM_merge_FORWARD_TYPES",
cfsTypes: "NIM_merge_TYPES",
cfsSeqTypes: "NIM_merge_SEQ_TYPES",
cfsTypeInfo: "NIM_merge_TYPE_INFO",
cfsProcHeaders: "NIM_merge_PROC_HEADERS",
cfsData: "NIM_merge_DATA",
cfsVars: "NIM_merge_VARS",
cfsProcs: "NIM_merge_PROCS",
cfsInitProc: "NIM_merge_INIT_PROC",
cfsDatInitProc: "NIM_merge_DATINIT_PROC",
cfsTypeInit1: "NIM_merge_TYPE_INIT1",
cfsTypeInit3: "NIM_merge_TYPE_INIT3",
cfsDynLibInit: "NIM_merge_DYNLIB_INIT"
]
CProcSectionNames: array[TCProcSection, string] = [
cpsLocals: "NIM_merge_PROC_LOCALS",
cpsInit: "NIM_merge_PROC_INIT",
cpsStmts: "NIM_merge_PROC_BODY"
]
NimMergeEndMark = "/*\tNIM_merge_END:*/"
proc genSectionStart*(fs: TCFileSection; conf: ConfigRef): Rope =
# useful for debugging and only adds at most a few lines in each file
result.add("\n/* section: ")
result.add(CFileSectionNames[fs])
result.add(" */\n")
if compilationCachePresent(conf):
result = nil
result.add("\n/*\t")
result.add(CFileSectionNames[fs])
result.add(":*/\n")
proc genSectionEnd*(fs: TCFileSection; conf: ConfigRef): Rope =
if compilationCachePresent(conf):
result = rope(NimMergeEndMark & "\n")
proc genSectionStart*(ps: TCProcSection; conf: ConfigRef): Rope =
if compilationCachePresent(conf):
result = rope("")
result.add("\n/*\t")
result.add(CProcSectionNames[ps])
result.add(":*/\n")
proc genSectionEnd*(ps: TCProcSection; conf: ConfigRef): Rope =
if compilationCachePresent(conf):
result = rope(NimMergeEndMark & "\n")
proc writeTypeCache(a: TypeCache, s: var string) =
var i = 0
for id, value in pairs(a):
if i == 10:
i = 0
s.add('\L')
else:
s.add(' ')
encodeStr($id, s)
s.add(':')
encodeStr($value, s)
inc i
s.add('}')
proc writeIntSet(a: IntSet, s: var string) =
var i = 0
for x in items(a):
if i == 10:
i = 0
s.add('\L')
else:
s.add(' ')
encodeVInt(x, s)
inc i
s.add('}')
proc genMergeInfo*(m: BModule): Rope =
if not compilationCachePresent(m.config): return nil
var s = "/*\tNIM_merge_INFO:\n"
s.add("typeCache:{")
writeTypeCache(m.typeCache, s)
s.add("declared:{")
writeIntSet(m.declaredThings, s)
when false:
s.add("typeInfo:{")
writeIntSet(m.typeInfoMarker, s)
s.add("labels:")
encodeVInt(m.labels, s)
s.add(" flags:")
encodeVInt(cast[int](m.flags), s)
s.add("\n*/")
result = s.rope
template `^`(pos: int): untyped = L.buf[pos]
proc skipWhite(L: var TBaseLexer) =
var pos = L.bufpos
while true:
case ^pos
of CR: pos = nimlexbase.handleCR(L, pos)
of LF: pos = nimlexbase.handleLF(L, pos)
of ' ': inc pos
else: break
L.bufpos = pos
proc skipUntilCmd(L: var TBaseLexer) =
var pos = L.bufpos
while true:
case ^pos
of CR: pos = nimlexbase.handleCR(L, pos)
of LF: pos = nimlexbase.handleLF(L, pos)
of '\0': break
of '/':
if ^(pos+1) == '*' and ^(pos+2) == '\t':
inc pos, 3
break
inc pos
else: inc pos
L.bufpos = pos
proc atEndMark(buf: cstring, pos: int): bool =
var s = 0
while s < NimMergeEndMark.len and buf[pos+s] == NimMergeEndMark[s]: inc s
result = s == NimMergeEndMark.len
proc readVerbatimSection(L: var TBaseLexer): Rope =
var pos = L.bufpos
var r = newStringOfCap(30_000)
while true:
case L.buf[pos]
of CR:
pos = nimlexbase.handleCR(L, pos)
r.add('\L')
of LF:
pos = nimlexbase.handleLF(L, pos)
r.add('\L')
of '\0':
doAssert(false, "ccgmerge: expected: " & NimMergeEndMark)
break
else:
if atEndMark(L.buf, pos):
inc pos, NimMergeEndMark.len
break
r.add(L.buf[pos])
inc pos
L.bufpos = pos
result = r.rope
proc readKey(L: var TBaseLexer, result: var string) =
var pos = L.bufpos
setLen(result, 0)
while L.buf[pos] in IdentChars:
result.add(L.buf[pos])
inc pos
if L.buf[pos] != ':': doAssert(false, "ccgmerge: ':' expected")
L.bufpos = pos + 1 # skip ':'
proc readTypeCache(L: var TBaseLexer, result: var TypeCache) =
if ^L.bufpos != '{': doAssert(false, "ccgmerge: '{' expected")
inc L.bufpos
while ^L.bufpos != '}':
skipWhite(L)
var key = decodeStr(L.buf, L.bufpos)
if ^L.bufpos != ':': doAssert(false, "ccgmerge: ':' expected")
inc L.bufpos
discard decodeStr(L.buf, L.bufpos)
inc L.bufpos
proc readIntSet(L: var TBaseLexer, result: var IntSet) =
if ^L.bufpos != '{': doAssert(false, "ccgmerge: '{' expected")
inc L.bufpos
while ^L.bufpos != '}':
skipWhite(L)
var key = decodeVInt(L.buf, L.bufpos)
result.incl(key)
inc L.bufpos
proc processMergeInfo(L: var TBaseLexer, m: BModule) =
var k = newStringOfCap("typeCache".len)
while true:
skipWhite(L)
if ^L.bufpos == '*' and ^(L.bufpos+1) == '/':
inc(L.bufpos, 2)
break
readKey(L, k)
case k
of "typeCache": readTypeCache(L, m.typeCache)
of "declared": readIntSet(L, m.declaredThings)
of "typeInfo":
when false: readIntSet(L, m.typeInfoMarker)
of "labels": m.labels = decodeVInt(L.buf, L.bufpos)
of "flags":
m.flags = cast[set[CodegenFlag]](decodeVInt(L.buf, L.bufpos) != 0)
else: doAssert(false, "ccgmerge: unknown key: " & k)
template withCFile(cfilename: AbsoluteFile, body: untyped) =
var s = llStreamOpen(cfilename, fmRead)
if s == nil: return
var L {.inject.}: TBaseLexer
openBaseLexer(L, s)
var k {.inject.} = newStringOfCap("NIM_merge_FORWARD_TYPES".len)
while true:
skipUntilCmd(L)
if ^L.bufpos == '\0': break
body
closeBaseLexer(L)
proc readMergeInfo*(cfilename: AbsoluteFile, m: BModule) =
## reads the merge meta information into `m`.
withCFile(cfilename):
readKey(L, k)
if k == "NIM_merge_INFO":
processMergeInfo(L, m)
break
type
TMergeSections = object
f: TCFileSections
p: TCProcSections
proc readMergeSections(cfilename: AbsoluteFile, m: var TMergeSections) =
## reads the merge sections into `m`.
withCFile(cfilename):
readKey(L, k)
if k == "NIM_merge_INFO":
discard
elif ^L.bufpos == '*' and ^(L.bufpos+1) == '/':
inc(L.bufpos, 2)
# read back into section
skipWhite(L)
var verbatim = readVerbatimSection(L)
skipWhite(L)
var sectionA = CFileSectionNames.find(k)
if sectionA > 0 and sectionA <= high(TCFileSection).int:
m.f[TCFileSection(sectionA)] = verbatim
else:
var sectionB = CProcSectionNames.find(k)
if sectionB >= 0 and sectionB <= high(TCProcSection).int:
m.p[TCProcSection(sectionB)] = verbatim
else:
doAssert(false, "ccgmerge: unknown section: " & k)
else:
doAssert(false, "ccgmerge: '*/' expected")
proc mergeRequired*(m: BModule): bool =
for i in cfsHeaders..cfsProcs:
if m.s[i] != nil:
#echo "not empty: ", i, " ", m.s[i]
return true
for i in TCProcSection:
if m.initProc.s(i) != nil:
#echo "not empty: ", i, " ", m.initProc.s[i]
return true
proc mergeFiles*(cfilename: AbsoluteFile, m: BModule) =
## merges the C file with the old version on hard disc.
var old: TMergeSections
readMergeSections(cfilename, old)
# do the merge; old section before new section:
for i in TCFileSection:
m.s[i] = old.f[i] & m.s[i]
for i in TCProcSection:
m.initProc.s(i) = old.p[i] & m.initProc.s(i)