diff options
Diffstat (limited to 'compiler')
77 files changed, 1884 insertions, 1025 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 6591105d4..694944631 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -657,7 +657,7 @@ type mNHint, mNWarning, mNError, mInstantiationInfo, mGetTypeInfo, mNimvm, mIntDefine, mStrDefine, mRunnableExamples, - mException, mBuiltinType + mException, mBuiltinType, mSymOwner # things that we can evaluate safely at compile time, even if not asked for it: const @@ -1594,15 +1594,17 @@ proc getInt*(a: PNode): BiggestInt = case a.kind of nkCharLit..nkUInt64Lit: result = a.intVal else: + raiseRecoverableError("cannot extract number from invalid AST node") #internalError(a.info, "getInt") - doAssert false, "getInt" + #doAssert false, "getInt" #result = 0 proc getFloat*(a: PNode): BiggestFloat = case a.kind of nkFloatLiterals: result = a.floatVal else: - doAssert false, "getFloat" + raiseRecoverableError("cannot extract number from invalid AST node") + #doAssert false, "getFloat" #internalError(a.info, "getFloat") #result = 0.0 @@ -1616,7 +1618,8 @@ proc getStr*(a: PNode): string = else: result = nil else: - doAssert false, "getStr" + raiseRecoverableError("cannot extract string from invalid AST node") + #doAssert false, "getStr" #internalError(a.info, "getStr") #result = "" @@ -1625,7 +1628,8 @@ proc getStrOrChar*(a: PNode): string = of nkStrLit..nkTripleStrLit: result = a.strVal of nkCharLit..nkUInt64Lit: result = $chr(int(a.intVal)) else: - doAssert false, "getStrOrChar" + raiseRecoverableError("cannot extract string from invalid AST node") + #doAssert false, "getStrOrChar" #internalError(a.info, "getStrOrChar") #result = "" diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 34963ee83..b716882dc 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -549,7 +549,8 @@ proc strTableAdd*(t: var TStrTable, n: PSym) = strTableRawInsert(t.data, n) inc(t.counter) -proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.discardable.} = +proc strTableInclReportConflict*(t: var TStrTable, n: PSym; + onConflictKeepOld = false): PSym = # returns true if n is already in the string table: # It is essential that `n` is written nevertheless! # This way the newest redefinition is picked by the semantic analyses! @@ -564,13 +565,13 @@ proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.d # So it is possible the very same sym is added multiple # times to the symbol table which we allow here with the 'it == n' check. if it.name.id == n.name.id: - if it == n: return false + if it == n: return nil replaceSlot = h h = nextTry(h, high(t.data)) if replaceSlot >= 0: if not onConflictKeepOld: t.data[replaceSlot] = n # overwrite it with newer definition! - return true # found it + return t.data[replaceSlot] # found it elif mustRehash(len(t.data), t.counter): strTableEnlarge(t) strTableRawInsert(t.data, n) @@ -578,7 +579,11 @@ proc strTableIncl*(t: var TStrTable, n: PSym; onConflictKeepOld=false): bool {.d assert(t.data[h] == nil) t.data[h] = n inc(t.counter) - result = false + result = nil + +proc strTableIncl*(t: var TStrTable, n: PSym; + onConflictKeepOld = false): bool {.discardable.} = + result = strTableInclReportConflict(t, n, onConflictKeepOld) != nil proc strTableGet*(t: TStrTable, name: PIdent): PSym = var h: Hash = name.h and high(t.data) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index d9e0ec479..fbedf6cc6 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1600,10 +1600,11 @@ proc genSwap(p: BProc, e: PNode, d: var TLoc) = genAssignment(p, a, b, {}) genAssignment(p, b, tmp, {}) -proc rdSetElemLoc(conf: ConfigRef; a: TLoc, setType: PType): Rope = +proc rdSetElemLoc(conf: ConfigRef; a: TLoc, typ: PType): Rope = # read a location of an set element; it may need a subtraction operation # before the set operation result = rdCharLoc(a) + let setType = typ.skipTypes(abstractPtrs) assert(setType.kind == tySet) if firstOrd(conf, setType) != 0: result = "($1- $2)" % [result, rope(firstOrd(conf, setType))] diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index 067a60c57..144a45816 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -12,7 +12,7 @@ import ast, astalgo, ropes, options, strutils, nimlexbase, msgs, cgendata, rodutils, - intsets, platform, llstream, tables, sighashes + intsets, platform, llstream, tables, sighashes, pathutils # Careful! Section marks need to contain a tabulator so that they cannot # be part of C string literals. @@ -226,7 +226,7 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) = when not defined(nimhygiene): {.pragma: inject.} -template withCFile(cfilename: string, body: untyped) = +template withCFile(cfilename: AbsoluteFile, body: untyped) = var s = llStreamOpen(cfilename, fmRead) if s == nil: return var L {.inject.}: TBaseLexer @@ -238,7 +238,7 @@ template withCFile(cfilename: string, body: untyped) = body closeBaseLexer(L) -proc readMergeInfo*(cfilename: string, m: BModule) = +proc readMergeInfo*(cfilename: AbsoluteFile, m: BModule) = ## reads the merge meta information into `m`. withCFile(cfilename): readKey(L, k) @@ -251,7 +251,7 @@ type f: TCFileSections p: TCProcSections -proc readMergeSections(cfilename: string, m: var TMergeSections) = +proc readMergeSections(cfilename: AbsoluteFile, m: var TMergeSections) = ## reads the merge sections into `m`. withCFile(cfilename): readKey(L, k) @@ -285,7 +285,7 @@ proc mergeRequired*(m: BModule): bool = #echo "not empty: ", i, " ", m.initProc.s[i] return true -proc mergeFiles*(cfilename: string, m: BModule) = +proc mergeFiles*(cfilename: AbsoluteFile, m: BModule) = ## merges the C file with the old version on hard disc. var old: TMergeSections readMergeSections(cfilename, old) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 59fbfc3e1..52a4a72f2 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -162,9 +162,9 @@ proc mapType(conf: ConfigRef; typ: PType): TCTypeKind = var base = skipTypes(typ.lastSon, typedescInst) case base.kind of tyOpenArray, tyArray, tyVarargs: result = ctPtrToArray - #of tySet: - # if mapSetType(base) == ctArray: result = ctPtrToArray - # else: result = ctPtr + of tySet: + if mapSetType(conf, base) == ctArray: result = ctPtrToArray + else: result = ctPtr # XXX for some reason this breaks the pegs module else: result = ctPtr of tyPointer: result = ctPtr @@ -449,7 +449,9 @@ proc genRecordFieldsAux(m: BModule, n: PNode, of nkRecCase: if n.sons[0].kind != nkSym: internalError(m.config, n.info, "genRecordFieldsAux") add(result, genRecordFieldsAux(m, n.sons[0], accessExpr, rectype, check)) - let uname = rope(mangle(n.sons[0].sym.name.s) & 'U') + # prefix mangled name with "_U" to avoid clashes with other field names, + # since identifiers are not allowed to start with '_' + let uname = rope("_U" & mangle(n.sons[0].sym.name.s)) let ae = if accessExpr != nil: "$1.$2" % [accessExpr, uname] else: uname var unionBody: Rope = nil @@ -639,10 +641,11 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = compileToCpp(m): "&" else: "*" var et = origTyp.skipTypes(abstractInst).lastSon var etB = et.skipTypes(abstractInst) - if etB.kind in {tyArray, tyOpenArray, tyVarargs}: - # this is correct! sets have no proper base type, so we treat - # ``var set[char]`` in `getParamTypeDesc` - et = elemType(etB) + if mapType(m.config, t) == ctPtrToArray: + if etB.kind == tySet: + et = getSysType(m.g.graph, unknownLineInfo(), tyUInt8) + else: + et = elemType(etB) etB = et.skipTypes(abstractInst) star[0] = '*' case etB.kind diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index 75cd3d35d..6d2f33f2d 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -92,6 +92,7 @@ proc mangle*(name: string): string = of '+': special "plus" of '-': special "minus" of '/': special "slash" + of '\\': special "backslash" of '=': special "eq" of '<': special "lt" of '>': special "gt" diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 2cb431ff9..3040f98da 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -14,7 +14,7 @@ import nversion, nimsets, msgs, std / sha1, bitsets, idents, types, ccgutils, os, ropes, math, passes, wordrecg, treetab, cgmeth, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, - lowerings, semparallel, tables, sets, ndi, lineinfos + lowerings, semparallel, tables, sets, ndi, lineinfos, pathutils import strutils except `%` # collides with ropes.`%` @@ -679,8 +679,10 @@ proc generateHeaders(m: BModule) = add(m.s[cfsHeaders], "#undef powerpc\L") add(m.s[cfsHeaders], "#undef unix\L") -proc openNamespaceNim(): Rope = - result.add("namespace Nim {\L") +proc openNamespaceNim(namespace: string): Rope = + result.add("namespace ") + result.add(namespace) + result.add(" {\L") proc closeNamespaceNim(): Rope = result.add("}\L") @@ -705,9 +707,10 @@ proc containsResult(n: PNode): bool = for i in 0..<n.safeLen: if containsResult(n[i]): return true +const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt, nkTemplateDef, nkMacroDef} + + declarativeDefs + proc easyResultAsgn(n: PNode): PNode = - const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt} + - declarativeDefs case n.kind of nkStmtList, nkStmtListExpr: var i = 0 @@ -723,6 +726,105 @@ proc easyResultAsgn(n: PNode): PNode = if result != nil: incl n.flags, nfPreventCg else: discard +type + InitResultEnum = enum Unknown, InitSkippable, InitRequired + +proc allPathsAsgnResult(n: PNode): InitResultEnum = + # Exceptions coming from calls don't have not be considered here: + # + # proc bar(): string = raise newException(...) + # + # proc foo(): string = + # # optimized out: 'reset(result)' + # result = bar() + # + # try: + # a = foo() + # except: + # echo "a was not written to" + # + template allPathsInBranch(it) = + let a = allPathsAsgnResult(it) + case a + of InitRequired: return InitRequired + of InitSkippable: discard + of Unknown: + # sticky, but can be overwritten by InitRequired: + result = Unknown + + result = Unknown + case n.kind + of nkStmtList, nkStmtListExpr: + for it in n: + result = allPathsAsgnResult(it) + if result != Unknown: return result + of nkAsgn, nkFastAsgn: + if n[0].kind == nkSym and n[0].sym.kind == skResult: + if not containsResult(n[1]): result = InitSkippable + else: result = InitRequired + elif containsResult(n): + result = InitRequired + of nkReturnStmt: + if n.len > 0: + result = allPathsAsgnResult(n[0]) + of nkIfStmt, nkIfExpr: + var exhaustive = false + result = InitSkippable + for it in n: + # Every condition must not use 'result': + if it.len == 2 and containsResult(it[0]): + return InitRequired + if it.len == 1: exhaustive = true + allPathsInBranch(it.lastSon) + # if the 'if' statement is not exhaustive and yet it touched 'result' + # in some way, say Unknown. + if not exhaustive: result = Unknown + of nkCaseStmt: + if containsResult(n[0]): return InitRequired + result = InitSkippable + var exhaustive = skipTypes(n[0].typ, + abstractVarRange-{tyTypeDesc}).kind notin {tyFloat..tyFloat128, tyString} + for i in 1..<n.len: + let it = n[i] + allPathsInBranch(it.lastSon) + if it.kind == nkElse: exhaustive = true + if not exhaustive: result = Unknown + of nkWhileStmt: + # some dubious code can assign the result in the 'while' + # condition and that would be fine. Everything else isn't: + result = allPathsAsgnResult(n[0]) + if result == Unknown: + result = allPathsAsgnResult(n[1]) + # we cannot assume that the 'while' loop is really executed at least once: + if result == InitSkippable: result = Unknown + of harmless: + result = Unknown + of nkGotoState, nkBreakState: + # give up for now. + result = InitRequired + of nkSym: + # some path reads from 'result' before it was written to! + if n.sym.kind == skResult: result = InitRequired + of nkTryStmt: + # We need to watch out for the following problem: + # try: + # result = stuffThatRaises() + # except: + # discard "result was not set" + # + # So ... even if the assignment to 'result' is the very first + # assignment this is not good enough! The only pattern we allow for + # is 'finally: result = x' + result = InitSkippable + for it in n: + if it.kind == nkFinally: + result = allPathsAsgnResult(it.lastSon) + else: + allPathsInBranch(it.lastSon) + else: + for i in 0..<safeLen(n): + allPathsInBranch(n[i]) + proc genProcAux(m: BModule, prc: PSym) = var p = newProc(prc, m) var header = genProcHeader(m, prc) @@ -749,7 +851,16 @@ proc genProcAux(m: BModule, prc: PSym) = else: fillResult(p.config, resNode) assignParam(p, res) - if sfNoInit notin prc.flags: resetLoc(p, res.loc) + # We simplify 'unsureAsgn(result, nil); unsureAsgn(result, x)' + # to 'unsureAsgn(result, x)' + # Sketch why this is correct: If 'result' points to a stack location + # the 'unsureAsgn' is a nop. If it points to a global variable the + # global is either 'nil' or points to valid memory and so the RC operation + # succeeds without touching not-initialized memory. + if sfNoInit in prc.flags: discard + elif allPathsAsgnResult(prc.getBody) == InitSkippable: discard + else: + resetLoc(p, res.loc) if skipTypes(res.typ, abstractInst).kind == tyArray: #incl(res.loc.flags, lfIndirect) res.loc.storage = OnUnknown @@ -927,7 +1038,10 @@ proc addIntTypes(result: var Rope; conf: ConfigRef) {.inline.} = addf(result, "#define NIM_NEW_MANGLING_RULES\L" & "#define NIM_INTBITS $1\L", [ platform.CPU[conf.target.targetCPU].intSize.rope]) - if optUseNimNamespace in conf.globalOptions: result.add("#define USE_NIM_NAMESPACE\L") + if conf.cppCustomNamespace.len > 0: + result.add("#define USE_NIM_NAMESPACE ") + result.add(conf.cppCustomNamespace) + result.add("\L") proc getCopyright(conf: ConfigRef; cfile: Cfile): Rope = if optCompileOnly in conf.globalOptions: @@ -955,7 +1069,8 @@ proc genFilenames(m: BModule): Rope = discard cgsym(m, "dbgRegisterFilename") result = nil for i in 0..<m.config.m.fileInfos.len: - result.addf("dbgRegisterFilename($1);$N", [m.config.m.fileInfos[i].projPath.makeCString]) + result.addf("dbgRegisterFilename($1);$N", + [m.config.m.fileInfos[i].projPath.string.makeCString]) proc genMainProc(m: BModule) = const @@ -1099,11 +1214,11 @@ proc genMainProc(m: BModule) = appcg(m, m.s[cfsProcs], nimMain, [m.g.mainModInit, initStackBottomCall, rope(m.labels)]) if optNoMain notin m.config.globalOptions: - if optUseNimNamespace in m.config.globalOptions: - m.s[cfsProcs].add closeNamespaceNim() & "using namespace Nim;\L" + if m.config.cppCustomNamespace.len > 0: + m.s[cfsProcs].add closeNamespaceNim() & "using namespace " & m.config.cppCustomNamespace & ";\L" appcg(m, m.s[cfsProcs], otherMain, []) - if optUseNimNamespace in m.config.globalOptions: m.s[cfsProcs].add openNamespaceNim() + if m.config.cppCustomNamespace.len > 0: m.s[cfsProcs].add openNamespaceNim(m.config.cppCustomNamespace) proc getSomeInitName(m: PSym, suffix: string): Rope = assert m.kind == skModule @@ -1225,10 +1340,10 @@ proc genModule(m: BModule, cfile: Cfile): Rope = add(result, genSectionStart(i, m.config)) add(result, m.s[i]) add(result, genSectionEnd(i, m.config)) - if optUseNimNamespace in m.config.globalOptions and i == cfsHeaders: - result.add openNamespaceNim() + if m.config.cppCustomNamespace.len > 0 and i == cfsHeaders: + result.add openNamespaceNim(m.config.cppCustomNamespace) add(result, m.s[cfsInitProc]) - if optUseNimNamespace in m.config.globalOptions: result.add closeNamespaceNim() + if m.config.cppCustomNamespace.len > 0: result.add closeNamespaceNim() proc newPreInitProc(m: BModule): BProc = result = newProc(nil, m) @@ -1239,7 +1354,7 @@ proc initProcOptions(m: BModule): TOptions = let opts = m.config.options if sfSystemModule in m.module.flags: opts-{optStackTrace} else: opts -proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = +proc rawNewModule(g: BModuleList; module: PSym, filename: AbsoluteFile): BModule = new(result) result.g = g result.tmpBase = rope("TM" & $hashOwner(module) & "_") @@ -1267,7 +1382,7 @@ proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = incl result.flags, preventStackTrace excl(result.preInitProc.options, optStackTrace) let ndiName = if optCDebug in g.config.globalOptions: changeFileExt(completeCFilePath(g.config, filename), "ndi") - else: "" + else: AbsoluteFile"" open(result.ndi, ndiName, g.config) proc nullify[T](arr: var T) = @@ -1318,7 +1433,7 @@ proc resetCgenModules*(g: BModuleList) = for m in cgenModules(g): resetModule(m) proc rawNewModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = - result = rawNewModule(g, module, toFullPath(conf, module.position.FileIndex)) + result = rawNewModule(g, module, AbsoluteFile toFullPath(conf, module.position.FileIndex)) proc newModule(g: BModuleList; module: PSym; conf: ConfigRef): BModule = # we should create only one cgen module for each module sym @@ -1337,7 +1452,7 @@ proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = injectG() result = newModule(g, module, graph.config) if optGenIndex in graph.config.globalOptions and g.generatedHeader == nil: - let f = if graph.config.headerFile.len > 0: graph.config.headerFile + let f = if graph.config.headerFile.len > 0: AbsoluteFile graph.config.headerFile else: graph.config.projectFull g.generatedHeader = rawNewModule(g, module, changeFileExt(completeCFilePath(graph.config, f), hExt)) @@ -1359,18 +1474,18 @@ proc writeHeader(m: BModule) = add(result, genSectionStart(i, m.config)) add(result, m.s[i]) add(result, genSectionEnd(i, m.config)) - if optUseNimNamespace in m.config.globalOptions and i == cfsHeaders: result.add openNamespaceNim() + if m.config.cppCustomNamespace.len > 0 and i == cfsHeaders: result.add openNamespaceNim(m.config.cppCustomNamespace) add(result, m.s[cfsInitProc]) if optGenDynLib in m.config.globalOptions: result.add("N_LIB_IMPORT ") result.addf("N_CDECL(void, NimMain)(void);$n", []) - if optUseNimNamespace in m.config.globalOptions: result.add closeNamespaceNim() + if m.config.cppCustomNamespace.len > 0: result.add closeNamespaceNim() result.addf("#endif /* $1 */$n", [guard]) if not writeRope(result, m.filename): - rawMessage(m.config, errCannotOpenFile, m.filename) + rawMessage(m.config, errCannotOpenFile, m.filename.string) -proc getCFile(m: BModule): string = +proc getCFile(m: BModule): AbsoluteFile = let ext = if m.compileToCpp: ".cpp" elif m.config.cmd == cmdCompileToOC or sfCompileToObjC in m.module.flags: ".m" @@ -1414,18 +1529,18 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool = if not equalsFile(code, cfile.cname): if isDefined(m.config, "nimdiff"): if fileExists(cfile.cname): - copyFile(cfile.cname, cfile.cname & ".backup") - echo "diff ", cfile.cname, ".backup ", cfile.cname + copyFile(cfile.cname.string, cfile.cname.string & ".backup") + echo "diff ", cfile.cname.string, ".backup ", cfile.cname.string else: - echo "new file ", cfile.cname + echo "new file ", cfile.cname.string if not writeRope(code, cfile.cname): - rawMessage(m.config, errCannotOpenFile, cfile.cname) + rawMessage(m.config, errCannotOpenFile, cfile.cname.string) return - if existsFile(cfile.obj) and os.fileNewer(cfile.obj, cfile.cname): + if fileExists(cfile.obj) and os.fileNewer(cfile.obj.string, cfile.cname.string): result = false else: if not writeRope(code, cfile.cname): - rawMessage(m.config, errCannotOpenFile, cfile.cname) + rawMessage(m.config, errCannotOpenFile, cfile.cname.string) # We need 2 different logics here: pending modules (including # 'nim__dat') may require file merging for the combination of dead code @@ -1461,14 +1576,14 @@ proc writeModule(m: BModule, pending: bool) = finishTypeDescriptions(m) var code = genModule(m, cf) if not writeRope(code, cfile): - rawMessage(m.config, errCannotOpenFile, cfile) + rawMessage(m.config, errCannotOpenFile, cfile.string) addFileToCompile(m.config, cf) else: # Consider: first compilation compiles ``system.nim`` and produces # ``system.c`` but then compilation fails due to an error. This means # that ``system.o`` is missing, so we need to call the C compiler for it: var cf = Cfile(cname: cfile, obj: completeCFilePath(m.config, toObjFile(m.config, cfile)), flags: {}) - if not existsFile(cf.obj): cf.flags = {CfileFlag.Cached} + if not fileExists(cf.obj): cf.flags = {CfileFlag.Cached} addFileToCompile(m.config, cf) close(m.ndi) @@ -1483,7 +1598,7 @@ proc updateCachedModule(m: BModule) = var code = genModule(m, cf) if not writeRope(code, cfile): - rawMessage(m.config, errCannotOpenFile, cfile) + rawMessage(m.config, errCannotOpenFile, cfile.string) else: cf.flags = {CfileFlag.Cached} addFileToCompile(m.config, cf) diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 56dbd65a2..406ee050f 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -11,7 +11,7 @@ import ast, astalgo, ropes, passes, options, intsets, platform, sighashes, - tables, ndi, lineinfos + tables, ndi, lineinfos, pathutils from modulegraphs import ModuleGraph @@ -136,8 +136,8 @@ type s*: TCFileSections # sections of the C file flags*: set[Codegenflag] module*: PSym - filename*: string - cfilename*: string # filename of the module (including path, + filename*: AbsoluteFile + cfilename*: AbsoluteFile # filename of the module (including path, # without extension) tmpBase*: Rope # base for temp identifier generation typeCache*: TypeCache # cache the generated types diff --git a/compiler/closureiters.nim b/compiler/closureiters.nim index 9e4885b66..b0857e6c7 100644 --- a/compiler/closureiters.nim +++ b/compiler/closureiters.nim @@ -239,7 +239,7 @@ proc toStmtList(n: PNode): PNode = proc addGotoOut(n: PNode, gotoOut: PNode): PNode = # Make sure `n` is a stmtlist, and ends with `gotoOut` result = toStmtList(n) - if result.len != 0 and result.sons[^1].kind != nkGotoState: + if result.len == 0 or result.sons[^1].kind != nkGotoState: result.add(gotoOut) proc newTempVar(ctx: var Ctx, typ: PType): PSym = @@ -678,7 +678,7 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = n[0] = ex result.add(n) - of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv: + of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv, nkObjDownConv: var ns = false for i in 0 ..< n.len: n[i] = ctx.lowerStmtListExprs(n[i], ns) @@ -687,9 +687,9 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = needsSplit = true result = newNodeI(nkStmtListExpr, n.info) result.typ = n.typ - let (st, ex) = exprToStmtList(n[1]) + let (st, ex) = exprToStmtList(n[^1]) result.add(st) - n[1] = ex + n[^1] = ex result.add(n) of nkAsgn, nkFastAsgn: @@ -712,6 +712,25 @@ proc lowerStmtListExprs(ctx: var Ctx, n: PNode, needsSplit: var bool): PNode = result.add(n) + of nkBracketExpr: + var lhsNeedsSplit = false + var rhsNeedsSplit = false + n[0] = ctx.lowerStmtListExprs(n[0], lhsNeedsSplit) + n[1] = ctx.lowerStmtListExprs(n[1], rhsNeedsSplit) + if lhsNeedsSplit or rhsNeedsSplit: + needsSplit = true + result = newNodeI(nkStmtListExpr, n.info) + if lhsNeedsSplit: + let (st, ex) = exprToStmtList(n[0]) + result.add(st) + n[0] = ex + + if rhsNeedsSplit: + let (st, ex) = exprToStmtList(n[1]) + result.add(st) + n[1] = ex + result.add(n) + of nkWhileStmt: var ns = false diff --git a/compiler/cmdlinehelper.nim b/compiler/cmdlinehelper.nim new file mode 100644 index 000000000..8bd073314 --- /dev/null +++ b/compiler/cmdlinehelper.nim @@ -0,0 +1,92 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Nim contributors +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Helpers for binaries that use compiler passes, eg: nim, nimsuggest, nimfix + +import + options, idents, nimconf, scriptconfig, extccomp, commands, msgs, + lineinfos, modulegraphs, condsyms, os, pathutils + +type + NimProg* = ref object + suggestMode*: bool + supportsStdinFile*: bool + processCmdLine*: proc(pass: TCmdLinePass, cmd: string; config: ConfigRef) + mainCommand*: proc(graph: ModuleGraph) + +proc initDefinesProg*(self: NimProg, conf: ConfigRef, name: string) = + condsyms.initDefines(conf.symbols) + defineSymbol conf.symbols, name + +proc processCmdLineAndProjectPath*(self: NimProg, conf: ConfigRef) = + self.processCmdLine(passCmd1, "", conf) + if self.supportsStdinFile and conf.projectName == "-": + conf.projectName = "stdinfile" + conf.projectFull = AbsoluteFile "stdinfile" + conf.projectPath = AbsoluteDir getCurrentDir() + conf.projectIsStdin = true + elif conf.projectName != "": + try: + conf.projectFull = canonicalizePath(conf, AbsoluteFile conf.projectName) + except OSError: + conf.projectFull = AbsoluteFile conf.projectName + let p = splitFile(conf.projectFull) + let dir = if p.dir.isEmpty: AbsoluteDir getCurrentDir() else: p.dir + conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile dir) + conf.projectName = p.name + else: + conf.projectPath = AbsoluteDir canonicalizePath(conf, AbsoluteFile getCurrentDir()) + +proc loadConfigsAndRunMainCommand*(self: NimProg, cache: IdentCache; conf: ConfigRef): bool = + loadConfigs(DefaultConfig, cache, conf) # load all config files + if self.suggestMode: + conf.command = "nimsuggest" + + proc runNimScriptIfExists(path: AbsoluteFile)= + if fileExists(path): + runNimScript(cache, path, freshDefines = false, conf) + + # Caution: make sure this stays in sync with `loadConfigs` + if optSkipSystemConfigFile notin conf.globalOptions: + runNimScriptIfExists(getSystemConfigPath(conf, DefaultConfigNims)) + + if optSkipUserConfigFile notin conf.globalOptions: + runNimScriptIfExists(getUserConfigPath(DefaultConfigNims)) + + if optSkipParentConfigFiles notin conf.globalOptions: + for dir in parentDirs(conf.projectPath.string, fromRoot = true, inclusive = false): + runNimScriptIfExists(AbsoluteDir(dir) / DefaultConfigNims) + + if optSkipProjConfigFile notin conf.globalOptions: + runNimScriptIfExists(conf.projectPath / DefaultConfigNims) + block: + let scriptFile = conf.projectFull.changeFileExt("nims") + if not self.suggestMode: + runNimScriptIfExists(scriptFile) + # 'nim foo.nims' means to just run the NimScript file and do nothing more: + if fileExists(scriptFile) and scriptFile == conf.projectFull: + return false + else: + if scriptFile != conf.projectFull: + runNimScriptIfExists(scriptFile) + else: + # 'nimsuggest foo.nims' means to just auto-complete the NimScript file + discard + + # now process command line arguments again, because some options in the + # command line can overwite the config file's settings + extccomp.initVars(conf) + self.processCmdLine(passCmd2, "", conf) + if conf.command == "": + rawMessage(conf, errGenerated, "command missing") + + let graph = newModuleGraph(cache, conf) + graph.suggestMode = self.suggestMode + self.mainCommand(graph) + return true diff --git a/compiler/commands.nim b/compiler/commands.nim index f7c8cf9f2..39967c4bc 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -26,7 +26,8 @@ bootSwitch(usedNoGC, defined(nogc), "--gc:none") import os, msgs, options, nversion, condsyms, strutils, extccomp, platform, - wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos + wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils, lineinfos, + pathutils # but some have deps to imported modules. Yay. bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") @@ -131,7 +132,10 @@ proc splitSwitch(conf: ConfigRef; switch: string, cmd, arg: var string, pass: TC else: break inc(i) if i >= len(switch): arg = "" - elif switch[i] in {':', '=', '['}: arg = substr(switch, i + 1) + # cmd:arg => (cmd,arg) + elif switch[i] in {':', '='}: arg = substr(switch, i + 1) + # cmd[sub]:rest => (cmd,[sub]:rest) + elif switch[i] == '[': arg = substr(switch, i) else: invalidCmdLineOption(conf, pass, switch, info) proc processOnOffSwitch(conf: ConfigRef; op: TOptions, arg: string, pass: TCmdLinePass, @@ -167,14 +171,20 @@ proc expectNoArg(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass, info: TLineInfo; orig: string; conf: ConfigRef) = - var id = "" # arg = "X]:on|off" + var id = "" # arg = key:val or [key]:val; with val=on|off var i = 0 var n = hintMin - while i < len(arg) and (arg[i] != ']'): + var isBracket = false + if i < len(arg) and arg[i] == '[': + isBracket = true + inc(i) + while i < len(arg) and (arg[i] notin {':', '=', ']'}): add(id, arg[i]) inc(i) - if i < len(arg) and (arg[i] == ']'): inc(i) - else: invalidCmdLineOption(conf, pass, orig, info) + if isBracket: + if i < len(arg) and arg[i] == ']': inc(i) + else: invalidCmdLineOption(conf, pass, orig, info) + if i < len(arg) and (arg[i] in {':', '='}): inc(i) else: invalidCmdLineOption(conf, pass, orig, info) if state == wHint: @@ -199,7 +209,7 @@ proc processSpecificNote*(arg: string, state: TSpecialWord, pass: TCmdLinePass, proc processCompile(conf: ConfigRef; filename: string) = var found = findFile(conf, filename) - if found == "": found = filename + if found.isEmpty: found = AbsoluteFile filename extccomp.addExternalFileToCompile(conf, found) const @@ -283,31 +293,32 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool else: invalidCmdLineOption(conf, passCmd1, switch, info) proc processPath(conf: ConfigRef; path: string, info: TLineInfo, - notRelativeToProj = false): string = + notRelativeToProj = false): AbsoluteDir = let p = if os.isAbsolute(path) or '$' in path: path elif notRelativeToProj: getCurrentDir() / path else: - conf.projectPath / path + conf.projectPath.string / path try: - result = pathSubs(conf, p, toFullPath(conf, info).splitFile().dir) + result = AbsoluteDir pathSubs(conf, p, toFullPath(conf, info).splitFile().dir) except ValueError: localError(conf, info, "invalid path: " & p) - result = p + result = AbsoluteDir p -proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): string = - let path = if path[0] == '"': strutils.unescape(path) else: path +proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): AbsoluteDir = + let path = if path.len > 0 and path[0] == '"': strutils.unescape(path) + else: path let basedir = toFullPath(conf, info).splitFile().dir let p = if os.isAbsolute(path) or '$' in path: path else: basedir / path try: - result = pathSubs(conf, p, basedir) + result = AbsoluteDir pathSubs(conf, p, basedir) except ValueError: localError(conf, info, "invalid path: " & p) - result = p + result = AbsoluteDir p const errInvalidNumber = "$1 is not a valid number" @@ -322,9 +333,9 @@ proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) = if parseUtils.parseInt(a[3], column) <= 0: localError(conf, info, errInvalidNumber % a[2]) - let dirtyOriginalIdx = fileInfoIdx(conf, a[1]) + let dirtyOriginalIdx = fileInfoIdx(conf, AbsoluteFile a[1]) if dirtyOriginalIdx.int32 >= 0: - msgs.setDirtyFile(conf, dirtyOriginalIdx, a[0]) + msgs.setDirtyFile(conf, dirtyOriginalIdx, AbsoluteFile a[0]) conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column) @@ -336,7 +347,7 @@ proc track(conf: ConfigRef; arg: string, info: TLineInfo) = localError(conf, info, errInvalidNumber % a[1]) if parseUtils.parseInt(a[2], column) <= 0: localError(conf, info, errInvalidNumber % a[2]) - conf.m.trackPos = newLineInfo(conf, a[0], line, column) + conf.m.trackPos = newLineInfo(conf, AbsoluteFile a[0], line, column) proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = if pass in {passCmd2, passPP}: @@ -350,14 +361,16 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; case switch.normalize of "path", "p": expectArg(conf, switch, arg, pass, info) - addPath(conf, if pass == passPP: processCfgPath(conf, arg, info) else: processPath(conf, arg, info), info) + addPath(conf, if pass == passPP: processCfgPath(conf, arg, info) + else: processPath(conf, arg, info), info) of "nimblepath", "babelpath": # keep the old name for compat if pass in {passCmd2, passPP} and optNoNimblePath notin conf.globalOptions: expectArg(conf, switch, arg, pass, info) var path = processPath(conf, arg, info, notRelativeToProj=true) - let nimbleDir = getEnv("NIMBLE_DIR") - if nimbleDir.len > 0 and pass == passPP: path = nimbleDir / "pkgs" + let nimbleDir = AbsoluteDir getEnv("NIMBLE_DIR") + if not nimbleDir.isEmpty and pass == passPP: + path = nimbleDir / RelativeDir"pkgs" nimblePath(conf, path, info) of "nonimblepath", "nobabelpath": expectNoArg(conf, switch, arg, pass, info) @@ -365,20 +378,14 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "excludepath": expectArg(conf, switch, arg, pass, info) let path = processPath(conf, arg, info) - - conf.searchPaths.keepItIf(cmpPaths(it, path) != 0) - conf.lazyPaths.keepItIf(cmpPaths(it, path) != 0) - - if (len(path) > 0) and (path[len(path) - 1] == DirSep): - let strippedPath = path[0 .. (len(path) - 2)] - conf.searchPaths.keepItIf(cmpPaths(it, strippedPath) != 0) - conf.lazyPaths.keepItIf(cmpPaths(it, strippedPath) != 0) + conf.searchPaths.keepItIf(it != path) + conf.lazyPaths.keepItIf(it != path) of "nimcache": expectArg(conf, switch, arg, pass, info) conf.nimcacheDir = processPath(conf, arg, info, true) of "out", "o": expectArg(conf, switch, arg, pass, info) - conf.outFile = arg + conf.outFile = AbsoluteFile arg of "docseesrcurl": expectArg(conf, switch, arg, pass, info) conf.docSeeSrcUrl = arg @@ -402,7 +409,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if pass in {passCmd2, passPP}: processCompile(conf, arg) of "link": expectArg(conf, switch, arg, pass, info) - if pass in {passCmd2, passPP}: addExternalFileToLink(conf, arg) + if pass in {passCmd2, passPP}: + addExternalFileToLink(conf, AbsoluteFile arg) of "debuginfo": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optCDebug) @@ -572,7 +580,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; if pass in {passCmd2, passPP}: conf.cLibs.add processPath(conf, arg, info) of "clib": expectArg(conf, switch, arg, pass, info) - if pass in {passCmd2, passPP}: conf.cLinkedLibs.add processPath(conf, arg, info) + if pass in {passCmd2, passPP}: + conf.cLinkedLibs.add processPath(conf, arg, info).string of "header": if conf != nil: conf.headerFile = arg incl(conf.globalOptions, optGenIndex) @@ -642,7 +651,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; else: localError(conf, info, "invalid option for --symbolFiles: " & arg) of "skipcfg": expectNoArg(conf, switch, arg, pass, info) - incl(conf.globalOptions, optSkipConfigFile) + incl(conf.globalOptions, optSkipSystemConfigFile) of "skipprojcfg": expectNoArg(conf, switch, arg, pass, info) incl(conf.globalOptions, optSkipProjConfigFile) @@ -726,14 +735,16 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "nep1": processOnOffSwitchG(conf, {optCheckNep1}, arg, pass, info) of "cppcompiletonamespace": - expectNoArg(conf, switch, arg, pass, info) - incl conf.globalOptions, optUseNimNamespace - defineSymbol(conf.symbols, "cppCompileToNamespace") + if arg.len > 0: + conf.cppCustomNamespace = arg + else: + conf.cppCustomNamespace = "Nim" + defineSymbol(conf.symbols, "cppCompileToNamespace", conf.cppCustomNamespace) else: if strutils.find(switch, '.') >= 0: options.setConfigVar(conf, switch, arg) else: invalidCmdLineOption(conf, pass, switch, info) -template gCmdLineInfo*(): untyped = newLineInfo(config, "command line", 1, 1) +template gCmdLineInfo*(): untyped = newLineInfo(config, AbsoluteFile"command line", 1, 1) proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) = var cmd, arg: string @@ -743,11 +754,11 @@ proc processCommand*(switch: string, pass: TCmdLinePass; config: ConfigRef) = proc processSwitch*(pass: TCmdLinePass; p: OptParser; config: ConfigRef) = # hint[X]:off is parsed as (p.key = "hint[X]", p.val = "off") - # we fix this here + # we transform it to (key = hint, val = [X]:off) var bracketLe = strutils.find(p.key, '[') if bracketLe >= 0: var key = substr(p.key, 0, bracketLe - 1) - var val = substr(p.key, bracketLe + 1) & ':' & p.val + var val = substr(p.key, bracketLe) & ':' & p.val processSwitch(key, val, pass, gCmdLineInfo, config) else: processSwitch(p.key, p.val, pass, gCmdLineInfo, config) diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 78645b6fd..a22b613f0 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -75,6 +75,7 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimNoZeroTerminator") defineSymbol("nimNotNil") defineSymbol("nimVmExportFixed") + defineSymbol("nimHasSymOwnerInMacro") defineSymbol("nimNewRuntime") defineSymbol("nimIncrSeqV3") defineSymbol("nimAshr") diff --git a/compiler/depends.nim b/compiler/depends.nim index d0a1139ef..c26593ea5 100644 --- a/compiler/depends.nim +++ b/compiler/depends.nim @@ -10,7 +10,8 @@ # This module implements a dependency file generator. import - os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths + os, options, ast, astalgo, msgs, ropes, idents, passes, modulepaths, + pathutils from modulegraphs import ModuleGraph @@ -45,10 +46,10 @@ proc addDotDependency(c: PPassContext, n: PNode): PNode = else: discard -proc generateDot*(graph: ModuleGraph; project: string) = +proc generateDot*(graph: ModuleGraph; project: AbsoluteFile) = let b = Backend(graph.backend) discard writeRope("digraph $1 {$n$2}$n" % [ - rope(changeFileExt(extractFilename(project), "")), b.dotGraph], + rope(project.splitFile.name), b.dotGraph], changeFileExt(project, "dot")) proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 3842f0ad4..83dd5de2a 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -14,9 +14,13 @@ import ast, strutils, strtabs, options, msgs, os, ropes, idents, wordrecg, syntaxes, renderer, lexer, packages/docutils/rstast, - packages/docutils/rst, packages/docutils/rstgen, times, + packages/docutils/rst, packages/docutils/rstgen, packages/docutils/highlite, sempass2, json, xmltree, cgi, - typesrenderer, astalgo, modulepaths, lineinfos, sequtils + typesrenderer, astalgo, modulepaths, lineinfos, sequtils, intsets, + pathutils + +const + exportSection = skTemp type TSections = array[TSymKind, Rope] @@ -31,6 +35,11 @@ type isPureRst: bool conf*: ConfigRef cache*: IdentCache + exampleCounter: int + emitted: IntSet # we need to track which symbols have been emitted + # already. See bug #3655 + destFile*: AbsoluteFile + thisDir*: AbsoluteDir PDoc* = ref TDocumentor ## Alias to type less. @@ -45,12 +54,12 @@ proc whichType(d: PDoc; n: PNode): PSym = proc attachToType(d: PDoc; p: PSym): PSym = let params = p.ast.sons[paramsPos] - # first check the first parameter, then the return type, - # then the other parameter: template check(i) = result = whichType(d, params[i]) if result != nil: return result + # first check the first parameter, then the return type, + # then the other parameter: if params.len > 1: check(1) if params.len > 0: check(0) for i in 2..<params.len: check(i) @@ -71,10 +80,10 @@ template declareClosures = of mwUnknownSubstitution: k = warnUnknownSubstitutionX of mwUnsupportedLanguage: k = warnLanguageXNotSupported of mwUnsupportedField: k = warnFieldXNotSupported - globalError(conf, newLineInfo(conf, filename, line, col), k, arg) + globalError(conf, newLineInfo(conf, AbsoluteFile filename, line, col), k, arg) proc docgenFindFile(s: string): string {.procvar.} = - result = options.findFile(conf, s) + result = options.findFile(conf, s).string if result.len == 0: result = getCurrentDir() / s if not existsFile(result): result = "" @@ -87,13 +96,29 @@ proc parseRst(text, filename: string, result = rstParse(text, filename, line, column, hasToc, rstOptions, docgenFindFile, compilerMsgHandler) -proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): PDoc = +proc getOutFile2(conf: ConfigRef; filename: RelativeFile, + ext: string, dir: RelativeDir; guessTarget: bool): AbsoluteFile = + if optWholeProject in conf.globalOptions: + # This is correct, for 'nim doc --project' we interpret the '--out' option as an + # absolute directory, not as a filename! + let d = if conf.outFile.isEmpty: conf.projectPath / dir else: AbsoluteDir(conf.outFile) + createDir(d) + result = d / changeFileExt(filename, ext) + elif guessTarget: + let d = if not conf.outFile.isEmpty: splitFile(conf.outFile).dir + else: conf.projectPath + createDir(d) + result = d / changeFileExt(filename, ext) + else: + result = getOutFile(conf, filename, ext) + +proc newDocumentor*(filename: AbsoluteFile; cache: IdentCache; conf: ConfigRef): PDoc = declareClosures() new(result) result.conf = conf result.cache = cache initRstGenerator(result[], (if conf.cmd != cmdRst2tex: outHtml else: outLatex), - conf.configVars, filename, {roSupportRawDirective}, + conf.configVars, filename.string, {roSupportRawDirective}, docgenFindFile, compilerMsgHandler) if conf.configVars.hasKey("doc.googleAnalytics"): @@ -117,7 +142,12 @@ proc newDocumentor*(filename: string; cache: IdentCache; conf: ConfigRef): PDoc result.jArray = newJArray() initStrTable result.types result.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = - localError(conf, newLineInfo(conf, d.filename, -1, -1), warnUser, "only 'rst2html' supports the ':test:' attribute") + localError(conf, newLineInfo(conf, AbsoluteFile d.filename, -1, -1), + warnUser, "only 'rst2html' supports the ':test:' attribute") + result.emitted = initIntSet() + result.destFile = getOutFile2(conf, relativeTo(filename, conf.projectPath), + HtmlExt, RelativeDir"htmldocs", false) + result.thisDir = result.destFile.splitFile.dir proc dispA(conf: ConfigRef; dest: var Rope, xml, tex: string, args: openArray[Rope]) = if conf.cmd != cmdRst2tex: addf(dest, xml, args) @@ -223,6 +253,19 @@ proc getPlainDocstring(n: PNode): string = result = getPlainDocstring(n.sons[i]) if result.len > 0: return +proc belongsToPackage(conf: ConfigRef; module: PSym): bool = + result = module.kind == skModule and module.owner != nil and + module.owner.id == conf.mainPackageId + +proc externalDep(d: PDoc; module: PSym): string = + if optWholeProject in d.conf.globalOptions: + let full = AbsoluteFile toFullPath(d.conf, FileIndex module.position) + let tmp = getOutFile2(d.conf, full.relativeTo(d.conf.projectPath), HtmlExt, + RelativeDir"htmldocs", sfMainModule notin module.flags) + result = relativeTo(tmp, d.thisDir, '/').string + else: + result = extractFilename toFullPath(d.conf, FileIndex module.position) + proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRenderFlags = {}) = var r: TSrcGen var literal = "" @@ -255,8 +298,17 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe dispA(d.conf, result, "<span class=\"FloatNumber\">$1</span>", "\\spanFloatNumber{$1}", [rope(esc(d.target, literal))]) of tkSymbol: - dispA(d.conf, result, "<span class=\"Identifier\">$1</span>", - "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) + let s = getTokSym(r) + if s != nil and s.kind == skType and sfExported in s.flags and + s.owner != nil and belongsToPackage(d.conf, s.owner) and + d.target == outHtml: + let external = externalDep(d, s.owner) + result.addf "<a href=\"$1#$2\"><span class=\"Identifier\">$3</span></a>", + [rope changeFileExt(external, "html").string, rope literal, + rope(esc(d.target, literal))] + else: + dispA(d.conf, result, "<span class=\"Identifier\">$1</span>", + "\\spanIdentifier{$1}", [rope(esc(d.target, literal))]) of tkSpaces, tkInvalid: add(result, literal) of tkCurlyDotLe: @@ -284,11 +336,59 @@ proc nodeToHighlightedHtml(d: PDoc; n: PNode; result: var Rope; renderFlags: TRe dispA(d.conf, result, "<span class=\"Other\">$1</span>", "\\spanOther{$1}", [rope(esc(d.target, literal))]) -proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = +proc testExample(d: PDoc; ex: PNode) = + if d.conf.errorCounter > 0: return + let outputDir = d.conf.getNimcacheDir / RelativeDir"runnableExamples" + createDir(outputDir) + inc d.exampleCounter + let outp = outputDir / RelativeFile(extractFilename(d.filename.changeFileExt"" & + "_examples" & $d.exampleCounter & ".nim")) + #let nimcache = outp.changeFileExt"" & "_nimcache" + renderModule(ex, d.filename, outp.string, conf = d.conf) + let backend = if isDefined(d.conf, "js"): "js" + elif isDefined(d.conf, "cpp"): "cpp" + elif isDefined(d.conf, "objc"): "objc" + else: "c" + if os.execShellCmd(os.getAppFilename() & " " & backend & + " --path:" & quoteShell(d.conf.projectPath) & + " --nimcache:" & quoteShell(outputDir) & + " -r " & quoteShell(outp)) != 0: + quit "[Examples] failed: see " & outp.string + else: + # keep generated source file `outp` to allow inspection. + rawMessage(d.conf, hintSuccess, ["runnableExamples: " & outp.string]) + removeFile(outp.changeFileExt(ExeExt)) + +proc extractImports(n: PNode; result: PNode) = + if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: + result.add copyTree(n) + n.kind = nkEmpty + return + for i in 0..<n.safeLen: extractImports(n[i], result) + +proc prepareExamples(d: PDoc; n: PNode) = + var runnableExamples = newTree(nkStmtList, + newTree(nkImportStmt, newStrNode(nkStrLit, d.filename))) + runnableExamples.info = n.info + let imports = newTree(nkStmtList) + var savedLastSon = copyTree n.lastSon + extractImports(savedLastSon, imports) + for imp in imports: runnableExamples.add imp + runnableExamples.add newTree(nkBlockStmt, newNode(nkEmpty), copyTree savedLastSon) + testExample(d, runnableExamples) + +proc isRunnableExample(n: PNode): bool = + # Templates and generics don't perform symbol lookups. + result = n.kind == nkSym and n.sym.magic == mRunnableExamples or + n.kind == nkIdent and n.ident.s == "runnableExamples" + +proc getAllRunnableExamplesRec(d: PDoc; n, orig: PNode; dest: var Rope) = + if n.info.fileIndex != orig.info.fileIndex: return case n.kind of nkCallKinds: - if n[0].kind == nkSym and n[0].sym.magic == mRunnableExamples and + if isRunnableExample(n[0]) and n.len >= 2 and n.lastSon.kind == nkStmtList: + prepareExamples(d, n) dispA(d.conf, dest, "\n<p><strong class=\"examples_text\">$1</strong></p>\n", "\n\\textbf{$1}\n", [rope"Examples:"]) inc d.listingCounter @@ -308,7 +408,10 @@ proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = dest.add(d.config.getOrDefault"doc.listing_end" % id) else: discard for i in 0 ..< n.safeLen: - getAllRunnableExamples(d, n[i], dest) + getAllRunnableExamplesRec(d, n[i], orig, dest) + +proc getAllRunnableExamples(d: PDoc; n: PNode; dest: var Rope) = + getAllRunnableExamplesRec(d, n, n, dest) when false: proc findDocComment(n: PNode): PNode = @@ -331,7 +434,7 @@ when false: else: result = n.comment.substr(2).replace("\n##", "\n").strip -proc isVisible(n: PNode): bool = +proc isVisible(d: PDoc; n: PNode): bool = result = false if n.kind == nkPostfix: if n.len == 2 and n.sons[0].kind == nkIdent: @@ -342,8 +445,10 @@ proc isVisible(n: PNode): bool = # exception tracking information here. Instead we copy over the comment # from the proc header. result = {sfExported, sfFromGeneric, sfForward}*n.sym.flags == {sfExported} + if result and containsOrIncl(d.emitted, n.sym.id): + result = false elif n.kind == nkPragmaExpr: - result = isVisible(n.sons[0]) + result = isVisible(d, n.sons[0]) proc getName(d: PDoc, n: PNode, splitAfter = -1): string = case n.kind @@ -398,10 +503,8 @@ proc newUniquePlainSymbol(d: PDoc, original: string): string = result = original d.seenSymbols[original] = "" return - # Iterate over possible numeric variants of the original name. var count = 2 - while true: result = original & "_" & $count if not d.seenSymbols.hasKey(result): @@ -409,7 +512,6 @@ proc newUniquePlainSymbol(d: PDoc, original: string): string = break count += 1 - proc complexName(k: TSymKind, n: PNode, baseName: string): string = ## Builds a complex unique href name for the node. ## @@ -431,11 +533,9 @@ proc complexName(k: TSymKind, n: PNode, baseName: string): string = of skTemplate: result.add(".t" & defaultParamSeparator) of skConverter: result.add(".c" & defaultParamSeparator) else: discard - if len(n) > paramsPos and n[paramsPos].kind == nkFormalParams: result.add(renderParamTypes(n[paramsPos])) - proc isCallable(n: PNode): bool = ## Returns true if `n` contains a callable node. case n.kind @@ -444,7 +544,6 @@ proc isCallable(n: PNode): bool = else: result = false - proc docstringSummary(rstText: string): string = ## Returns just the first line or a brief chunk of text from a rst string. ## @@ -472,9 +571,8 @@ proc docstringSummary(rstText: string): string = result.delete(pos, last) result.add("…") - proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = - if not isVisible(nameNode): return + if not isVisible(d, nameNode): return let name = getName(d, nameNode) nameRope = name.rope @@ -494,8 +592,8 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = break plainName.add(literal) - # Render the HTML hyperlink. - nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, renderDocComments}) + nodeToHighlightedHtml(d, n, result, {renderNoBody, renderNoComments, + renderDocComments, renderSyms}) inc(d.id) let @@ -512,16 +610,18 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = var seeSrcRope: Rope = nil let docItemSeeSrc = getConfigVar(d.conf, "doc.item.seesrc") if docItemSeeSrc.len > 0: - let cwd = canonicalizePath(d.conf, getCurrentDir()) - var path = toFullPath(d.conf, n.info) - if path.startsWith(cwd): - path = path[cwd.len+1 .. ^1].replace('\\', '/') + let path = relativeTo(AbsoluteFile toFullPath(d.conf, n.info), d.conf.projectPath, '/') + when false: + let cwd = canonicalizePath(d.conf, getCurrentDir()) + var path = toFullPath(d.conf, n.info) + if path.startsWith(cwd): + path = path[cwd.len+1 .. ^1].replace('\\', '/') let gitUrl = getConfigVar(d.conf, "git.url") if gitUrl.len > 0: let commit = getConfigVar(d.conf, "git.commit", "master") let develBranch = getConfigVar(d.conf, "git.devel", "devel") dispA(d.conf, seeSrcRope, "$1", "", [ropeFormatNamedVars(d.conf, docItemSeeSrc, - ["path", "line", "url", "commit", "devel"], [rope path, + ["path", "line", "url", "commit", "devel"], [rope path.string, rope($n.info.line), rope gitUrl, rope commit, rope develBranch])]) add(d.section[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item"), @@ -530,11 +630,22 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = [nameRope, result, comm, itemIDRope, plainNameRope, plainSymbolRope, symbolOrIdRope, plainSymbolEncRope, symbolOrIdEncRope, seeSrcRope])) + let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string + var attype: Rope if k in routineKinds and nameNode.kind == nkSym: let att = attachToType(d, nameNode.sym) if att != nil: attype = rope esc(d.target, att.name.s) + elif k == skType and nameNode.kind == nkSym and nameNode.sym.typ.kind in {tyEnum, tyBool}: + let etyp = nameNode.sym.typ + for e in etyp.n: + if e.sym.kind != skEnumField: continue + let plain = renderPlainSymbolName(e) + let symbolOrId = d.newUniquePlainSymbol(plain) + setIndexTerm(d[], external, symbolOrId, plain, nameNode.sym.name.s & '.' & plain, + xmltree.escape(getPlainDocstring(e).docstringSummary)) + add(d.toc[k], ropeFormatNamedVars(d.conf, getConfigVar(d.conf, "doc.item.toc"), ["name", "header", "desc", "itemID", "header_plain", "itemSym", "itemSymOrID", "itemSymEnc", "itemSymOrIDEnc", "attype"], @@ -545,24 +656,22 @@ proc genItem(d: PDoc, n, nameNode: PNode, k: TSymKind) = # Ironically for types the complexSymbol is *cleaner* than the plainName # because it doesn't include object fields or documentation comments. So we # use the plain one for callable elements, and the complex for the rest. - var linkTitle = changeFileExt(extractFilename(d.filename), "") & " : " + var linkTitle = changeFileExt(extractFilename(d.filename), "") & ": " if n.isCallable: linkTitle.add(xmltree.escape(plainName.strip)) else: linkTitle.add(xmltree.escape(complexSymbol.strip)) - setIndexTerm(d[], symbolOrId, name, linkTitle, + setIndexTerm(d[], external, symbolOrId, name, linkTitle, xmltree.escape(plainDocstring.docstringSummary)) if k == skType and nameNode.kind == nkSym: d.types.strTableAdd nameNode.sym proc genJsonItem(d: PDoc, n, nameNode: PNode, k: TSymKind): JsonNode = - if not isVisible(nameNode): return + if not isVisible(d, nameNode): return var name = getName(d, nameNode) comm = $genRecComment(d, n) r: TSrcGen - initTokRender(r, n, {renderNoBody, renderNoComments, renderDocComments}) - result = %{ "name": %name, "type": %($k), "line": %n.info.line.int, "col": %n.info.col} if comm.len > 0: @@ -575,7 +684,6 @@ proc checkForFalse(n: PNode): bool = proc traceDeps(d: PDoc, it: PNode) = const k = skModule - if it.kind == nkInfix and it.len == 3 and it[2].kind == nkBracket: let sep = it[0] let dir = it[1] @@ -586,13 +694,36 @@ proc traceDeps(d: PDoc, it: PNode) = for x in it[2]: a.sons[2] = x traceDeps(d, a) - else: + elif it.kind == nkSym and belongsToPackage(d.conf, it.sym): + let external = externalDep(d, it.sym) if d.section[k] != nil: add(d.section[k], ", ") dispA(d.conf, d.section[k], - "<a class=\"reference external\" href=\"$1.html\">$1</a>", - "$1", [rope(splitFile(getModuleName(d.conf, it)).name)]) - -proc generateDoc*(d: PDoc, n: PNode) = + "<a class=\"reference external\" href=\"$2\">$1</a>", + "$1", [rope esc(d.target, changeFileExt(external, "")), + rope changeFileExt(external, "html")]) + +proc exportSym(d: PDoc; s: PSym) = + const k = exportSection + if s.kind == skModule and belongsToPackage(d.conf, s): + let external = externalDep(d, s) + if d.section[k] != nil: add(d.section[k], ", ") + dispA(d.conf, d.section[k], + "<a class=\"reference external\" href=\"$2\">$1</a>", + "$1", [rope esc(d.target, changeFileExt(external, "")), + rope changeFileExt(external, "html")]) + elif s.owner != nil: + let module = originatingModule(s) + if belongsToPackage(d.conf, module): + let external = externalDep(d, module) + if d.section[k] != nil: add(d.section[k], ", ") + # XXX proper anchor generation here + dispA(d.conf, d.section[k], + "<a href=\"$2#$1\"><span class=\"Identifier\">$1</span></a>", + "$1", [rope esc(d.target, s.name.s), + rope changeFileExt(external, "html")]) + +proc generateDoc*(d: PDoc, n, orig: PNode) = + if orig.info.fileIndex != n.info.fileIndex: return case n.kind of nkCommentStmt: add(d.modDesc, genComment(d, n)) of nkProcDef: @@ -619,14 +750,22 @@ proc generateDoc*(d: PDoc, n: PNode) = genItem(d, n.sons[i], n.sons[i].sons[0], succ(skType, ord(n.kind)-ord(nkTypeSection))) of nkStmtList: - for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i]) + for i in countup(0, sonsLen(n) - 1): generateDoc(d, n.sons[i], orig) of nkWhenStmt: # generate documentation for the first branch only: if not checkForFalse(n.sons[0].sons[0]): - generateDoc(d, lastSon(n.sons[0])) + generateDoc(d, lastSon(n.sons[0]), orig) of nkImportStmt: - for i in 0 .. sonsLen(n)-1: traceDeps(d, n.sons[i]) + for it in n: traceDeps(d, it) + of nkExportStmt: + for it in n: + if it.kind == nkSym: exportSym(d, it.sym) + of nkExportExceptStmt: discard "transformed into nkExportStmt by semExportExcept" of nkFromStmt, nkImportExceptStmt: traceDeps(d, n.sons[0]) + of nkCallKinds: + var comm: Rope = nil + getAllRunnableExamples(d, n, comm) + if comm > nil: add(d.modDesc, comm) else: discard proc add(d: PDoc; j: JsonNode) = @@ -717,8 +856,8 @@ proc generateTags*(d: PDoc, n: PNode, r: var Rope) = else: discard proc genSection(d: PDoc, kind: TSymKind) = - const sectionNames: array[skModule..skTemplate, string] = [ - "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs", + const sectionNames: array[skTemp..skTemplate, string] = [ + "Exports", "Imports", "Types", "Vars", "Lets", "Consts", "Vars", "Procs", "Funcs", "Methods", "Iterators", "Converters", "Macros", "Templates" ] if d.section[kind] == nil: return @@ -748,7 +887,8 @@ proc genOutFile(d: PDoc): Rope = # Extract the title. Non API modules generate an entry in the index table. if d.meta[metaTitle].len != 0: title = d.meta[metaTitle] - setIndexTerm(d[], "", title) + let external = AbsoluteFile(d.filename).relativeTo(d.conf.projectPath, '/').changeFileExt(HtmlExt).string + setIndexTerm(d[], external, "", title) else: # Modules get an automatic title for the HTML, but no entry in the index. title = "Module " & extractFilename(changeFileExt(d.filename, "")) @@ -774,29 +914,27 @@ proc genOutFile(d: PDoc): Rope = proc generateIndex*(d: PDoc) = if optGenIndex in d.conf.globalOptions: - writeIndexFile(d[], splitFile(d.conf.outFile).dir / - splitFile(d.filename).name & IndexExt) - -proc getOutFile2(conf: ConfigRef; filename, ext, dir: string): string = - if optWholeProject in conf.globalOptions: - let d = if conf.outFile != "": conf.outFile else: dir - createDir(d) - result = d / changeFileExt(filename, ext) - else: - result = getOutFile(conf, filename, ext) - -proc writeOutput*(d: PDoc, filename, outExt: string, useWarning = false) = + let dir = if d.conf.outFile.isEmpty: d.conf.projectPath / RelativeDir"htmldocs" + elif optWholeProject in d.conf.globalOptions: AbsoluteDir(d.conf.outFile) + else: AbsoluteDir(d.conf.outFile.string.splitFile.dir) + createDir(dir) + let dest = dir / changeFileExt(relativeTo(AbsoluteFile d.filename, + d.conf.projectPath), IndexExt) + writeIndexFile(d[], dest.string) + +proc writeOutput*(d: PDoc, useWarning = false) = var content = genOutFile(d) if optStdout in d.conf.globalOptions: writeRope(stdout, content) else: - let outfile = getOutFile2(d.conf, filename, outExt, "htmldocs") - createDir(outfile.parentDir) + template outfile: untyped = d.destFile + #let outfile = getOutFile2(d.conf, shortenDir(d.conf, filename), outExt, "htmldocs") + createDir(outfile.splitFile.dir) if not writeRope(content, outfile): - rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, outfile) + rawMessage(d.conf, if useWarning: warnCannotOpenFile else: errCannotOpenFile, + outfile.string) -proc writeOutputJson*(d: PDoc, filename, outExt: string, - useWarning = false) = +proc writeOutputJson*(d: PDoc, useWarning = false) = let content = %*{"orig": d.filename, "nimble": getPackageName(d.conf, d.filename), "entries": d.jArray} @@ -804,8 +942,7 @@ proc writeOutputJson*(d: PDoc, filename, outExt: string, write(stdout, $content) else: var f: File - if open(f, getOutFile2(d.conf, splitFile(filename).name, - outExt, "jsondocs"), fmWrite): + if open(f, d.destFile.string, fmWrite): write(f, $content) close(f) else: @@ -816,42 +953,43 @@ proc commandDoc*(cache: IdentCache, conf: ConfigRef) = if ast == nil: return var d = newDocumentor(conf.projectFull, cache, conf) d.hasToc = true - generateDoc(d, ast) - writeOutput(d, conf.projectFull, HtmlExt) + generateDoc(d, ast, ast) + writeOutput(d) generateIndex(d) -proc commandRstAux(cache: IdentCache, conf: ConfigRef; filename, outExt: string) = +proc commandRstAux(cache: IdentCache, conf: ConfigRef; + filename: AbsoluteFile, outExt: string) = var filen = addFileExt(filename, "txt") var d = newDocumentor(filen, cache, conf) d.onTestSnippet = proc (d: var RstGenerator; filename, cmd: string; status: int; content: string) = - var outp: string + var outp: AbsoluteFile if filename.len == 0: inc(d.id) let nameOnly = splitFile(d.filename).name - let subdir = getNimcacheDir(conf) / nameOnly + let subdir = getNimcacheDir(conf) / RelativeDir(nameOnly) createDir(subdir) - outp = subdir / (nameOnly & "_snippet_" & $d.id & ".nim") + outp = subdir / RelativeFile(nameOnly & "_snippet_" & $d.id & ".nim") elif isAbsolute(filename): - outp = filename + outp = AbsoluteFile filename else: # Nim's convention: every path is relative to the file it was written in: - outp = splitFile(d.filename).dir / filename + outp = splitFile(d.filename).dir.AbsoluteDir / RelativeFile(filename) writeFile(outp, content) - let cmd = cmd % quoteShell(outp) - rawMessage(conf, hintExecuting, cmd) - if execShellCmd(cmd) != status: - rawMessage(conf, errGenerated, "executing of external program failed: " & cmd) + let c = if cmd.startsWith("nim "): os.getAppFilename() & cmd.substr(3) + else: cmd + let c2 = c % quoteShell(outp) + rawMessage(conf, hintExecuting, c2) + if execShellCmd(c2) != status: + rawMessage(conf, errGenerated, "executing of external program failed: " & c2) d.isPureRst = true - var rst = parseRst(readFile(filen), filen, 0, 1, d.hasToc, + var rst = parseRst(readFile(filen.string), filen.string, 0, 1, d.hasToc, {roSupportRawDirective}, conf) var modDesc = newStringOfCap(30_000) - #d.modDesc = newMutableRope(30_000) renderRstToOut(d[], rst, modDesc) - #freezeMutableRope(d.modDesc) d.modDesc = rope(modDesc) - writeOutput(d, filename, outExt) + writeOutput(d) generateIndex(d) proc commandRst2Html*(cache: IdentCache, conf: ConfigRef) = @@ -873,9 +1011,9 @@ proc commandJson*(cache: IdentCache, conf: ConfigRef) = writeRope(stdout, content) else: #echo getOutFile(gProjectFull, JsonExt) - let filename = getOutFile(conf, conf.projectFull, JsonExt) + let filename = getOutFile(conf, RelativeFile conf.projectName, JsonExt) if not writeRope(content, filename): - rawMessage(conf, errCannotOpenFile, filename) + rawMessage(conf, errCannotOpenFile, filename.string) proc commandTags*(cache: IdentCache, conf: ConfigRef) = var ast = parseFile(conf.projectMainIdx, cache, conf) @@ -890,12 +1028,12 @@ proc commandTags*(cache: IdentCache, conf: ConfigRef) = writeRope(stdout, content) else: #echo getOutFile(gProjectFull, TagsExt) - let filename = getOutFile(conf, conf.projectFull, TagsExt) + let filename = getOutFile(conf, RelativeFile conf.projectName, TagsExt) if not writeRope(content, filename): - rawMessage(conf, errCannotOpenFile, filename) + rawMessage(conf, errCannotOpenFile, filename.string) proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) = - var content = mergeIndexes(conf.projectFull).rope + var content = mergeIndexes(conf.projectFull.string).rope let code = ropeFormatNamedVars(conf, getConfigVar(conf, "doc.file"), ["title", "tableofcontents", "moduledesc", "date", "time", @@ -903,6 +1041,6 @@ proc commandBuildIndex*(cache: IdentCache, conf: ConfigRef) = ["Index".rope, nil, nil, rope(getDateStr()), rope(getClockStr()), content, nil, nil, nil]) # no analytics because context is not available - let filename = getOutFile(conf, "theindex", HtmlExt) + let filename = getOutFile(conf, RelativeFile"theindex", HtmlExt) if not writeRope(code, filename): - rawMessage(conf, errCannotOpenFile, filename) + rawMessage(conf, errCannotOpenFile, filename.string) diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 068c47bb3..01acdd4ca 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -11,7 +11,8 @@ # semantic checking. import - os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos + os, options, ast, astalgo, msgs, ropes, idents, passes, docgen, lineinfos, + pathutils from modulegraphs import ModuleGraph @@ -21,12 +22,15 @@ type module: PSym PGen = ref TGen +template shouldProcess(g): bool = + (g.module.owner.id == g.doc.conf.mainPackageId and optWholeProject in g.doc.conf.globalOptions) or + sfMainModule in g.module.flags + template closeImpl(body: untyped) {.dirty.} = var g = PGen(p) let useWarning = sfMainModule notin g.module.flags #echo g.module.name.s, " ", g.module.owner.id, " ", gMainPackageId - if (g.module.owner.id == g.doc.conf.mainPackageId and optWholeProject in g.doc.conf.globalOptions) or - sfMainModule in g.module.flags: + if shouldProcess(g): body try: generateIndex(g.doc) @@ -35,27 +39,30 @@ template closeImpl(body: untyped) {.dirty.} = proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: - writeOutput(g.doc, toFilename(graph.config, FileIndex g.module.position), HtmlExt, useWarning) + writeOutput(g.doc, useWarning) proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: - writeOutputJson(g.doc, toFilename(graph.config, FileIndex g.module.position), ".json", useWarning) + writeOutputJson(g.doc, useWarning) proc processNode(c: PPassContext, n: PNode): PNode = result = n var g = PGen(c) - generateDoc(g.doc, n) + if shouldProcess(g): + generateDoc(g.doc, n, n) proc processNodeJson(c: PPassContext, n: PNode): PNode = result = n var g = PGen(c) - generateJson(g.doc, n) + if shouldProcess(g): + generateJson(g.doc, n) proc myOpen(graph: ModuleGraph; module: PSym): PPassContext = var g: PGen new(g) g.module = module - var d = newDocumentor(toFilename(graph.config, FileIndex module.position), graph.cache, graph.config) + var d = newDocumentor(AbsoluteFile toFullPath(graph.config, FileIndex module.position), + graph.cache, graph.config) d.hasToc = true g.doc = d result = g diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index 16b0d614d..69698ae09 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -14,7 +14,7 @@ import ropes, os, strutils, osproc, platform, condsyms, options, msgs, - lineinfos, std / sha1, streams + lineinfos, std / sha1, streams, pathutils type TInfoCCProp* = enum # properties of the C compiler: @@ -429,12 +429,13 @@ proc initVars*(conf: ConfigRef) = if len(conf.ccompilerpath) == 0: conf.ccompilerpath = getConfigVar(conf, conf.cCompiler, ".path") -proc completeCFilePath*(conf: ConfigRef; cfile: string, createSubDir: bool = true): string = +proc completeCFilePath*(conf: ConfigRef; cfile: AbsoluteFile, + createSubDir: bool = true): AbsoluteFile = result = completeGeneratedFilePath(conf, cfile, createSubDir) -proc toObjFile*(conf: ConfigRef; filename: string): string = +proc toObjFile*(conf: ConfigRef; filename: AbsoluteFile): AbsoluteFile = # Object file for compilation - result = filename & "." & CC[conf.cCompiler].objExt + result = AbsoluteFile(filename.string & "." & CC[conf.cCompiler].objExt) proc addFileToCompile*(conf: ConfigRef; cf: Cfile) = conf.toCompile.add(cf) @@ -447,8 +448,8 @@ proc resetCompilationLists*(conf: ConfigRef) = # Maybe we can do that in checkDep on the other hand? conf.externalToLink.setLen 0 -proc addExternalFileToLink*(conf: ConfigRef; filename: string) = - conf.externalToLink.insert(filename, 0) +proc addExternalFileToLink*(conf: ConfigRef; filename: AbsoluteFile) = + conf.externalToLink.insert(filename.string, 0) proc execWithEcho(conf: ConfigRef; cmd: string, msg = hintExecuting): int = rawMessage(conf, msg, cmd) @@ -459,14 +460,15 @@ proc execExternalProgram*(conf: ConfigRef; cmd: string, msg = hintExecuting) = rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % cmd) -proc generateScript(conf: ConfigRef; projectFile: string, script: Rope) = - let (dir, name, ext) = splitFile(projectFile) - let filename = getNimcacheDir(conf) / addFileExt("compile_" & name, - platform.OS[conf.target.targetOS].scriptExt) +proc generateScript(conf: ConfigRef; projectFile: AbsoluteFile, script: Rope) = + let (_, name, _) = splitFile(projectFile) + let filename = getNimcacheDir(conf) / RelativeFile(addFileExt("compile_" & name, + platform.OS[conf.target.targetOS].scriptExt)) if writeRope(script, filename): - copyFile(conf.libpath / "nimbase.h", getNimcacheDir(conf) / "nimbase.h") + copyFile(conf.libpath / RelativeFile"nimbase.h", + getNimcacheDir(conf) / RelativeFile"nimbase.h") else: - rawMessage(conf, errGenerated, "could not write to file: " & filename) + rawMessage(conf, errGenerated, "could not write to file: " & filename.string) proc getOptSpeed(conf: ConfigRef; c: TSystemCC): string = result = getConfigVar(conf, c, ".options.speed") @@ -490,7 +492,7 @@ proc noAbsolutePaths(conf: ConfigRef): bool {.inline.} = # `optGenMapping` is included here for niminst. result = conf.globalOptions * {optGenScript, optGenMapping} != {} -proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string = +proc cFileSpecificOptions(conf: ConfigRef; cfilename: AbsoluteFile): string = result = conf.compileOptions for option in conf.compileOptionsCmd: if strutils.find(result, option, 0) < 0: @@ -513,7 +515,7 @@ proc cFileSpecificOptions(conf: ConfigRef; cfilename: string): string = if existsConfigVar(conf, key): addOpt(result, getConfigVar(conf, key)) proc getCompileOptions(conf: ConfigRef): string = - result = cFileSpecificOptions(conf, "__dummy__") + result = cFileSpecificOptions(conf, AbsoluteFile"__dummy__") proc getLinkOptions(conf: ConfigRef): string = result = conf.linkOptions & " " & conf.linkOptionsCmd & " " @@ -526,8 +528,8 @@ proc needsExeExt(conf: ConfigRef): bool {.inline.} = result = (optGenScript in conf.globalOptions and conf.target.targetOS == osWindows) or (conf.target.hostOS == osWindows) -proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: string): string = - result = if conf.cmd == cmdCompileToCpp and not cfile.endsWith(".c"): +proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: AbsoluteFile): string = + result = if conf.cmd == cmdCompileToCpp and not cfile.string.endsWith(".c"): CC[compiler].cppCompiler else: CC[compiler].compilerExe @@ -539,7 +541,7 @@ proc getCompilerExe(conf: ConfigRef; compiler: TSystemCC; cfile: string): string proc getLinkerExe(conf: ConfigRef; compiler: TSystemCC): string = result = if CC[compiler].linkerExe.len > 0: CC[compiler].linkerExe elif optMixedMode in conf.globalOptions and conf.cmd != cmdCompileToCpp: CC[compiler].cppCompiler - else: getCompilerExe(conf, compiler, "") + else: getCompilerExe(conf, compiler, AbsoluteFile"") proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = var c = conf.cCompiler @@ -565,43 +567,42 @@ proc getCompileCFileCmd*(conf: ConfigRef; cfile: Cfile): string = includeCmd = "" compilePattern = getCompilerExe(conf, c, cfile.cname) - var cf = if noAbsolutePaths(conf): extractFilename(cfile.cname) + var cf = if noAbsolutePaths(conf): AbsoluteFile extractFilename(cfile.cname.string) else: cfile.cname var objfile = - if cfile.obj.len == 0: + if cfile.obj.isEmpty: if not cfile.flags.contains(CfileFlag.External) or noAbsolutePaths(conf): - toObjFile(conf, cf) + toObjFile(conf, cf).string else: - completeCFilePath(conf, toObjFile(conf, cf)) + completeCFilePath(conf, toObjFile(conf, cf)).string elif noAbsolutePaths(conf): - extractFilename(cfile.obj) + extractFilename(cfile.obj.string) else: - cfile.obj + cfile.obj.string # D files are required by nintendo switch libs for # compilation. They are basically a list of all includes. let dfile = objfile.changeFileExt(".d").quoteShell() objfile = quoteShell(objfile) - cf = quoteShell(cf) + let cfsh = quoteShell(cf) result = quoteShell(compilePattern % [ "dfile", dfile, - "file", cf, "objfile", objfile, "options", options, - "include", includeCmd, "nim", getPrefixDir(conf), - "nim", getPrefixDir(conf), "lib", conf.libpath]) + "file", cfsh, "objfile", objfile, "options", options, + "include", includeCmd, "nim", getPrefixDir(conf).string, + "lib", conf.libpath.string]) add(result, ' ') addf(result, CC[c].compileTmpl, [ "dfile", dfile, - "file", cf, "objfile", objfile, + "file", cfsh, "objfile", objfile, "options", options, "include", includeCmd, "nim", quoteShell(getPrefixDir(conf)), - "nim", quoteShell(getPrefixDir(conf)), "lib", quoteShell(conf.libpath)]) proc footprint(conf: ConfigRef; cfile: Cfile): SecureHash = result = secureHash( - $secureHashFile(cfile.cname) & + $secureHashFile(cfile.cname.string) & platform.OS[conf.target.targetOS].name & platform.CPU[conf.target.targetCPU].name & extccomp.CC[conf.cCompiler].name & @@ -614,14 +615,14 @@ proc externalFileChanged(conf: ConfigRef; cfile: Cfile): bool = var hashFile = toGeneratedFile(conf, conf.withPackageName(cfile.cname), "sha1") var currentHash = footprint(conf, cfile) var f: File - if open(f, hashFile, fmRead): + if open(f, hashFile.string, fmRead): let oldHash = parseSecureHash(f.readLine()) close(f) result = oldHash != currentHash else: result = true if result: - if open(f, hashFile, fmWrite): + if open(f, hashFile.string, fmWrite): f.writeLine($currentHash) close(f) @@ -630,7 +631,7 @@ proc addExternalFileToCompile*(conf: ConfigRef; c: var Cfile) = c.flags.incl CfileFlag.Cached conf.toCompile.add(c) -proc addExternalFileToCompile*(conf: ConfigRef; filename: string) = +proc addExternalFileToCompile*(conf: ConfigRef; filename: AbsoluteFile) = var c = Cfile(cname: filename, obj: toObjFile(conf, completeCFilePath(conf, filename, false)), flags: {CfileFlag.External}) @@ -650,11 +651,11 @@ proc compileCFile(conf: ConfigRef; list: CFileList, script: var Rope, cmds: var add(script, compileCmd) add(script, "\n") -proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = +proc getLinkCmd(conf: ConfigRef; projectfile: AbsoluteFile, objfiles: string): string = if optGenStaticLib in conf.globalOptions: var libname: string - if conf.outFile.len > 0: - libname = conf.outFile.expandTilde + if not conf.outFile.isEmpty: + libname = conf.outFile.string.expandTilde if not libname.isAbsolute(): libname = getCurrentDir() / libname else: @@ -679,13 +680,13 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = else: exefile = splitFile(projectfile).name & platform.OS[conf.target.targetOS].exeExt builddll = "" - if conf.outFile.len > 0: - exefile = conf.outFile.expandTilde + if not conf.outFile.isEmpty: + exefile = conf.outFile.string.expandTilde if not exefile.isAbsolute(): exefile = getCurrentDir() / exefile if not noAbsolutePaths(conf): if not exefile.isAbsolute(): - exefile = joinPath(splitFile(projectfile).dir, exefile) + exefile = string(splitFile(projectfile).dir / RelativeFile(exefile)) when false: if optCDebug in conf.globalOptions: writeDebugInfo(exefile.changeFileExt("ndb")) @@ -693,7 +694,7 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = # Map files are required by Nintendo Switch compilation. They are a list # of all function calls in the library and where they come from. - let mapfile = quoteShell(getNimcacheDir(conf) / splitFile(projectFile).name & ".map") + let mapfile = quoteShell(getNimcacheDir(conf) / RelativeFile(splitFile(projectFile).name & ".map")) let linkOptions = getLinkOptions(conf) & " " & getConfigVar(conf, conf.cCompiler, ".options.linker") @@ -703,7 +704,7 @@ proc getLinkCmd(conf: ConfigRef; projectfile, objfiles: string): string = result = quoteShell(result % ["builddll", builddll, "mapfile", mapfile, "buildgui", buildgui, "options", linkOptions, "objfiles", objfiles, - "exefile", exefile, "nim", getPrefixDir(conf), "lib", conf.libpath]) + "exefile", exefile, "nim", getPrefixDir(conf).string, "lib", conf.libpath.string]) result.add ' ' addf(result, linkTmpl, ["builddll", builddll, "mapfile", mapfile, @@ -761,7 +762,7 @@ proc execCmdsInParallel(conf: ConfigRef; cmds: seq[string]; prettyCb: proc (idx: rawMessage(conf, errGenerated, "execution of an external program failed: '$1'" % cmds.join()) -proc callCCompiler*(conf: ConfigRef; projectfile: string) = +proc callCCompiler*(conf: ConfigRef; projectfile: AbsoluteFile) = var linkCmd: string if conf.globalOptions * {optCompileOnly, optGenScript} == {optCompileOnly}: @@ -787,7 +788,7 @@ proc callCCompiler*(conf: ConfigRef; projectfile: string) = add(objfiles, quoteShell( addFileExt(objFile, CC[conf.cCompiler].objExt))) for x in conf.toCompile: - let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj + let objFile = if noAbsolutePaths(conf): x.obj.extractFilename else: x.obj.string add(objfiles, ' ') add(objfiles, quoteShell(objFile)) @@ -804,7 +805,7 @@ proc callCCompiler*(conf: ConfigRef; projectfile: string) = #from json import escapeJson import json -proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = +proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) = template lit(x: untyped) = f.write x template str(x: untyped) = when compiles(escapeJson(x, buf)): @@ -821,7 +822,7 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = let compileCmd = getCompileCFileCmd(conf, it) if pastStart: lit "],\L" lit "[" - str it.cname + str it.cname.string lit ", " str compileCmd pastStart = true @@ -851,11 +852,10 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = var buf = newStringOfCap(50) - let file = projectfile.splitFile.name - let jsonFile = toGeneratedFile(conf, file, "json") + let jsonFile = toGeneratedFile(conf, projectfile, "json") var f: File - if open(f, jsonFile, fmWrite): + if open(f, jsonFile.string, fmWrite): lit "{\"compile\":[\L" cfiles(conf, f, buf, conf.toCompile, false) lit "],\L\"link\":[\L" @@ -868,11 +868,10 @@ proc writeJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = lit "\L}\L" close(f) -proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = - let file = projectfile.splitFile.name - let jsonFile = toGeneratedFile(conf, file, "json") +proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: AbsoluteFile) = + let jsonFile = toGeneratedFile(conf, projectfile, "json") try: - let data = json.parseFile(jsonFile) + let data = json.parseFile(jsonFile.string) let toCompile = data["compile"] doAssert toCompile.kind == JArray var cmds: TStringSeq = @[] @@ -896,11 +895,11 @@ proc runJsonBuildInstructions*(conf: ConfigRef; projectfile: string) = except: when declared(echo): echo getCurrentException().getStackTrace() - quit "error evaluating JSON file: " & jsonFile + quit "error evaluating JSON file: " & jsonFile.string proc genMappingFiles(conf: ConfigRef; list: CFileList): Rope = for it in list: - addf(result, "--file:r\"$1\"$N", [rope(it.cname)]) + addf(result, "--file:r\"$1\"$N", [rope(it.cname.string)]) proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) = if optGenMapping notin conf.globalOptions: return @@ -914,9 +913,9 @@ proc writeMapping*(conf: ConfigRef; symbolMapping: Rope) = getConfigVar(conf, conf.cCompiler, ".options.linker"))) add(code, "\n[Environment]\nlibpath=") - add(code, strutils.escape(conf.libpath)) + add(code, strutils.escape(conf.libpath.string)) addf(code, "\n[Symbols]$n$1", [symbolMapping]) - let filename = joinPath(conf.projectPath, "mapping.txt") + let filename = conf.projectPath / RelativeFile"mapping.txt" if not writeRope(code, filename): - rawMessage(conf, errGenerated, "could not write to file: " & filename) + rawMessage(conf, errGenerated, "could not write to file: " & filename.string) diff --git a/compiler/filter_tmpl.nim b/compiler/filter_tmpl.nim index 09455ced7..b884b1ec3 100644 --- a/compiler/filter_tmpl.nim +++ b/compiler/filter_tmpl.nim @@ -11,7 +11,7 @@ import llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options, - renderer, filters, lineinfos + renderer, filters, lineinfos, pathutils type TParseState = enum @@ -199,7 +199,8 @@ proc parseLine(p: var TTmplParser) = inc(j) llStreamWrite(p.outp, "\\n\"") -proc filterTmpl*(stdin: PLLStream, filename: string, call: PNode; conf: ConfigRef): PLLStream = +proc filterTmpl*(stdin: PLLStream, filename: AbsoluteFile, + call: PNode; conf: ConfigRef): PLLStream = var p: TTmplParser p.config = conf p.info = newLineInfo(conf, filename, 0, 0) diff --git a/compiler/filters.nim b/compiler/filters.nim index 3ebbad678..d9e8e41da 100644 --- a/compiler/filters.nim +++ b/compiler/filters.nim @@ -11,7 +11,7 @@ import llstream, os, wordrecg, idents, strutils, ast, astalgo, msgs, options, - renderer + renderer, pathutils proc invalidPragma(conf: ConfigRef; n: PNode) = localError(conf, n.info, @@ -47,7 +47,7 @@ proc boolArg*(conf: ConfigRef; n: PNode, name: string, pos: int, default: bool): elif x.kind == nkIdent and cmpIgnoreStyle(x.ident.s, "false") == 0: result = false else: invalidPragma(conf, n) -proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream = +proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream = var pattern = strArg(conf, call, "startswith", 1, "") var leading = boolArg(conf, call, "leading", 2, true) var trailing = boolArg(conf, call, "trailing", 3, true) @@ -61,7 +61,7 @@ proc filterStrip*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNo llStreamWriteln(result, line) llStreamClose(stdin) -proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: string, call: PNode): PLLStream = +proc filterReplace*(conf: ConfigRef; stdin: PLLStream, filename: AbsoluteFile, call: PNode): PLLStream = var sub = strArg(conf, call, "sub", 1, "") if len(sub) == 0: invalidPragma(conf, call) var by = strArg(conf, call, "by", 2, "") diff --git a/compiler/gorgeimpl.nim b/compiler/gorgeimpl.nim index 44ad46136..44636f382 100644 --- a/compiler/gorgeimpl.nim +++ b/compiler/gorgeimpl.nim @@ -10,7 +10,7 @@ ## Module that implements ``gorge`` for the compiler. import msgs, std / sha1, os, osproc, streams, strutils, options, - lineinfos + lineinfos, pathutils proc readOutput(p: Process): (string, int) = result[0] = "" @@ -26,7 +26,7 @@ proc opGorge*(cmd, input, cache: string, info: TLineInfo; conf: ConfigRef): (str let workingDir = parentDir(toFullPath(conf, info)) if cache.len > 0:# and optForceFullMake notin gGlobalOptions: let h = secureHash(cmd & "\t" & input & "\t" & cache) - let filename = options.toGeneratedFile(conf, "gorge_" & $h, "txt") + let filename = toGeneratedFile(conf, AbsoluteFile("gorge_" & $h), "txt").string var f: File if open(f, filename): result = (f.readAll, 0) diff --git a/compiler/idgen.nim b/compiler/idgen.nim index 7d103ffd7..239df0c57 100644 --- a/compiler/idgen.nim +++ b/compiler/idgen.nim @@ -9,7 +9,7 @@ ## This module contains a simple persistent id generator. -import idents, strutils, os, options +import idents, strutils, os, options, pathutils var gFrontEndId*: int @@ -36,18 +36,18 @@ proc setId*(id: int) {.inline.} = proc idSynchronizationPoint*(idRange: int) = gFrontEndId = (gFrontEndId div idRange + 1) * idRange + 1 -proc toGid(conf: ConfigRef; f: string): string = +proc toGid(conf: ConfigRef; f: AbsoluteFile): string = # we used to use ``f.addFileExt("gid")`` (aka ``$project.gid``), but this # will cause strange bugs if multiple projects are in the same folder, so # we simply use a project independent name: - result = options.completeGeneratedFilePath(conf, "nim.gid") + result = options.completeGeneratedFilePath(conf, AbsoluteFile"nim.gid").string -proc saveMaxIds*(conf: ConfigRef; project: string) = +proc saveMaxIds*(conf: ConfigRef; project: AbsoluteFile) = var f = open(toGid(conf, project), fmWrite) f.writeLine($gFrontEndId) f.close() -proc loadMaxIds*(conf: ConfigRef; project: string) = +proc loadMaxIds*(conf: ConfigRef; project: AbsoluteFile) = var f: File if open(f, toGid(conf, project), fmRead): var line = newStringOfCap(20) diff --git a/compiler/importer.nim b/compiler/importer.nim index acc3d26d2..60b7872fe 100644 --- a/compiler/importer.nim +++ b/compiler/importer.nim @@ -7,15 +7,12 @@ # distribution, for details about the copyright. # -# This module implements the symbol importing mechanism. +## This module implements the symbol importing mechanism. import intsets, strutils, os, ast, astalgo, msgs, options, idents, lookups, semdata, passes, renderer, modulepaths, sigmatch, lineinfos -proc evalImport*(c: PContext, n: PNode): PNode -proc evalFrom*(c: PContext, n: PNode): PNode - proc readExceptSet*(c: PContext, n: PNode): IntSet = assert n.kind in {nkImportExceptStmt, nkExportExceptStmt} result = initIntSet() @@ -24,9 +21,15 @@ proc readExceptSet*(c: PContext, n: PNode): IntSet = result.incl(ident.id) proc importPureEnumField*(c: PContext; s: PSym) = - var check = strTableGet(c.importTable.symbols, s.name) + let check = strTableGet(c.importTable.symbols, s.name) if check == nil: - strTableAdd(c.pureEnumFields, s) + let checkB = strTableGet(c.pureEnumFields, s.name) + if checkB == nil: + strTableAdd(c.pureEnumFields, s) + else: + # mark as ambigous: + incl(c.ambiguousSymbols, checkB.id) + incl(c.ambiguousSymbols, s.id) proc rawImportSymbol(c: PContext, s: PSym) = # This does not handle stubs, because otherwise loading on demand would be @@ -134,7 +137,7 @@ proc importModuleAs(c: PContext; n: PNode, realModule: PSym): PSym = c.config.options) proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym = - var f = checkModuleName(c.config, n) + let f = checkModuleName(c.config, n) if f != InvalidFileIDX: let L = c.graph.importStack.len let recursion = c.graph.importStack.find(f) @@ -162,9 +165,19 @@ proc myImportModule(c: PContext, n: PNode; importStmtResult: PNode): PSym = else: message(c.config, n.info, warnDeprecated, result.name.s) suggestSym(c.config, n.info, result, c.graph.usageSym, false) - importStmtResult.add newStrNode(toFullPath(c.config, f), n.info) + importStmtResult.add newSymNode(result, n.info) + #newStrNode(toFullPath(c.config, f), n.info) + +proc transformImportAs(c: PContext; n: PNode): PNode = + if n.kind == nkInfix and considerQuotedIdent(c, n[0]).s == "as": + result = newNodeI(nkImportAs, n.info) + result.add n.sons[1] + result.add n.sons[2] + else: + result = n proc impMod(c: PContext; it: PNode; importStmtResult: PNode) = + let it = transformImportAs(c, it) let m = myImportModule(c, it, importStmtResult) if m != nil: var emptySet: IntSet @@ -173,7 +186,7 @@ proc impMod(c: PContext; it: PNode; importStmtResult: PNode) = importAllSymbolsExcept(c, m, emptySet) #importForwarded(c, m.ast, emptySet) -proc evalImport(c: PContext, n: PNode): PNode = +proc evalImport*(c: PContext, n: PNode): PNode = result = newNodeI(nkImportStmt, n.info) for i in countup(0, sonsLen(n) - 1): let it = n.sons[i] @@ -185,27 +198,22 @@ proc evalImport(c: PContext, n: PNode): PNode = imp.add dir imp.add sep # dummy entry, replaced in the loop for x in it[2]: + # transform `a/b/[c as d]` to `/a/b/c as d` if x.kind == nkInfix and x.sons[0].ident.s == "as": + let impAs = copyTree(x) imp.sons[2] = x.sons[1] - let impAs = newNodeI(nkImportAs, it.info) - impAs.add imp - impAs.add x.sons[2] - imp = impAs + impAs.sons[1] = imp impMod(c, imp, result) else: imp.sons[2] = x impMod(c, imp, result) - elif it.kind == nkInfix and it.sons[0].ident.s == "as": - let imp = newNodeI(nkImportAs, it.info) - imp.add it.sons[1] - imp.add it.sons[2] - impMod(c, imp, result) else: impMod(c, it, result) -proc evalFrom(c: PContext, n: PNode): PNode = +proc evalFrom*(c: PContext, n: PNode): PNode = result = newNodeI(nkImportStmt, n.info) checkMinSonsLen(n, 2, c.config) + n.sons[0] = transformImportAs(c, n.sons[0]) var m = myImportModule(c, n.sons[0], result) if m != nil: n.sons[0] = newSymNode(m) @@ -217,6 +225,7 @@ proc evalFrom(c: PContext, n: PNode): PNode = proc evalImportExcept*(c: PContext, n: PNode): PNode = result = newNodeI(nkImportStmt, n.info) checkMinSonsLen(n, 2, c.config) + n.sons[0] = transformImportAs(c, n.sons[0]) var m = myImportModule(c, n.sons[0], result) if m != nil: n.sons[0] = newSymNode(m) diff --git a/compiler/installer.ini b/compiler/installer.ini index 79eb7178a..212ec438b 100644 --- a/compiler/installer.ini +++ b/compiler/installer.ini @@ -68,6 +68,7 @@ Files: "compiler" Files: "doc" Files: "doc/html" Files: "tools" +Files: "nimpretty" Files: "nimsuggest" Files: "nimsuggest/tests/*.nim" Files: "web/website.ini" @@ -114,9 +115,9 @@ Download: r"Aporia Text Editor|dist|aporia.zip|97997|https://nim-lang.org/downlo ; for now only NSIS supports optional downloads [WinBin] -Files: "$NIMINSTDEPS/makelink.exe" -Files: "$NIMINSTDEPS/7zG.exe" -Files: "$NIMINSTDEPS/*.dll" +Files: "bin/makelink.exe" +Files: "bin/7zG.exe" +Files: "bin/*.dll" [UnixBin] Files: "bin/nim" diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index b9b22d825..aa2386526 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -32,7 +32,7 @@ import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, nversion, nimsets, msgs, std / sha1, bitsets, idents, types, os, tables, times, ropes, math, passes, ccgutils, wordrecg, renderer, - intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils + intsets, cgmeth, lowerings, sighashes, lineinfos, rodutils, pathutils from modulegraphs import ModuleGraph @@ -243,7 +243,8 @@ proc mangleName(m: BModule, s: PSym): Rope = x.add("HEX" & toHex(ord(c), 2)) inc i result = rope(x) - if s.name.s != "this" and s.kind != skField: + # From ES5 on reserved words can be used as object field names + if s.kind != skField: if optHotCodeReloading in m.config.options: # When hot reloading is enabled, we must ensure that the names # of functions and types will be preserved across rebuilds: @@ -657,15 +658,16 @@ proc genTry(p: PProc, n: PNode, r: var TCompRes) = line(p, "}\L") proc genRaiseStmt(p: PProc, n: PNode) = - genLineDir(p, n) if n.sons[0].kind != nkEmpty: var a: TCompRes gen(p, n.sons[0], a) let typ = skipTypes(n.sons[0].typ, abstractPtrs) + genLineDir(p, n) useMagic(p, "raiseException") lineF(p, "raiseException($1, $2);$n", [a.rdLoc, makeJSString(typ.sym.name.s)]) else: + genLineDir(p, n) useMagic(p, "reraiseException") line(p, "reraiseException();\L") @@ -763,11 +765,22 @@ proc genAsmOrEmitStmt(p: PProc, n: PNode) = of nkSym: let v = it.sym # for backwards compatibility we don't deref syms here :-( - if v.kind in {skVar, skLet, skTemp, skConst, skResult, skParam, skForVar}: - p.body.add mangleName(p.module, v) + if false: + discard else: var r: TCompRes gen(p, it, r) + + if it.typ.kind == tyPointer: + # A fat pointer is disguised as an array + r.res = r.address + r.address = nil + elif r.typ == etyBaseIndex: + # Deference first + r.res = "$1[$2]" % [r.address, r.res] + r.address = nil + r.typ = etyNone + p.body.add(r.rdLoc) else: var r: TCompRes @@ -844,6 +857,7 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = else: gen(p, x, a) + genLineDir(p, y) gen(p, y, b) # we don't care if it's an etyBaseIndex (global) of a string, it's @@ -880,11 +894,9 @@ proc genAsgnAux(p: PProc, x, y: PNode, noCopyNeeded: bool) = lineF(p, "$1 = $2;$n", [a.res, b.res]) proc genAsgn(p: PProc, n: PNode) = - genLineDir(p, n) genAsgnAux(p, n.sons[0], n.sons[1], noCopyNeeded=false) proc genFastAsgn(p: PProc, n: PNode) = - genLineDir(p, n) # 'shallowCopy' always produced 'noCopyNeeded = true' here but this is wrong # for code like # while j >= pos: @@ -1528,18 +1540,18 @@ proc genConStrStr(p: PProc, n: PNode, r: var TCompRes) = if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyChar: r.res.add("[$1].concat(" % [a.res]) else: - r.res.add("($1.slice(0,-1)).concat(" % [a.res]) + r.res.add("($1).concat(" % [a.res]) for i in countup(2, sonsLen(n) - 2): gen(p, n.sons[i], a) if skipTypes(n.sons[i].typ, abstractVarRange).kind == tyChar: r.res.add("[$1]," % [a.res]) else: - r.res.add("$1.slice(0,-1)," % [a.res]) + r.res.add("$1," % [a.res]) gen(p, n.sons[sonsLen(n) - 1], a) if skipTypes(n.sons[sonsLen(n) - 1].typ, abstractVarRange).kind == tyChar: - r.res.add("[$1, 0])" % [a.res]) + r.res.add("[$1])" % [a.res]) else: r.res.add("$1)" % [a.res]) @@ -1646,14 +1658,20 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = else: unaryExpr(p, n, r, "subInt", "subInt($1, 1)") of mAppendStrCh: binaryExpr(p, n, r, "addChar", - "if ($1 != null) { addChar($1, $2); } else { $1 = [$2, 0]; }") + "if ($1 != null) { addChar($1, $2); } else { $1 = [$2]; }") of mAppendStrStr: + var lhs, rhs: TCompRes + gen(p, n[1], lhs) + gen(p, n[2], rhs) + + let rhsIsLit = n[2].kind in nkStrKinds if skipTypes(n.sons[1].typ, abstractVarRange).kind == tyCString: - binaryExpr(p, n, r, "", "if ($1 != null) { $1 += $2; } else { $1 = $2; }") + r.res = "if ($1 != null) { $1 += $2; } else { $1 = $2$3; }" % [ + lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] else: - binaryExpr(p, n, r, "", - "if ($1 != null) { $1 = ($1.slice(0, -1)).concat($2); } else { $1 = $2;}") - # XXX: make a copy of $2, because of Javascript's sucking semantics + r.res = "if ($1 != null) { $1 = ($1).concat($2); } else { $1 = $2$3; }" % [ + lhs.rdLoc, rhs.rdLoc, if rhsIsLit: nil else: ~".slice()"] + r.kind = resExpr of mAppendSeqElem: var x, y: TCompRes gen(p, n.sons[1], x) @@ -1681,21 +1699,12 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = of mSizeOf: r.res = rope(getSize(p.config, n.sons[1].typ)) of mChr, mArrToSeq: gen(p, n.sons[1], r) # nothing to do of mOrd: genOrd(p, n, r) - of mLengthStr: - if n.sons[1].typ.skipTypes(abstractInst).kind == tyCString: - unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - else: - unaryExpr(p, n, r, "", "($1 != null ? $1.length-1 : 0)") - of mXLenStr: unaryExpr(p, n, r, "", "$1.length-1") - of mLengthSeq, mLengthOpenArray, mLengthArray: + of mLengthStr, mLengthSeq, mLengthOpenArray, mLengthArray: unaryExpr(p, n, r, "", "($1 != null ? $1.length : 0)") - of mXLenSeq: + of mXLenStr, mXLenSeq: unaryExpr(p, n, r, "", "$1.length") of mHigh: - if skipTypes(n.sons[1].typ, abstractVar).kind == tyString: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-2) : -1)") - else: - unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") + unaryExpr(p, n, r, "", "($1 != null ? ($1.length-1) : -1)") of mInc: if n[1].typ.skipTypes(abstractRange).kind in tyUInt .. tyUInt64: binaryUintExpr(p, n, r, "+", true) @@ -1709,7 +1718,7 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2") else: binaryExpr(p, n, r, "subInt", "$1 = subInt($1, $2)") of mSetLengthStr: - binaryExpr(p, n, r, "", "$1.length = $2+1; $1[$1.length-1] = 0") + binaryExpr(p, n, r, "", "$1.length = $2") of mSetLengthSeq: var x, y: TCompRes gen(p, n.sons[1], x) @@ -1738,8 +1747,6 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) = localError(p.config, n.info, errXMustBeCompileTime % n.sons[0].sym.name.s) of mCopyStr: binaryExpr(p, n, r, "", "($1.slice($2))") - of mCopyStrLast: - ternaryExpr(p, n, r, "", "($1.slice($2, ($3)+1).concat(0))") of mNewString: unaryExpr(p, n, r, "mnewString", "mnewString($1)") of mNewStringOfCap: unaryExpr(p, n, r, "mnewString", "mnewString(0)") @@ -2064,8 +2071,11 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) = r.kind = resExpr of nkStrLit..nkTripleStrLit: if skipTypes(n.typ, abstractVarRange).kind == tyString: - useMagic(p, "makeNimstrLit") - r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + if n.strVal.len != 0: + useMagic(p, "makeNimstrLit") + r.res = "makeNimstrLit($1)" % [makeJSString(n.strVal)] + else: + r.res = rope"[]" else: r.res = makeJSString(n.strVal, false) r.kind = resExpr @@ -2253,7 +2263,7 @@ proc genClass(conf: ConfigRef; obj: PType; content: Rope; ext: string) = "class $#$# {$n$#$n}$n") % [rope(VersionAsString), cls, extends, content] - let outfile = changeFileExt(completeCFilePath(conf, $cls), ext) + let outfile = changeFileExt(completeCFilePath(conf, AbsoluteFile($cls)), ext) discard writeRopeIfNotEqual(result, outfile) proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = @@ -2267,11 +2277,11 @@ proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = else: "nimsystem" let code = wholeCode(graph, m) let outfile = - if m.config.outFile.len > 0: - if m.config.outFile.isAbsolute: m.config.outFile - else: getCurrentDir() / m.config.outFile + if not m.config.outFile.isEmpty: + if m.config.outFile.string.isAbsolute: m.config.outFile + else: AbsoluteFile(getCurrentDir() / m.config.outFile.string) else: - changeFileExt(completeCFilePath(m.config, f), ext) + changeFileExt(completeCFilePath(m.config, AbsoluteFile f), ext) discard writeRopeIfNotEqual(genHeader() & code, outfile) for obj, content in items(globals.classes): genClass(m.config, obj, content, ext) diff --git a/compiler/layouter.nim b/compiler/layouter.nim index 36ad08696..cc6ec48b7 100644 --- a/compiler/layouter.nim +++ b/compiler/layouter.nim @@ -9,7 +9,8 @@ ## Layouter for nimpretty. -import idents, lexer, lineinfos, llstream, options, msgs, strutils +import idents, lexer, lineinfos, llstream, options, msgs, strutils, + pathutils from os import changeFileExt const @@ -39,7 +40,7 @@ type proc openEmitter*(em: var Emitter, cache: IdentCache; config: ConfigRef, fileIdx: FileIndex) = - let fullPath = config.toFullPath(fileIdx) + let fullPath = Absolutefile config.toFullPath(fileIdx) em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), cache, config) if em.indWidth == 0: em.indWidth = 2 @@ -55,7 +56,7 @@ proc openEmitter*(em: var Emitter, cache: IdentCache; proc closeEmitter*(em: var Emitter) = var f = llStreamOpen(em.config.outFile, fmWrite) if f == nil: - rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile) + rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile.string) f.llStreamWrite em.content llStreamClose(f) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index 6ad1d9fc6..4cb800017 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -17,7 +17,7 @@ import hashes, options, msgs, strutils, platform, idents, nimlexbase, llstream, - wordrecg, lineinfos + wordrecg, lineinfos, pathutils const MaxLineLength* = 80 # lines longer than this lead to a warning @@ -232,7 +232,7 @@ proc openLexer*(lex: var TLexer, fileIdx: FileIndex, inputstream: PLLStream; lex.previousToken.fileIndex = fileIdx lex.config = config -proc openLexer*(lex: var TLexer, filename: string, inputstream: PLLStream; +proc openLexer*(lex: var TLexer, filename: AbsoluteFile, inputstream: PLLStream; cache: IdentCache; config: ConfigRef) = openLexer(lex, fileInfoIdx(config, filename), inputstream, cache, config) @@ -884,6 +884,42 @@ proc getOperator(L: var TLexer, tok: var TToken) = if buf[pos] in {CR, LF, nimlexbase.EndOfFile}: tok.strongSpaceB = -1 +proc getPrecedence*(tok: TToken, strongSpaces: bool): int = + ## Calculates the precedence of the given token. + template considerStrongSpaces(x): untyped = + x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) + + case tok.tokType + of tkOpr: + let L = tok.ident.s.len + let relevantChar = tok.ident.s[0] + + # arrow like? + if L > 1 and tok.ident.s[L-1] == '>' and + tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) + + template considerAsgn(value: untyped) = + result = if tok.ident.s[L-1] == '=': 1 else: value + + case relevantChar + of '$', '^': considerAsgn(10) + of '*', '%', '/', '\\': considerAsgn(9) + of '~': result = 8 + of '+', '-', '|': considerAsgn(8) + of '&': considerAsgn(7) + of '=', '<', '>', '!': result = 5 + of '.': considerAsgn(6) + of '?': result = 2 + else: considerAsgn(2) + of tkDiv, tkMod, tkShl, tkShr: result = 9 + of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 + of tkDotDot: result = 6 + of tkAnd: result = 4 + of tkOr, tkXor, tkPtr, tkRef: result = 3 + else: return -10 + result = considerStrongSpaces(result) + + proc newlineFollows*(L: TLexer): bool = var pos = L.bufpos var buf = L.buf @@ -1249,3 +1285,14 @@ proc getIndentWidth*(fileIdx: FileIndex, inputstream: PLLStream; result = tok.indent if result > 0 or tok.tokType == tkEof: break closeLexer(lex) + +proc getPrecedence*(ident: PIdent): int = + ## assumes ident is binary operator already + var tok: TToken + initToken(tok) + tok.ident = ident + tok.tokType = + if tok.ident.id in ord(tokKeywordLow) - ord(tkSymbol) .. ord(tokKeywordHigh) - ord(tkSymbol): + TTokType(tok.ident.id + ord(tkSymbol)) + else: tkOpr + getPrecedence(tok, false) diff --git a/compiler/lineinfos.nim b/compiler/lineinfos.nim index c5a641713..8749e764d 100644 --- a/compiler/lineinfos.nim +++ b/compiler/lineinfos.nim @@ -10,7 +10,7 @@ ## This module contains the ``TMsgKind`` enum as well as the ## ``TLineInfo`` object. -import ropes, tables +import ropes, tables, pathutils const explanationsBaseUrl* = "https://nim-lang.org/docs/manual" @@ -179,8 +179,8 @@ const type TFileInfo* = object - fullPath*: string # This is a canonical full filesystem path - projPath*: string # This is relative to the project's root + fullPath*: AbsoluteFile # This is a canonical full filesystem path + projPath*: RelativeFile # This is relative to the project's root shortName*: string # short name of the module quotedName*: Rope # cached quoted short name for codegen # purposes @@ -191,7 +191,7 @@ type # used for better error messages and # embedding the original source in the # generated code - dirtyfile*: string # the file that is actually read into memory + dirtyfile*: AbsoluteFile # the file that is actually read into memory # and parsed; usually "" but is used # for 'nimsuggest' hash*: string # the checksum of the file @@ -223,6 +223,9 @@ type proc `==`*(a, b: FileIndex): bool {.borrow.} +proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = + raise newException(ERecoverableError, msg) + const InvalidFileIDX* = FileIndex(-1) diff --git a/compiler/linter.nim b/compiler/linter.nim index 7c9cdec83..0b69db8cb 100644 --- a/compiler/linter.nim +++ b/compiler/linter.nim @@ -14,7 +14,7 @@ import strutils, os, intsets, strtabs import options, ast, astalgo, msgs, semdata, ropes, idents, - lineinfos + lineinfos, pathutils const Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'} @@ -42,7 +42,7 @@ proc overwriteFiles*(conf: ConfigRef) = let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim") try: - var f = open(newFile, fmWrite) + var f = open(newFile.string, fmWrite) for line in conf.m.fileInfos[i].lines: if doStrip: f.write line.strip(leading = false, trailing = true) @@ -51,7 +51,7 @@ proc overwriteFiles*(conf: ConfigRef) = f.write(conf.m.fileInfos[i], "\L") f.close except IOError: - rawMessage(conf, errGenerated, "cannot open file: " & newFile) + rawMessage(conf, errGenerated, "cannot open file: " & newFile.string) proc `=~`(s: string, a: openArray[string]): bool = for x in a: diff --git a/compiler/llstream.nim b/compiler/llstream.nim index 42bbb7600..e1108147f 100644 --- a/compiler/llstream.nim +++ b/compiler/llstream.nim @@ -10,7 +10,7 @@ ## Low-level streams for high performance. import - strutils + strutils, pathutils # support '-d:useGnuReadline' for backwards compatibility: when not defined(windows) and (defined(useGnuReadline) or defined(useLinenoise)): @@ -41,10 +41,10 @@ proc llStreamOpen*(f: File): PLLStream = result.f = f result.kind = llsFile -proc llStreamOpen*(filename: string, mode: FileMode): PLLStream = +proc llStreamOpen*(filename: AbsoluteFile, mode: FileMode): PLLStream = new(result) result.kind = llsFile - if not open(result.f, filename, mode): result = nil + if not open(result.f, filename.string, mode): result = nil proc llStreamOpen*(): PLLStream = new(result) @@ -87,9 +87,9 @@ proc endsWithOpr*(x: string): bool = result = x.endsWith(LineContinuationOprs) proc continueLine(line: string, inTripleString: bool): bool {.inline.} = - result = inTripleString or - line[0] == ' ' or - line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs) + result = inTripleString or line.len > 0 and ( + line[0] == ' ' or + line.endsWith(LineContinuationOprs+AdditionalLineContinuationOprs)) proc countTriples(s: string): int = var i = 0 diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 7f0882754..d2e7fdcfa 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -58,8 +58,8 @@ proc considerQuotedIdent*(c: PContext; n: PNode, origin: PNode = nil): PIdent = template addSym*(scope: PScope, s: PSym) = strTableAdd(scope.symbols, s) -proc addUniqueSym*(scope: PScope, s: PSym): bool = - result = not strTableIncl(scope.symbols, s) +proc addUniqueSym*(scope: PScope, s: PSym): PSym = + result = strTableInclReportConflict(scope.symbols, s) proc openScope*(c: PContext): PScope {.discardable.} = result = PScope(parent: c.currentScope, @@ -177,24 +177,30 @@ proc ensureNoMissingOrUnusedSymbols(c: PContext; scope: PScope) = message(c.config, s.info, hintXDeclaredButNotUsed, getSymRepr(c.config, s)) s = nextIter(it, scope.symbols) -proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string) = +proc wrongRedefinition*(c: PContext; info: TLineInfo, s: string; + conflictsWith: TLineInfo) = if c.config.cmd != cmdInteractive: - localError(c.config, info, "redefinition of '$1'" % s) + localError(c.config, info, + "redefinition of '$1'; previous declaration here: $2" % + [s, c.config $ conflictsWith]) proc addDecl*(c: PContext, sym: PSym, info: TLineInfo) = - if not c.currentScope.addUniqueSym(sym): - wrongRedefinition(c, info, sym.name.s) + let conflict = c.currentScope.addUniqueSym(sym) + if conflict != nil: + wrongRedefinition(c, info, sym.name.s, conflict.info) proc addDecl*(c: PContext, sym: PSym) = - if not c.currentScope.addUniqueSym(sym): - wrongRedefinition(c, sym.info, sym.name.s) + let conflict = c.currentScope.addUniqueSym(sym) + if conflict != nil: + wrongRedefinition(c, sym.info, sym.name.s, conflict.info) proc addPrelimDecl*(c: PContext, sym: PSym) = discard c.currentScope.addUniqueSym(sym) proc addDeclAt*(c: PContext; scope: PScope, sym: PSym) = - if not scope.addUniqueSym(sym): - wrongRedefinition(c, sym.info, sym.name.s) + let conflict = scope.addUniqueSym(sym) + if conflict != nil: + wrongRedefinition(c, sym.info, sym.name.s, conflict.info) proc addInterfaceDeclAux(c: PContext, sym: PSym) = if sfExported in sym.flags: @@ -212,7 +218,7 @@ proc addOverloadableSymAt*(c: PContext; scope: PScope, fn: PSym) = return let check = strTableGet(scope.symbols, fn.name) if check != nil and check.kind notin OverloadableSyms: - wrongRedefinition(c, fn.info, fn.name.s) + wrongRedefinition(c, fn.info, fn.name.s, check.info) else: scope.addSym(fn) @@ -244,7 +250,7 @@ else: template fixSpelling(n: PNode; ident: PIdent; op: untyped) = discard proc errorUseQualifier*(c: PContext; info: TLineInfo; s: PSym) = - var err = "Error: ambiguous identifier: '" & s.name.s & "'" + var err = "ambiguous identifier: '" & s.name.s & "'" var ti: TIdentIter var candidate = initIdentIter(ti, c.importTable.symbols, s.name) var i = 0 diff --git a/compiler/main.nim b/compiler/main.nim index cd05ded62..6c8b0343e 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -19,7 +19,7 @@ import cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, docgen2, parser, modules, ccgutils, sigmatch, ropes, - modulegraphs, tables, rod, lineinfos + modulegraphs, tables, rod, lineinfos, pathutils from magicsys import resetSysTypes @@ -30,8 +30,8 @@ proc semanticPasses(g: ModuleGraph) = registerPass g, verbosePass registerPass g, semPass -proc writeDepsFile(g: ModuleGraph; project: string) = - let f = open(changeFileExt(project, "deps"), fmWrite) +proc writeDepsFile(g: ModuleGraph; project: AbsoluteFile) = + let f = open(changeFileExt(project, "deps").string, fmWrite) for m in g.modules: if m != nil: f.writeLine(toFullPath(g.config, m.position.FileIndex)) @@ -47,8 +47,9 @@ proc commandGenDepend(graph: ModuleGraph) = let project = graph.config.projectFull writeDepsFile(graph, project) generateDot(graph, project) - execExternalProgram(graph.config, "dot -Tpng -o" & changeFileExt(project, "png") & - ' ' & changeFileExt(project, "dot")) + execExternalProgram(graph.config, "dot -Tpng -o" & + changeFileExt(project, "png").string & + ' ' & changeFileExt(project, "dot").string) proc commandCheck(graph: ModuleGraph) = graph.config.errorMax = high(int) # do not stop after first error @@ -126,7 +127,7 @@ proc commandEval(graph: ModuleGraph; exp: string) = makeStdinModule(graph)) proc commandScan(cache: IdentCache, config: ConfigRef) = - var f = addFileExt(mainCommandArg(config), NimExt) + var f = addFileExt(AbsoluteFile mainCommandArg(config), NimExt) var stream = llStreamOpen(f, fmRead) if stream != nil: var @@ -140,7 +141,7 @@ proc commandScan(cache: IdentCache, config: ConfigRef) = if tok.tokType == tkEof: break closeLexer(L) else: - rawMessage(config, errGenerated, "cannot open file: " & f) + rawMessage(config, errGenerated, "cannot open file: " & f.string) const PrintRopeCacheStats = false @@ -231,11 +232,11 @@ proc mainCommand*(graph: ModuleGraph) = for s in definedSymbolNames(conf.symbols): definedSymbols.elems.add(%s) var libpaths = newJArray() - for dir in conf.searchPaths: libpaths.elems.add(%dir) + for dir in conf.searchPaths: libpaths.elems.add(%dir.string) var dumpdata = % [ (key: "version", val: %VersionAsString), - (key: "project_path", val: %conf.projectFull), + (key: "project_path", val: %conf.projectFull.string), (key: "defined_symbols", val: definedSymbols), (key: "lib_paths", val: libpaths) ] @@ -247,7 +248,7 @@ proc mainCommand*(graph: ModuleGraph) = for s in definedSymbolNames(conf.symbols): msgWriteln(conf, s, {msgStdout, msgSkipHook}) msgWriteln(conf, "-- end of list --", {msgStdout, msgSkipHook}) - for it in conf.searchPaths: msgWriteln(conf, it) + for it in conf.searchPaths: msgWriteln(conf, it.string) of "check": conf.cmd = cmdCheck commandCheck(graph) diff --git a/compiler/modulepaths.nim b/compiler/modulepaths.nim index 118002fcf..f0718c4eb 100644 --- a/compiler/modulepaths.nim +++ b/compiler/modulepaths.nim @@ -7,9 +7,8 @@ # distribution, for details about the copyright. # -import ast, renderer, strutils, msgs, options, idents, os, lineinfos - -import nimblecmd +import ast, renderer, strutils, msgs, options, idents, os, lineinfos, + pathutils, nimblecmd when false: const @@ -160,7 +159,7 @@ proc checkModuleName*(conf: ConfigRef; n: PNode; doLocalError=true): FileIndex = # This returns the full canonical path for a given module import let modulename = getModuleName(conf, n) let fullPath = findModule(conf, modulename, toFullPath(conf, n.info)) - if fullPath.len == 0: + if fullPath.isEmpty: if doLocalError: let m = if modulename.len > 0: modulename else: $n localError(conf, n.info, "cannot open file: " & m) diff --git a/compiler/modules.nim b/compiler/modules.nim index b3a1e90d6..8fedba10a 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -12,7 +12,7 @@ import ast, astalgo, magicsys, std / sha1, msgs, cgendata, sigmatch, options, idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod, - lineinfos + lineinfos, pathutils proc resetSystemArtifacts*(g: ModuleGraph) = magicsys.resetSysTypes(g) @@ -102,19 +102,21 @@ proc connectCallbacks*(graph: ModuleGraph) = proc compileSystemModule*(graph: ModuleGraph) = if graph.systemModule == nil: connectCallbacks(graph) - graph.config.m.systemFileIdx = fileInfoIdx(graph.config, graph.config.libpath / "system.nim") + graph.config.m.systemFileIdx = fileInfoIdx(graph.config, + graph.config.libpath / RelativeFile"system.nim") discard graph.compileModule(graph.config.m.systemFileIdx, {sfSystemModule}) proc wantMainModule*(conf: ConfigRef) = - if conf.projectFull.len == 0: - fatal(conf, newLineInfo(conf, "command line", 1, 1), errGenerated, "command expects a filename") + if conf.projectFull.isEmpty: + fatal(conf, newLineInfo(conf, AbsoluteFile"command line", 1, 1), errGenerated, + "command expects a filename") conf.projectMainIdx = fileInfoIdx(conf, addFileExt(conf.projectFull, NimExt)) proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) = connectCallbacks(graph) let conf = graph.config wantMainModule(conf) - let systemFileIdx = fileInfoIdx(conf, conf.libpath / "system.nim") + let systemFileIdx = fileInfoIdx(conf, conf.libpath / RelativeFile"system.nim") let projectFile = if projectFileIdx == InvalidFileIDX: conf.projectMainIdx else: projectFileIdx graph.importStack.add projectFile if projectFile == systemFileIdx: @@ -123,8 +125,11 @@ proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) = graph.compileSystemModule() discard graph.compileModule(projectFile, {sfMainModule}) -proc makeModule*(graph: ModuleGraph; filename: string): PSym = +proc makeModule*(graph: ModuleGraph; filename: AbsoluteFile): PSym = result = graph.newModule(fileInfoIdx(graph.config, filename)) result.id = getID() -proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule"stdin" +proc makeModule*(graph: ModuleGraph; filename: string): PSym = + result = makeModule(graph, AbsoluteFile filename) + +proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule(AbsoluteFile"stdin") diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 47ab878f1..d817b2956 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -9,7 +9,7 @@ import options, strutils, os, tables, ropes, platform, terminal, macros, - lineinfos + lineinfos, pathutils proc toCChar*(c: char; result: var string) = case c @@ -35,20 +35,20 @@ proc makeCString*(s: string): Rope = add(result, rope(res)) -proc newFileInfo(fullPath, projPath: string): TFileInfo = +proc newFileInfo(fullPath: AbsoluteFile, projPath: RelativeFile): TFileInfo = result.fullPath = fullPath #shallow(result.fullPath) result.projPath = projPath #shallow(result.projPath) - let fileName = projPath.extractFilename + let fileName = fullPath.extractFilename result.shortName = fileName.changeFileExt("") result.quotedName = fileName.makeCString - result.quotedFullName = fullPath.makeCString + result.quotedFullName = fullPath.string.makeCString result.lines = @[] when defined(nimpretty): - if result.fullPath.len > 0: + if not result.fullPath.isEmpty: try: - result.fullContent = readFile(result.fullPath) + result.fullContent = readFile(result.fullPath.string) except IOError: #rawMessage(errCannotOpenFile, result.fullPath) # XXX fixme @@ -58,39 +58,39 @@ when defined(nimpretty): proc fileSection*(conf: ConfigRef; fid: FileIndex; a, b: int): string = substr(conf.m.fileInfos[fid.int].fullContent, a, b) -proc fileInfoKnown*(conf: ConfigRef; filename: string): bool = +proc fileInfoKnown*(conf: ConfigRef; filename: AbsoluteFile): bool = var - canon: string + canon: AbsoluteFile try: canon = canonicalizePath(conf, filename) - except: + except OSError: canon = filename - result = conf.m.filenameToIndexTbl.hasKey(canon) + result = conf.m.filenameToIndexTbl.hasKey(canon.string) -proc fileInfoIdx*(conf: ConfigRef; filename: string; isKnownFile: var bool): FileIndex = +proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile; isKnownFile: var bool): FileIndex = var - canon: string + canon: AbsoluteFile pseudoPath = false try: canon = canonicalizePath(conf, filename) - shallow(canon) - except: + shallow(canon.string) + except OSError: canon = filename # The compiler uses "filenames" such as `command line` or `stdin` # This flag indicates that we are working with such a path here pseudoPath = true - if conf.m.filenameToIndexTbl.hasKey(canon): - result = conf.m.filenameToIndexTbl[canon] + if conf.m.filenameToIndexTbl.hasKey(canon.string): + result = conf.m.filenameToIndexTbl[canon.string] else: isKnownFile = false result = conf.m.fileInfos.len.FileIndex - conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: filename - else: shortenDir(conf, canon))) - conf.m.filenameToIndexTbl[canon] = result + conf.m.fileInfos.add(newFileInfo(canon, if pseudoPath: RelativeFile filename + else: relativeTo(canon, conf.projectPath))) + conf.m.filenameToIndexTbl[canon.string] = result -proc fileInfoIdx*(conf: ConfigRef; filename: string): FileIndex = +proc fileInfoIdx*(conf: ConfigRef; filename: AbsoluteFile): FileIndex = var dummy: bool result = fileInfoIdx(conf, filename, dummy) @@ -99,12 +99,9 @@ proc newLineInfo*(fileInfoIdx: FileIndex, line, col: int): TLineInfo = result.line = uint16(line) result.col = int16(col) -proc newLineInfo*(conf: ConfigRef; filename: string, line, col: int): TLineInfo {.inline.} = +proc newLineInfo*(conf: ConfigRef; filename: AbsoluteFile, line, col: int): TLineInfo {.inline.} = result = newLineInfo(fileInfoIdx(conf, filename), line, col) -proc raiseRecoverableError*(msg: string) {.noinline, noreturn.} = - raise newException(ERecoverableError, msg) - proc concat(strings: openarray[string]): string = var totalLen = 0 @@ -155,13 +152,16 @@ proc getInfoContext*(conf: ConfigRef; index: int): TLineInfo = else: result = conf.m.msgContext[i] template toFilename*(conf: ConfigRef; fileIdx: FileIndex): string = - (if fileIdx.int32 < 0 or conf == nil: "???" else: conf.m.fileInfos[fileIdx.int32].projPath) + if fileIdx.int32 < 0 or conf == nil: + "???" + else: + conf.m.fileInfos[fileIdx.int32].projPath.string proc toFullPath*(conf: ConfigRef; fileIdx: FileIndex): string = if fileIdx.int32 < 0 or conf == nil: result = "???" - else: result = conf.m.fileInfos[fileIdx.int32].fullPath + else: result = conf.m.fileInfos[fileIdx.int32].fullPath.string -proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: string) = +proc setDirtyFile*(conf: ConfigRef; fileIdx: FileIndex; filename: AbsoluteFile) = assert fileIdx.int32 >= 0 conf.m.fileInfos[fileIdx.int32].dirtyFile = filename @@ -173,10 +173,10 @@ proc getHash*(conf: ConfigRef; fileIdx: FileIndex): string = assert fileIdx.int32 >= 0 shallowCopy(result, conf.m.fileInfos[fileIdx.int32].hash) -proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): string = +proc toFullPathConsiderDirty*(conf: ConfigRef; fileIdx: FileIndex): AbsoluteFile = if fileIdx.int32 < 0: - result = "???" - elif conf.m.fileInfos[fileIdx.int32].dirtyFile.len > 0: + result = AbsoluteFile"???" + elif not conf.m.fileInfos[fileIdx.int32].dirtyFile.isEmpty: result = conf.m.fileInfos[fileIdx.int32].dirtyFile else: result = conf.m.fileInfos[fileIdx.int32].fullPath @@ -191,9 +191,9 @@ proc toMsgFilename*(conf: ConfigRef; info: TLineInfo): string = if info.fileIndex.int32 < 0: result = "???" elif optListFullPaths in conf.globalOptions: - result = conf.m.fileInfos[info.fileIndex.int32].fullPath + result = conf.m.fileInfos[info.fileIndex.int32].fullPath.string else: - result = conf.m.fileInfos[info.fileIndex.int32].projPath + result = conf.m.fileInfos[info.fileIndex.int32].projPath.string proc toLinenumber*(info: TLineInfo): int {.inline.} = result = int info.line diff --git a/compiler/ndi.nim b/compiler/ndi.nim index 9708c388d..f672b1b76 100644 --- a/compiler/ndi.nim +++ b/compiler/ndi.nim @@ -10,7 +10,7 @@ ## This module implements the generation of ``.ndi`` files for better debugging ## support of Nim code. "ndi" stands for "Nim debug info". -import ast, msgs, ropes, options +import ast, msgs, ropes, options, pathutils type NdiFile* = object @@ -30,10 +30,10 @@ proc doWrite(f: var NdiFile; s: PSym; conf: ConfigRef) = template writeMangledName*(f: NdiFile; s: PSym; conf: ConfigRef) = if f.enabled: doWrite(f, s, conf) -proc open*(f: var NdiFile; filename: string; conf: ConfigRef) = - f.enabled = filename.len > 0 +proc open*(f: var NdiFile; filename: AbsoluteFile; conf: ConfigRef) = + f.enabled = not filename.isEmpty if f.enabled: - f.f = open(filename, fmWrite, 8000) + f.f = open(filename.string, fmWrite, 8000) f.buf = newStringOfCap(20) proc close*(f: var NdiFile) = diff --git a/compiler/nim.cfg b/compiler/nim.cfg index 1bd3fbfd6..5104f9fcd 100644 --- a/compiler/nim.cfg +++ b/compiler/nim.cfg @@ -1,8 +1,6 @@ # Special configuration file for the Nim project hint[XDeclaredButNotUsed]:off -path:"llvm" -path:"$projectPath/.." define:booting define:nimcore diff --git a/compiler/nim.nim b/compiler/nim.nim index 90049bdfb..5f3347255 100644 --- a/compiler/nim.nim +++ b/compiler/nim.nim @@ -21,7 +21,8 @@ when defined(i386) and defined(windows) and defined(vcc): import commands, lexer, condsyms, options, msgs, nversion, nimconf, ropes, extccomp, strutils, os, osproc, platform, main, parseopt, - nodejs, scriptconfig, idents, modulegraphs, lineinfos + nodejs, scriptconfig, idents, modulegraphs, lineinfos, cmdlinehelper, + pathutils when hasTinyCBackend: import tccgen @@ -30,10 +31,10 @@ when defined(profiler) or defined(memProfiler): {.hint: "Profiling support is turned on!".} import nimprof -proc prependCurDir(f: string): string = +proc prependCurDir(f: AbsoluteFile): AbsoluteFile = when defined(unix): - if os.isAbsolute(f): result = f - else: result = "./" & f + if os.isAbsolute(f.string): result = f + else: result = AbsoluteFile("./" & f.string) else: result = f @@ -57,69 +58,43 @@ proc processCmdLine(pass: TCmdLinePass, cmd: string; config: ConfigRef) = rawMessage(config, errGenerated, errArgsNeedRunOption) proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = - condsyms.initDefines(conf.symbols) + let self = NimProg( + supportsStdinFile: true, + processCmdLine: processCmdLine, + mainCommand: mainCommand + ) + self.initDefinesProg(conf, "nim_compiler") if paramCount() == 0: writeCommandLineUsage(conf, conf.helpWritten) - else: - # Process command line arguments: - processCmdLine(passCmd1, "", conf) - if conf.projectName == "-": - conf.projectName = "stdinfile" - conf.projectFull = "stdinfile" - conf.projectPath = canonicalizePath(conf, getCurrentDir()) - conf.projectIsStdin = true - elif conf.projectName != "": - try: - conf.projectFull = canonicalizePath(conf, conf.projectName) - except OSError: - conf.projectFull = conf.projectName - let p = splitFile(conf.projectFull) - let dir = if p.dir.len > 0: p.dir else: getCurrentDir() - conf.projectPath = canonicalizePath(conf, dir) - conf.projectName = p.name + return + + self.processCmdLineAndProjectPath(conf) + if not self.loadConfigsAndRunMainCommand(cache, conf): return + if optHints in conf.options and hintGCStats in conf.notes: echo(GC_getStatistics()) + #echo(GC_getStatistics()) + if conf.errorCounter != 0: return + when hasTinyCBackend: + if conf.cmd == cmdRun: + tccgen.run(conf.arguments) + if optRun in conf.globalOptions: + if conf.cmd == cmdCompileToJS: + var ex: string + if not conf.outFile.isEmpty: + ex = conf.outFile.prependCurDir.quoteShell + else: + ex = quoteShell( + completeCFilePath(conf, changeFileExt(conf.projectFull, "js").prependCurDir)) + execExternalProgram(conf, findNodeJs() & " " & ex & ' ' & conf.arguments) else: - conf.projectPath = canonicalizePath(conf, getCurrentDir()) - loadConfigs(DefaultConfig, cache, conf) # load all config files - let scriptFile = conf.projectFull.changeFileExt("nims") - if fileExists(scriptFile): - runNimScript(cache, scriptFile, freshDefines=false, conf) - # 'nim foo.nims' means to just run the NimScript file and do nothing more: - if scriptFile == conf.projectFull: return - elif fileExists(conf.projectPath / "config.nims"): - # directory wide NimScript file - runNimScript(cache, conf.projectPath / "config.nims", freshDefines=false, conf) - # now process command line arguments again, because some options in the - # command line can overwite the config file's settings - extccomp.initVars(conf) - processCmdLine(passCmd2, "", conf) - if conf.command == "": - rawMessage(conf, errGenerated, "command missing") - mainCommand(newModuleGraph(cache, conf)) - if optHints in conf.options and hintGCStats in conf.notes: echo(GC_getStatistics()) - #echo(GC_getStatistics()) - if conf.errorCounter == 0: - when hasTinyCBackend: - if conf.cmd == cmdRun: - tccgen.run(conf.arguments) - if optRun in conf.globalOptions: - if conf.cmd == cmdCompileToJS: - var ex: string - if conf.outFile.len > 0: - ex = conf.outFile.prependCurDir.quoteShell - else: - ex = quoteShell( - completeCFilePath(conf, changeFileExt(conf.projectFull, "js").prependCurDir)) - execExternalProgram(conf, findNodeJs() & " " & ex & ' ' & conf.arguments) - else: - var binPath: string - if conf.outFile.len > 0: - # If the user specified an outFile path, use that directly. - binPath = conf.outFile.prependCurDir - else: - # Figure out ourselves a valid binary name. - binPath = changeFileExt(conf.projectFull, ExeExt).prependCurDir - var ex = quoteShell(binPath) - execExternalProgram(conf, ex & ' ' & conf.arguments) + var binPath: AbsoluteFile + if not conf.outFile.isEmpty: + # If the user specified an outFile path, use that directly. + binPath = conf.outFile.prependCurDir + else: + # Figure out ourselves a valid binary name. + binPath = changeFileExt(conf.projectFull, ExeExt).prependCurDir + var ex = quoteShell(binPath) + execExternalProgram(conf, ex & ' ' & conf.arguments) when declared(GC_setMaxPause): GC_setMaxPause 2_000 diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim index 9a23535bf..fa938556b 100644 --- a/compiler/nimblecmd.nim +++ b/compiler/nimblecmd.nim @@ -10,9 +10,9 @@ ## Implements some helper procs for Nimble (Nim's package manager) support. import parseutils, strutils, strtabs, os, options, msgs, sequtils, - lineinfos + lineinfos, pathutils -proc addPath*(conf: ConfigRef; path: string, info: TLineInfo) = +proc addPath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) = if not conf.searchPaths.contains(path): conf.searchPaths.insert(path, 0) @@ -112,9 +112,9 @@ proc addNimblePath(conf: ConfigRef; p: string, info: TLineInfo) = if not path.isAbsolute(): path = p / path - if not contains(conf.searchPaths, path): + if not contains(conf.searchPaths, AbsoluteDir path): message(conf, info, hintPath, path) - conf.lazyPaths.insert(path, 0) + conf.lazyPaths.insert(AbsoluteDir path, 0) proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) = var packages = newStringTable(modeStyleInsensitive) @@ -126,9 +126,9 @@ proc addPathRec(conf: ConfigRef; dir: string, info: TLineInfo) = for p in packages.chosen: addNimblePath(conf, p, info) -proc nimblePath*(conf: ConfigRef; path: string, info: TLineInfo) = - addPathRec(conf, path, info) - addNimblePath(conf, path, info) +proc nimblePath*(conf: ConfigRef; path: AbsoluteDir, info: TLineInfo) = + addPathRec(conf, path.string, info) + addNimblePath(conf, path.string, info) when isMainModule: proc v(s: string): Version = s.newVersion @@ -140,18 +140,19 @@ when isMainModule: doAssert v"#aaaqwe" < v"1.1" # We cannot assume that a branch is newer. doAssert v"#a111" < v"#head" + let conf = newConfigRef() var rr = newStringTable() - addPackage rr, "irc-#a111" - addPackage rr, "irc-#head" - addPackage rr, "irc-0.1.0" - addPackage rr, "irc" - addPackage rr, "another" - addPackage rr, "another-0.1" + addPackage conf, rr, "irc-#a111", unknownLineInfo() + addPackage conf, rr, "irc-#head", unknownLineInfo() + addPackage conf, rr, "irc-0.1.0", unknownLineInfo() + #addPackage conf, rr, "irc", unknownLineInfo() + #addPackage conf, rr, "another", unknownLineInfo() + addPackage conf, rr, "another-0.1", unknownLineInfo() - addPackage rr, "ab-0.1.3" - addPackage rr, "ab-0.1" - addPackage rr, "justone" + addPackage conf, rr, "ab-0.1.3", unknownLineInfo() + addPackage conf, rr, "ab-0.1", unknownLineInfo() + addPackage conf, rr, "justone-1.0", unknownLineInfo() doAssert toSeq(rr.chosen) == - @["irc-#head", "another-0.1", "ab-0.1.3", "justone"] + @["irc-#head", "another-0.1", "ab-0.1.3", "justone-1.0"] diff --git a/compiler/nimconf.nim b/compiler/nimconf.nim index 1a8a0acb5..c0aeab7e3 100644 --- a/compiler/nimconf.nim +++ b/compiler/nimconf.nim @@ -11,7 +11,7 @@ import llstream, nversion, commands, os, strutils, msgs, platform, condsyms, lexer, - options, idents, wordrecg, strtabs, lineinfos + options, idents, wordrecg, strtabs, lineinfos, pathutils # ---------------- configuration file parser ----------------------------- # we use Nim's scanner here to save space and work @@ -175,9 +175,9 @@ proc parseAssignment(L: var TLexer, tok: var TToken; confTok(L, tok, config, condStack) if tok.tokType == tkBracketLe: # BUGFIX: val, not s! - # BUGFIX: do not copy '['! confTok(L, tok, config, condStack) checkSymbol(L, tok) + add(val, '[') add(val, tokToStr(tok)) confTok(L, tok, config, condStack) if tok.tokType == tkBracketRi: confTok(L, tok, config, condStack) @@ -201,8 +201,8 @@ proc parseAssignment(L: var TLexer, tok: var TToken; else: processSwitch(s, val, passPP, info, config) -proc readConfigFile( - filename: string; cache: IdentCache; config: ConfigRef): bool = +proc readConfigFile(filename: AbsoluteFile; cache: IdentCache; + config: ConfigRef): bool = var L: TLexer tok: TToken @@ -219,38 +219,38 @@ proc readConfigFile( closeLexer(L) return true -proc getUserConfigPath(filename: string): string = - result = joinPath([getConfigDir(), "nim", filename]) +proc getUserConfigPath*(filename: RelativeFile): AbsoluteFile = + result = getConfigDir().AbsoluteDir / RelativeDir"nim" / filename -proc getSystemConfigPath(conf: ConfigRef; filename: string): string = +proc getSystemConfigPath*(conf: ConfigRef; filename: RelativeFile): AbsoluteFile = # try standard configuration file (installation did not distribute files # the UNIX way) let p = getPrefixDir(conf) - result = joinPath([p, "config", filename]) + result = p / RelativeDir"config" / filename when defined(unix): - if not existsFile(result): result = joinPath([p, "etc/nim", filename]) - if not existsFile(result): result = "/etc/nim/" & filename + if not fileExists(result): result = p / RelativeDir"etc/nim" / filename + if not fileExists(result): result = AbsoluteDir"/etc/nim" / filename -proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) = +proc loadConfigs*(cfg: RelativeFile; cache: IdentCache; conf: ConfigRef) = setDefaultLibpath(conf) - var configFiles = newSeq[string]() + var configFiles = newSeq[AbsoluteFile]() - template readConfigFile(path: string) = + template readConfigFile(path) = let configPath = path if readConfigFile(configPath, cache, conf): add(configFiles, configPath) - if optSkipConfigFile notin conf.globalOptions: + if optSkipSystemConfigFile notin conf.globalOptions: readConfigFile(getSystemConfigPath(conf, cfg)) if optSkipUserConfigFile notin conf.globalOptions: readConfigFile(getUserConfigPath(cfg)) - let pd = if conf.projectPath.len > 0: conf.projectPath else: getCurrentDir() + let pd = if not conf.projectPath.isEmpty: conf.projectPath else: AbsoluteDir(getCurrentDir()) if optSkipParentConfigFiles notin conf.globalOptions: - for dir in parentDirs(pd, fromRoot=true, inclusive=false): - readConfigFile(dir / cfg) + for dir in parentDirs(pd.string, fromRoot=true, inclusive=false): + readConfigFile(AbsoluteDir(dir) / cfg) if optSkipProjConfigFile notin conf.globalOptions: readConfigFile(pd / cfg) @@ -263,4 +263,5 @@ proc loadConfigs*(cfg: string; cache: IdentCache; conf: ConfigRef) = readConfigFile(projectConfig) for filename in configFiles: - rawMessage(conf, hintConf, filename) + # delayed to here so that `hintConf` is honored + rawMessage(conf, hintConf, filename.string) diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index f20b5642c..841c38a46 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -11,7 +11,7 @@ import ast, astalgo, modules, passes, condsyms, options, sem, semdata, llstream, vm, vmdef, - modulegraphs, idents, os + modulegraphs, idents, os, pathutils type Interpreter* = ref object ## Use Nim as an interpreter with this object @@ -103,8 +103,8 @@ proc createInterpreter*(scriptName: string; registerPass(graph, evalPass) for p in searchPaths: - conf.searchPaths.add(p) - if conf.libpath.len == 0: conf.libpath = p + conf.searchPaths.add(AbsoluteDir p) + if conf.libpath.isEmpty: conf.libpath = AbsoluteDir p var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) diff --git a/compiler/nversion.nim b/compiler/nversion.nim index 4b8cf7100..8981ae213 100644 --- a/compiler/nversion.nim +++ b/compiler/nversion.nim @@ -15,6 +15,6 @@ const VersionAsString* = system.NimVersion RodFileVersion* = "1223" # modify this if the rod-format changes! - NimCompilerApiVersion* = 2 ## Check for the existance of this before accessing it + NimCompilerApiVersion* = 3 ## Check for the existance of this before accessing it ## as older versions of the compiler API do not ## declare this. diff --git a/compiler/options.nim b/compiler/options.nim index 04b14c65f..b4d2bb64e 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -9,9 +9,10 @@ import os, strutils, strtabs, osproc, sets, lineinfos, platform, - prefixmatches + prefixmatches, pathutils from terminal import isatty +from times import utc, fromUnix, local, getTime, format, DateTime const hasTinyCBackend* = defined(tinyc) @@ -54,10 +55,10 @@ type # please make sure we have under 32 options optGenMapping, # generate a mapping file optRun, # run the compiled project optCheckNep1, # check that the names adhere to NEP-1 - optSkipConfigFile, # skip the general config file - optSkipProjConfigFile, # skip the project's config file - optSkipUserConfigFile, # skip the users's config file - optSkipParentConfigFiles, # skip parent dir's config files + optSkipSystemConfigFile, # skip the system's cfg/nims config file + optSkipProjConfigFile, # skip the project's cfg/nims config file + optSkipUserConfigFile, # skip the users's cfg/nims config file + optSkipParentConfigFiles, # skip parent dir's cfg/nims config files optNoMain, # do not generate a "main" proc optUseColors, # use colors for hints, warnings, and errors optThreads, # support for multi-threading @@ -77,7 +78,6 @@ type # please make sure we have under 32 options optListFullPaths optNoNimblePath optDynlibOverrideAll - optUseNimNamespace TGlobalOptions* = set[TGlobalOption] @@ -120,7 +120,8 @@ type notnil, dynamicBindSym, forLoopMacros, - caseStmtMacros + caseStmtMacros, + codeReordering, SymbolFilesOption* = enum disabledSf, writeOnlySf, readOnlySf, v2Sf @@ -134,7 +135,7 @@ type External ## file was introduced via .compile pragma Cfile* = object - cname*, obj*: string + cname*, obj*: AbsoluteFile flags*: set[CFileFlag] CfileList* = seq[Cfile] @@ -202,13 +203,14 @@ type ## symbols are always guaranteed to be style ## insensitive. Otherwise hell would break lose. packageCache*: StringTableRef - searchPaths*: seq[string] - lazyPaths*: seq[string] - outFile*, prefixDir*, libpath*, nimcacheDir*: string + searchPaths*: seq[AbsoluteDir] + lazyPaths*: seq[AbsoluteDir] + outFile*: AbsoluteFile + prefixDir*, libpath*, nimcacheDir*: AbsoluteDir dllOverrides, moduleOverrides*: StringTableRef projectName*: string # holds a name like 'nim' - projectPath*: string # holds a path like /home/alice/projects/nim/compiler/ - projectFull*: string # projectPath/projectName + projectPath*: AbsoluteDir # holds a path like /home/alice/projects/nim/compiler/ + projectFull*: AbsoluteFile # projectPath/projectName projectIsStdin*: bool # whether we're compiling from stdin projectMainIdx*: FileIndex # the canonical path id of the main module command*: string # the main command (e.g. cc, check, scan, etc) @@ -220,9 +222,9 @@ type # The string uses the formatting variables `path` and `line`. # the used compiler - cIncludes*: seq[string] # directories to search for included files - cLibs*: seq[string] # directories to search for lib files - cLinkedLibs*: seq[string] # libraries to link + cIncludes*: seq[AbsoluteDir] # directories to search for included files + cLibs*: seq[AbsoluteDir] # directories to search for lib files + cLinkedLibs*: seq[string] # libraries to link externalToLink*: seq[string] # files to link in addition to the file # we compiled (*) @@ -239,6 +241,7 @@ type writelnHook*: proc (output: string) {.closure.} structuredErrorHook*: proc (config: ConfigRef; info: TLineInfo; msg: string; severity: Severity) {.closure.} + cppCustomNamespace*: string template depConfigFields*(fn) {.dirty.} = fn(target) @@ -259,6 +262,24 @@ const optPatterns, optNilCheck, optMoveCheck} DefaultGlobalOptions* = {optThreadAnalysis} +proc getSrcTimestamp(): DateTime = + try: + result = utc(fromUnix(parseInt(getEnv("SOURCE_DATE_EPOCH", + "not a number")))) + except ValueError: + # Environment variable malformed. + # https://reproducible-builds.org/specs/source-date-epoch/: "If the + # value is malformed, the build process SHOULD exit with a non-zero + # error code", which this doesn't do. This uses local time, because + # that maintains compatibility with existing usage. + result = utc getTime() + +proc getDateStr*(): string = + result = format(getSrcTimestamp(), "yyyy-MM-dd") + +proc getClockStr*(): string = + result = format(getSrcTimestamp(), "HH:mm:ss") + template newPackageCache*(): untyped = newStringTable(when FileSystemCaseSensitive: modeCaseInsensitive @@ -283,12 +304,13 @@ proc newConfigRef*(): ConfigRef = packageCache: newPackageCache(), searchPaths: @[], lazyPaths: @[], - outFile: "", prefixDir: "", libpath: "", nimcacheDir: "", + outFile: AbsoluteFile"", prefixDir: AbsoluteDir"", + libpath: AbsoluteDir"", nimcacheDir: AbsoluteDir"", dllOverrides: newStringTable(modeCaseInsensitive), moduleOverrides: newStringTable(modeStyleInsensitive), projectName: "", # holds a name like 'nim' - projectPath: "", # holds a path like /home/alice/projects/nim/compiler/ - projectFull: "", # projectPath/projectName + projectPath: AbsoluteDir"", # holds a path like /home/alice/projects/nim/compiler/ + projectFull: AbsoluteFile"", # projectPath/projectName projectIsStdin: false, # whether we're compiling from stdin projectMainIdx: FileIndex(0'i32), # the canonical path id of the main module command: "", # the main command (e.g. cc, check, scan, etc) @@ -382,7 +404,7 @@ template optPreserveOrigSource*(conf: ConfigRef): untyped = optEmbedOrigSrc in conf.globalOptions const - genSubDir* = "nimcache" + genSubDir* = RelativeDir"nimcache" NimExt* = "nim" RodExt* = "rod" HtmlExt* = "html" @@ -390,9 +412,10 @@ const TagsExt* = "tags" TexExt* = "tex" IniExt* = "ini" - DefaultConfig* = "nim.cfg" - DocConfig* = "nimdoc.cfg" - DocTexConfig* = "nimdoc.tex.cfg" + DefaultConfig* = RelativeFile"nim.cfg" + DefaultConfigNims* = RelativeFile"config.nims" + DocConfig* = RelativeFile"nimdoc.cfg" + DocTexConfig* = RelativeFile"nimdoc.tex.cfg" const oKeepVariableNames* = true @@ -417,56 +440,61 @@ proc getConfigVar*(conf: ConfigRef; key: string, default = ""): string = proc setConfigVar*(conf: ConfigRef; key, val: string) = conf.configVars[key] = val -proc getOutFile*(conf: ConfigRef; filename, ext: string): string = - if conf.outFile != "": result = conf.outFile - else: result = changeFileExt(filename, ext) +proc getOutFile*(conf: ConfigRef; filename: RelativeFile, ext: string): AbsoluteFile = + if not conf.outFile.isEmpty: result = conf.outFile + else: result = conf.projectPath / changeFileExt(filename, ext) -proc getPrefixDir*(conf: ConfigRef): string = +proc getPrefixDir*(conf: ConfigRef): AbsoluteDir = ## Gets the prefix dir, usually the parent directory where the binary resides. ## ## This is overridden by some tools (namely nimsuggest) via the ``conf.prefixDir`` - ## global. - if conf.prefixDir != "": result = conf.prefixDir - else: result = splitPath(getAppDir()).head + ## field. + if not conf.prefixDir.isEmpty: result = conf.prefixDir + else: result = AbsoluteDir splitPath(getAppDir()).head proc setDefaultLibpath*(conf: ConfigRef) = # set default value (can be overwritten): - if conf.libpath == "": + if conf.libpath.isEmpty: # choose default libpath: var prefix = getPrefixDir(conf) when defined(posix): - if prefix == "/usr": conf.libpath = "/usr/lib/nim" - elif prefix == "/usr/local": conf.libpath = "/usr/local/lib/nim" - else: conf.libpath = joinPath(prefix, "lib") - else: conf.libpath = joinPath(prefix, "lib") + if prefix == AbsoluteDir"/usr": + conf.libpath = AbsoluteDir"/usr/lib/nim" + elif prefix == AbsoluteDir"/usr/local": + conf.libpath = AbsoluteDir"/usr/local/lib/nim" + else: + conf.libpath = prefix / RelativeDir"lib" + else: + conf.libpath = prefix / RelativeDir"lib" # Special rule to support other tools (nimble) which import the compiler # modules and make use of them. let realNimPath = findExe("nim") # Find out if $nim/../../lib/system.nim exists. let parentNimLibPath = realNimPath.parentDir.parentDir / "lib" - if not fileExists(conf.libpath / "system.nim") and + if not fileExists(conf.libpath.string / "system.nim") and fileExists(parentNimlibPath / "system.nim"): - conf.libpath = parentNimLibPath + conf.libpath = AbsoluteDir parentNimLibPath -proc canonicalizePath*(conf: ConfigRef; path: string): string = +proc canonicalizePath*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile = # on Windows, 'expandFilename' calls getFullPathName which doesn't do # case corrections, so we have to use this convoluted way of retrieving # the true filename (see tests/modules and Nimble uses 'import Uri' instead # of 'import uri'): when defined(windows): - result = path.expandFilename - for x in walkFiles(result): - return x + result = AbsoluteFile path.string.expandFilename + for x in walkFiles(result.string): + return AbsoluteFile x else: - result = path.expandFilename + result = AbsoluteFile path.string.expandFilename -proc shortenDir*(conf: ConfigRef; dir: string): string = +proc shortenDir*(conf: ConfigRef; dir: string): string {. + deprecated: "use 'relativeTo' instead".} = ## returns the interesting part of a dir - var prefix = conf.projectPath & DirSep + var prefix = conf.projectPath.string & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) - prefix = getPrefixDir(conf) & DirSep + prefix = getPrefixDir(conf).string & DirSep if startsWith(dir, prefix): return substr(dir, len(prefix)) result = dir @@ -487,89 +515,89 @@ proc getOsCacheDir(): string = when defined(posix): result = getEnv("XDG_CACHE_HOME", getHomeDir() / ".cache") / "nim" else: - result = getHomeDir() / genSubDir + result = getHomeDir() / genSubDir.string -proc getNimcacheDir*(conf: ConfigRef): string = +proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir = # XXX projectName should always be without a file extension! - result = if conf.nimcacheDir.len > 0: + result = if not conf.nimcacheDir.isEmpty: conf.nimcacheDir elif conf.cmd == cmdCompileToJS: - shortenDir(conf, conf.projectPath) / genSubDir - else: getOsCacheDir() / splitFile(conf.projectName).name & - (if isDefined(conf, "release"): "_r" else: "_d") + conf.projectPath / genSubDir + else: + AbsoluteDir(getOsCacheDir() / splitFile(conf.projectName).name & + (if isDefined(conf, "release"): "_r" else: "_d")) proc pathSubs*(conf: ConfigRef; p, config: string): string = let home = removeTrailingDirSep(os.getHomeDir()) result = unixToNativePath(p % [ - "nim", getPrefixDir(conf), - "lib", conf.libpath, + "nim", getPrefixDir(conf).string, + "lib", conf.libpath.string, "home", home, "config", config, "projectname", conf.projectName, - "projectpath", conf.projectPath, - "projectdir", conf.projectPath, - "nimcache", getNimcacheDir(conf)]) + "projectpath", conf.projectPath.string, + "projectdir", conf.projectPath.string, + "nimcache", getNimcacheDir(conf).string]) if "~/" in result: result = result.replace("~/", home & '/') -proc toGeneratedFile*(conf: ConfigRef; path, ext: string): string = +proc toGeneratedFile*(conf: ConfigRef; path: AbsoluteFile, + ext: string): AbsoluteFile = ## converts "/home/a/mymodule.nim", "rod" to "/home/a/nimcache/mymodule.rod" - var (head, tail) = splitPath(path) - #if len(head) > 0: head = shortenDir(head & dirSep) - result = joinPath([getNimcacheDir(conf), changeFileExt(tail, ext)]) - #echo "toGeneratedFile(", path, ", ", ext, ") = ", result - -proc completeGeneratedFilePath*(conf: ConfigRef; f: string, createSubDir: bool = true): string = - var (head, tail) = splitPath(f) - #if len(head) > 0: head = removeTrailingDirSep(shortenDir(head & dirSep)) - var subdir = getNimcacheDir(conf) # / head + let (head, tail) = splitPath(path.string) + result = getNimcacheDir(conf) / RelativeFile changeFileExt(tail, ext) + +proc completeGeneratedFilePath*(conf: ConfigRef; f: AbsoluteFile, + createSubDir: bool = true): AbsoluteFile = + let (head, tail) = splitPath(f.string) + let subdir = getNimcacheDir(conf) if createSubDir: try: - createDir(subdir) + createDir(subdir.string) except OSError: - writeLine(stdout, "cannot create directory: " & subdir) + writeLine(stdout, "cannot create directory: " & subdir.string) quit(1) - result = joinPath(subdir, tail) + result = subdir / RelativeFile tail #echo "completeGeneratedFilePath(", f, ") = ", result -proc rawFindFile(conf: ConfigRef; f: string; suppressStdlib: bool): string = +proc rawFindFile(conf: ConfigRef; f: RelativeFile; suppressStdlib: bool): AbsoluteFile = for it in conf.searchPaths: - if suppressStdlib and it.startsWith(conf.libpath): + if suppressStdlib and it.string.startsWith(conf.libpath.string): continue - result = joinPath(it, f) - if existsFile(result): + result = it / f + if fileExists(result): return canonicalizePath(conf, result) - result = "" + result = AbsoluteFile"" -proc rawFindFile2(conf: ConfigRef; f: string): string = +proc rawFindFile2(conf: ConfigRef; f: RelativeFile): AbsoluteFile = for i, it in conf.lazyPaths: - result = joinPath(it, f) - if existsFile(result): + result = it / f + if fileExists(result): # bring to front for j in countDown(i,1): swap(conf.lazyPaths[j], conf.lazyPaths[j-1]) return canonicalizePath(conf, result) - result = "" + result = AbsoluteFile"" template patchModule(conf: ConfigRef) {.dirty.} = - if result.len > 0 and conf.moduleOverrides.len > 0: - let key = getPackageName(conf, result) & "_" & splitFile(result).name + if not result.isEmpty and conf.moduleOverrides.len > 0: + let key = getPackageName(conf, result.string) & "_" & splitFile(result).name if conf.moduleOverrides.hasKey(key): let ov = conf.moduleOverrides[key] - if ov.len > 0: result = ov + if ov.len > 0: result = AbsoluteFile(ov) -proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): string {.procvar.} = +proc findFile*(conf: ConfigRef; f: string; suppressStdlib = false): AbsoluteFile {.procvar.} = if f.isAbsolute: - result = if f.existsFile: f else: "" + result = if f.existsFile: AbsoluteFile(f) else: AbsoluteFile"" else: - result = rawFindFile(conf, f, suppressStdlib) - if result.len == 0: - result = rawFindFile(conf, f.toLowerAscii, suppressStdlib) - if result.len == 0: - result = rawFindFile2(conf, f) - if result.len == 0: - result = rawFindFile2(conf, f.toLowerAscii) + result = rawFindFile(conf, RelativeFile f, suppressStdlib) + if result.isEmpty: + result = rawFindFile(conf, RelativeFile f.toLowerAscii, suppressStdlib) + if result.isEmpty: + result = rawFindFile2(conf, RelativeFile f) + if result.isEmpty: + result = rawFindFile2(conf, RelativeFile f.toLowerAscii) patchModule(conf) const stdlibDirs = [ @@ -579,7 +607,7 @@ const stdlibDirs = [ "wrappers", "wrappers/linenoise", "windows", "posix", "js"] -proc findModule*(conf: ConfigRef; modulename, currentModule: string): string = +proc findModule*(conf: ConfigRef; modulename, currentModule: string): AbsoluteFile = # returns path to module const pkgPrefix = "pkg/" const stdPrefix = "std/" @@ -590,13 +618,13 @@ proc findModule*(conf: ConfigRef; modulename, currentModule: string): string = if m.startsWith(stdPrefix): let stripped = m.substr(stdPrefix.len) for candidate in stdlibDirs: - let path = (conf.libpath / candidate / stripped) + let path = (conf.libpath.string / candidate / stripped) if fileExists(path): m = path break let currentPath = currentModule.splitFile.dir - result = currentPath / m - if not existsFile(result): + result = AbsoluteFile currentPath / m + if not fileExists(result): result = findFile(conf, m) patchModule(conf) diff --git a/compiler/packagehandling.nim b/compiler/packagehandling.nim index 7414aeb71..f94c3d72c 100644 --- a/compiler/packagehandling.nim +++ b/compiler/packagehandling.nim @@ -41,10 +41,10 @@ proc getPackageName*(conf: ConfigRef; path: string): string = dec parents if parents <= 0: break -proc withPackageName*(conf: ConfigRef; path: string): string = - let x = getPackageName(conf, path) +proc withPackageName*(conf: ConfigRef; path: AbsoluteFile): AbsoluteFile = + let x = getPackageName(conf, path.string) if x.len == 0: result = path else: let (p, file, ext) = path.splitFile - result = (p / (x & '_' & file)) & ext + result = p / RelativeFile((x & '_' & file) & ext) diff --git a/compiler/parser.nim b/compiler/parser.nim index 246dcb814..69e372e4e 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -27,7 +27,8 @@ when isMainModule: outp.close import - llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos + llstream, lexer, idents, strutils, ast, astalgo, msgs, options, lineinfos, + pathutils when defined(nimpretty2): import layouter @@ -114,7 +115,7 @@ proc openParser*(p: var TParser, fileIdx: FileIndex, inputStream: PLLStream, p.strongSpaces = strongSpaces p.emptyNode = newNode(nkEmpty) -proc openParser*(p: var TParser, filename: string, inputStream: PLLStream, +proc openParser*(p: var TParser, filename: AbsoluteFile, inputStream: PLLStream, cache: IdentCache; config: ConfigRef; strongSpaces=false) = openParser(p, fileInfoIdx(config, filename), inputStream, cache, config, strongSpaces) @@ -252,41 +253,6 @@ proc isRightAssociative(tok: TToken): bool {.inline.} = result = tok.tokType == tkOpr and tok.ident.s[0] == '^' # or (let L = tok.ident.s.len; L > 1 and tok.ident.s[L-1] == '>')) -proc getPrecedence(tok: TToken, strongSpaces: bool): int = - ## Calculates the precedence of the given token. - template considerStrongSpaces(x): untyped = - x + (if strongSpaces: 100 - tok.strongSpaceA.int*10 else: 0) - - case tok.tokType - of tkOpr: - let L = tok.ident.s.len - let relevantChar = tok.ident.s[0] - - # arrow like? - if L > 1 and tok.ident.s[L-1] == '>' and - tok.ident.s[L-2] in {'-', '~', '='}: return considerStrongSpaces(1) - - template considerAsgn(value: untyped) = - result = if tok.ident.s[L-1] == '=': 1 else: value - - case relevantChar - of '$', '^': considerAsgn(10) - of '*', '%', '/', '\\': considerAsgn(9) - of '~': result = 8 - of '+', '-', '|': considerAsgn(8) - of '&': considerAsgn(7) - of '=', '<', '>', '!': result = 5 - of '.': considerAsgn(6) - of '?': result = 2 - else: considerAsgn(2) - of tkDiv, tkMod, tkShl, tkShr: result = 9 - of tkIn, tkNotin, tkIs, tkIsnot, tkNot, tkOf, tkAs: result = 5 - of tkDotDot: result = 6 - of tkAnd: result = 4 - of tkOr, tkXor, tkPtr, tkRef: result = 3 - else: return -10 - result = considerStrongSpaces(result) - proc isOperator(tok: TToken): bool = ## Determines if the given token is an operator type token. tok.tokType in {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, @@ -572,7 +538,7 @@ proc parsePar(p: var TParser): PNode = flexComment(p, result) if p.tok.tokType in {tkDiscard, tkInclude, tkIf, tkWhile, tkCase, tkTry, tkDefer, tkFinally, tkExcept, tkFor, tkBlock, - tkConst, tkLet, tkWhen, tkVar, + tkConst, tkLet, tkWhen, tkVar, tkFor, tkMixin}: # XXX 'bind' used to be an expression, so we exclude it here; # tests/reject/tbind2 fails otherwise. @@ -1147,7 +1113,7 @@ proc parseProcExpr(p: var TParser; isExpr: bool; kind: TNodeKind): PNode = proc isExprStart(p: TParser): bool = case p.tok.tokType - of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, + of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkFor, tkProc, tkFunc, tkIterator, tkBind, tkBuiltInMagics, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, tkTuple, tkObject, tkWhen, tkCase, tkOut: @@ -1187,16 +1153,35 @@ proc parseTypeDescKAux(p: var TParser, kind: TNodeKind, result.addSon list parseSymbolList(p, list) +proc parseFor(p: var TParser): PNode = + #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt + #| forExpr = forStmt + result = newNodeP(nkForStmt, p) + getTokNoInd(p) + var a = identWithPragma(p) + addSon(result, a) + while p.tok.tokType == tkComma: + getTok(p) + optInd(p, a) + a = identWithPragma(p) + addSon(result, a) + eat(p, tkIn) + addSon(result, parseExpr(p)) + colcom(p, result) + addSon(result, parseStmt(p)) + proc parseExpr(p: var TParser): PNode = #| expr = (blockExpr #| | ifExpr #| | whenExpr #| | caseExpr + #| | forExpr #| | tryExpr) #| / simpleExpr case p.tok.tokType: of tkBlock: result = parseBlock(p) of tkIf: result = parseIfExpr(p, nkIfExpr) + of tkFor: result = parseFor(p) of tkWhen: result = parseIfExpr(p, nkWhenExpr) of tkCase: result = parseCase(p) of tkTry: result = parseTry(p, isExpr=true) @@ -1532,7 +1517,7 @@ proc parseCase(p: var TParser): PNode = #| | IND{=} ofBranches) var b: PNode - inElif= false + inElif = false wasIndented = false result = newNodeP(nkCaseStmt, p) getTok(p) @@ -1603,22 +1588,6 @@ proc parseExceptBlock(p: var TParser, kind: TNodeKind): PNode = colcom(p, result) addSon(result, parseStmt(p)) -proc parseFor(p: var TParser): PNode = - #| forStmt = 'for' (identWithPragma ^+ comma) 'in' expr colcom stmt - result = newNodeP(nkForStmt, p) - getTokNoInd(p) - var a = identWithPragma(p) - addSon(result, a) - while p.tok.tokType == tkComma: - getTok(p) - optInd(p, a) - a = identWithPragma(p) - addSon(result, a) - eat(p, tkIn) - addSon(result, parseExpr(p)) - colcom(p, result) - addSon(result, parseStmt(p)) - proc parseBlock(p: var TParser): PNode = #| blockStmt = 'block' symbol? colcom stmt #| blockExpr = 'block' symbol? colcom stmt @@ -2270,7 +2239,7 @@ proc parseString*(s: string; cache: IdentCache; config: ConfigRef; # XXX for now the builtin 'parseStmt/Expr' functions do not know about strong # spaces... parser.lex.errorHandler = errorHandler - openParser(parser, filename, stream, cache, config, false) + openParser(parser, AbsoluteFile filename, stream, cache, config, false) result = parser.parseAll closeParser(parser) diff --git a/compiler/passes.nim b/compiler/passes.nim index 45c726f2a..365731669 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -14,7 +14,7 @@ import strutils, options, ast, astalgo, llstream, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, nimsets, syntaxes, times, idgen, modulegraphs, reorder, rod, - lineinfos + lineinfos, pathutils type @@ -106,7 +106,7 @@ proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool = proc resolveMod(conf: ConfigRef; module, relativeTo: string): FileIndex = let fullPath = findModule(conf, module, relativeTo) - if fullPath.len == 0: + if fullPath.isEmpty: result = InvalidFileIDX else: result = fileInfoIdx(conf, fullPath) @@ -160,7 +160,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream): bool { let filename = toFullPathConsiderDirty(graph.config, fileIdx) s = llStreamOpen(filename, fmRead) if s == nil: - rawMessage(graph.config, errCannotOpenFile, filename) + rawMessage(graph.config, errCannotOpenFile, filename.string) return false else: s = stream diff --git a/compiler/pathutils.nim b/compiler/pathutils.nim new file mode 100644 index 000000000..03fcfe07e --- /dev/null +++ b/compiler/pathutils.nim @@ -0,0 +1,260 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Path handling utilities for Nim. Strictly typed code in order +## to avoid the never ending time sink in getting path handling right. +## Might be a candidate for the stdlib later. + +import os, strutils + +type + AbsoluteFile* = distinct string + AbsoluteDir* = distinct string + RelativeFile* = distinct string + RelativeDir* = distinct string + +proc isEmpty*(x: AbsoluteFile): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: AbsoluteDir): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: RelativeFile): bool {.inline.} = x.string.len == 0 +proc isEmpty*(x: RelativeDir): bool {.inline.} = x.string.len == 0 + +proc copyFile*(source, dest: AbsoluteFile) = + os.copyFile(source.string, dest.string) + +proc removeFile*(x: AbsoluteFile) {.borrow.} + +proc splitFile*(x: AbsoluteFile): tuple[dir: AbsoluteDir, name, ext: string] = + let (a, b, c) = splitFile(x.string) + result = (dir: AbsoluteDir(a), name: b, ext: c) + +proc extractFilename*(x: AbsoluteFile): string {.borrow.} + +proc fileExists*(x: AbsoluteFile): bool {.borrow.} +proc dirExists*(x: AbsoluteDir): bool {.borrow.} + +proc quoteShell*(x: AbsoluteFile): string {.borrow.} +proc quoteShell*(x: AbsoluteDir): string {.borrow.} + +proc cmpPaths*(x, y: AbsoluteDir): int {.borrow.} + +proc createDir*(x: AbsoluteDir) {.borrow.} + +type + PathIter = object + i, prev: int + notFirst: bool + +proc hasNext(it: PathIter; x: string): bool = + it.i < x.len + +proc next(it: var PathIter; x: string): (int, int) = + it.prev = it.i + if not it.notFirst and x[it.i] in {DirSep, AltSep}: + # absolute path: + inc it.i + else: + while it.i < x.len and x[it.i] notin {DirSep, AltSep}: inc it.i + if it.i > it.prev: + result = (it.prev, it.i-1) + elif hasNext(it, x): + result = next(it, x) + + # skip all separators: + while it.i < x.len and x[it.i] in {DirSep, AltSep}: inc it.i + it.notFirst = true + +iterator dirs(x: string): (int, int) = + var it: PathIter + while hasNext(it, x): yield next(it, x) + +when false: + iterator dirs(x: string): (int, int) = + var i = 0 + var first = true + while i < x.len: + let prev = i + if first and x[i] in {DirSep, AltSep}: + # absolute path: + inc i + else: + while i < x.len and x[i] notin {DirSep, AltSep}: inc i + if i > prev: + yield (prev, i-1) + first = false + # skip all separators: + while i < x.len and x[i] in {DirSep, AltSep}: inc i + +proc isDot(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] and x[bounds[0]] == '.' + +proc isDotDot(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] + 1 and x[bounds[0]] == '.' and x[bounds[0]+1] == '.' + +proc isSlash(x: string; bounds: (int, int)): bool = + bounds[1] == bounds[0] and x[bounds[0]] in {DirSep, AltSep} + +proc canon(x: string; result: var string; state: var int) = + # state: 0th bit set if isAbsolute path. Other bits count + # the number of path components. + for b in dirs(x): + if (state shr 1 == 0) and isSlash(x, b): + result.add DirSep + state = state or 1 + elif result.len > (state and 1) and isDotDot(x, b): + var d = result.len + # f/.. + while d > (state and 1) and result[d-1] != DirSep: + dec d + if d > 0: setLen(result, d-1) + elif isDot(x, b): + discard "discard the dot" + elif b[1] >= b[0]: + if result.len > 0 and result[^1] != DirSep: + result.add DirSep + result.add substr(x, b[0], b[1]) + inc state, 2 + +proc canon(x: string): string = + # - Turn multiple slashes into single slashes. + # - Resolve '/foo/../bar' to '/bar'. + # - Remove './' from the path. + result = newStringOfCap(x.len) + var state = 0 + canon(x, result, state) + +when FileSystemCaseSensitive: + template `!=?`(a, b: char): bool = toLowerAscii(a) != toLowerAscii(b) +else: + template `!=?`(a, b: char): bool = a != b + +proc relativeTo(full, base: string; sep = DirSep): string = + if full.len == 0: return "" + var f, b: PathIter + var ff = (0, -1) + var bb = (0, -1) # (int, int) + result = newStringOfCap(full.len) + # skip the common prefix: + while f.hasNext(full) and b.hasNext(base): + ff = next(f, full) + bb = next(b, base) + let diff = ff[1] - ff[0] + if diff != bb[1] - bb[0]: break + var same = true + for i in 0..diff: + if full[i + ff[0]] !=? base[i + bb[0]]: + same = false + break + if not same: break + ff = (0, -1) + bb = (0, -1) + # for i in 0..diff: + # result.add base[i + bb[0]] + + # /foo/bar/xxx/ -- base + # /foo/bar/baz -- full path + # ../baz + # every directory that is in 'base', needs to add '..' + while true: + if bb[1] >= bb[0]: + if result.len > 0 and result[^1] != sep: + result.add sep + result.add ".." + if not b.hasNext(base): break + bb = b.next(base) + + # add the rest of 'full': + while true: + if ff[1] >= ff[0]: + if result.len > 0 and result[^1] != sep: + result.add sep + for i in 0..ff[1] - ff[0]: + result.add full[i + ff[0]] + if not f.hasNext(full): break + ff = f.next(full) + +when true: + proc eqImpl(x, y: string): bool = + when FileSystemCaseSensitive: + result = cmpIgnoreCase(canon x, canon y) == 0 + else: + result = canon(x) == canon(y) + + proc `==`*(x, y: AbsoluteFile): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: AbsoluteDir): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: RelativeFile): bool = eqImpl(x.string, y.string) + proc `==`*(x, y: RelativeDir): bool = eqImpl(x.string, y.string) + + proc `/`*(base: AbsoluteDir; f: RelativeFile): AbsoluteFile = + #assert isAbsolute(base.string) + assert(not isAbsolute(f.string)) + result = AbsoluteFile newStringOfCap(base.string.len + f.string.len) + var state = 0 + canon(base.string, result.string, state) + canon(f.string, result.string, state) + + proc `/`*(base: AbsoluteDir; f: RelativeDir): AbsoluteDir = + #assert isAbsolute(base.string) + assert(not isAbsolute(f.string)) + result = AbsoluteDir newStringOfCap(base.string.len + f.string.len) + var state = 0 + canon(base.string, result.string, state) + canon(f.string, result.string, state) + + proc relativeTo*(fullPath: AbsoluteFile, baseFilename: AbsoluteDir; + sep = DirSep): RelativeFile = + RelativeFile(relativeTo(fullPath.string, baseFilename.string, sep)) + + proc toAbsolute*(file: string; base: AbsoluteDir): AbsoluteFile = + if isAbsolute(file): result = AbsoluteFile(file) + else: result = base / RelativeFile file + + proc changeFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.} + proc changeFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.} + + proc addFileExt*(x: AbsoluteFile; ext: string): AbsoluteFile {.borrow.} + proc addFileExt*(x: RelativeFile; ext: string): RelativeFile {.borrow.} + + proc writeFile*(x: AbsoluteFile; content: string) {.borrow.} + +when isMainModule and defined(posix): + doAssert canon"/foo/../bar" == "/bar" + doAssert canon"foo/../bar" == "bar" + + doAssert canon"/f/../bar///" == "/bar" + doAssert canon"f/..////bar" == "bar" + + doAssert canon"../bar" == "../bar" + doAssert canon"/../bar" == "/../bar" + + doAssert canon("foo/../../bar/") == "../bar" + doAssert canon("./bla/blob/") == "bla/blob" + doAssert canon(".hiddenFile") == ".hiddenFile" + doAssert canon("./bla/../../blob/./zoo.nim") == "../blob/zoo.nim" + + doAssert canon("C:/file/to/this/long") == "C:/file/to/this/long" + doAssert canon("") == "" + doAssert canon("foobar") == "foobar" + doAssert canon("f/////////") == "f" + + doAssert relativeTo("/foo/bar//baz.nim", "/foo") == "bar/baz.nim" + + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other/bad") == "../../me/bar/z.nim" + + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/other") == "../me/bar/z.nim" + doAssert relativeTo("/Users///me/bar//z.nim", "//Users/") == "me/bar/z.nim" + doAssert relativeTo("/Users/me/bar/z.nim", "/Users/me") == "bar/z.nim" + doAssert relativeTo("", "/users/moo") == "" + doAssert relativeTo("foo", "") == "foo" + + doAssert AbsoluteDir"/Users/me///" / RelativeFile"z.nim" == AbsoluteFile"/Users/me/z.nim" + doAssert relativeTo("/foo/bar.nim", "/foo/") == "bar.nim" + +when isMainModule and defined(windows): + let nasty = string(AbsoluteDir(r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\linkedPkgs\pkgB-#head\../../simplePkgs/pkgB-#head/") / RelativeFile"pkgA/module.nim") + doAssert nasty == r"C:\Users\rumpf\projects\nim\tests\nimble\nimbleDir\simplePkgs\pkgB-#head\pkgA\module.nim" diff --git a/compiler/platform.nim b/compiler/platform.nim index 7eb897816..ef1d00e08 100644 --- a/compiler/platform.nim +++ b/compiler/platform.nim @@ -230,9 +230,6 @@ proc setTarget*(t: var Target; o: TSystemOS, c: TSystemCPU) = #echo "new Target: OS: ", o, " CPU: ", c t.targetCPU = c t.targetOS = o - # assume no cross-compiling - t.hostCPU = c - t.hostOS = o t.intSize = CPU[c].intSize div 8 t.floatSize = CPU[c].floatSize div 8 t.ptrSize = CPU[c].bit div 8 @@ -251,4 +248,6 @@ proc nameToCPU*(name: string): TSystemCPU = result = cpuNone proc setTargetFromSystem*(t: var Target) = - t.setTarget(nameToOS(system.hostOS), nameToCPU(system.hostCPU)) + t.hostOS = nameToOS(system.hostOS) + t.hostCPU = nameToCPU(system.hostCPU) + t.setTarget(t.hostOS, t.hostCPU) diff --git a/compiler/plugins/active.nim b/compiler/plugins/active.nim index 7b5306f9c..19c320aae 100644 --- a/compiler/plugins/active.nim +++ b/compiler/plugins/active.nim @@ -10,7 +10,7 @@ ## Include file that imports all plugins that are active. import - "../compiler" / [pluginsupport, idents, ast], locals, itersgen + ".." / [pluginsupport, idents, ast], locals, itersgen const plugins: array[2, Plugin] = [ diff --git a/compiler/plugins/locals.nim b/compiler/plugins/locals.nim index 0048ff985..4dd3e8f9c 100644 --- a/compiler/plugins/locals.nim +++ b/compiler/plugins/locals.nim @@ -29,7 +29,7 @@ proc semLocals*(c: PContext, n: PNode): PNode = {tyVarargs, tyOpenArray, tyTypeDesc, tyStatic, tyExpr, tyStmt, tyEmpty}: var field = newSym(skField, it.name, getCurrOwner(c), n.info) - field.typ = it.typ.skipTypes({tyGenericInst, tyVar}) + field.typ = it.typ.skipTypes({tyVar}) field.position = counter inc(counter) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 66682650d..36c79bc9e 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -12,7 +12,7 @@ import os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer, wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees, - types, lookups, lineinfos + types, lookups, lineinfos, pathutils const FirstCallConv* = wNimcall @@ -53,7 +53,7 @@ const lambdaPragmas* = {FirstCallConv..LastCallConv, wImportc, wExportc, wNodecl, wNosideeffect, wSideeffect, wNoreturn, wDynlib, wHeader, wDeprecated, wExtern, wThread, wImportCpp, wImportObjC, wAsmNoStackFrame, - wRaises, wLocks, wTags, wGcSafe} + wRaises, wLocks, wTags, wGcSafe, wCodegenDecl} typePragmas* = {wImportc, wExportc, wDeprecated, wMagic, wAcyclic, wNodecl, wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wExtern, wShallow, wImportCpp, wImportObjC, wError, wIncompleteStruct, wByCopy, wByRef, @@ -96,7 +96,9 @@ const errIntLiteralExpected = "integer literal expected" proc invalidPragma*(c: PContext; n: PNode) = - localError(c.config, n.info, "invalid pragma: " % renderTree(n, {renderNoComments})) + localError(c.config, n.info, "invalid pragma: " & renderTree(n, {renderNoComments})) +proc illegalCustomPragma*(c: PContext, n: PNode, s: PSym) = + localError(c.config, n.info, "cannot attach a custom pragma to '" & s.name.s & "'") proc pragmaAsm*(c: PContext, n: PNode): char = result = '\0' @@ -229,8 +231,17 @@ proc onOff(c: PContext, n: PNode, op: TOptions, resOptions: var TOptions) = else: resOptions = resOptions - op proc pragmaNoForward(c: PContext, n: PNode; flag=sfNoForward) = - if isTurnedOn(c, n): incl(c.module.flags, flag) - else: excl(c.module.flags, flag) + if isTurnedOn(c, n): + incl(c.module.flags, flag) + c.features.incl codeReordering + else: + excl(c.module.flags, flag) + # c.features.excl codeReordering + + # deprecated as of 0.18.1 + message(c.config, n.info, warnDeprecated, + "use {.experimental: \"codeReordering.\".} instead; " & + (if flag == sfNoForward: "{.noForward.}" else: "{.reorder.}")) proc processCallConv(c: PContext, n: PNode) = if n.kind in nkPragmaCallKinds and n.len == 2 and n.sons[1].kind == nkIdent: @@ -341,6 +352,26 @@ proc pragmaToOptions(w: TSpecialWord): TOptions {.inline.} = of wPatterns: {optPatterns} else: {} +proc processExperimental(c: PContext; n: PNode) = + if n.kind notin nkPragmaCallKinds or n.len != 2: + c.features.incl oldExperimentalFeatures + else: + n[1] = c.semConstExpr(c, n[1]) + case n[1].kind + of nkStrLit, nkRStrLit, nkTripleStrLit: + try: + let feature = parseEnum[Feature](n[1].strVal) + c.features.incl feature + if feature == codeReordering: + if not isTopLevel(c): + localError(c.config, n.info, + "Code reordering experimental pragma only valid at toplevel") + c.module.flags.incl sfReorder + except ValueError: + localError(c.config, n[1].info, "unknown experimental feature") + else: + localError(c.config, n.info, errStringLiteralExpected) + proc tryProcessOption(c: PContext, n: PNode, resOptions: var TOptions): bool = result = true if n.kind notin nkPragmaCallKinds or n.len != 2: result = false @@ -348,6 +379,9 @@ proc tryProcessOption(c: PContext, n: PNode, resOptions: var TOptions): bool = elif n.sons[0].kind != nkIdent: result = false else: let sw = whichKeyword(n.sons[0].ident) + if sw == wExperimental: + processExperimental(c, n) + return true let opts = pragmaToOptions(sw) if opts != {}: onOff(c, n, opts, resOptions) @@ -386,6 +420,7 @@ proc processPush(c: PContext, n: PNode, start: int) = x.defaultCC = y.defaultCC x.dynlib = y.dynlib x.notes = c.config.notes + x.features = c.features c.optionStack.add(x) for i in countup(start, sonsLen(n) - 1): if not tryProcessOption(c, n.sons[i], c.config.options): @@ -405,6 +440,7 @@ proc processPop(c: PContext, n: PNode) = else: c.config.options = c.optionStack[^1].options c.config.notes = c.optionStack[^1].notes + c.features = c.optionStack[^1].features c.optionStack.setLen(c.optionStack.len - 1) proc processDefine(c: PContext, n: PNode) = @@ -421,26 +457,22 @@ proc processUndef(c: PContext, n: PNode) = else: invalidPragma(c, n) -type - TLinkFeature = enum - linkNormal, linkSys - -proc relativeFile(c: PContext; n: PNode; ext=""): string = +proc relativeFile(c: PContext; n: PNode; ext=""): AbsoluteFile = var s = expectStrLit(c, n) if ext.len > 0 and splitFile(s).ext == "": s = addFileExt(s, ext) - result = parentDir(toFullPath(c.config, n.info)) / s + result = AbsoluteFile parentDir(toFullPath(c.config, n.info)) / s if not fileExists(result): - if isAbsolute(s): result = s + if isAbsolute(s): result = AbsoluteFile s else: result = findFile(c.config, s) - if result.len == 0: result = s + if result.isEmpty: result = AbsoluteFile s proc processCompile(c: PContext, n: PNode) = - proc docompile(c: PContext; it: PNode; src, dest: string) = + proc docompile(c: PContext; it: PNode; src, dest: AbsoluteFile) = var cf = Cfile(cname: src, obj: dest, flags: {CfileFlag.External}) extccomp.addExternalFileToCompile(c.config, cf) - recordPragma(c, it, "compile", src, dest) + recordPragma(c, it, "compile", src.string, dest.string) proc getStrLit(c: PContext, n: PNode; i: int): string = n.sons[i] = c.semConstExpr(c, n[i]) @@ -457,30 +489,23 @@ proc processCompile(c: PContext, n: PNode) = let dest = getStrLit(c, it, 1) var found = parentDir(toFullPath(c.config, n.info)) / s for f in os.walkFiles(found): - let obj = completeCFilePath(c.config, dest % extractFilename(f)) - docompile(c, it, f, obj) + let obj = completeCFilePath(c.config, AbsoluteFile(dest % extractFilename(f))) + docompile(c, it, AbsoluteFile f, obj) else: let s = expectStrLit(c, n) - var found = parentDir(toFullPath(c.config, n.info)) / s + var found = AbsoluteFile(parentDir(toFullPath(c.config, n.info)) / s) if not fileExists(found): - if isAbsolute(s): found = s + if isAbsolute(s): found = AbsoluteFile s else: found = findFile(c.config, s) - if found.len == 0: found = s + if found.isEmpty: found = AbsoluteFile s let obj = toObjFile(c.config, completeCFilePath(c.config, found, false)) docompile(c, it, found, obj) -proc processCommonLink(c: PContext, n: PNode, feature: TLinkFeature) = +proc processLink(c: PContext, n: PNode) = let found = relativeFile(c, n, CC[c.config.cCompiler].objExt) - case feature - of linkNormal: - extccomp.addExternalFileToLink(c.config, found) - recordPragma(c, n, "link", found) - of linkSys: - let dest = c.config.libpath / completeCFilePath(c.config, found, false) - extccomp.addExternalFileToLink(c.config, dest) - recordPragma(c, n, "link", dest) - else: internalError(c.config, n.info, "processCommonLink") + extccomp.addExternalFileToLink(c.config, found) + recordPragma(c, n, "link", found.string) proc pragmaBreakpoint(c: PContext, n: PNode) = discard getOptionalStr(c, n, "") @@ -573,8 +598,14 @@ proc pragmaLine(c: PContext, n: PNode) = elif y.kind != nkIntLit: localError(c.config, n.info, errIntLiteralExpected) else: - # XXX this produces weird paths which are not properly resolved: - n.info.fileIndex = msgs.fileInfoIdx(c.config, x.strVal) + if c.config.projectPath.isEmpty: + n.info.fileIndex = fileInfoIdx(c.config, AbsoluteFile(x.strVal)) + else: + # XXX this is still suspicous: + let dir = toFullPath(c.config, n.info).splitFile.dir + let rel = if isAbsolute(x.strVal): relativeTo(AbsoluteFile(x.strVal), c.config.projectPath) + else: RelativeFile(x.strVal) + n.info.fileIndex = fileInfoIdx(c.config, AbsoluteDir(dir) / rel) n.info.line = uint16(y.intVal) else: localError(c.config, n.info, "tuple expected") @@ -715,22 +746,6 @@ proc semCustomPragma(c: PContext, n: PNode): PNode = elif n.kind == nkExprColonExpr: result.kind = n.kind # pragma(arg) -> pragma: arg -proc processExperimental(c: PContext; n: PNode; s: PSym) = - if not isTopLevel(c): - localError(c.config, n.info, "'experimental' pragma only valid as toplevel statement") - if n.kind notin nkPragmaCallKinds or n.len != 2: - c.features.incl oldExperimentalFeatures - else: - n[1] = c.semConstExpr(c, n[1]) - case n[1].kind - of nkStrLit, nkRStrLit, nkTripleStrLit: - try: - c.features.incl parseEnum[Feature](n[1].strVal) - except ValueError: - localError(c.config, n[1].info, "unknown experimental feature") - else: - localError(c.config, n.info, errStringLiteralExpected) - proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, validPragmas: TSpecialWords): bool = var it = n.sons[i] @@ -817,7 +832,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, incl(sym.flags, {sfThread, sfGlobal}) of wDeadCodeElimUnused: discard # deprecated, dead code elim always on of wNoForward: pragmaNoForward(c, it) - of wReorder: pragmaNoForward(c, it, sfReorder) + of wReorder: pragmaNoForward(c, it, flag = sfReorder) of wMagic: processMagic(c, it, sym) of wCompileTime: noVal(c, it) @@ -954,8 +969,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, of wDefine: processDefine(c, it) of wUndef: processUndef(c, it) of wCompile: processCompile(c, it) - of wLink: processCommonLink(c, it, linkNormal) - of wLinksys: processCommonLink(c, it, linkSys) + of wLink: processLink(c, it) of wPassl: let s = expectStrLit(c, it) extccomp.addLinkOption(c.config, s) @@ -1069,7 +1083,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: it.sons[1] = c.semExpr(c, it.sons[1]) of wExperimental: - processExperimental(c, it, sym) + if not isTopLevel(c): + localError(c.config, n.info, "'experimental' pragma only valid as toplevel statement or in a 'push' environment") + processExperimental(c, it) of wThis: if it.kind in nkPragmaCallKinds and it.len == 2: c.selfName = considerQuotedIdent(c, it[1]) @@ -1094,9 +1110,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, else: sym.flags.incl sfUsed of wLiftLocals: discard else: invalidPragma(c, it) - else: + elif sym.kind in {skVar,skLet,skParam,skField,skProc,skFunc,skConverter,skMethod,skType}: n.sons[i] = semCustomPragma(c, it) - + else: + illegalCustomPragma(c, it, sym) proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 8a25d76e8..964af0591 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -15,11 +15,12 @@ import type TRenderFlag* = enum renderNone, renderNoBody, renderNoComments, renderDocComments, - renderNoPragmas, renderIds, renderNoProcDefs + renderNoPragmas, renderIds, renderNoProcDefs, renderSyms TRenderFlags* = set[TRenderFlag] TRenderTok* = object kind*: TTokType length*: int16 + sym*: PSym TRenderTokSeq* = seq[TRenderTok] TSrcGen* = object @@ -105,11 +106,12 @@ proc initSrcGen(g: var TSrcGen, renderFlags: TRenderFlags; config: ConfigRef) = g.inGenericParams = false g.config = config -proc addTok(g: var TSrcGen, kind: TTokType, s: string) = +proc addTok(g: var TSrcGen, kind: TTokType, s: string; sym: PSym = nil) = var length = len(g.tokens) setLen(g.tokens, length + 1) g.tokens[length].kind = kind g.tokens[length].length = int16(len(s)) + g.tokens[length].sym = sym add(g.buf, s) proc addPendingNL(g: var TSrcGen) = @@ -165,11 +167,11 @@ proc dedent(g: var TSrcGen) = dec(g.pendingNL, IndentWidth) dec(g.lineLen, IndentWidth) -proc put(g: var TSrcGen, kind: TTokType, s: string) = +proc put(g: var TSrcGen, kind: TTokType, s: string; sym: PSym = nil) = if kind != tkSpaces: addPendingNL(g) if len(s) > 0: - addTok(g, kind, s) + addTok(g, kind, s, sym) inc(g.lineLen, len(s)) else: g.pendingWhitespace = s.len @@ -307,14 +309,19 @@ proc lsub(g: TSrcGen; n: PNode): int proc litAux(g: TSrcGen; n: PNode, x: BiggestInt, size: int): string = proc skip(t: PType): PType = result = t - while result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, + while result != nil and result.kind in {tyGenericInst, tyRange, tyVar, tyLent, tyDistinct, tyOrdinal, tyAlias, tySink}: result = lastSon(result) - if n.typ != nil and n.typ.skip.kind in {tyBool, tyEnum}: - let enumfields = n.typ.skip.n + let typ = n.typ.skip + if typ != nil and typ.kind in {tyBool, tyEnum}: + if sfPure in typ.sym.flags: + result = typ.sym.name.s & '.' + let enumfields = typ.n # we need a slow linear search because of enums with holes: for e in items(enumfields): - if e.sym.position == x: return e.sym.name.s + if e.sym.position == x: + result &= e.sym.name.s + return if nfBase2 in n.flags: result = "0b" & toBin(x, size * 8) elif nfBase8 in n.flags: result = "0o" & toOct(x, size * 3) @@ -752,7 +759,8 @@ proc gproc(g: var TSrcGen, n: PNode) = gsub(g, n.sons[genericParamsPos]) g.inGenericParams = oldInGenericParams gsub(g, n.sons[paramsPos]) - gsub(g, n.sons[pragmasPos]) + if renderNoPragmas notin g.flags: + gsub(g, n.sons[pragmasPos]) if renderNoBody notin g.flags: if n.sons[bodyPos].kind != nkEmpty: put(g, tkSpaces, Space) @@ -830,7 +838,7 @@ proc gident(g: var TSrcGen, n: PNode) = t = tkSymbol else: t = tkOpr - put(g, t, s) + put(g, t, s, if n.kind == nkSym and renderSyms in g.flags: n.sym else: nil) if n.kind == nkSym and (renderIds in g.flags or sfGenSym in n.sym.flags): when defined(debugMagics): put(g, tkIntLit, $n.sym.id & $n.sym.magic) @@ -860,6 +868,47 @@ proc isBracket*(n: PNode): bool = of nkSym: result = n.sym.name.s == "[]" else: result = false +proc skipHiddenNodes(n: PNode): PNode = + result = n + while result != nil: + if result.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and result.len > 1: + result = result[1] + elif result.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, nkStringToCString, nkCStringToString} and + result.len > 0: + result = result[0] + else: break + +proc accentedName(g: var TSrcGen, n: PNode) = + if n == nil: return + let isOperator = + if n.kind == nkIdent and n.ident.s.len > 0 and n.ident.s[0] in OpChars: true + elif n.kind == nkSym and n.sym.name.s.len > 0 and n.sym.name.s[0] in OpChars: true + else: false + + if isOperator: + put(g, tkAccent, "`") + gident(g, n) + put(g, tkAccent, "`") + else: + gsub(g, n) + +proc infixArgument(g: var TSrcGen, n: PNode, i: int) = + if i >= n.len: return + + var needsParenthesis = false + let n_next = n[i].skipHiddenNodes + if n_next.kind == nkInfix: + if n_next[0].kind in {nkSym, nkIdent} and n[0].kind in {nkSym, nkIdent}: + let nextId = if n_next[0].kind == nkSym: n_next[0].sym.name else: n_next[0].ident + let nnId = if n[0].kind == nkSym: n[0].sym.name else: n[0].ident + if getPrecedence(nextId) < getPrecedence(nnId): + needsParenthesis = true + if needsParenthesis: + put(g, tkParLe, "(") + gsub(g, n, i) + if needsParenthesis: + put(g, tkParRi, ")") + proc gsub(g: var TSrcGen, n: PNode, c: TContext) = if isNil(n): return var @@ -895,7 +944,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gcomma(g, n, 2) put(g, tkBracketRi, "]") elif n.len > 1 and n.lastSon.kind == nkStmtList: - gsub(g, n[0]) + accentedName(g, n[0]) if n.len > 2: put(g, tkParLe, "(") gcomma(g, n, 1, -2) @@ -903,16 +952,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = put(g, tkColon, ":") gsub(g, n, n.len-1) else: - if sonsLen(n) >= 1: gsub(g, n.sons[0]) + if sonsLen(n) >= 1: accentedName(g, n[0]) put(g, tkParLe, "(") gcomma(g, n, 1) put(g, tkParRi, ")") of nkCallStrLit: - gsub(g, n, 0) + if n.len > 0: accentedName(g, n[0]) if n.len > 1 and n.sons[1].kind == nkRStrLit: put(g, tkRStrLit, '\"' & replace(n[1].strVal, "\"", "\"\"") & '\"') else: - gsub(g, n.sons[1]) + gsub(g, n, 1) of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: if n.len >= 2: gsub(g, n.sons[1]) @@ -950,7 +999,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = gsub(g, n, 0) gcomma(g, n, 1) of nkCommand: - gsub(g, n, 0) + accentedName(g, n[0]) put(g, tkSpaces, Space) gcomma(g, n, 1) of nkExprEqExpr, nkAsgn, nkFastAsgn: @@ -1063,14 +1112,14 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = putWithSpace(g, tkColon, ":") gsub(g, n, 1) of nkInfix: - gsub(g, n, 1) + infixArgument(g, n, 1) put(g, tkSpaces, Space) gsub(g, n, 0) # binary operator if not fits(g, lsub(g, n.sons[2]) + lsub(g, n.sons[0]) + 1): optNL(g, g.indent + longIndentWid) else: put(g, tkSpaces, Space) - gsub(g, n, 2) + infixArgument(g, n, 2) of nkPrefix: gsub(g, n, 0) if n.len > 1: @@ -1078,10 +1127,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = elif n[0].kind == nkSym: n[0].sym.name elif n[0].kind in {nkOpenSymChoice, nkClosedSymChoice}: n[0][0].sym.name else: nil - var n_next = n[1] - while n_next.kind in {nkCheckedFieldExpr, nkHiddenAddr, nkHiddenDeref, - nkStringToCString, nkCStringToString} and n_next.len > 0: - n_next = n_next[0] + let n_next = skipHiddenNodes(n[1]) if n_next.kind == nkPrefix or (opr != nil and renderer.isKeyword(opr)): put(g, tkSpaces, Space) if n_next.kind == nkInfix: @@ -1297,17 +1343,16 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) = putWithSpace(g, tkContinue, "continue") gsub(g, n, 0) of nkPragma: - if renderNoPragmas notin g.flags: - if g.inPragma <= 0: - inc g.inPragma - #if not previousNL(g): - put(g, tkSpaces, Space) - put(g, tkCurlyDotLe, "{.") - gcomma(g, n, emptyContext) - put(g, tkCurlyDotRi, ".}") - dec g.inPragma - else: - gcomma(g, n, emptyContext) + if g.inPragma <= 0: + inc g.inPragma + #if not previousNL(g): + put(g, tkSpaces, Space) + put(g, tkCurlyDotLe, "{.") + gcomma(g, n, emptyContext) + put(g, tkCurlyDotRi, ".}") + dec g.inPragma + else: + gcomma(g, n, emptyContext) of nkImportStmt, nkExportStmt: if n.kind == nkImportStmt: putWithSpace(g, tkImport, "import") @@ -1498,3 +1543,9 @@ proc getNextTok*(r: var TSrcGen, kind: var TTokType, literal: var string) = inc(r.idx) else: kind = tkEof + +proc getTokSym*(r: TSrcGen): PSym = + if r.idx > 0 and r.idx <= len(r.tokens): + result = r.tokens[r.idx-1].sym + else: + result = nil diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim index 7d24e4e67..b5891fcfd 100644 --- a/compiler/rodimpl.nim +++ b/compiler/rodimpl.nim @@ -11,7 +11,7 @@ import strutils, os, intsets, tables, ropes, db_sqlite, msgs, options, types, renderer, rodutils, idents, astalgo, btrees, magicsys, cgmeth, extccomp, - btrees, trees, condsyms, nversion + btrees, trees, condsyms, nversion, pathutils ## Todo: ## - Dependency computation should use *signature* hashes in order to @@ -796,7 +796,7 @@ proc replay(g: ModuleGraph; module: PSym; n: PNode) = flags: {CfileFlag.External}) extccomp.addExternalFileToCompile(g.config, cf) of "link": - extccomp.addExternalFileToLink(g.config, n[1].strVal) + extccomp.addExternalFileToLink(g.config, AbsoluteFile n[1].strVal) of "passl": extccomp.addLinkOption(g.config, n[1].strVal) of "passc": diff --git a/compiler/ropes.nim b/compiler/ropes.nim index 81ee01dbf..0d6d7d78f 100644 --- a/compiler/ropes.nim +++ b/compiler/ropes.nim @@ -58,6 +58,8 @@ import hashes +from pathutils import AbsoluteFile + type FormatStr* = string # later we may change it to CString for better # performance of the code generator (assignments @@ -183,9 +185,9 @@ proc writeRope*(f: File, r: Rope) = ## writes a rope to a file. for s in leaves(r): write(f, s) -proc writeRope*(head: Rope, filename: string): bool = +proc writeRope*(head: Rope, filename: AbsoluteFile): bool = var f: File - if open(f, filename, fmWrite): + if open(f, filename.string, fmWrite): if head != nil: writeRope(f, head) close(f) result = true @@ -314,16 +316,16 @@ proc equalsFile*(r: Rope, f: File): bool = result = readBuffer(f, addr(buf[0]), 1) == 0 and btotal == rtotal # check that we've read all -proc equalsFile*(r: Rope, filename: string): bool = +proc equalsFile*(r: Rope, filename: AbsoluteFile): bool = ## returns true if the contents of the file `f` equal `r`. If `f` does not ## exist, false is returned. var f: File - result = open(f, filename) + result = open(f, filename.string) if result: result = equalsFile(r, f) close(f) -proc writeRopeIfNotEqual*(r: Rope, filename: string): bool = +proc writeRopeIfNotEqual*(r: Rope, filename: AbsoluteFile): bool = # returns true if overwritten if not equalsFile(r, filename): result = writeRope(r, filename) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 659206a40..cf69e29f1 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -13,7 +13,7 @@ import ast, modules, idents, passes, passaux, condsyms, options, nimconf, sem, semdata, llstream, vm, vmdef, commands, msgs, - os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos + os, times, osproc, wordrecg, strtabs, modulegraphs, lineinfos, pathutils # we support 'cmpIgnoreStyle' natively for efficiency: from strutils import cmpIgnoreStyle, contains @@ -105,7 +105,7 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf exists: setResult(a, options.existsConfigVar(conf, a.getString 0)) cbconf nimcacheDir: - setResult(a, options.getNimcacheDir(conf)) + setResult(a, options.getNimcacheDir(conf).string) cbconf paramStr: setResult(a, os.paramStr(int a.getInt 0)) cbconf paramCount: @@ -120,8 +120,8 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; if arg.len > 0: conf.projectName = arg let path = - if conf.projectName.isAbsolute: conf.projectName - else: conf.projectPath / conf.projectName + if conf.projectName.isAbsolute: AbsoluteFile(conf.projectName) + else: conf.projectPath / RelativeFile(conf.projectName) try: conf.projectFull = canonicalizePath(conf, path) except OSError: @@ -149,9 +149,9 @@ proc setupVM*(module: PSym; cache: IdentCache; scriptName: string; cbconf cppDefine: options.cppDefine(conf, a.getString(0)) -proc runNimScript*(cache: IdentCache; scriptName: string; +proc runNimScript*(cache: IdentCache; scriptName: AbsoluteFile; freshDefines=true; conf: ConfigRef) = - rawMessage(conf, hintConf, scriptName) + rawMessage(conf, hintConf, scriptName.string) let graph = newModuleGraph(cache, conf) connectCallbacks(graph) @@ -169,9 +169,9 @@ proc runNimScript*(cache: IdentCache; scriptName: string; var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) - graph.vm = setupVM(m, cache, scriptName, graph) + graph.vm = setupVM(m, cache, scriptName.string, graph) - graph.compileSystemModule() + graph.compileSystemModule() # TODO: see why this unsets hintConf in conf.notes discard graph.processModule(m, llStreamOpen(scriptName, fmRead)) # ensure we load 'system.nim' again for the real non-config stuff! diff --git a/compiler/sem.nim b/compiler/sem.nim index 6128c02d1..5e5205c20 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -37,7 +37,7 @@ proc changeType(c: PContext; n: PNode, newType: PType, check: bool) proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode proc semTypeNode(c: PContext, n: PNode, prev: PType): PType -proc semStmt(c: PContext, n: PNode): PNode +proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode proc semOpAux(c: PContext, n: PNode) proc semParamList(c: PContext, n, genericParams: PNode, s: PSym) proc addParams(c: PContext, n: PNode, kind: TSymKind) @@ -399,7 +399,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, excl(result.flags, nfSem) #resetSemFlag n if s.typ.sons[0] == nil: - result = semStmt(c, result) + result = semStmt(c, result, flags) else: case s.typ.sons[0].kind of tyExpr: @@ -408,7 +408,7 @@ proc semAfterMacroCall(c: PContext, call, macroResult: PNode, # semExprWithType(c, result) result = semExpr(c, result, flags) of tyStmt: - result = semStmt(c, result) + result = semStmt(c, result, flags) of tyTypeDesc: if result.kind == nkStmtList: result.kind = nkStmtListType var typ = semTypeNode(c, result, nil) @@ -557,7 +557,7 @@ proc semStmtAndGenerateGenerics(c: PContext, n: PNode): PNode = result = semAllTypeSections(c, n) else: result = n - result = semStmt(c, result) + result = semStmt(c, result, {}) when false: # Code generators are lazy now and can deal with undeclared procs, so these # steps are not required anymore and actually harmful for the upcoming @@ -607,28 +607,6 @@ proc myProcess(context: PPassContext, n: PNode): PNode = #if c.config.cmd == cmdIdeTools: findSuggest(c, n) rod.storeNode(c.graph, c.module, result) -proc testExamples(c: PContext) = - let outputDir = c.config.getNimcacheDir / "runnableExamples" - createDir(outputDir) - let inp = toFullPath(c.config, c.module.info) - let outp = outputDir / extractFilename(inp.changeFileExt"" & "_examples.nim") - let nimcache = outp.changeFileExt"" & "_nimcache" - renderModule(c.runnableExamples, inp, outp, conf = c.config) - let backend = if isDefined(c.config, "js"): "js" - elif isDefined(c.config, "cpp"): "cpp" - elif isDefined(c.config, "objc"): "objc" - else: "c" - if os.execShellCmd(os.getAppFilename() & " " & backend & " --nimcache:" & nimcache & " -r " & outp) != 0: - quit "[Examples] failed: see " & outp - else: - # keep generated source file `outp` to allow inspection. - rawMessage(c.config, hintSuccess, ["runnableExamples: " & outp]) - removeFile(outp.changeFileExt(ExeExt)) - try: - removeDir(nimcache) - except OSError: - discard - proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = var c = PContext(context) if c.config.cmd == cmdIdeTools and not c.suggestionsMade: @@ -644,7 +622,6 @@ proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = popOwner(c) popProcCon(c) storeRemaining(c.graph, c.module) - if c.runnableExamples != nil: testExamples(c) const semPass* = makePass(myOpen, myProcess, myClose, isFrontend = true) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 980cfb691..53f7045dd 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -138,7 +138,9 @@ proc effectProblem(f, a: PType; result: var string) = proc renderNotLValue(n: PNode): string = result = $n - if n.kind in {nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv} and n.len == 2: + if n.kind == nkHiddenCallConv and n.len > 1: + result = $n[0] & "(" & result & ")" + elif n.kind in {nkHiddenStdConv, nkHiddenSubConv} and n.len == 2: result = typeToString(n.typ.skipTypes(abstractVar)) & "(" & result & ")" proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): @@ -164,18 +166,20 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): prefer = preferModuleInfo break - # we pretend procs are attached to the type of the first - # argument in order to remove plenty of candidates. This is - # comparable to what C# does and C# is doing fine. - var filterOnlyFirst = false - for err in errors: - if err.firstMismatch > 1: - filterOnlyFirst = true - break + when false: + # we pretend procs are attached to the type of the first + # argument in order to remove plenty of candidates. This is + # comparable to what C# does and C# is doing fine. + var filterOnlyFirst = false + for err in errors: + if err.firstMismatch > 1: + filterOnlyFirst = true + break var candidates = "" for err in errors: - if filterOnlyFirst and err.firstMismatch == 1: continue + when false: + if filterOnlyFirst and err.firstMismatch == 1: continue if err.sym.kind in routineKinds and err.sym.ast != nil: add(candidates, renderTree(err.sym.ast, {renderNoBody, renderNoComments, renderNoPragmas})) @@ -185,15 +189,18 @@ proc presentFailedCandidates(c: PContext, n: PNode, errors: CandidateErrors): if err.firstMismatch != 0 and n.len > 1: let cond = n.len > 2 if cond: - candidates.add(" first type mismatch at position: " & $err.firstMismatch & - "\n required type: ") + candidates.add(" first type mismatch at position: " & $abs(err.firstMismatch)) + if err.firstMismatch >= 0: candidates.add("\n required type: ") + else: candidates.add("\n unknown named parameter: " & $n[-err.firstMismatch][0]) var wanted, got: PType = nil - if err.firstMismatch < err.sym.typ.len: + if err.firstMismatch < 0: + discard + elif err.firstMismatch < err.sym.typ.len: wanted = err.sym.typ.sons[err.firstMismatch] if cond: candidates.add typeToString(wanted) else: if cond: candidates.add "none" - if err.firstMismatch < n.len: + if err.firstMismatch > 0 and err.firstMismatch < n.len: if cond: candidates.add "\n but expression '" candidates.add renderTree(n[err.firstMismatch]) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 4189a5214..6d6627690 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -22,6 +22,7 @@ type defaultCC*: TCallingConvention dynlib*: PLib notes*: TNoteKinds + features*: set[Feature] otherPragmas*: PNode # every pragma can be pushed POptionEntry* = ref TOptionEntry @@ -140,7 +141,6 @@ type # the generic type has been constructed completely. See # tests/destructor/topttree.nim for an example that # would otherwise fail. - runnableExamples*: PNode template config*(c: PContext): ConfigRef = c.graph.config diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 5ff692fd5..e683984c5 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -113,6 +113,7 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = if castDest.kind notin IntegralTypes+{tyRange}: result = convNotNeedeed return + # Save for later var d = skipTypes(castDest, abstractVar) var s = src if s.kind in tyUserTypeClasses and s.isResolvedUserTypeClass: @@ -135,7 +136,7 @@ proc checkConvertible(c: PContext, castDest, src: PType): TConvStatus = # we use d, s here to speed up that operation a bit: case cmpTypes(c, d, s) of isNone, isGeneric: - if not compareTypes(castDest, src, dcEqIgnoreDistinct): + if not compareTypes(castDest.skipTypes(abstractVar), src, dcEqIgnoreDistinct): result = convNotLegal else: discard @@ -917,7 +918,7 @@ proc semExprNoType(c: PContext, n: PNode): PNode = let isPush = hintExtendedContext in c.config.notes if isPush: pushInfoContext(c.config, n.info) result = semExpr(c, n, {efWantStmt}) - discardCheck(c, result) + discardCheck(c, result, {}) if isPush: popInfoContext(c.config) proc isTypeExpr(n: PNode): bool = @@ -1391,8 +1392,8 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = n.sons[1] = semConstExpr(c, n.sons[1]) if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal, tyAlias, tySink}).kind in {tyInt..tyInt64}: - var idx = getOrdValue(n.sons[1]) - if idx >= 0 and idx < sonsLen(arr): n.typ = arr.sons[int(idx)] + let idx = getOrdValue(n.sons[1]) + if idx >= 0 and idx < len(arr): n.typ = arr.sons[int(idx)] else: localError(c.config, n.info, "invalid index value for tuple subscript") result = n else: @@ -1529,7 +1530,7 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = # unfortunately we need to rewrite ``(x, y) = foo()`` already here so # that overloading of the assignment operator still works. Usually we # prefer to do these rewritings in transf.nim: - return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner)) + return semStmt(c, lowerTupleUnpackingForAsgn(c.graph, n, c.p.owner), {}) else: a = semExprWithType(c, a, {efLValue}) else: @@ -1559,6 +1560,8 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = rhsTyp = rhsTyp.lastSon if cmpTypes(c, lhs.typ, rhsTyp) in {isGeneric, isEqual}: internalAssert c.config, c.p.resultSym != nil + # Make sure the type is valid for the result variable + typeAllowedCheck(c.config, n.info, rhsTyp, skResult) lhs.typ = rhsTyp c.p.resultSym.typ = rhsTyp c.p.owner.typ.sons[0] = rhsTyp @@ -1613,7 +1616,7 @@ proc semProcBody(c: PContext, n: PNode): PNode = a.sons[1] = result result = semAsgn(c, a) else: - discardCheck(c, result) + discardCheck(c, result, {}) if c.p.owner.kind notin {skMacro, skTemplate} and c.p.resultSym != nil and c.p.resultSym.typ.isMetaType: @@ -1953,13 +1956,6 @@ proc setMs(n: PNode, s: PSym): PNode = n.sons[0] = newSymNode(s) n.sons[0].info = n.info -proc extractImports(n: PNode; result: PNode) = - if n.kind in {nkImportStmt, nkImportExceptStmt, nkFromStmt}: - result.add copyTree(n) - n.kind = nkEmpty - return - for i in 0..<n.safeLen: extractImports(n[i], result) - proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = # this is a hotspot in the compiler! # DON'T forget to update ast.SpecialSemMagics if you add a magic here! @@ -1996,7 +1992,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = var x = n.lastSon if x.kind == nkDo: x = x.sons[bodyPos] inc c.inParallelStmt - result.sons[1] = semStmt(c, x) + result.sons[1] = semStmt(c, x, {}) dec c.inParallelStmt of mSpawn: result = setMs(n, s) @@ -2033,16 +2029,17 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = result = magicsAfterOverloadResolution(c, result, flags) of mRunnableExamples: if c.config.cmd == cmdDoc and n.len >= 2 and n.lastSon.kind == nkStmtList: - if sfMainModule in c.module.flags: - let inp = toFullPath(c.config, c.module.info) - if c.runnableExamples == nil: - c.runnableExamples = newTree(nkStmtList, - newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) - let imports = newTree(nkStmtList) - var saved_lastSon = copyTree n.lastSon - extractImports(saved_lastSon, imports) - for imp in imports: c.runnableExamples.add imp - c.runnableExamples.add newTree(nkBlockStmt, c.graph.emptyNode, copyTree saved_lastSon) + when false: + if sfMainModule in c.module.flags: + let inp = toFullPath(c.config, c.module.info) + if c.runnableExamples == nil: + c.runnableExamples = newTree(nkStmtList, + newTree(nkImportStmt, newStrNode(nkStrLit, expandFilename(inp)))) + let imports = newTree(nkStmtList) + var savedLastSon = copyTree n.lastSon + extractImports(savedLastSon, imports) + for imp in imports: c.runnableExamples.add imp + c.runnableExamples.add newTree(nkBlockStmt, c.graph.emptyNode, copyTree savedLastSon) result = setMs(n, s) else: result = c.graph.emptyNode @@ -2246,7 +2243,7 @@ proc isTupleType(n: PNode): bool = include semobjconstr -proc semBlock(c: PContext, n: PNode): PNode = +proc semBlock(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n inc(c.p.nestedBlockCounter) checkSonsLen(n, 2, c.config) @@ -2258,7 +2255,7 @@ proc semBlock(c: PContext, n: PNode): PNode = n.sons[0] = newSymNode(labl, n.sons[0].info) suggestSym(c.config, n.sons[0].info, labl, c.graph.usageSym) styleCheckDef(c.config, labl) - n.sons[1] = semExpr(c, n.sons[1]) + n.sons[1] = semExpr(c, n.sons[1], flags) n.typ = n.sons[1].typ if isEmptyType(n.typ): n.kind = nkBlockStmt else: n.kind = nkBlockExpr @@ -2272,6 +2269,7 @@ proc semExportExcept(c: PContext, n: PNode): PNode = return n let exceptSet = readExceptSet(c, n) let exported = moduleName.sym + result = newNodeI(nkExportStmt, n.info) strTableAdd(c.module.tab, exported) var i: TTabIter var s = initTabIter(i, exported.tab) @@ -2279,11 +2277,12 @@ proc semExportExcept(c: PContext, n: PNode): PNode = if s.kind in ExportableSymKinds+{skModule} and s.name.id notin exceptSet: strTableAdd(c.module.tab, s) + result.add newSymNode(s, n.info) s = nextIter(i, exported.tab) - result = n proc semExport(c: PContext, n: PNode): PNode = - var x = newNodeI(n.kind, n.info) + result = newNodeI(nkExportStmt, n.info) + for i in 0..<n.len: let a = n.sons[i] var o: TOverloadIter @@ -2293,20 +2292,19 @@ proc semExport(c: PContext, n: PNode): PNode = elif s.kind == skModule: # forward everything from that module: strTableAdd(c.module.tab, s) - x.add(newSymNode(s, a.info)) var ti: TTabIter var it = initTabIter(ti, s.tab) while it != nil: if it.kind in ExportableSymKinds+{skModule}: strTableAdd(c.module.tab, it) + result.add newSymNode(it, a.info) it = nextIter(ti, s.tab) else: while s != nil: if s.kind in ExportableSymKinds+{skModule}: - x.add(newSymNode(s, a.info)) + result.add(newSymNode(s, a.info)) strTableAdd(c.module.tab, s) s = nextOverloadIter(o, c, a) - result = n proc shouldBeBracketExpr(n: PNode): bool = assert n.kind in nkCallKinds @@ -2503,7 +2501,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = checkSonsLen(n, 1, c.config) n.sons[0] = semExpr(c, n.sons[0], flags) of nkCast: result = semCast(c, n) - of nkIfExpr, nkIfStmt: result = semIf(c, n) + of nkIfExpr, nkIfStmt: result = semIf(c, n, flags) of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkHiddenCallConv: checkSonsLen(n, 2, c.config) considerGenSyms(c, n) @@ -2524,7 +2522,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = discard of nkStaticExpr: result = semStaticExpr(c, n[0]) of nkAsgn: result = semAsgn(c, n) - of nkBlockStmt, nkBlockExpr: result = semBlock(c, n) + of nkBlockStmt, nkBlockExpr: result = semBlock(c, n, flags) of nkStmtList, nkStmtListExpr: result = semStmtList(c, n, flags) of nkRaiseStmt: result = semRaise(c, n) of nkVarSection: result = semVarOrLet(c, n, skVar) @@ -2532,11 +2530,11 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkConstSection: result = semConst(c, n) of nkTypeSection: result = semTypeSection(c, n) of nkDiscardStmt: result = semDiscard(c, n) - of nkWhileStmt: result = semWhile(c, n) - of nkTryStmt: result = semTry(c, n) + of nkWhileStmt: result = semWhile(c, n, flags) + of nkTryStmt: result = semTry(c, n, flags) of nkBreakStmt, nkContinueStmt: result = semBreakOrContinue(c, n) - of nkForStmt, nkParForStmt: result = semFor(c, n) - of nkCaseStmt: result = semCase(c, n) + of nkForStmt, nkParForStmt: result = semFor(c, n, flags) + of nkCaseStmt: result = semCase(c, n, flags) of nkReturnStmt: result = semReturn(c, n) of nkUsingStmt: result = semUsing(c, n) of nkAsmStmt: result = semAsm(c, n) diff --git a/compiler/semfields.nim b/compiler/semfields.nim index 869f5ae74..07321f477 100644 --- a/compiler/semfields.nim +++ b/compiler/semfields.nim @@ -70,7 +70,7 @@ proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) = openScope(c.c) inc c.c.inUnrolledContext let body = instFieldLoopBody(fc, lastSon(forLoop), forLoop) - father.add(semStmt(c.c, body)) + father.add(semStmt(c.c, body, {})) dec c.c.inUnrolledContext closeScope(c.c) of nkNilLit: discard @@ -145,7 +145,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = fc.replaceByFieldName = m == mFieldPairs var body = instFieldLoopBody(fc, loopBody, n) inc c.inUnrolledContext - stmts.add(semStmt(c, body)) + stmts.add(semStmt(c, body, {})) dec c.inUnrolledContext closeScope(c) else: diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 444940144..0018f0755 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -11,7 +11,7 @@ # and evaluation phase import - strutils, options, ast, astalgo, trees, treetab, nimsets, times, + strutils, options, ast, astalgo, trees, treetab, nimsets, nversion, platform, math, msgs, os, condsyms, idents, renderer, types, commands, magicsys, modulegraphs, strtabs, lineinfos @@ -214,7 +214,24 @@ proc evalIs(n: PNode, lhs: PSym, g: ModuleGraph): PNode = result = newIntNode(nkIntLit, ord(res)) result.typ = n.typ +proc fitLiteral(c: ConfigRef, n: PNode): PNode = + # Trim the literal value in order to make it fit in the destination type + if n == nil: + # `n` may be nil if the overflow check kicks in + return + + doAssert n.kind in {nkIntLit, nkCharLit} + + result = n + + let typ = n.typ.skipTypes(abstractRange) + if typ.kind in tyUInt..tyUint32: + result.intVal = result.intVal and lastOrd(c, typ, fixedUnsigned=true) + proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = + template doAndFit(op: untyped): untyped = + # Implements wrap-around behaviour for unsigned types + fitLiteral(g.config, op) # b and c may be nil result = nil case m @@ -224,12 +241,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of mUnaryMinusF64: result = newFloatNodeT(- getFloat(a), n, g) of mNot: result = newIntNodeT(1 - getInt(a), n, g) of mCard: result = newIntNodeT(nimsets.cardSet(g.config, a), n, g) - of mBitnotI: - case skipTypes(n.typ, abstractRange).kind - of tyUInt..tyUInt64: - result = newIntNodeT((not getInt(a)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g) - else: - result = newIntNodeT(not getInt(a), n, g) + of mBitnotI: result = doAndFit(newIntNodeT(not getInt(a), n, g)) of mLengthArray: result = newIntNodeT(lengthOrd(g.config, a.typ), n, g) of mLengthSeq, mLengthOpenArray, mXLenSeq, mLengthStr, mXLenStr: if a.kind == nkNilLit: @@ -251,9 +263,9 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of mToU8: result = newIntNodeT(getInt(a) and 0x000000FF, n, g) of mToU16: result = newIntNodeT(getInt(a) and 0x0000FFFF, n, g) of mToU32: result = newIntNodeT(getInt(a) and 0x00000000FFFFFFFF'i64, n, g) - of mUnaryLt: result = foldSub(getOrdValue(a), 1, n, g) - of mSucc: result = foldAdd(getOrdValue(a), getInt(b), n, g) - of mPred: result = foldSub(getOrdValue(a), getInt(b), n, g) + of mUnaryLt: result = doAndFit(foldSub(getOrdValue(a), 1, n, g)) + of mSucc: result = doAndFit(foldAdd(getOrdValue(a), getInt(b), n, g)) + of mPred: result = doAndFit(foldSub(getOrdValue(a), getInt(b), n, g)) of mAddI: result = foldAdd(getInt(a), getInt(b), n, g) of mSubI: result = foldSub(getInt(a), getInt(b), n, g) of mMulI: result = foldMul(getInt(a), getInt(b), n, g) @@ -271,7 +283,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = of tyInt64, tyInt: result = newIntNodeT(`shl`(getInt(a), getInt(b)), n, g) of tyUInt..tyUInt64: - result = newIntNodeT(`shl`(getInt(a), getInt(b)) and lastOrd(g.config, a.typ, fixedUnsigned=true), n, g) + result = doAndFit(newIntNodeT(`shl`(getInt(a), getInt(b)), n, g)) else: internalError(g.config, n.info, "constant folding for shl") of mShrI: case skipTypes(n.typ, abstractRange).kind @@ -324,14 +336,14 @@ proc evalOp(m: TMagic, n, a, b, c: PNode; g: ModuleGraph): PNode = result = newIntNodeT(ord(`<%`(getOrdValue(a), getOrdValue(b))), n, g) of mLeU, mLeU64: result = newIntNodeT(ord(`<=%`(getOrdValue(a), getOrdValue(b))), n, g) - of mBitandI, mAnd: result = newIntNodeT(a.getInt and b.getInt, n, g) - of mBitorI, mOr: result = newIntNodeT(getInt(a) or getInt(b), n, g) - of mBitxorI, mXor: result = newIntNodeT(a.getInt xor b.getInt, n, g) - of mAddU: result = newIntNodeT(`+%`(getInt(a), getInt(b)), n, g) - of mSubU: result = newIntNodeT(`-%`(getInt(a), getInt(b)), n, g) - of mMulU: result = newIntNodeT(`*%`(getInt(a), getInt(b)), n, g) - of mModU: result = foldModU(getInt(a), getInt(b), n, g) - of mDivU: result = foldDivU(getInt(a), getInt(b), n, g) + of mBitandI, mAnd: result = doAndFit(newIntNodeT(a.getInt and b.getInt, n, g)) + of mBitorI, mOr: result = doAndFit(newIntNodeT(getInt(a) or getInt(b), n, g)) + of mBitxorI, mXor: result = doAndFit(newIntNodeT(a.getInt xor b.getInt, n, g)) + of mAddU: result = doAndFit(newIntNodeT(`+%`(getInt(a), getInt(b)), n, g)) + of mSubU: result = doAndFit(newIntNodeT(`-%`(getInt(a), getInt(b)), n, g)) + of mMulU: result = doAndFit(newIntNodeT(`*%`(getInt(a), getInt(b)), n, g)) + of mModU: result = doAndFit(foldModU(getInt(a), getInt(b), n, g)) + of mDivU: result = doAndFit(foldDivU(getInt(a), getInt(b), n, g)) of mLeSet: result = newIntNodeT(ord(containsSets(g.config, a, b)), n, g) of mEqSet: result = newIntNodeT(ord(equalSets(g.config, a, b)), n, g) of mLtSet: @@ -450,21 +462,45 @@ proc rangeCheck(n: PNode, value: BiggestInt; g: ModuleGraph) = localError(g.config, n.info, "cannot convert " & $value & " to " & typeToString(n.typ)) -proc foldConv*(n, a: PNode; g: ModuleGraph; check = false): PNode = +proc foldConv(n, a: PNode; g: ModuleGraph; check = false): PNode = + let dstTyp = skipTypes(n.typ, abstractRange) + let srcTyp = skipTypes(a.typ, abstractRange) + # XXX range checks? - case skipTypes(n.typ, abstractRange).kind - of tyInt..tyInt64, tyUInt..tyUInt64: - case skipTypes(a.typ, abstractRange).kind + case dstTyp.kind + of tyInt..tyInt64, tyUint..tyUInt64: + case srcTyp.kind of tyFloat..tyFloat64: result = newIntNodeT(int(getFloat(a)), n, g) - of tyChar: result = newIntNodeT(getOrdValue(a), n, g) + of tyChar: + result = newIntNodeT(getOrdValue(a), n, g) + of tyUInt..tyUInt64, tyInt..tyInt64: + let toSigned = dstTyp.kind in tyInt..tyInt64 + var val = a.getOrdValue + + if dstTyp.kind in {tyInt, tyInt64, tyUint, tyUInt64}: + # No narrowing needed + discard + elif dstTyp.kind in {tyInt..tyInt64}: + # Signed type: Overflow check (if requested) and conversion + if check: rangeCheck(n, val, g) + let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1) + let valSign = val < 0 + val = abs(val) and mask + if valSign: val = -val + else: + # Unsigned type: Conversion + let mask = (`shl`(1, getSize(g.config, dstTyp) * 8) - 1) + val = val and mask + + result = newIntNodeT(val, n, g) else: result = a result.typ = n.typ if check and result.kind in {nkCharLit..nkUInt64Lit}: rangeCheck(n, result.intVal, g) of tyFloat..tyFloat64: - case skipTypes(a.typ, abstractRange).kind + case srcTyp.kind of tyInt..tyInt64, tyEnum, tyBool, tyChar: result = newFloatNodeT(toBiggestFloat(getOrdValue(a)), n, g) else: @@ -549,19 +585,6 @@ proc newSymNodeTypeDesc*(s: PSym; info: TLineInfo): PNode = proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = result = nil - - proc getSrcTimestamp(): DateTime = - try: - result = utc(fromUnix(parseInt(getEnv("SOURCE_DATE_EPOCH", - "not a number")))) - except ValueError: - # Environment variable malformed. - # https://reproducible-builds.org/specs/source-date-epoch/: "If the - # value is malformed, the build process SHOULD exit with a non-zero - # error code", which this doesn't do. This uses local time, because - # that maintains compatibility with existing usage. - result = local(getTime()) - case n.kind of nkSym: var s = n.sym @@ -571,10 +594,8 @@ proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = of skConst: case s.magic of mIsMainModule: result = newIntNodeT(ord(sfMainModule in m.flags), n, g) - of mCompileDate: result = newStrNodeT(format(getSrcTimestamp(), - "yyyy-MM-dd"), n, g) - of mCompileTime: result = newStrNodeT(format(getSrcTimestamp(), - "HH:mm:ss"), n, g) + of mCompileDate: result = newStrNodeT(getDateStr(), n, g) + of mCompileTime: result = newStrNodeT(getClockStr(), n, g) of mCpuEndian: result = newIntNodeT(ord(CPU[g.config.target.targetCPU].endian), n, g) of mHostOS: result = newStrNodeT(toLowerAscii(platform.OS[g.config.target.targetOS].name), n, g) of mHostCPU: result = newStrNodeT(platform.CPU[g.config.target.targetCPU].name.toLowerAscii, n, g) @@ -742,7 +763,8 @@ proc getConstExpr(m: PSym, n: PNode; g: ModuleGraph): PNode = of nkHiddenStdConv, nkHiddenSubConv, nkConv: var a = getConstExpr(m, n.sons[1], g) if a == nil: return - result = foldConv(n, a, g, check=n.kind == nkHiddenStdConv) + # XXX: we should enable `check` for other conversion types too + result = foldConv(n, a, g, check=n.kind == nkHiddenSubConv) of nkCast: var a = getConstExpr(m, n.sons[1], g) if a == nil: return diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index e3c750f5b..7be0610a2 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -215,7 +215,7 @@ proc semGenericStmt(c: PContext, n: PNode, checkMinSonsLen(n, 1, c.config) let fn = n.sons[0] var s = qualifiedLookUp(c, fn, {}) - if s == nil and + if s == nil and {withinMixin, withinConcept}*flags == {} and fn.kind in {nkIdent, nkAccQuoted} and considerQuotedIdent(c, fn).id notin ctx.toMixin: @@ -225,7 +225,7 @@ proc semGenericStmt(c: PContext, n: PNode, var mixinContext = false if s != nil: incl(s.flags, sfUsed) - mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles} + mixinContext = s.magic in {mDefined, mDefinedInScope, mCompiles, mRunnableExamples} let sc = symChoice(c, fn, s, if s.isMixedIn: scForceOpen else: scOpen) case s.kind of skMacro: diff --git a/compiler/seminst.nim b/compiler/seminst.nim index f9d7c3754..4bf1e6ef2 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -326,7 +326,8 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, # no need to instantiate generic templates/macros: internalAssert c.config, fn.kind notin {skMacro, skTemplate} # generates an instantiated proc - if c.instCounter > 1000: internalError(c.config, fn.ast.info, "nesting too deep") + if c.instCounter > 50: + globalError(c.config, info, "generic instantiation too nested") inc(c.instCounter) # careful! we copy the whole AST including the possibly nil body! var n = copyTree(fn.ast) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 425bb24dc..75a4198a5 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -76,17 +76,19 @@ proc semAsm(c: PContext, n: PNode): PNode = if marker == '\0': marker = '`' # default marker result = semAsmOrEmit(c, n, marker) -proc semWhile(c: PContext, n: PNode): PNode = +proc semWhile(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n checkSonsLen(n, 2, c.config) openScope(c) n.sons[0] = forceBool(c, semExprWithType(c, n.sons[0])) inc(c.p.nestedLoopCounter) - n.sons[1] = semStmt(c, n.sons[1]) + n.sons[1] = semStmt(c, n.sons[1], flags) dec(c.p.nestedLoopCounter) closeScope(c) if n.sons[1].typ == c.enforceVoidContext: result.typ = c.enforceVoidContext + elif efInTypeof in flags: + result.typ = n[1].typ proc toCover(c: PContext, t: PType): BiggestInt = let t2 = skipTypes(t, abstractVarRange-{tyTypeDesc}) @@ -97,8 +99,8 @@ proc toCover(c: PContext, t: PType): BiggestInt = proc semProc(c: PContext, n: PNode): PNode -proc semExprBranch(c: PContext, n: PNode): PNode = - result = semExpr(c, n) +proc semExprBranch(c: PContext, n: PNode; flags: TExprFlags = {}): PNode = + result = semExpr(c, n, flags) if result.typ != nil: # XXX tyGenericInst here? if result.typ.kind in {tyVar, tyLent}: result = newDeref(result) @@ -130,8 +132,9 @@ proc fixNilType(c: PContext; n: PNode) = for it in n: fixNilType(c, it) n.typ = nil -proc discardCheck(c: PContext, result: PNode) = - if c.matchedConcept != nil: return +proc discardCheck(c: PContext, result: PNode, flags: TExprFlags) = + if c.matchedConcept != nil or efInTypeof in flags: return + if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}: if implicitlyDiscardable(result): var n = newNodeI(nkDiscardStmt, result.info, 1) @@ -148,7 +151,7 @@ proc discardCheck(c: PContext, result: PNode) = s.add "; for a function call use ()" localError(c.config, n.info, s) -proc semIf(c: PContext, n: PNode): PNode = +proc semIf(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n var typ = commonTypeBegin var hasElse = false @@ -165,8 +168,9 @@ proc semIf(c: PContext, n: PNode): PNode = it.sons[0] = semExprBranchScope(c, it.sons[0]) typ = commonType(typ, it.sons[0]) else: illFormedAst(it, c.config) - if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: - for it in n: discardCheck(c, it.lastSon) + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or + (not hasElse and efInTypeof notin flags): + for it in n: discardCheck(c, it.lastSon, flags) result.kind = nkIfStmt # propagate any enforced VoidContext: if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext @@ -178,8 +182,7 @@ proc semIf(c: PContext, n: PNode): PNode = result.kind = nkIfExpr result.typ = typ -proc semTry(c: PContext, n: PNode): PNode = - +proc semTry(c: PContext, n: PNode; flags: TExprFlags): PNode = var check = initIntSet() template semExceptBranchType(typeNode: PNode): bool = # returns true if exception type is imported type @@ -246,12 +249,12 @@ proc semTry(c: PContext, n: PNode): PNode = dec c.p.inTryStmt if isEmptyType(typ) or typ.kind in {tyNil, tyExpr}: - discardCheck(c, n.sons[0]) - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) + discardCheck(c, n.sons[0], flags) + for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags) if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext else: - if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon) + if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon, flags) n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info) for i in 1..last: var it = n.sons[i] @@ -492,12 +495,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = # keep documentation information: b.comment = a.comment addSon(b, newSymNode(v)) - # keep type desc for doc generator, but only if the user explicitly - # added it - if a.sons[length-2].kind != nkEmpty: - addSon(b, newNodeIT(nkType, a.info, typ)) - else: - addSon(b, a.sons[length-2]) + # keep type desc for doc generator + addSon(b, a.sons[length-2]) addSon(b, copyTree(def)) addToVarSection(c, result, n, b) else: @@ -570,7 +569,7 @@ proc symForVar(c: PContext, n: PNode): PSym = if n.kind == nkPragmaExpr: pragma(c, result, n.sons[1], forVarPragmas) -proc semForVars(c: PContext, n: PNode): PNode = +proc semForVars(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n var length = sonsLen(n) let iterBase = n.sons[length-2].typ @@ -601,7 +600,7 @@ proc semForVars(c: PContext, n: PNode): PNode = addForVarDecl(c, v) inc(c.p.nestedLoopCounter) openScope(c) - n.sons[length-1] = semStmt(c, n.sons[length-1]) + n.sons[length-1] = semExprBranch(c, n.sons[length-1], flags) closeScope(c) dec(c.p.nestedLoopCounter) @@ -685,7 +684,7 @@ proc handleCaseStmtMacro(c: PContext; n: PNode): PNode = when false: result = handleStmtMacro(c, n, n[0], "CaseStmt") -proc semFor(c: PContext, n: PNode): PNode = +proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode = checkMinSonsLen(n, 3, c.config) var length = sonsLen(n) if forLoopMacros in c.features: @@ -702,14 +701,14 @@ proc semFor(c: PContext, n: PNode): PNode = if isCallExpr and call[0].kind == nkSym and call[0].sym.magic in {mFields, mFieldPairs, mOmpParFor}: if call.sons[0].sym.magic == mOmpParFor: - result = semForVars(c, n) + result = semForVars(c, n, flags) result.kind = nkParForStmt else: result = semForFields(c, n, call.sons[0].sym.magic) elif isCallExpr and call.sons[0].typ.callConv == ccClosure and tfIterator in call.sons[0].typ.flags: # first class iterator: - result = semForVars(c, n) + result = semForVars(c, n, flags) elif not isCallExpr or call.sons[0].kind != nkSym or call.sons[0].sym.kind != skIterator: if length == 3: @@ -718,15 +717,17 @@ proc semFor(c: PContext, n: PNode): PNode = n.sons[length-2] = implicitIterator(c, "pairs", n.sons[length-2]) else: localError(c.config, n.sons[length-2].info, "iterator within for loop context expected") - result = semForVars(c, n) + result = semForVars(c, n, flags) else: - result = semForVars(c, n) + result = semForVars(c, n, flags) # propagate any enforced VoidContext: if n.sons[length-1].typ == c.enforceVoidContext: result.typ = c.enforceVoidContext + elif efInTypeof in flags: + result.typ = result.lastSon.typ closeScope(c) -proc semCase(c: PContext, n: PNode): PNode = +proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode = result = n checkMinSonsLen(n, 2, c.config) openScope(c) @@ -782,8 +783,9 @@ proc semCase(c: PContext, n: PNode): PNode = else: localError(c.config, n.info, "not all cases are covered") closeScope(c) - if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse: - for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon) + if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or + (not hasElse and efInTypeof notin flags): + for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon, flags) # propagate any enforced VoidContext: if typ == c.enforceVoidContext: result.typ = c.enforceVoidContext @@ -804,7 +806,7 @@ proc semRaise(c: PContext, n: PNode): PNode = if not isImportedException(typ, c.config): if typ.kind != tyRef or typ.lastSon.kind != tyObject: localError(c.config, n.info, errExprCannotBeRaised) - if not isException(typ.lastSon): + if typ.len > 0 and not isException(typ.lastSon): localError(c.config, n.info, "raised object of type $1 does not inherit from Exception", [typeToString(typ)]) @@ -1173,6 +1175,9 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) = if b != nil: # store the alias: n.sons[bodyPos] = newSymNode(b) + # Carry over the original symbol magic, this is necessary in order to ensure + # the semantic pass is correct + s.magic = b.magic else: localError(c.config, n.info, errNoSymbolToBorrowFromFound) @@ -1595,7 +1600,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, localError(c.config, n.sons[pragmasPos].info, errPragmaOnlyInHeaderOfProcX % ("'" & proto.name.s & "' from " & c.config$proto.info)) if sfForward notin proto.flags and proto.magic == mNone: - wrongRedefinition(c, n.info, proto.name.s) + wrongRedefinition(c, n.info, proto.name.s, proto.info) excl(proto.flags, sfForward) closeScope(c) # close scope with wrong parameter symbols openScope(c) # open scope for old (correct) parameter symbols @@ -1792,7 +1797,7 @@ proc evalInclude(c: PContext, n: PNode): PNode = if containsOrIncl(c.includedFiles, f.int): localError(c.config, n.info, errRecursiveDependencyX % toFilename(c.config, f)) else: - addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f))) + addSon(result, semStmt(c, c.graph.includeFileCallback(c.graph, c.module, f), {})) excl(c.includedFiles, f.int) proc setLine(n: PNode, info: TLineInfo) = @@ -1819,7 +1824,7 @@ proc semStaticStmt(c: PContext, n: PNode): PNode = #writeStackTrace() inc c.inStaticContext openScope(c) - let a = semStmt(c, n.sons[0]) + let a = semStmt(c, n.sons[0], {}) closeScope(c) dec c.inStaticContext n.sons[0] = a @@ -1895,11 +1900,11 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = if n.sons[i].typ == c.enforceVoidContext: #or usesResult(n.sons[i]): voidContext = true n.typ = c.enforceVoidContext - if i == last and (length == 1 or efWantValue in flags): + if i == last and (length == 1 or ({efWantValue, efInTypeof} * flags != {})): n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr elif i != last or voidContext: - discardCheck(c, n.sons[i]) + discardCheck(c, n.sons[i], flags) else: n.typ = n.sons[i].typ if not isEmptyType(n.typ): n.kind = nkStmtListExpr @@ -1928,6 +1933,8 @@ proc semStmtList(c: PContext, n: PNode, flags: TExprFlags): PNode = # it is an old-style comment statement: we replace it with 'discard ""': prettybase.replaceComment(result.info) -proc semStmt(c: PContext, n: PNode): PNode = - # now: simply an alias: - result = semExprNoType(c, n) +proc semStmt(c: PContext, n: PNode; flags: TExprFlags): PNode = + if efInTypeof notin flags: + result = semExprNoType(c, n) + else: + result = semExpr(c, n, flags) diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 2952831e9..396696422 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -210,7 +210,7 @@ proc addLocalDecl(c: var TemplCtx, n: var PNode, k: TSymKind) = if s != nil and s.owner == c.owner and sfGenSym in s.flags: styleCheckUse(n.info, s) replaceIdentBySym(c.c, n, newSymNode(s, n.info)) - else: + elif not (n.kind == nkSym and sfGenSym in n.sym.flags): let local = newGenSym(k, ident, c) addPrelimDecl(c.c, local) styleCheckDef(c.c.config, n.info, local) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 86f3a17ab..5394e291f 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -125,8 +125,8 @@ proc semEnum(c: PContext, n: PNode, prev: PType): PType = if sfGenSym notin e.flags: if not isPure: addDecl(c, e) else: importPureEnumField(c, e) - if isPure and strTableIncl(symbols, e): - wrongRedefinition(c, e.info, e.name.s) + if isPure and (let conflict = strTableInclReportConflict(symbols, e); conflict != nil): + wrongRedefinition(c, e.info, e.name.s, conflict.info) inc(counter) if not hasNull: incl(result.flags, tfNeedsInit) @@ -1161,7 +1161,7 @@ proc semStmtListType(c: PContext, n: PNode, prev: PType): PType = checkMinSonsLen(n, 1, c.config) var length = sonsLen(n) for i in countup(0, length - 2): - n.sons[i] = semStmt(c, n.sons[i]) + n.sons[i] = semStmt(c, n.sons[i], {}) if length > 0: result = semTypeNode(c, n.sons[length - 1], prev) n.typ = result @@ -1406,6 +1406,13 @@ proc semStaticType(c: PContext, childNode: PNode, prev: PType): PType = result.rawAddSon(base) result.flags.incl tfHasStatic +proc semTypeof(c: PContext; n: PNode; prev: PType): PType = + openScope(c) + let t = semExprWithType(c, n, {efInTypeof}) + closeScope(c) + fixupTypeOf(c, prev, t) + result = t.typ + proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = nil inc c.inTypeContext @@ -1416,9 +1423,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of nkTypeOfExpr: # for ``type(countup(1,3))``, see ``tests/ttoseq``. checkSonsLen(n, 1, c.config) - let typExpr = semExprWithType(c, n.sons[0], {efInTypeof}) - fixupTypeOf(c, prev, typExpr) - result = typExpr.typ + result = semTypeof(c, n.sons[0], prev) if result.kind == tyTypeDesc: result.flags.incl tfExplicit of nkPar: if sonsLen(n) == 1: result = semTypeNode(c, n.sons[0], prev) @@ -1487,9 +1492,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semAnyRef(c, n, tyRef, prev) elif op.id == ord(wType): checkSonsLen(n, 2, c.config) - let typExpr = semExprWithType(c, n.sons[1], {efInTypeof}) - fixupTypeOf(c, prev, typExpr) - result = typExpr.typ + result = semTypeof(c, n[1], prev) else: if c.inGenericContext > 0 and n.kind == nkCall: result = makeTypeFromExpr(c, n.copyTree) diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index c315cbebb..a6067dfc9 100644 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -367,8 +367,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = # can come here for tyGenericInst too, see tests/metatype/ttypeor.nim # need to look into this issue later assert newbody.kind in {tyRef, tyPtr} - assert newbody.lastSon.typeInst == nil - newbody.lastSon.typeInst = result + if newbody.lastSon.typeInst != nil: + #internalError(cl.c.config, cl.info, "ref already has a 'typeInst' field") + discard + else: + newbody.lastSon.typeInst = result cl.c.typesWithOps.add((newbody, result)) let methods = skipTypes(bbody, abstractPtrs).methods for col, meth in items(methods): diff --git a/compiler/sighashes.nim b/compiler/sighashes.nim index 8f95175e5..218011b1d 100644 --- a/compiler/sighashes.nim +++ b/compiler/sighashes.nim @@ -68,6 +68,8 @@ else: toBase64a(cast[cstring](unsafeAddr u), sizeof(u)) proc `&=`(c: var MD5Context, s: string) = md5Update(c, s, s.len) proc `&=`(c: var MD5Context, ch: char) = md5Update(c, unsafeAddr ch, 1) + proc `&=`(c: var MD5Context, r: Rope) = + for l in leaves(r): md5Update(c, l, l.len) proc `&=`(c: var MD5Context, i: BiggestInt) = md5Update(c, cast[cstring](unsafeAddr i), sizeof(i)) @@ -173,19 +175,23 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]) = c.hashSym(t.sym) of tyObject, tyEnum: if t.typeInst != nil: - assert t.typeInst.kind == tyGenericInst - for i in countup(0, sonsLen(t.typeInst) - 2): - c.hashType t.typeInst.sons[i], flags + # prevent against infinite recursions here, see bug #8883: + let inst = t.typeInst + t.typeInst = nil + assert inst.kind == tyGenericInst + for i in countup(0, inst.len - 2): + c.hashType inst.sons[i], flags + t.typeInst = inst return c &= char(t.kind) # Every cyclic type in Nim need to be constructed via some 't.sym', so this # is actually safe without an infinite recursion check: if t.sym != nil: - #if "Future:" in t.sym.name.s and t.typeInst == nil: - # writeStackTrace() - # echo "yes ", t.sym.name.s - # #quit 1 - if CoOwnerSig in flags: + if {sfCompilerProc} * t.sym.flags != {}: + doAssert t.sym.loc.r != nil + # The user has set a specific name for this type + c &= t.sym.loc.r + elif CoOwnerSig in flags: c.hashTypeSym(t.sym) else: c.hashSym(t.sym) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 932163055..407e34619 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -1921,6 +1921,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, result.typ.n = arg return + let oldInheritancePenalty = m.inheritancePenalty var r = typeRel(m, f, a) # This special typing rule for macros and templates is not documented @@ -2002,7 +2003,7 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType, if arg.typ == nil: result = arg elif skipTypes(arg.typ, abstractVar-{tyTypeDesc}).kind == tyTuple or - m.inheritancePenalty > 0: + m.inheritancePenalty > oldInheritancePenalty: result = implicitConv(nkHiddenSubConv, f, arg, m, c) elif arg.typ.isEmptyContainer: result = arg.copyTree @@ -2131,6 +2132,10 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, styleCheckUse(arg.info, arg.sons[best].sym) result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best], argOrig) + when false: + if m.calleeSym != nil and m.calleeSym.name.s == "[]": + echo m.c.config $ arg.info, " for ", m.calleeSym.name.s, " ", m.c.config $ m.calleeSym.info + writeMatches(m) proc setSon(father: PNode, at: int, son: PNode) = let oldLen = father.len @@ -2226,12 +2231,20 @@ proc matchesAux(c: PContext, n, nOrig: PNode, if a >= formalLen-1 and f < formalLen and m.callee.n[f].typ.isVarargsUntyped: formal = m.callee.n.sons[f].sym incl(marker, formal.position) - if container.isNil: - container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) - setSon(m.call, formal.position + 1, container) + + if n.sons[a].kind == nkHiddenStdConv: + doAssert n.sons[a].sons[0].kind == nkEmpty and + n.sons[a].sons[1].kind == nkArgList and + n.sons[a].sons[1].len == 0 + # Steal the container and pass it along + setSon(m.call, formal.position + 1, n.sons[a].sons[1]) else: - incrIndexType(container.typ) - addSon(container, n.sons[a]) + if container.isNil: + container = newNodeIT(nkArgList, n.sons[a].info, arrayConstr(c, n.info)) + setSon(m.call, formal.position + 1, container) + else: + incrIndexType(container.typ) + addSon(container, n.sons[a]) elif n.sons[a].kind == nkExprEqExpr: # named param # check if m.callee has such a param: @@ -2239,11 +2252,13 @@ proc matchesAux(c: PContext, n, nOrig: PNode, if n.sons[a].sons[0].kind != nkIdent: localError(c.config, n.sons[a].info, "named parameter has to be an identifier") m.state = csNoMatch + m.firstMismatch = -a return formal = getSymFromList(m.callee.n, n.sons[a].sons[0].ident, 1) if formal == nil: # no error message! m.state = csNoMatch + m.firstMismatch = -a return if containsOrIncl(marker, formal.position): # already in namedParams, so no match @@ -2261,6 +2276,7 @@ proc matchesAux(c: PContext, n, nOrig: PNode, n.sons[a].sons[1], n.sons[a].sons[1]) if arg == nil: m.state = csNoMatch + m.firstMismatch = a return checkConstraint(n.sons[a].sons[1]) if m.baseTypeMatch: @@ -2379,6 +2395,11 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = if m.magic in {mArrGet, mArrPut}: m.state = csMatch m.call = n + # Note the following doesn't work as it would produce ambiguities. + # Instead we patch system.nim, see bug #8049. + when false: + inc m.genericMatches + inc m.exactMatches return var marker = initIntSet() matchesAux(c, n, nOrig, m, marker) @@ -2390,7 +2411,10 @@ proc matches*(c: PContext, n, nOrig: PNode, m: var TCandidate) = if not containsOrIncl(marker, formal.position): if formal.ast == nil: if formal.typ.kind == tyVarargs: - var container = newNodeIT(nkBracket, n.info, arrayConstr(c, n.info)) + # For consistency with what happens in `matchesAux` select the + # container node kind accordingly + let cnKind = if formal.typ.isVarargsUntyped: nkArgList else: nkBracket + var container = newNodeIT(cnKind, n.info, arrayConstr(c, n.info)) setSon(m.call, formal.position + 1, implicitConv(nkHiddenStdConv, formal.typ, container, m, c)) else: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index f99a2d432..b6b8d713c 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -32,7 +32,7 @@ # included from sigmatch.nim -import algorithm, prefixmatches, lineinfos +import algorithm, prefixmatches, lineinfos, pathutils from wordrecg import wDeprecated when defined(nimsuggest): @@ -319,7 +319,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) if n.kind == nkSym and n.sym.kind == skError and c.config.suggestVersion == 0: # consider 'foo.|' where 'foo' is some not imported module. let fullPath = findModule(c.config, n.sym.name.s, toFullPath(c.config, n.info)) - if fullPath.len == 0: + if fullPath.isEmpty: # error: no known module name: typ = nil else: diff --git a/compiler/syntaxes.nim b/compiler/syntaxes.nim index 069f65eee..b5fcee7b1 100644 --- a/compiler/syntaxes.nim +++ b/compiler/syntaxes.nim @@ -11,7 +11,7 @@ import strutils, llstream, ast, astalgo, idents, lexer, options, msgs, parser, - filters, filter_tmpl, renderer, lineinfos + filters, filter_tmpl, renderer, lineinfos, pathutils type TFilterKind* = enum @@ -58,7 +58,7 @@ proc containsShebang(s: string, i: int): bool = while j < s.len and s[j] in Whitespace: inc(j) result = s[j] == '/' -proc parsePipe(filename: string, inputStream: PLLStream; cache: IdentCache; +proc parsePipe(filename: AbsoluteFile, inputStream: PLLStream; cache: IdentCache; config: ConfigRef): PNode = result = newNode(nkEmpty) var s = llStreamOpen(filename, fmRead) @@ -100,7 +100,7 @@ proc getCallee(conf: ConfigRef; n: PNode): PIdent = else: localError(conf, n.info, "invalid filter: " & renderTree(n)) -proc applyFilter(p: var TParsers, n: PNode, filename: string, +proc applyFilter(p: var TParsers, n: PNode, filename: AbsoluteFile, stdin: PLLStream): PLLStream = var ident = getCallee(p.config, n) var f = getFilter(ident) @@ -121,7 +121,7 @@ proc applyFilter(p: var TParsers, n: PNode, filename: string, msgWriteln(p.config, result.s) rawMessage(p.config, hintCodeEnd, []) -proc evalPipe(p: var TParsers, n: PNode, filename: string, +proc evalPipe(p: var TParsers, n: PNode, filename: AbsoluteFile, start: PLLStream): PLLStream = assert p.config != nil result = start @@ -161,8 +161,8 @@ proc parseFile*(fileIdx: FileIndex; cache: IdentCache; config: ConfigRef): PNode p: TParsers f: File let filename = toFullPathConsiderDirty(config, fileIdx) - if not open(f, filename): - rawMessage(config, errGenerated, "cannot open file: " & filename) + if not open(f, filename.string): + rawMessage(config, errGenerated, "cannot open file: " & filename.string) return openParsers(p, fileIdx, llStreamOpen(f), cache, config) result = parseAll(p) diff --git a/compiler/tccgen.nim b/compiler/tccgen.nim index ea0fb590f..2301ad404 100644 --- a/compiler/tccgen.nim +++ b/compiler/tccgen.nim @@ -39,24 +39,24 @@ proc setupEnvironment = addIncludePath(gTinyC, libpath) when defined(windows): - addSysincludePath(gTinyC, nimrodDir / "tinyc/win32/include") - addSysincludePath(gTinyC, nimrodDir / "tinyc/include") + addSysincludePath(gTinyC, nimDir / "tinyc/win32/include") + addSysincludePath(gTinyC, nimDir / "tinyc/include") when defined(windows): defineSymbol(gTinyC, "_WIN32", nil) # we need Mingw's headers too: var gccbin = getConfigVar("gcc.path") % ["nim", nimDir] addSysincludePath(gTinyC, gccbin /../ "include") - #addFile(nimrodDir / r"tinyc\win32\wincrt1.o") - addFile(nimrodDir / r"tinyc\win32\alloca86.o") - addFile(nimrodDir / r"tinyc\win32\chkstk.o") - #addFile(nimrodDir / r"tinyc\win32\crt1.o") + #addFile(nimDir / r"tinyc\win32\wincrt1.o") + addFile(nimDir / r"tinyc\win32\alloca86.o") + addFile(nimDir / r"tinyc\win32\chkstk.o") + #addFile(nimDir / r"tinyc\win32\crt1.o") - #addFile(nimrodDir / r"tinyc\win32\dllcrt1.o") - #addFile(nimrodDir / r"tinyc\win32\dllmain.o") - addFile(nimrodDir / r"tinyc\win32\libtcc1.o") + #addFile(nimDir / r"tinyc\win32\dllcrt1.o") + #addFile(nimDir / r"tinyc\win32\dllmain.o") + addFile(nimDir / r"tinyc\win32\libtcc1.o") - #addFile(nimrodDir / r"tinyc\win32\lib\crt1.c") - #addFile(nimrodDir / r"tinyc\lib\libtcc1.c") + #addFile(nimDir / r"tinyc\win32\lib\crt1.c") + #addFile(nimDir / r"tinyc\lib\libtcc1.c") else: addSysincludePath(gTinyC, "/usr/include") when defined(amd64): diff --git a/compiler/transf.nim b/compiler/transf.nim index 84297aa6a..b31be71a3 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -624,7 +624,11 @@ proc transformCase(c: PTransf, n: PNode): PTransNode = case it.kind of nkElifBranch: if ifs.PNode == nil: - ifs = newTransNode(nkIfStmt, it.info, 0) + # Generate the right node depending on whether `n` is used as a stmt or + # as an expr + let kind = if n.typ != nil: nkIfExpr else: nkIfStmt + ifs = newTransNode(kind, it.info, 0) + ifs.PNode.typ = n.typ ifs.add(e) of nkElse: if ifs.PNode == nil: result.add(e) @@ -910,7 +914,8 @@ proc transform(c: PTransf, n: PNode): PTransNode = # ensure that e.g. discard "some comment" gets optimized away # completely: result = PTransNode(newNode(nkCommentStmt)) - of nkCommentStmt, nkTemplateDef, nkImportStmt, nkStaticStmt: + of nkCommentStmt, nkTemplateDef, nkImportStmt, nkStaticStmt, + nkExportStmt, nkExportExceptStmt: return n.PTransNode of nkConstSection: # do not replace ``const c = 3`` with ``const 3 = 3`` @@ -930,12 +935,11 @@ proc transform(c: PTransf, n: PNode): PTransNode = else: result = transformSons(c, n) of nkIdentDefs, nkConstDef: - when true: - result = transformSons(c, n) - else: - result = n.PTransNode - let L = n.len-1 - result[L] = transform(c, n.sons[L]) + result = PTransNode(n) + result[0] = transform(c, n[0]) + # Skip the second son since it only contains an unsemanticized copy of the + # variable type used by docgen + result[2] = transform(c, n[2]) # XXX comment handling really sucks: if importantComments(c.graph.config): PNode(result).comment = n.comment diff --git a/compiler/vm.nim b/compiler/vm.nim index fcbd97b8d..faff81697 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -81,14 +81,16 @@ proc stackTraceAux(c: PCtx; x: PStackFrame; pc: int; recursionLimit=100) = msgWriteln(c.config, s) proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, - msg: string, n: PNode = nil) = + msg: string, lineInfo: TLineInfo) = msgWriteln(c.config, "stack trace: (most recent call last)") stackTraceAux(c, tos, pc) # XXX test if we want 'globalError' for every mode - let lineInfo = if n == nil: c.debug[pc] else: n.info if c.mode == emRepl: globalError(c.config, lineInfo, msg) else: localError(c.config, lineInfo, msg) +proc stackTrace(c: PCtx, tos: PStackFrame, pc: int, msg: string) = + stackTrace(c, tos, pc, msg, c.debug[pc]) + proc bailOut(c: PCtx; tos: PStackFrame) = stackTrace(c, tos, c.exceptionInstr, "unhandled exception: " & c.currentExceptionA.sons[3].skipColon.strVal) @@ -271,7 +273,7 @@ proc cleanUpOnException(c: PCtx; tos: PStackFrame): abstractPtrs) else: nil #echo typeToString(exceptType), " ", typeToString(raisedType) - if exceptType.isNil or inheritanceDiff(exceptType, raisedType) <= 0: + if exceptType.isNil or inheritanceDiff(raisedType, exceptType) <= 0: # mark exception as handled but keep it in B for # the getCurrentException() builtin: c.currentExceptionB = c.currentExceptionA @@ -555,11 +557,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = # a = b.c decodeBC(rkNode) let src = regs[rb].node - if src.kind notin {nkEmpty..nkNilLit}: - let n = src.sons[rc + ord(src.kind == nkObjConstr)].skipColon + case src.kind + of nkEmpty..nkNilLit: + stackTrace(c, tos, pc, errNilAccess) + of nkObjConstr: + let n = src.sons[rc + 1].skipColon regs[ra].node = n else: - stackTrace(c, tos, pc, errNilAccess) + let n = src.sons[rc] + regs[ra].node = n of opcWrObj: # a.b = c decodeBC(rkNode) @@ -818,7 +824,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].intVal = ord((regs[rb].node.kind == nkNilLit and regs[rc].node.kind == nkNilLit) or regs[rb].node == regs[rc].node) - of opcEqNimrodNode: + of opcEqNimNode: decodeBC(rkInt) regs[ra].intVal = ord(exprStructuralEquivalent(regs[rb].node, regs[rc].node, @@ -918,6 +924,15 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = regs[ra].node.flags.incl nfIsRef else: stackTrace(c, tos, pc, "node is not a symbol") + of opcSymOwner: + decodeB(rkNode) + let a = regs[rb].node + if a.kind == nkSym: + regs[ra].node = if a.sym.owner.isNil: newNode(nkNilLit) + else: newSymNode(a.sym.skipGenericOwner) + regs[ra].node.flags.incl nfIsRef + else: + stackTrace(c, tos, pc, "node is not a symbol") of opcEcho: let rb = instr.regB if rb == 1: @@ -1385,15 +1400,17 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = c.debug[pc], c.config)[0] else: globalError(c.config, c.debug[pc], "VM is not built with 'gorge' support") - of opcNError: + of opcNError, opcNWarning, opcNHint: decodeB(rkNode) let a = regs[ra].node let b = regs[rb].node - stackTrace(c, tos, pc, a.strVal, if b.kind == nkNilLit: nil else: b) - of opcNWarning: - message(c.config, c.debug[pc], warnUser, regs[ra].node.strVal) - of opcNHint: - message(c.config, c.debug[pc], hintUser, regs[ra].node.strVal) + let info = if b.kind == nkNilLit: c.debug[pc] else: b.info + if instr.opcode == opcNError: + stackTrace(c, tos, pc, a.strVal, info) + elif instr.opcode == opcNWarning: + message(c.config, info, warnUser, a.strVal) + elif instr.opcode == opcNHint: + message(c.config, info, hintUser, a.strVal) of opcParseExprToAst: decodeB(rkNode) # c.debug[pc].line.int - countLines(regs[rb].strVal) ? diff --git a/compiler/vmdef.nim b/compiler/vmdef.nim index 1abd9ae4a..d642043dc 100644 --- a/compiler/vmdef.nim +++ b/compiler/vmdef.nim @@ -62,7 +62,7 @@ type opcBitandInt, opcBitorInt, opcBitxorInt, opcAddu, opcSubu, opcMulu, opcDivu, opcModu, opcEqInt, opcLeInt, opcLtInt, opcEqFloat, opcLeFloat, opcLtFloat, opcLeu, opcLtu, - opcEqRef, opcEqNimrodNode, opcSameNodeType, + opcEqRef, opcEqNimNode, opcSameNodeType, opcXor, opcNot, opcUnaryMinusInt, opcUnaryMinusFloat, opcBitnotInt, opcEqStr, opcLeStr, opcLtStr, opcEqSet, opcLeSet, opcLtSet, opcMulSet, opcPlusSet, opcMinusSet, opcSymdiffSet, opcConcatStr, @@ -141,7 +141,8 @@ type opcSetType, # dest.typ = types[Bx] opcTypeTrait, opcMarshalLoad, opcMarshalStore, - opcToNarrowInt + opcToNarrowInt, + opcSymOwner TBlock* = object label*: PSym diff --git a/compiler/vmdeps.nim b/compiler/vmdeps.nim index bf2418eaf..eb6111165 100644 --- a/compiler/vmdeps.nim +++ b/compiler/vmdeps.nim @@ -13,7 +13,7 @@ proc opSlurp*(file: string, info: TLineInfo, module: PSym; conf: ConfigRef): str try: var filename = parentDir(toFullPath(conf, info)) / file if not fileExists(filename): - filename = findFile(conf, file) + filename = findFile(conf, file).string result = readFile(filename) # we produce a fake include statement for every slurped filename, so that # the module dependencies are accurate: diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index a36f559ca..b6b5bf4f2 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -1118,6 +1118,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mStaticExec: genBinaryABCD(c, n, dest, opcGorge) of mNLen: genUnaryABI(c, n, dest, opcLenSeq, nimNodeFlag) of mGetImpl: genUnaryABC(c, n, dest, opcGetImpl) + of mSymOwner: genUnaryABC(c, n, dest, opcSymOwner) of mNChild: genBinaryABC(c, n, dest, opcNChild) of mNSetChild: genVoidABC(c, n, dest, opcNSetChild) of mNDel: genVoidABC(c, n, dest, opcNDel) @@ -1178,7 +1179,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = of mNBindSym: genBindSym(c, n, dest) of mStrToIdent: genUnaryABC(c, n, dest, opcStrToIdent) of mEqIdent: genBinaryABC(c, n, dest, opcEqIdent) - of mEqNimrodNode: genBinaryABC(c, n, dest, opcEqNimrodNode) + of mEqNimrodNode: genBinaryABC(c, n, dest, opcEqNimNode) of mSameNodeType: genBinaryABC(c, n, dest, opcSameNodeType) of mNLineInfo: case n[0].sym.name.s @@ -1192,10 +1193,10 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) = else: internalAssert c.config, false of mNHint: unused(c, n, dest) - genUnaryStmt(c, n, opcNHint) + genBinaryStmt(c, n, opcNHint) of mNWarning: unused(c, n, dest) - genUnaryStmt(c, n, opcNWarning) + genBinaryStmt(c, n, opcNWarning) of mNError: if n.len <= 1: # query error condition: @@ -1841,6 +1842,9 @@ proc gen(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}) = let s = n.sons[0].sym if s.magic != mNone: genMagic(c, n, dest, s.magic) + elif s.kind == skMethod: + localError(c.config, n.info, "cannot call method " & s.name.s & + " at compile time") elif matches(s, "stdlib", "marshal", "to"): # XXX marshal load&store should not be opcodes, but use the # general callback mechanisms. diff --git a/compiler/vmhooks.nim b/compiler/vmhooks.nim index 548a3af97..39e435e4b 100644 --- a/compiler/vmhooks.nim +++ b/compiler/vmhooks.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +import pathutils + template setX(k, field) {.dirty.} = var s: seq[TFullReg] move(s, cast[seq[TFullReg]](a.slots)) @@ -38,6 +40,8 @@ proc setResult*(a: VmArgs; n: PNode) = s[a.ra].kind = rkNode s[a.ra].node = n +proc setResult*(a: VmArgs; v: AbsoluteDir) = setResult(a, v.string) + proc setResult*(a: VmArgs; v: seq[string]) = var s: seq[TFullReg] move(s, cast[seq[TFullReg]](a.slots)) diff --git a/compiler/vmops.nim b/compiler/vmops.nim index a7d47d7a3..83e65279a 100644 --- a/compiler/vmops.nim +++ b/compiler/vmops.nim @@ -82,7 +82,7 @@ proc registerAdditionalOps*(c: PCtx) = setResult a, newTree(nkTupleConstr, newStrNode(nkStrLit, s), newIntNode(nkIntLit, e)) proc getProjectPathWrapper(a: VmArgs) = - setResult a, c.config.projectPath + setResult a, c.config.projectPath.string wrap1f_math(sqrt) wrap1f_math(ln) |